Commit Graph
56 Commits
Author SHA1 Message Date
Paul BakausandClaude Fable 5 7557935fdb Expand concept system: modes, ratings, re-roll, breadth strategy
Catalog: mode-aligned staging surfaces (persuade/operate/read/experience),
star ratings on approvals feeding challenger draw weights, family
retirements, authoring strategy and territory guide, rework and breadth
authoring rounds, composition mining from rejected worlds.

Seed: six challengers (two per tier), --reroll chains, --mode staging
filter, rating-weighted draws. New-work: Present/visualize/re-roll flow,
image-gen requirement, register-neutral vocabulary.

Pipeline: per-mode staging prompts with split frames, hero-from-board
reference generation, render-safety guards. Labs: ratings UI, unrated
filter, mode chips, composition approve-guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 22:10:10 -07:00
Paul Bakaus 144cee5c36 Fix detector coverage for generated UI tells
Remove provider gating, share grid-background detection across source and rendered scan paths, and update the detector catalog and tests.\n\nAI-assisted: prepared by Codex at Paul's request.
2026-07-18 14:21:06 -07:00
Paul Bakaus 77c7d8e0fc Refine product and visual work lifecycle 2026-07-17 16:10:13 -07:00
Paul Bakaus bbed6eef08 Refresh the Impeccable product experience
Rework the landing page proof, steering demo, feature grid, slop catalog, detector coverage, theming, Live workflow, and responsive behavior.\n\nAI-assisted implementation by OpenAI Codex.
2026-07-15 23:29:47 -07:00
Paul Bakaus 8682c85c57 Fix Live side-tab validation gaps
Scan Astro style blocks for inset-shadow stripes, recognize semantically chromatic external tokens without flagging neutral unknowns, and make the polling generator run advisory detector checks before publication. Sync the affected detector bundles and add a paired regression fixture.\n\nAI-assisted: Codex analyzed the failed Live task, implemented the detector and generator changes, and ran the validation suites under maintainer direction.
2026-07-15 16:23:49 -07:00
Paul BakausandClaude Fable 5 ed7a6fbe4e detector: text-occlusion + first-viewport-column-overflow (57 -> 59)
Two browser-engine quality rules, both warning severity.

text-occlusion / element-overlap fires on three shapes: an opaque
decorated box painted over a text element (elementFromPoint confirms
real coverage, box >= 30%), one text run buried under another when at
least one side is a positioned layer (text >= 45%, so line-box leading
bleed between stacked flow blocks does not count), and an inline element
whose opaque fill leaks past its line onto a neighbour (the class-name
collision bug). A large headline whose edge overhangs a bounded content
card is caught as an element collision even when the text stays on top.
Gradient scrims, decorative SVG emblems, fixed/sticky overlays, floats,
and raw image backdrops (contrast territory, deduped against the pixel
low-contrast rule) are exempt.

first-viewport-column-overflow fires when a multi-column opening section
runs one column past 140% of the viewport while a sibling fits inside
one screen, the stretched-hero signature. Single-column pages and
full-page heroes with no fitting sibling are exempt.

Validated: fires on the diagnosed repros, clean across a 60-sample
sweep. Fixtures + browser tests added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 14:53:03 -07:00
Paul BakausandClaude Fable 5 dc0b25d393 detector: hero pulsing-dot promotion, nav-CTA contrast gap closure, shape-assembled-illustration (56 -> 57)
Item 1 (hero liveness theater):
- pulsing-dot now merges declarations per selector across rule blocks
  (cascade-approximate), descends into media queries, and strips
  prefers-reduced-motion: reduce overrides before the predicate runs.
  Catches the shipped split-block constructions (size in the base rule,
  animation added later or inside a no-preference media block).
- Dots whose element sits inside a header/nav landmark are promoted to
  error severity (string-level landmark ranges in both engines); the
  browser engine additionally promotes dots resting in the first ~900px.
- blinking-cursor findings in the first ~900px or inside header/nav are
  promoted from advisory to warning.
- Per-finding severity overrides now flow through static-html,
  browser-injected serialization, and detect-url.

Item 2 (nav-CTA contrast constructions):
- The a24-opus 01/002 header CTA already fires (specificity cascade +
  oklch + var() all resolved); systematic sweep found two remaining
  escapes and closes both:
  - own gradient background on a SAFE_TAGS element (checkColors styled-
    control exception now treats an own gradient as an own surface,
    contrast measured against the worst stop)
  - ::before/::after full-cover surface (static cascade marks pseudo
    surfaces; browser adapter reads the pseudo computed style) so text is
    measured against the surface the browser actually paints
- nav-cta-constructions fixture locks all eight computable construction
  families; background-image: url() remains unflaggable by design.

Item 3 (shape-assembled-illustration, slop/advisory):
- New rule for large inline SVGs composing a pictorial scene from >= 8
  primitive shapes at >= 200x200 intrinsic size with >= 3 distinct fills.
  Charts (axis labels), stroke-only technical drawings, icons/logos
  (small explicit size), and pattern-tiled backgrounds are exempt.
  1.8 percent fire rate over the 3069-sample eval corpus, all verified
  pictorial scenes; zero fires across val-a22/val-a24.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 19:29:29 -07:00
Paul BakausandClaude Fable 5 c98f5d42ed detector: script-error, content-hidden-at-rest, edge-flush-cards + chip contrast and inline-overflow widenings (53 -> 56)
Three new rules and three widenings, all from confirmed eval-corpus
escapes found by eye:

script-error (quality, error severity, URL engine): pageerror listener
attached before goto catches uncaught exceptions AND parse errors (a
syntax error fires during the initial parse, long before load). Deduped
by message, capped at 3. A JS typo was silently deleting whole pages.

content-hidden-at-rest (quality, error, URL engine): after the main
at-rest scan, an instant-scroll reveal sweep (bypasses scroll-behavior:
smooth, which silently defeated the first sweep design) gives every
IntersectionObserver reveal its chance to fire, returns to top, then
measures the share of text characters still at opacity 0 / visibility
hidden. display:none / [hidden] / aria-hidden subtrees stay out of the
denominator. Fires above 30% with a 200/150-char floor. Calibration on
30 corpus samples: broken repro holds 83% after the sweep, all clean
samples (including 0.75-0.93 at-rest reveal pages) drop to <= 7%.

edge-flush-cards (quality, warning, browser): cards with their own
opaque background or 2+ borders inside a horizontal scroller, flush
against one edge of the clip box at rest (< 8px, > -24px so deliberate
mid-card peeks stay exempt) while keeping a gutter on the other side.
Grouped per scroller. Repro: transit-mobile pager whose first snap
panel is 407px wide inside a 390px clip. New --viewport WxH CLI flag
makes mobile-width URL scans reachable (--viewport 390x844).

Chip/badge contrast widening: the SAFE_TAGS styled-button exception in
checkColors now covers any text-bearing element painting its own opaque
background at >= 9px font, not just a/button. The shipped miss: a span
SEV-2 chip whose white text lost a specificity fight and rendered
muted-on-red at 1.2:1. Static adapter also resolves var() own-bg via
the custom-property map so the gate engages on token backgrounds.

background:none cascade fix: the background shorthand now resets
background-color/-image when it names neither (and no var()). Exposed
by the chip widening: pre code { background: none } left an earlier
surface color standing and manufactured 1.1:1 phantom findings.

text-overflow inline-owner widening: inline elements have no client
geometry (clientWidth 0) so the scrollWidth path never saw them, and
their block parent owns no direct text. New branch measures the inline
rect against the nearest block container's padding box (16px floor,
transform-path exempt). Repro: nowrap span.v spilling 45px past its
grid cell.

The round-3 nav-CTA contrast escape (val-a22-opus obs 003 header CTA)
was verified already covered at HEAD by the earlier parseAnyColor
oklch fallback; both engines fire 3.6:1 on the repro, no change needed.

FP sweep across 36 val-a21/a22/a23 samples: new rules fire only on
their repros (script-error also catches a second genuinely broken
sample); static-engine delta is limited to the chip repro plus two
borderline-but-real chip findings on one sample.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:10:44 -07:00
Paul BakausandClaude Fable 5 cc8906ecaa detector: add heading-rhythm and blinking-cursor rules (51 -> 53)
heading-rhythm (quality): a heading binds to the content it introduces,
so its rendered space above must exceed its space below. Browser-only:
measures real getBoundingClientRect gaps (margin collapsing, flex rows,
and section padding make authored margins untrustworthy), merges eyebrow
labels into the heading cluster, requires same-column edges, and exempts
first-in-container headings, bounded bands, and small cards. Fires only
when 2+ headings on a page invert the rhythm.

blinking-cursor (slop, advisory): a decorative blinking caret (solid
block, underscore bar, or block glyph) bound to an infinite blink
animation in the landing region of a page. Real editable surfaces
(contenteditable, role=textbox, inputs) are exempt; round pulsing dots
stay with the pulsing-dot rule.

Verified against eval corpus repros: heading-rhythm fires on the
val-a18 observability sample Paul flagged (6 headings, 0px above vs
40px below) and blinking-cursor on the val-a19 hero terminal cursor;
10 other samples across both runs stay clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 09:35:03 -07:00
Paul BakausandClaude Fable 5 7a99e1725d detector: four human-review rules — nav-CTA oklch contrast, numbered section labels, floating side-tab stripes, repeated card text
Four gaps found shipping in Opus 4.8 eval samples during human review:

1. low-contrast (extended): the browser adapters parsed text/own-bg
   colors with parseRgb only, so Chrome's oklch()-serialized computed
   colors silently skipped every contrast check — a flat dark-on-dark
   nav CTA (broader nav selector beating the button class) shipped at
   1.5:1 undetected. checkElementColorsDOM and readOwnBackgroundColor
   now fall back to parseAnyColor. Near-threshold ratios print two
   decimals so a 4.497 finding no longer reads "4.5 needs 4.5".

2. NEW numbered-section-labels (slop, advisory): tiny (<=13px) styled
   numeric index labels riding beside section headings, repeated across
   2+ sections with distinct indices. Sibling of repeated-section-kickers
   (which deliberately excludes bare numeric labels); handles both the
   direct prev-sibling shape and label-before-heading-wrapper shape.
   List/nav/table/card-item numbering is exempt.

3. side-tab (extended): the vertical pseudo-element stripe scan required
   the stripe to touch both corners (top/bottom 0 or height 100%), so a
   left accent bar inset a few px from each end evaded it; small end
   insets (<=20px each) now count. Added a browser-side pseudo-element
   check (getComputedStyle(el, '::before'/'::after')) since runtime-
   assigned custom-property colors are invisible to the text scanner.
   Selection-state exemptions stay as narrowed: only aria-selected=true /
   aria-current / active-class markers exempt, plus button/link
   affordances on the horizontal variant.

4. NEW repeated-container-text (quality): the same literal string (>=4
   chars, contains letters) rendered 3+ times at 3+ structurally distinct
   positions inside one bordered/elevated container. Parallel/templated
   repetition (table cells, calendar grids, nav lists, identical sibling
   rows) never counts — structural signatures, not word lists.

Verified: each rule fires on its repro sample via the file:// browser
scan; clean eval samples add no new findings (the new low-contrast hits
on other samples are genuine sub-AA oklch button pairs). Full test
suite green; browser bundle regenerated; README/homepage rule counts
bumped 49 -> 51 (docs-integrity test enforces them).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 13:09:54 -07:00
Paul BakausandClaude Fable 5 b6304913ef detector: narrow the tab-strip stripe exemption to actual selection state
Tab-strip MEMBERSHIP no longer exempts chromatic top/bottom stripes —
only a real selection marker does: aria-selected="true", aria-current
(any non-false value), or an active/current/selected class hint. A
stripe repeated on every tab in the group ([role=tab], .tabs items,
aria-selected="false" tabs) is decoration and flags as side-tab; the
selected tab's own underline — including the reserved-space
transparent-border pattern — stays legal. Applied consistently across
the element border path (isTabContextElement), the pseudo-element
stripe scan, and the inset box-shadow stripe scan.

Also replaces a stray NUL byte in the marquee scanner's dedupe key
that made tools treat checks.mjs as binary.

Browser bundle regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 20:04:22 -07:00
Paul BakausandClaude Fable 5 ea44f514f9 detector: grid-background variants, dash-prefix eyebrow, marquee rule, inset-shadow stripes
Four gaps from human review of gpt-5.6 eval artifacts:

1. codex-grid-background variants: the block scan now also matches the
   inverted end-of-tile hairline form (transparent calc(100% - Npx))
   and reads the tile cell from the background shorthand's `/ Npx Npx`
   slot, not just background-size declarations. A single hairline layer
   qualifies when tiled by a px pair cell (page-scale line field);
   percent-tiled single hairlines (background-size: 25% 100% rules on
   data-viz tracks/graphs) stay legal.

2. hero-eyebrow-chip branch C (dash-prefix): sentence-case, regular-
   weight microlabels above the h1 announced by a short chromatic
   ::before/::after bar (8-80px x 1-6px, accent fill). Static cascade
   marks dash-pseudo targets during rule collection; the browser path
   reads getComputedStyle(el, '::before'/'::after').

3. New `marquee` slop rule: <marquee> elements, and infinite animations
   bound to keyframes with >= 20 percentage points of X travel. Percent
   travel only — px-travel loops are bespoke product animations
   (waveform playheads, progress sweeps). Centered elements animating
   other properties (constant -50% X), non-infinite slide-ins, rotations,
   and pulses never qualify.

4. side-tab inset box-shadow variant: single-edge inset shadows
   (3-12px offset on one axis, no blur/spread, chromatic) drawn as
   stripes on cards/badges/menu items. Selection-state indicators
   ([aria-current], [aria-selected], [role=tab], active/current/selected
   hints, interaction states) stay exempt; the same stripe repeated
   unconditionally on every item flags. Narrow fixed-width glyphs
   (logo marks) are exempt. isTabContextElement narrowed to match:
   bare nav ancestry no longer blanket-exempts top/bottom border
   stripes — only explicit tab semantics or state markers do.

Browser bundle regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:38:59 -07:00
Paul BakausandClaude Fable 5 1a4b5c2fa2 detector: hover-state contrast + color-mix/compositing, radial-halo rule, top/bottom stripe variant, file:// browser scans
Four changes driven by human design review of eval artifacts:

1. Static engine contrast fidelity (nav-CTA cascade miss):
   - parseAnyColor evaluates color-mix() (premultiplied sRGB mix; exact
     for the dominant `color-mix(in oklab, C n%, transparent)` chip form)
   - extractStaticColor captures color-mix() balanced instead of plucking
     "transparent" out of the expression
   - resolveBackground composites translucent layers over the opaque base
     in both engines instead of skipping (static) or returning them
     as-if-opaque (browser)
   - NEW hover pass in the static cascade: :hover rules are matched via
     state-stripped selectors, merged per-property against the resting
     cascade with real specificity, and checked for WCAG contrast on
     styled controls (checkHoverContrast). Catches the recurring miss
     where a broader selector (.nav-links a:hover) beats the CTA's own
     hover color and drops the pair below AA.

2. New `radial-halo` slop rule: chromatic radial-gradient wash (visible
   saturated center -> transparent) as a decorative background on a dark
   page. Exempts achromatic vignettes, opaque-end sheens, px-stop dot
   textures, url() photo layers, and translucent (<0.7 alpha) staged-
   light washes. Separate id from dark-glow so dashboards track the
   gradient-drawn variant independently.

3. side-tab horizontal variant: 3-12px chromatic border-top/bottom (and
   top/bottom-anchored full-width pseudo stripes) on cards/badges flag as
   side-tab. Exempt: tablist/nav/aria-selected underlines, link/button
   affordances, table cells, hr, state-conditional pseudo stripes, and
   >12px bands. Badge-shaped spans (own visible background) participate.

4. CLI: file:// URLs route to the Puppeteer browser engine (~2s on a
   50KB page), and detect --json findings now carry the registry
   `category` field so downstream QA loops can separate mechanical slop
   tells from judgment calls.

Fixture policy update: flat 3px top-accent cards moved from should-pass
to flag columns; tablist-underline and 16px-band pass cases added.
Browser bundle regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:18:33 -07:00
Paul BakausandClaude Fable 5 cfdb7d4c81 detector: catch pseudo-element side stripes; add pulsing-dot rule
Two gaps surfaced by human eval review of real artifacts:

1. side-tab missed the pseudo-element variant. The accent stripe drawn as
   an absolutely-positioned ::before/::after (left/right: 0, top+bottom: 0
   or height: 100%, narrow width, colored background) uses no border
   property at all, so neither the element-level border checks (pseudo
   elements never enter the static cascade or DOM walk) nor the
   border-left/right regexes could see it. New scanCssTextForPseudoStripe
   scans stylesheet text for that shape, mirroring the border rule's
   gates: >= 3px thick (<= 12px), chromatic fill (var()-resolved, neutral
   dividers skipped), full height against a side edge, with the
   blockquote/prose exemptions preserved.

2. New pulsing-dot rule (slop): small circular "live" indicator dots
   (<= 16px, border-radius >= 40% or pill values) bound to an infinite
   animation whose keyframes vary opacity, scale, or box-shadow — or
   pulse/blink/ping names when the keyframes aren't in the scanned text —
   plus the Tailwind animate-ping/pulse + rounded-full + tiny-size utility
   combo. Rotation-only keyframes (spinners) never flag, including when
   they hide behind a pulse-like name.

Both scanners live in checkHtmlPatterns, so the static-html engine and
the browser bundle share the same detection path. Browser/extension
bundles regenerated; docs rule count bumped to 47.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:06:41 -07:00
Paul BakausandClaude Fable 5 099c69ab65 detector: regenerate browser bundle after single-font rewording
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 18:37:02 -07:00
Paul BakausandClaude Fable 5 2c62f0f4f9 de-SaaS the skill: mode-aware rules, neutral runtime injections, diversified examples
Fix batch from the visitor-mode bias audit. The skill's four modes
(Persuade / Operate / Read / Experience) now reach the places that were
still hard-coded to a SaaS-marketing default:

- palette.mjs: rewrote 45 seed blurbs in material/world terms. The 29
  tech-tool-world moods (13 Linear-indigo variants, 6 Figma-era, 5
  climate-tech, 3 fintech, 2 Glossier DTC, incl. seed-201's docs-page
  CTA red) lose all company names and product-category words; Aesop
  trimmed from 17 blurbs to 4 and Klim from 7 to 4, excess rewritten
  as unnamed material terms. Also carries the earlier bg-block rewrite
  (brand refs out of the composition doc).
- init.md: register explainer now names the four modes and the family
  each belongs to (stored value stays brand/product for compatibility);
  Conversion & proof interview + PRODUCT.md section gated to Persuade
  surfaces only (Experience/Read get no CTA/belief-ladder/proof).
- critique.md: Nielsen heuristics 7 and 10 may score n/a on Persuade
  and Experience surfaces, total renormalized to the applicable max,
  snapshot records which were n/a; working-memory examples diversified
  beyond dashboard/pricing anatomy.
- Register headers in bolder/delight/quieter/colorize/layout/animate/
  typeset renamed from Brand:/Product: to Persuade + Experience: /
  Operate + Read:; typeset and layout gain one Read-specific sentence
  (steady reading measure; navigable linearity).
- animate.md: plan checklist and implementation order lead with
  feedback and transitions; the single entrance moment comes after,
  scoped to modes that invite it.
- codex.md: mock inventory says "primary-action treatment (when the
  surface has one)" instead of assuming a CTA.
- delight.md: loading/empty-state/console-egg examples diversified
  beyond SaaS; streaks/badges scoped to Operate surfaces with
  recurring tasks.
- distill.md: step-removal and next-action lines neutralized away
  from signup/checkout/CTA vocabulary.
- document.md: canonical button label GET STARTED -> SAVE CHANGES;
  signature components gain a non-marketing example.
- antipatterns registry: single-font rule renamed to "Single font
  without hierarchy" with a description that permits one family when
  weight/size contrast carries hierarchy.

Staged provider copies regenerated via build:skills:release for the
touched files only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 18:35:53 -07:00
Paul BakausandClaude Fable 5 d5af1112a4 detector: regenerate browser bundle after glow hardening
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:03:08 -07:00
Paul BakausandClaude Fable 5 0d72991bc8 detector: catch glow shadows in any color format, zero-offset halos on any background, and text-shadow glows
- parseAnyColor now covers oklab(), hsl()/hsla(), hwb(), and ~35 common
  named colors on top of rgb/rgba/hex/oklch, so checkGlow sees the color
  regardless of authoring format (Chrome preserves oklch() in computed
  styles, which the old rgba-only match silently passed).
- checkGlow gains a second tell: a zero-offset chromatic box/text-shadow
  with blur > 4px is flagged on ANY background (the halo pattern);
  achromatic zero-offset shadows and focus rings stay legal. The
  existing chromatic-blur-on-dark-background rule is unchanged in
  semantics but now parses every color format.
- text-shadow is checked wherever box-shadow was (browser DOM path with
  inherited-value dedupe, static engine via new textShadow cascade
  support, text engines).
- The page-level text scan (regex engine + checkHtmlPatterns) is now a
  shared scanCssTextForGlow that resolves single-level var() refs
  against custom properties collected from the same text; unresolvable
  var() in a shadow color position is skipped, never guessed. Its
  dark-page heuristic accepts var()/oklch backgrounds but only when
  declared at root scope (body/html/:root or body inline style).
- dark-glow keeps its id; registry name/description updated to cover
  both cases.

Validated: three eval repro samples with oklch / var(--x) glows that
previously produced zero findings now flag on the static CLI path; ten
known-good largerun samples stay clean except one with genuine amber
status-dot halos (0 0 12px oklch(.73 .17 65/.4)).

Note: cli/engine/detect-antipatterns-browser.js and the extension
detector are generated and still need 'node scripts/build-browser-detector.js'
+ 'node scripts/build-extension.js' once builds are unblocked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:01:54 -07:00
f40e2f8f0a Add mechanical pre-scan for typeset and layout (#345)
* Add mechanical pre-scan for typeset and layout commands.

Introduce --scope filtering, layout/type rule scopes, DESIGN.md font-size validation, and pre-scan steps in the skill references so agents run detect before LLM judgment.

Fixes #149

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add isolated sub-agent orchestration for typeset and layout pre-scans.

Run the mechanical detector and visual assessment in parallel sub-agents so deterministic findings cannot anchor LLM judgment, matching the critique pattern Paul requested on PR #345.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: reject bare --scope so detect never scans unscoped by mistake.

When --scope had no value, the CLI dropped the flag and ran a full scan instead of failing, which could silently use the wrong rule set during typeset/layout pre-scans.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: require both typeset and layout assessments in sub-agents.

Close a loophole where agents ran only the mechanical pre-scan inline by interpreting "running both" as permitting one inline assessment.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Abdul Wahab <abdulwahab@Abduls-MacBook-Pro-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 08:29:21 -07:00
Abdul WahabandGitHub ca121aa35f Fix: file-scoped wildcard ignores suppress non-value-bearing rules (#296) (#309)
A file-scoped wildcard ignore (add-value <rule> "*" --file <glob>) silently no-op'd for rules with no extractable value, such as side-tab. isIgnoredFindingValue bailed on an empty value before the wildcard/file-scope branch could run.

Require a value only on the specific-value path; let the scoped wildcard match on rule + file. Mirrored in skill/scripts/hook-lib.mjs for CLI/hook parity.
2026-07-07 17:23:39 -07:00
c775e03c1d Fix Pi global install path (#338)
* Fix Pi global install path

* Simplify Pi skills-path helpers and consolidate tests

One userProviderSkillsDir helper owns the HOME_SKILLS_DIR_OVERRIDES
lookup, read paths share existingSkillsDirs, and the five Pi install
tests collapse into two that keep the same coverage: global detection
plus the agent-path write, and project scope in a home-rooted repo.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Respect requested scope when resolving Pi skills dirs

An explicit install scope now narrows providerSkillsDirCandidates to
the matching layout, so a project-scope install in a home-rooted repo
no longer matches an existing global Pi install and get swallowed by
the already-installed refresh path. Update/check flows still probe
both layouts since they have no scope. Covers the T-Rex repro in the
home-rooted regression test.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Refresh every existing Pi layout on unscoped update

deduplicateProviders keeps one entry per existing layout instead of
only the first, so unscoped check/update refresh both ~/.pi/agent/skills
and ~/.pi/skills when a home-rooted repo holds copies in each. Home-dir
detection now compares realpaths, since findProjectRoot resolves
symlinks while homedir() does not.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Abdul Wahab <abdulwahab@Abduls-MacBook-Pro-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 17:19:13 -07:00
Dustin PersekandGitHub 9f49cb85cc Fix Google Fonts css2 family parsing (#349) 2026-07-07 17:16:11 -07:00
f5c1bd65ae Add codex-grid-background detector rule (#328)
* Add codex-grid-background detector rule

Detects the Codex two-axis grid-line background tell: a single background
value carrying two or more hairline `linear-gradient(... 1px, transparent
1px)` layers (one per axis), usually paired with a repeating
`background-size` cell. Gated behind --gpt like the sibling codex tells,
off by default.

Counts hairline stops within a single background declaration (not across
the page) so unrelated single-axis ruled lines don't add up to a false
flag, and matches the stop directly rather than parsing whole gradient
layers, since colors like oklch(...) carry nested parens.

Extends the gpt-tells fixture with one flag case and two pass cases
(single-axis rule, two-color blend), regenerates the browser detector
bundle, and bumps the rule count 44 -> 45.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Require tiling background-size for codex-grid-background

Address review: two hairline gradients alone draw a fixed crosshair, not a
grid. Scope detection to a single style block (CSS rule body or inline
style attr) and require both >=2 hairline stops AND a tiling
`background-size` px cell in the same block, matching the skill rule's
"plus background-size" wording. Add a crosshair-without-tiling pass case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Scope codex-grid-background hairline count to background values

Address review: count hairline stops only inside background/background-image
declaration values, not the whole style block, so a hairline in an unrelated
property (mask-image, border-image) can't stand in for the grid's second
axis. Add a bg+mask-image hairline pass case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 01:29:40 -07:00
Abdul WahabandGitHub 467efe4632 Fix: preserve external ~/.claude/skills symlink on first install (#295) (#308)
* Fix: preserve external skills symlink on first install (#295)

* Fix review comments: target-based in-project link detection (#295, #308)

- isInProjectProviderLink now inspects the symlink TARGET lexically instead of comparing shared realpaths, so two providers pointing at the same external dir are no longer misflagged as in-project (cursor High / greptile P1).
- A dangling in-project cross-provider link is now correctly replaced with a real per-provider dir (cursor Medium).
- Adds regression tests for both scenarios.
2026-06-25 17:21:02 -07:00
776c019041 Add inline, in-file ignore comments for the detector (#283) (#285)
* Add inline, in-file ignore comments for the detector (issue #283)

Complement config ignores with eslint-disable-style waivers that live where
they apply and travel with the file when it leaves the repo. The motivating
case is a generated/exported standalone document that legitimately uses a
first-party brand typeface (on the overused-font list) and is later scanned
without .impeccable/config.json present.

Marker is comment-syntax-agnostic (works in //, /* */, <!-- -->, #, {/* */}):

  impeccable-disable <rule>[, <rule>...] [-- reason | : reason]   whole file
  impeccable-disable-line <rule>...                               same line
  impeccable-disable-next-line <rule>...                          next line

Bare directive or * means every rule; reason is optional and discarded at
scan time. Behavior is suppression, for parity with config ignores.

Implementation:
- New pure module cli/engine/shared/inline-ignores.mjs (parser + filter, no
  Node deps). Static-HTML findings have no line number, so only whole-file
  directives apply there -- exactly the standalone-document case; the
  regex/text engine additionally honors the line-scoped forms.
- Wired into detectText and detectHtml, gated by options.inlineIgnores.
- detect CLI applies inline ignores by default; --no-inline-ignores skips
  just them, --no-config skips config and inline ignores together.

Docs: config.md (new section), detector.md, README. skill/reference/hooks.md
reversed its prior "inline comments are not supported" guidance and now points
the agent to inline waivers for the travels-with-the-file case. Changelog 3.x.

Tests: tests/inline-ignores.test.mjs (parser units, detectText/detectHtml
integration, CLI end-to-end), registered in the detector suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Reconcile design hook wording with inline ignores

Two hook-side fixes prompted by review of the new inline-ignore feature:

1. Clean-ack steer line. The old line ("Keep typography hierarchy, spacing
   rhythm, and color contrast intentional on the next change.") read as an
   odd non-sequitur after "No anti-patterns." Reworded the whole clean ack to
   say what it means: a clean scan only clears the deterministic rule set, not
   overall design quality, so keep following the design system and skill
   guidance. Now: "Design hook scanned X. No deterministic design-quality
   issues found. That does not mean the design is good: keep following the
   project design system and the impeccable skill guidance."

2. Directive footer. It still told the agent "Do not add source comments such
   as `impeccable: ignore`; those pollute the code and do not suppress hook
   findings." That is now misleading: the hook runs the same detector engine
   as the CLI, which honors inline `impeccable-disable` waivers, so they DO
   suppress hook findings (consistent with config ignores, which filterFindings
   already honors). Reworded to: don't silence a real finding to skip fixing
   it; suppress only after the user confirms intent; prefer a config ignore,
   and reach for an inline `impeccable-disable <rule>` comment only when the
   waiver must travel with a file that leaves the repo.

Added a hook test asserting an inline `impeccable-disable-line` comment makes
the hook scan the file clean (locks in the cross-cutting behavior), and updated
the clean-ack / footer assertions to the new wording.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address review on inline-ignores parser

- Case-insensitive fast-path bail-out (Cursor): the cheap substring guard was
  lowercase-only while DIRECTIVE_RE has the `i` flag, so a mixed-case marker
  like `Impeccable-Disable` skipped parsing entirely and never suppressed.
  Switched the guard to `/impeccable-disable/i.test(...)`. Added a regression
  test.
- Removed the unreachable `-->` branch from TRAILING_CLOSER_RE (Greptile):
  `--+>` already matches `-->` and any longer dash run.
- Replaced the always-truthy lazy-match + `if (sep)` reason strip with an
  explicit first-separator slice (Greptile): clearer and drops the dead branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Align inline-ignore line numbering with the detector (CRLF/CR endings)

parseInlineIgnores split lines with /\r\n|\r|\n/, but detectText numbers lines
with split('\n'). On classic `\r`-only endings the two diverged, so a
disable-line / disable-next-line directive could key a different line than the
finding it should waive (Cursor review). Split on '\n' only, matching the
detector exactly; the directive regex already excludes '\r', so a trailing '\r'
on CRLF files is never captured into the rule list. Added a CRLF regression test
through the real detectText.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:41:36 +09:00
Abdul WahabandGitHub f1e9b3df3a Fix: fail loudly on unknown CLI subcommands (#270)
Unknown/mistyped CLI subcommands now print 'Unknown command' and exit non-zero instead of silently routing to the detector. Closes #266. Version bump and changelog entry deferred (batching). Co-Authored-By: abdulwahabone
2026-06-20 19:34:38 +09:00
41ff946121 Add GitHub Copilot hook support (CLI + cloud agent) (#279)
* Add GitHub Copilot hook support (CLI + cloud agent)

Wire the Impeccable design detector into GitHub Copilot's hook system so
direct file edits get the same post-edit design feedback the Claude Code,
Codex, and Cursor harnesses already receive.

GitHub Copilot's contract differs from the existing harnesses (verified
against Copilot CLI 1.0.63):
- Repo-level manifest at `.github/hooks/impeccable.json` (read by both the
  CLI, once committed to the default branch, and the cloud/app agent).
- Flat `postToolUse` entries with `bash`/`timeoutSec` and a full-match
  `matcher` regex; the file-editing tools are `edit` and `create`.
- The stdin event uses camelCase `toolName`/`toolArgs`, where `toolArgs` is
  a JSON *string* carrying the touched file under `path`.
- Context is injected via a top-level `additionalContext` string.

Changes:
- hooks.js: buildGitHubHooksManifest() + route `github` in hooksJsonFor().
- providers.js: emitHooks/hooksManifestRel for the github provider.
- hook-lib.mjs: detect the github harness, normalize the camelCase event
  (parse the JSON-string toolArgs -> tool_input.file_path), and emit the
  `additionalContext` payload shape.
- hook-admin.mjs / skills.mjs: install + idempotent-repair the
  `.github/hooks/impeccable.json` manifest (bash-aware marker stripping).
- hooks.md: document GitHub Copilot as a supported harness.
- Tests for the builder, routing, event normalization, and end-to-end run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Cover Copilot apply_patch edits in the hook (live-verified)

The first cut matched only `edit|create`, the tool names `copilot -p` uses.
A live trace against Copilot CLI 1.0.63 in an interactive session showed it
edits files via `apply_patch`, whose toolArgs is a raw OpenAI-format patch
string (`*** Begin Patch` / `*** Add File:`), not JSON. With the narrow
matcher the hook command never ran.

- hooks.js / hook-admin.mjs: matcher -> `edit|create|apply_patch`.
- hook-lib.mjs: normalizeGitHubEvent now routes apply_patch's raw patch
  string into tool_input.command (reusing the existing parseApplyPatchPaths /
  resolveTargetFiles plumbing) and only JSON-parses toolArgs for the
  edit/create/view tools. tool_name is normalized to apply_patch so the patch
  path is extracted even if a future build relabels the tool.
- Tests: apply_patch matcher assertions, event normalization, and an
  end-to-end runHook covering the interactive/cloud path.

Verified live: a trusted interactive `apply_patch` edit fires the hook and
returns the expected `additionalContext` design reminder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address review feedback + add changelog entry

- hook-lib.mjs (Bugbot, low): looksLikeApplyPatch no longer misroutes an
  edit/create event whose edited *content* contains apply_patch markers. A
  real apply_patch payload is a raw string that does not parse as JSON; an
  edit payload is a JSON object, so only non-JSON-object strings are treated
  as apply_patch. Edit events keep extracting `path`. Adds a regression test.
- skills.mjs (Bugbot, medium): document why `.github` is intentionally
  excluded from hookScriptPathForProvider. Its hook manifest is committed and
  shared (read by the Copilot cloud agent and teammates), so the command must
  stay portable via `$(git rev-parse ...)`; rewriting it to a machine-local
  absolute path would break those. GitHub skills are project-scoped, so the
  project-relative path resolves.
- changelog: add an Upcoming (v3.x placeholder) entry for the Copilot hook.
  Version is not bumped yet (batching with other changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 02:24:18 -07:00
Abdul WahabandGitHub 07667ed08f Add quiet mode to detect CLI (#259) 2026-06-18 18:47:02 -07:00
Paul Bakaus c7539c867d Fix live picker sizing and divider detection 2026-06-17 13:10:53 +09:00
Paul BakausandGitHub 8b0c895703 [codex] Fix CLI skill update detection (#257)
* Fix CLI skill update detection

* Preserve linked skills during install refresh

* Keep existing installs working offline

* Respect provider scope during install refresh
2026-06-16 19:50:40 -07:00
Paul BakausandGitHub 51d01e3a5f [codex] Add design-aware detector rules (#252)
* Add design-aware detector rules

* Fix design-aware detector noise

* Unify CLI and hook detector ignores

* Fix remaining design-system review findings

* Add detector ignore CLI

* Fix design detector review findings

* Fix design color source false positives

* Fix core test suite registration

* Add design-aware detector docs

* Fix font priority design-system parsing

* Fix color ignore value matching
2026-06-15 21:06:17 -07:00
c27a75ad41 fix(cli): replace extract-zip with fflate to fix silent install on Node v24.16.0+ (#253)
On Node v24.16.0 / v26.1.0+, `impeccable install` printed "Downloading
impeccable skills...", exited 0, and installed nothing. A Node streams
regression (nodejs/node#63487) made pause()/resume() no-ops on destroyed
streams, stalling extract-zip's yauzl/fd-slicer read stack partway through;
its promise never settled and the process exited clean with no error.

Swap extract-zip for fflate across both extraction call sites
(downloadAndExtractBundle, copyOrExtractLocalBundle) via a new extractZip
helper. fflate decompresses from an in-memory buffer and never touches the
fs stream path, so it is immune on every Node version. It is pure JS with
zero dependencies, so the Windows fix from #198 (no `unzip` binary) holds.
Unlike extract-zip, fflate is actively maintained.

Because extractZip writes entries itself, it guards against zip-slip (`../`
entries escaping the target dir). Tests add a many-file regression guard
(fails on partial extraction) and a zip-slip rejection test.

Verified end-to-end: the real 1,194-file universal bundle extracts and
installs completely.

Fixes #250.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-15 20:33:43 -07:00
Paul Bakaus 858b9bbea6 Improve hook false-positive handling 2026-06-15 13:30:28 +09:00
Paul Bakaus 6443980117 Improve CLI install prompts 2026-06-15 13:04:25 +09:00
Paul Bakaus 32c01595e2 Prepare CLI 3.0.1 install targeting fix 2026-06-15 09:14:29 +09:00
Paul Bakaus 9b0b63c04f Prepare CLI 3.0.0, skill 3.6.0, extension 1.2.0 2026-06-14 21:51:14 +09:00
8cf2be110d feat(cli): interactive hook consent + unified .impeccable/config.json (#245)
* feat(cli): interactive hook consent + unified .impeccable/config.json

Make the design-hook install a conscious choice and unify scattered config
into one file.

Interactive consent
- On an interactive `skills install`/`update`, the CLI explains what the hook
  does and offers to install it (default yes), then records the per-developer
  decision in the gitignored `.impeccable/config.local.json`, so it never
  re-asks. A recorded decision or an already-installed hook short-circuits;
  `-y`/non-TTY keeps the historical install-by-default behavior; `--no-hooks`
  is a one-off skip that records nothing. The trigger keys on "is the hook
  installed?" + "is there a recorded decision?", not a brittle version check.

Unified config
- `.impeccable/config.json` (shared) and `.impeccable/config.local.json`
  (gitignored) now hold all Impeccable settings: hook settings under a `hook`
  key, plus top-level `updateCheck`. `/impeccable hooks` writes the `hook`
  subtree, preserving siblings. The hook runtime reads `hook.quiet` and
  `hook.auditLog`; context boot reads `updateCheck`. The legacy
  `IMPECCABLE_HOOK_DISABLED|QUIET|LOG` and `IMPECCABLE_NO_UPDATE_CHECK` env vars
  still work and override config; docs now lead with config and treat env vars
  as a legacy note.
- No backward compat for the pre-unification `hook.json`/`hook.local.json`
  (the hook shipped an hour ago; nothing in the wild uses it). This repo's own
  hook config is migrated to `.impeccable/config.json`.

The CLI and skill scripts are separate trees, so a small CLI-side config module
(cli/lib/impeccable-config.mjs) duplicates the config-path and .git/info/exclude
handling; comments flag the duplication.

Tests: new cli config unit test; skills-cli consent tests (declined skips,
accepted installs, --no-hooks records nothing); hook.test.mjs back-compat
removed and quiet/auditLog-from-config + gitexclude coverage added. Full suite
green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): preserve sibling config fields + resolve audit log from event cwd (Bugbot)

Two Bugbot findings:

- High: `/impeccable hooks` edits replaced the whole `hook` object with the
  merge-helper output, dropping fields those helpers don't manage — so an
  `ignore-value --local` could wipe the recorded install consent and make the
  CLI re-prompt. writeConfig now merges over the existing hook object, keeping
  consent/quiet/auditLog.
- Medium: config-based audit logging resolved hook.auditLog from process.cwd(),
  which can differ from the hook event's project root (and Cursor's pre-edit
  hook passed no cwd). The hook now stamps the resolved project root on the
  audit entry, and writeAuditLog reads config from entry.cwd when present.

Tests: a /impeccable hooks edit preserves consent + quiet; writeAuditLog
resolves config auditLog from entry.cwd, not the fallback cwd.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): resolve a relative auditLog path against the project root (Bugbot)

A relative hook.auditLog was read from the project root but written relative to
the hook process cwd, so when those differ the log went to the wrong place.
writeAuditLog now resolves a relative target (from env or config) against the
same project root it reads config from. Absolute and ~/ paths are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix hook consent recovery and smoke config

* Fix hook consent explainer for Cursor

* Fix empty hook target consent

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 02:42:19 -07:00
9c0012d4e1 feat(hooks): package design hook in plugin, install to settings.local.json (#243)
* feat(hooks): package design hook in plugin, install to settings.local.json

Three related changes to how the Impeccable design hook is distributed,
plus an unrelated build fix discovered along the way.

Package the hook in the Claude Code plugin
- The marketplace / `/plugin install` path previously shipped the skill and
  agents but no hook, so those users never got the design detector. The build
  now emits `plugin/hooks/hooks.json` (auto-discovered at the plugin root),
  resolving the script via `${CLAUDE_PLUGIN_ROOT}` so it works wherever Claude
  Code unpacks the plugin instead of assuming a `.claude/skills/` layout.

CLI installs the hook into settings.local.json, not shared settings.json
- `npx impeccable skills install/update` now writes the Claude hook to the
  gitignored `.claude/settings.local.json` (a machine-local install side
  effect) rather than the team-shared `settings.json`, which could otherwise
  be committed and break for teammates without the skill installed.
- Graceful handling (leave-it-never-duplicate): if our hook already lives in
  the shared `settings.json` (a legacy install or a deliberate user move), it
  is honored in place and never duplicated into the local override, which
  would otherwise run the detector twice per edit.
- The skill's `/impeccable hooks on|off` toggle is unaffected: it only writes
  `.impeccable/hook.json`, never the settings files.

Fix universal.zip build failure under archiver v8
- `archiver` was bumped to v8 (now ESM, factory function removed) but
  `scripts/lib/zip.js` still used the old `archiver('zip', ...)` API, so every
  build silently failed to produce `dist/universal.zip` (the skill-release
  artifact). Switched to `new ZipArchive({...})`.

Also folds in a pre-existing local rename of the hook status message
("Scanning design" -> "Checking UI changes") and its regenerated provider
output.

Tests: new coverage for the plugin-packaged hook manifest and the
shared-settings honor-in-place path; existing CLI assertions moved to
settings.local.json. Full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(cli): detect hook by marker, not file existence (Bugbot)

hookInstalledForProvider treated any existing settings.local.json (or
hooks.json) as proof the hook was installed. Those files commonly hold
unrelated local settings, so the already-installed `skills install` path
would skip repairing a genuinely missing hook that `update` would add.

Detect the Impeccable marker in the file instead of mere existence. Adds a
test for the exact case: a settings.local.json with only permissions still
triggers hook repair and preserves the unrelated content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(build): fail loud on a broken release zip + cover the zip writer

Close the gap that let the archiver v8 break ship a 0-byte universal.zip
with a green test suite:

- createProviderZip no longer swallows failures. It throws on a missing
  source, an archive with zero entries, or a 0-byte output, and build() now
  exits non-zero on any such rejection. A build that can't produce its release
  artifact fails instead of deploying an empty bundle.
- New tests/zip.test.mjs exercises the real zip writer and round-trips through
  extract-zip (the unpacker the CLI uses): a valid bundle unpacks to the skill
  tree, and the empty/missing-source cases throw. Wired into the core suite so
  it runs in `bun run test`.

Why this matters: the prior CLI e2e tests stub the bundle as a local
directory, so they never built, downloaded, or unzipped a real archive. The
zip writer had no coverage and failed soft, so Dependabot's archiver 7->8
major bump merged green and the deploy shipped an unusable bundle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(cli): scope hook marker scan to the hooks subtree + prune local dupes (Bugbot)

Two follow-ups from Bugbot:

- fileHasImpeccableHookMarker scanned the whole settings file as raw text, so
  an unrelated string (e.g. a permissions allow entry that mentions the hook
  path) could falsely read as an installed hook and block install/repair or
  the shared-settings skip. Now it parses the JSON and scans only the `hooks`
  subtree.
- When the hook is honored in the shared settings.json, copyProviderHooks
  skipped the local write but left a stale hook in settings.local.json from an
  earlier machine-local install, so Claude Code loaded both and ran the
  detector twice per edit. It now prunes the local copy (preserving unrelated
  local settings, dropping the file if only our scaffolding remained).

Adds tests for both: a permissions string mentioning the hook path still
triggers repair, and a shared hook prunes the stale local duplicate while
keeping unrelated permissions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:54:46 -07:00
672517f76e Add automatic design hook install and exceptions (#170)
* docs: add PRD for design detector hook integration

Plans a PostToolUse hook for Claude Code and Codex that runs the
existing design detector after every relevant file write and feeds
findings back to the agent as advisory system-reminder context. No
implementation in this commit; covers UX, technical design, build
pipeline changes, distribution, coverage tradeoffs, and rollout.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: revise hook PRD with best-practices review

Folds in the P0/P1/P2 findings from an online best-practices critique
against the official Claude Code and Codex hook references plus 10+
2026 community guides and similar prior-art tools (claw-hooks,
claude-code-hooks-mastery).

Key changes:
- Exec form everywhere (Codex snippet was shell form), with Windows
  rationale.
- Default timeout dropped from 10s to 5s.
- Re-entrancy guard (CLAUDE_HOOK_DEPTH) and per-file edit counter.
- Session-scoped finding dedup promoted from open question to v1.
- Per-language inline-ignore syntax map (HTML/JSX/CSS/JS).
- Hard-skip rules for sensitive paths and generated/lock files.
- Honest framing about Claude Code lacking per-plugin hook disable.
- Honest framing about Bash-written files being invisible in v1.
- Codex Windows-not-supported call-out, feature flag note, trust ceremony detail.
- Optional NDJSON audit log via IMPECCABLE_HOOK_LOG.
- Findings cap lowered 8 → 5 with attention-budget rationale.
- Versioned envelope ([impeccable@1]) on rendered template.
- Expanded test plan, decision log, and stdin payload appendix.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(hooks): ship the design detector hook for Claude Code and Codex

Implements docs/hooks-prd.md: a PostToolUse hook that runs the
impeccable design detector after every Edit/Write/MultiEdit on a UI
file and pushes findings into the agent's next-turn context as a
short system reminder. Silent on clean files. Never blocks an edit.

Why this matters: today, design slop (side-tab borders, gradient
text, purple/cyan palettes, bounce easing, etc.) only gets caught
when a human notices or someone explicitly runs /impeccable audit.
The hook closes the loop at the moment slop is written.

What ships in v1
- skill/scripts/hook.mjs: PostToolUse entry. Reads stdin, runs the
  detector in-process (no `npx impeccable` cold start), emits
  hookSpecificOutput.additionalContext when fresh findings exist.
- skill/scripts/hook-lib.mjs: extracted helpers (config, cache,
  filter, render, audit log, runHook orchestrator). 100% unit-testable.
- skill/scripts/hook-session-start.mjs: SessionStart greeting,
  gated by a project-scannable probe + 30-day throttle.
- skill/scripts/hook-admin.mjs: backs /impeccable hooks
  on/off/status/ignore-rule/ignore-file/reset.

Hardening built in
- Re-entrancy guard (IMPECCABLE_HOOK_DEPTH) so the hook can never
  recursively spawn itself.
- Hard-skip regexes for sensitive paths (.env, .pem, id_rsa,
  secrets, credentials, .git) and generated/lock/build output. These
  fire before the file is even read; cannot be turned off via config.
- Path-traversal check on the inbound file_path.
- Session-scoped dedup keyed by (session, file, rule, line) so the
  same finding never lands in context twice. Prevents the ~12.5K
  wasted tokens per chatty session called out in the PRD.
- Per-(session, file) edit counter with a one-shot suppression
  notice on the 7th edit, silent after.
- Fail-open contract: every error path returns exit 0 with no
  stdout. Optional NDJSON audit log via IMPECCABLE_HOOK_LOG.

Three kill switches (precedence high to low):
1. IMPECCABLE_HOOK_DISABLED env var (1/true/yes/on, case-insensitive)
2. .impeccable/hook.json `enabled: false`
3. /impeccable hooks off slash command (writes the JSON)

Inline ignores are language-aware. `// impeccable: ignore <rule>` for
JS/TS, `<!-- impeccable: ignore <rule> -->` for HTML/Vue/Svelte/Astro,
`{/* impeccable: ignore <rule> */}` for JSX/TSX, `/* impeccable:
ignore <rule> */` for CSS. `*` matches any rule. Directive applies
to the next non-blank line. Same shape as ESLint, Stylelint, Biome.

Build pipeline
- scripts/lib/transformers/hooks.js: per-provider hooks.json
  builders, plus the slim .codex-plugin/plugin.json manifest.
- providers.js: emitHooks: 'claude' for claude-code, emitHooks:
  'codex' for codex and agents. Codex also emits emitCodexPlugin.
- factory.js: emits hooks/hooks.json next to the skills tree.
- build.js: syncs hooks/ into harness roots and into the slim
  plugin/ subtree; writes .codex-plugin/plugin.json. Build is
  idempotent (verified: 98 staged files unchanged across two runs).

Claude Code wiring uses exec form (command + args) and the
${CLAUDE_PLUGIN_ROOT} placeholder. Matcher: Edit|Write|MultiEdit.
`if:` glob filters to UI extensions before spawning Node. PostToolUse
timeout 5s, SessionStart timeout 3s.

Codex wiring uses ${PLUGIN_ROOT} (Codex's native placeholder),
matcher Edit|Write|apply_patch, no `if:` analog (the script does the
extension filter). macOS and Linux only; hooks are disabled on
Windows in current Codex builds. The trust ceremony and feature flag
are documented in README.md.

Routing
- /impeccable hooks lives outside the 23-command router table on
  purpose: it is plumbing, not a design skill. The hidden
  routing slot is added to SKILL.md alongside pin/unpin so the LLM
  knows to dispatch it. The 23-command count and all stale-count
  validators remain happy.

Tests
- tests/hook.test.mjs: 38 unit tests covering env parsing, config
  load + defaults + malformed, cache round-trip + GC,
  ignoreRules/minSeverity/inline ignores (all four languages),
  globbing with **/*/{a,b}, render template with cap + clamp + 0-line
  prefix drop, audit log NDJSON, payload event-name parameterization,
  re-entrancy, kill switches, sensitive-path + generated-path +
  traversal skips, allowlist filter, config ignoreFiles, edit
  counter cycle including the 7th-edit notice, MultiEdit and
  apply_patch payload shapes, detector throw swallow, malformed
  stdin, missing file race.
- tests/hook-build.test.mjs: 18 integration tests covering hook
  manifest shape (matcher, timeouts, exec form, if: glob, placeholders),
  Codex differences (${PLUGIN_ROOT}, no if:, no SessionStart),
  Codex plugin manifest (no inline hooks field to avoid the
  duplicate-file error), routing across the hooksJsonFor table, and
  presence of all three committed artifacts plus the bundled detector
  the runtime relative-import path depends on.

Full suite: 175 bun tests + 186 node tests, all green.

Docs
- README.md: new "Design hook" section explaining default behavior,
  per-project / global / inline disable paths, the JSON schema knobs,
  the audit log debug flag, and the slop / a11y coverage split.
- HARNESSES.md: flips the `hooks` row for Codex from No -> Yes
  (Claude was already Yes), adds a per-harness hook-surface table
  with the manifest location and matcher each provider uses.

Open questions from the PRD intentionally deferred to v2: Bash-write
blind spot, effort-aware suppression, Stop-hook session summary,
per-rule severity, async hook mode. None block v1.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix Codex hook scanning: apply_patch paths and co-located stylesheets

Parse file targets from Codex apply_patch command bodies, co-scan imported
and sibling CSS when UI components are edited, drop the git-sweep PostToolUse
group, and align Codex SessionStart manifest and trust docs with the official
hooks spec.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Gitignore hook session cache and drop local test HTML

Hook dedup/throttle state in .impeccable/hook.cache.json is per-project
runtime data like other .impeccable/ sidecars. Remove an untracked
bad-nested-flexbox scratch page from site/public/.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix Claude Code hook: drop Edit-only if filter so Write/MultiEdit fire

Claude's if permission rule binds to one tool name, so Edit(*.{…}) never
spawned the hook on Write or MultiEdit despite the matcher listing them.
Extension filtering now lives in hook-lib on both Claude and Codex.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Surface Cursor design findings via stop-hook followup

Replace dropped postToolUse additional_context with afterFileEdit recording
and a one-shot stop followup_message so anti-pattern nudges reach the agent.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix design hook packaging and scans

* Fix Cursor hook pending bucket fallback

* Fix Sass hook scan coverage

* Fix Cursor hook review findings

* Fix session start dead hook normalization

* Fix hook config and relative scan paths

* Remove SessionStart design hook

* Remove redundant afterFileEdit normalization

* Fix Cursor suppression and module style scans

* Fix sensitive path hook filter

* Fix disabled Cursor stop hook emission

* Refresh hook harness artifacts

* Fix Cursor hook manifest install

* Add hook ignore-value support

* Ignore hook runtime files locally

* Fix Codex plugin hook packaging

* fix: address PR review bot findings

Block numeric hook depth counters from re-entering.

Avoid following stylesheet imports from traversal-looking hook targets.

* fix: gate ignore-value suggestions by supported rules

Only render exact ignore-value commands when the same finding can be suppressed by ignoreValues.

* Package Codex plugin as hook-only

* Remove Codex plugin packaging

* Recover hook install probe plumbing

* Remove Codex hook packaging follow-up doc

* Remove extra hook docs and skill wording changes

* Install real design hooks via skills CLI

* Add provider hook smoke runner

* Fix Cursor hook delivery with preToolUse gate

* Simplify Cursor hook install to preToolUse

* Clarify confirmed hook exceptions

* Persist hook ignores in shared config

* Guard font hook exceptions

* Fix hook install after main rebase

* Fix hook scan target handling

* fix: address hook review findings

* Address hook review feedback

* Stabilize DeepSeek insert live fixture

* Fix Cursor hook Python shell write bypass

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 21:19:19 -07:00
Paul BakausandGitHub 05f9797957 Remove deprecated cleanup migration (#240) 2026-06-09 15:12:58 -07:00
Paul BakausandGitHub e3e22007a9 [codex] Improve detector false positive handling (#232)
* Improve detector false positive handling

* Register docs integrity test

* Fix clipped overflow decorative skip
2026-06-09 10:56:32 -07:00
Paul BakausandGitHub 82801a4894 [codex] Improve CI test coverage (#212)
* Improve CI test coverage

* Stabilize live E2E harness

* Shard live E2E CI

* Cache live E2E CI dependencies

* Stabilize live E2E smoke CI

* Update generated live browser bundles

* Tighten live E2E smoke runtime

* Prevent live E2E smoke hangs

* Stabilize live E2E CI coverage

* Fix stale accept DOM cleanup

* Regenerate live browser outputs
2026-06-08 10:39:12 -07:00
Paul Bakaus 1aedbcf538 Add Git submodule skill linking 2026-06-05 18:11:15 -07:00
Abdul WahabandGitHub 4e251061b8 Fix sr-only text overflow false positive (#197) 2026-06-05 15:25:34 -07:00
Abdul WahabandGitHub 347a0c06a2 Fix Windows skill bundle extraction (#198) 2026-06-05 11:16:23 -07:00
Abdul WahabandGitHub 69b5f3af49 Fix live detector empty state (#190)
* Fix live detector empty state

* Fix live detector stale scan results
2026-06-01 14:44:45 -07:00
Paul BakausandClaude Opus 4.8 b913668ba4 Remove the i- command prefix from the CLI
The `i-` prefix install option was a holdover from the multi-skill era.
With a single `impeccable` skill it only ever renamed that one skill to
`i-impeccable`, while the install message wrongly advertised `/i-audit`
style commands that never existed, and the unscoped rename could clobber
unrelated third-party skills in the same harness folder.

- Drop `--prefix=`, the interactive prompt, and all prefix machinery
  (renameSkillsWithPrefix, prefixSkillContent, detectPrefix, undoPrefix,
  prefixedCommandHint, isImpeccableSkillName).
- Add migrateUnprefixImpeccable: install --force and update rename any old
  `<prefix>impeccable` back to canonical `impeccable` before the fresh copy
  lands, scoped by name so foreign `i-*` skills are left untouched.
- Fix FAQ + editorial that wrongly described pinned commands as `i-`
  prefixed (pins are bare `skills/<command>/` dirs).
- Tests now exercise the real exported migration, not a reimplementation.
- CLI 2.3.1 -> 2.3.2 with a changelog entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:33:16 -07:00
83dd99bf9f refactor(codex): drop the .codex/agents sidecar; rely on nested skill agents (#173)
Codex auto-discovers subagents bundled inside an installed skill's own
agents/ folder, so the separate .codex/agents/*.toml sidecar was redundant.

- cli: remove installCodexAgents/isCodexLikely and their install/update calls
- context.mjs: remove the CODEX_AGENT_MISSING self-heal directive
- build: drop codex agentFormat so no top-level .codex/agents is emitted; the
  nested in-skill .toml bundling is the whole delivery
- remove the tracked .codex/agents/*.toml and the gitignore exception
- docs + build.test.js updated for the nested layout
- CLI patch version bump; skill version unchanged

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:20:42 -07:00
Paul BakausandClaude Opus 4.8 f7f2bfc800 feat(detector): deprecate --fast (now a no-op, full scan always)
Since the jsdom removal the static HTML/CSS analysis is fast (~4ms/file) and
covers every rule, so the regex-only `--fast` path only loses coverage (it
ran ~10 of 41 rules) for no real speed win. It's a foot-gun: a `--fast` scan
can read "clean" because most rules silently don't run.

Deprecate gracefully rather than hard-remove: the flag is still accepted (so
existing CI scripts don't break) but ignored, with a one-line stderr notice,
and the full scan always runs. Dropped from --help and the example. Removed
the `--fast` suggestion from the many-files warning and from critique.md's
scan guidance.

Ships to users via a CLI release (npm) and rides the next skill release in
the bundled detector. Tests updated to assert the deprecation behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 15:50:53 -07:00
Paul BakausandClaude Opus 4.8 7253b3870a Deliver the Codex asset-producer subagent reliably (#161)
Codex reads custom subagents from .codex/agents/*.toml, a directory
separate from where it reads skills (.agents/skills). Skill installers
(notably `npx skills add`, see vercel-labs/skills#1290) only carry the
skills/ subtree, so the asset-producer agent was never delivered.

- build: bundle the codex .toml inside the skill dir for the variants
  Codex loads as a skill (agents, codex), so it travels with the skill.
- cli: skills install/update now write .codex/agents/ for Codex-likely
  projects (a .agents target or a global ~/.codex); update heals a
  missing sidecar. Non-Codex projects are untouched.
- context.mjs: on boot under a Codex install, emit a self-healing
  CODEX_AGENT_MISSING directive pointing at the bundled copy when the
  project's .codex/agents/ definition is absent. Self-resolves on copy.

CLI 2.2.0 -> 2.3.0 (published). Skill stays 3.5.0 (unpublished); the
note is folded into the existing 3.5.0 changelog entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 15:07:40 -07:00