mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
8b326fc81e026fffbcdcecd94ce34af956ebea79
46
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fa44839f72 |
Fix detector URL scans and advisory handling (#709)
* Fix detector URL and advisory handling Recover joined URL arguments without splitting local paths, derive advisory behavior from registry severity across consumers, inspect readable linked CSS in URL scans, and report only the dominant primary font. AI assistance disclosure: Implemented and verified with Codex under maintainer direction. * Filter linked CSS to rendered selectors Flatten linked stylesheet grouping rules and collect only selector rules that target the live DOM, preventing unused grouped and selector-less patterns from leaking into URL findings. AI assistance disclosure: Implemented and verified with Codex under maintainer direction. * Fix detector review edge cases AI assistance disclosure: Codex implemented and verified these fixes under maintainer direction. * Preserve unresolved linked CSS selectors AI assistance disclosure: Codex implemented and verified this fix under maintainer direction. * Fix linked CSS selector filtering Resolve pseudo-element selectors to live hosts, reject unresolvable linked CSS findings, and make the regression assertions independent. Also ignore comment delimiters when recovering CSS rule selectors. AI assistance disclosure: This commit was prepared with Codex under maintainer direction. * Skip unresolved container query CSS Exclude linked container-query groups when their current applicability cannot be resolved, with a browser regression proving inactive styles do not leak. AI assistance disclosure: This commit was prepared with Codex under maintainer direction. * Detect active container query CSS Use a temporary custom-property probe so the browser decides whether a nested style rule actually applies in the current container layout. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction. * Filter inactive linked CSS states Keep valid empty pseudo-class matches authoritative and omit selector-less linked at-rules that cannot be tied to rendered nodes. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction. * Parse pseudo-elements without rewriting literals Preserve quoted attribute values and escaped identifiers while resolving real pseudo-elements to live hosts. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction. * Restore live linked keyframes AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Handle grouped linked keyframes AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Respect keyframe definition order AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Resolve effective linked keyframes AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Fix keyframe easing detection Serialize effective per-keyframe easing back into the linked stylesheet corpus so overshoot motion is detected. Add a browser regression with a neutral animation name.\n\nAI assistance disclosure: Codex helped implement and test this fix under maintainer direction. |
||
|
|
fc442be223 |
Fix: make hooks reset remove installed manifest entries (#668)
Fixes #512 and resolves the verified reproduction in #667.\n\nAI-assisted change: reviewed and merged by Codex under maintainer direction. |
||
|
|
cbd7870159 |
Address review: collision-resistant slugs, os.homedir() tilde expansion
- The per-project state dir key is now the readable separator-mapped slug plus an 8-hex sha256 of the resolved project path. The readable part alone is lossy (/x/my.app and /x/my-app both mapped to -x-my-app and shared hook state); the digest keeps distinct projects' cache and pending state apart while the dir name stays human-scannable. - Tilde roots now expand via os.homedir() instead of HOME/USERPROFILE with a '.' fallback. When no home dir can be determined, expansion is rejected and state falls back to the project-local default rather than anchoring under the hook process's working directory. - Tests updated to the digest-suffixed slug via a mirrored slugFor() helper, plus two new cases: colliding readable slugs get distinct state dirs, and the tilde form resolves identically to the explicit homedir-joined form. Prepared with AI assistance (Claude Code) under direction of 0xDarkMatter, per the maintainer-approved issue #422. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
30b3628f5b |
Expand a leading ~ in IMPECCABLE_CACHE_ROOT against the home dir
Env files and settings JSON hand '~/caches' to Node unexpanded; without this it would resolve to a literal '~' directory under the process cwd. Mirrors the exact treatment IMPECCABLE_HOOK_LOG already gets in writeAuditLog (HOME || USERPROFILE fallback), plus the Windows '~\' spelling. Prepared with AI assistance (Claude Code) under direction of 0xDarkMatter, per the maintainer-approved issue #422. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5c82d58b7e |
Harden IMPECCABLE_CACHE_ROOT edges: normalization, opt-in gate, failure path
- hookStateDir now trims the env value (stray whitespace in env files) and path.resolve()s both the root and the cwd, so trailing separators and relative segments slug to the same per-project dir. - The #344/#305 persist gate also treats an existing (possibly redirected) cache file as the opted-in marker. Without this, once state relocated, clean-edit editCount bumps stopped persisting because the project-local .impeccable/ dir never appears. No-op under stock paths, where the cache file lives inside .impeccable/. - New tests: slug normalization equivalences, whitespace trim, graceful persistCache failure on an unusable root, and three runHook end-to-end cases (findings persist + dedup through the redirect, clean-edit editCount persistence, and the no-footprint no-op gate holding under redirect). Prepared with AI assistance (Claude Code) under direction of 0xDarkMatter, per the maintainer-approved issue #422. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
77a2eae861 |
Add IMPECCABLE_CACHE_ROOT to relocate hook state out of project roots (#422)
Honor an optional IMPECCABLE_CACHE_ROOT env var in getCachePath() / getPendingPath(): when set, hook.cache.json and hook.pending.json land under $IMPECCABLE_CACHE_ROOT/<project-slug>/ (slug = project path with [:\/.] mapped to hyphens, mirroring Claude Code's ~/.claude/projects/ convention). Unset or blank env keeps stock project-local behavior. User-authored config (config.json, config.local.json, design.json) deliberately stays project-local - only disposable state relocates. Also clears ambient IMPECCABLE_CACHE_ROOT at the top of hook.test.mjs so a developer running the suite with the redirect active still gets deterministic stock-path assertions; the new suite sets and restores the var explicitly. Prepared with AI assistance (Claude Code) under direction of 0xDarkMatter, per the maintainer-approved issue #422. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
be87f5eb86 |
Fix: refuse inert exact ignore-value entries (#662)
ignore-value stored exact values for rules that cannot extract one, so the entries never matched. Refuse them and point at "*" --file. AI assistance: implemented with Cursor Grok 4.6. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
2064b0696f |
Merge origin/main into fix/603-codex-stop-payload
Keep Codex as its own Stop harness (decision/block) while taking main's Grok envelope detection and Stop cache sync. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
bfe634e254 |
Trim the Grok normalizer to the fields the hook reads
The hook_event_name mapping (and its pre_tool_use entry) had no reader: hook.mjs routes on the raw stdin via isStopEvent, and nothing downstream keys on the normalized event name. The trailing-slash strip duplicated the path.resolve every consumer already does. Pin the one commit-2 behavior that had no test: a Stop detector failure must leave the remembered set alone. Prepared with AI assistance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3c442af7ad |
Fix: sync Stop finding cache after a clean Grok scan
A clean Stop never replaced remembered keys, so a finding that was fixed and then reintroduced stayed silent. Remember the live scan, including empty, and persist that write. Prepared with AI assistance. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
35ae07339b |
Fix: parse Grok Build camelCase hook stdin (#646)
Grok was classified as GitHub Copilot, so the design hook skipped every edit with no-file-path and never ran Stop. Normalize toolInput/sessionId and treat Stop additionalContext as the Grok product. Prepared with AI assistance. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
c9e7cd8a64 |
Fix: emit Codex Stop hook as decision/block (#603)
Codex Stop rejects Claude's hookSpecificOutput shape. Detect Codex from
turn_id at runtime and emit { decision: "block", reason } so existing
installs keep working without rewriting hook commands.
AI-assisted change, prepared with Cursor Grok under maintainer direction.
Fixes #603
Fixes #643
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
611147a333 |
Sync marketplace Claude hook repair
Keep the committed marketplace repair script aligned with Claude Code's supported Edit and Write tools, and strengthen regression coverage after automated review.\n\nThis change was prepared with AI assistance under maintainer authorization. |
||
|
|
7d5c60d291 |
Remove stale Claude MultiEdit matcher
Claude Code now folds multi-edit behavior into Edit, so keep generated and repaired hook manifests aligned with the current Edit and Write tools. Grok keeps its compatibility matcher unchanged. AI assistance was used to implement and validate this change. |
||
|
|
dc4e4a4bd6 |
Denoise the design hook and let agents self-serve confident ignores (#508)
* Denoise the design hook and let agents self-serve confident ignores (#497)
The directive footer now emits in full once per session (a one-line
reminder after), the DESIGN.md staleness note is mentioned once per
session, rule descriptions dedupe within an emission, and the per-line
ignore suggestion shrinks to the bare rule/value pair. The footer and
hooks.md replace the confirmation-gated ignore policy with a three-way
triage: fix real problems, self-serve the narrowest ignore for confident
false positives or sanctioned exceptions and disclose it (with an honest
--reason), ask when unsure. Self-serve stops at ignore-value, and the
footer now gives a runnable hook-admin.mjs command instead of a slash
command agents cannot execute.
Measured on a seeded lab session replaying 11 hook events: 33,658 to
14,063 chars of agent-visible output (-58%).
AI-assisted (Cursor agent), directed and reviewed by @abdulwahabone.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Preserve the policy footer and honor maxChars under constrained budgets
Greptile's runtime check found two pre-existing clamp gaps that matter
more now that the full policy emits once per session: the last-resort
tail slice cut the footer off an over-budget emission, and the DESIGN.md
staleness note was appended after clamping, pushing past maxChars.
The clamp now gives the footer the budget first, clipping the finding
line and downgrading full to short policy when needed. The staleness
note defers, without consuming its session flag, to a later emission
with room.
AI-assisted (Cursor agent), directed and reviewed by @abdulwahabone.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden the constrained-budget clamp: keep findings, honest flags, guaranteed note
Review follow-ups from Bugbot and Greptile on the clamp fix:
- The clamp retries with the short policy before dropping finding lines
that fit beside it, and a grouped result that kept only a file header
no longer counts as a fit.
- The full-footer session flag commits only when the full policy
actually survived the clamp, so a downgraded emission does not mark
the session as having seen a policy it never received.
- Render paths reserve room for a pending DESIGN.md staleness note, so
it is delivered inside the budget on the first emission instead of
deferring behind full emissions indefinitely.
AI-assisted (Cursor agent), directed and reviewed by @abdulwahabone.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Route the Cursor deny cap through the clamp and match the whole footer
Bugbot follow-up: cursorBlockMessage tail-sliced at 4000 chars after
render, which the default 8000-char budget made reachable, and a cut
that spared the footer's opening words still committed the session
flag. The 4000 cap now feeds through the renderer's footer-preserving
clamp, and commitFooterShown matches the complete footer text instead
of a sentinel.
AI-assisted (Cursor agent), directed and reviewed by @abdulwahabone.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reconcile hook.test.mjs expectations with main's per-platform quoting
Three tests fell out of sync when main's quoteCommandArg change (#533,
building on #476) met this branch's footer/hint rework. Test-only
changes; production logic untouched:
- The full-footer test now accepts either close quote after the
hook-admin.mjs path, since quoteCommandArg single-quotes absolute
paths on POSIX and double-quotes them on Windows. The short-footer
guard rejects `node '` and `node "` alike.
- The #476 hostile-value test asserts the new bare
`ignore-value <rule> '<value>'` hint format. The security property is
unchanged: the value still passes through quoteCommandArg, so
$(touch pwned) stays single-quoted and inert.
- The #533 test previously asserted a concrete quoted `--file` path in
the footer; directiveFooter() now carries only literal placeholders,
so that surface is gone. The per-platform assertion moves to the
per-finding ignore hint, the remaining user-visible surface where
scanned file content flows through quoteCommandArg.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix two Bugbot findings: Cursor prefix budget and footer-cutting tail slice
Both flagged by Cursor Bugbot on PR #508 after the main merge; both real.
1. cursorBlockMessage computed min(maxChars, 4000 - prefix), so a
configured maxChars at or below the Cursor ceiling never charged the
BLOCK_PREFIX against the budget: the final deny text could exceed
maxChars by the prefix length, and appendDesignSystemNoteOnce's size
check lost exactly the room designNoteReserve had held back. The
prefix now comes off whichever limit binds. Default-config behavior
is unchanged (min(8000, 4000) - 60 equals the old 4000 - 60).
2. The note reservation is subtracted after renderTemplate's 500-char
floor, so the clamp can run below the budget clampLastLine assumed
safe, and its last-resort path tail-sliced the rendered text, cutting
the policy footer (the failure mode this PR exists to eliminate) when
a deep file path met a pending DESIGN.md note. The reservation order
stays (the staleness-note delivery guarantee at floor budgets depends
on it); the last resort now drops the finding line and clips the head
instead, so the footer survives every path. New regression test pins
it: 6 findings, 100-char path, maxChars 500, reserveChars 134.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Charge the Cursor deny prefix after the renderer's floor, not before
Greptile's runtime check caught the residual from
|
||
|
|
490dcfd678 |
Fix #476: stop using JSON.stringify/double quotes as shell quoting in four exec sites (#533)
* Fix: use argv exec and single-quote escaping for the four #476 shell-injection sites JSON.stringify and raw double-quote interpolation were used as shell quoting, but /bin/sh still expands $(...), backticks, and ${} inside double quotes. - is-generated.mjs / live.mjs runScript: switch execSync string commands to execFileSync argv form, which never invokes a shell. Closes the remote path where a source file named `$(...)` executes during the live-mode walk. - skills.mjs hook command + hook-lib.mjs ignore-value suggestion: values that must stay shell strings now use POSIX single-quote escaping instead of JSON/double quotes. The doctor's hook-token parser learns the single-quoted absolute form so it keeps verifying user-level installs. Adds regression tests for the single-quoted absolute hook form and the single-quoted ignore-value suggestion. Verified end to end in a browser through a real live-mode wrap walk against a hostile-named source file. Prepared with AI assistance (Cursor) under maintainer instruction. Co-authored-by: Cursor <cursoragent@cursor.com> * Test: lock in POSIX single-quoting for a $(...) absolute install path (#476) Follow-up from security review: prove an install path embedding $(...) is single-quoted in the written hook manifest, not double-quoted. Prepared with AI assistance (Cursor) under maintainer instruction. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix: quote ignore-command args per platform so Windows cmd.exe keeps spaces (#533) Greptile flagged that switching quoteCommandArg to POSIX single quotes fixed $(...) injection on /bin/sh but regressed Windows cmd.exe, where single quotes are literal, so a --file path containing spaces was split and the ignore scope was stored malformed. The suggested command runs on the same machine the hook fired on, so branch on process.platform (the pattern skills.mjs already uses): single-quote on POSIX for the #476 fix, and keep the original double-quote escaping on Windows so that path's behavior is unchanged. Adds a regression test asserting both forms. Prepared with AI assistance (Cursor) under maintainer instruction. Co-authored-by: Cursor <cursoragent@cursor.com> * Test: prove the POSIX hook guard is inert under /bin/sh and Windows keeps double quotes (#533) Greptile's probe could not reach the generated manifest, leaving the hook command contract unverified. Convert that into committed proof: - POSIX: install with a $(touch pwned) absolute path, then actually execute the generated guard under /bin/sh from a clean cwd and assert no marker file appears and the guard exits 0 (single-quoted substitution stays inert). - Windows: drive copyProviderHooks as win32 in-process and assert the command keeps the double-quoted absolute path (usable when the install path has spaces; $(...) is inert on cmd.exe anyway). Test-only; source quoting is unchanged. Prepared with AI assistance (Cursor) under maintainer instruction. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
2345868c7b |
Preserve detector extension mappings
Keep unmanaged detector fields when ignore-file updates the canonical detector configuration. Add a regression covering existing extension mappings.\n\nAI assistance: Codex implemented and validated this change under maintainer authorization. |
||
|
|
57ce11288f |
Preserve detector extensions
AI assistance: Codex addressed review feedback and validated this follow-up under maintainer authorization. |
||
|
|
ae118ebf57 |
Migrate legacy advisory settings
AI assistance: Codex identified, implemented, and validated this review follow-up under maintainer authorization. |
||
|
|
3125864d1a |
Preserve advisory detector settings
AI assistance was used to reproduce and fix automated review feedback, add regression coverage, and run validation. |
||
|
|
b32a02d02f |
Fix ignore-file flag handling
AI assistance was used to reproduce the issue, implement the fix, and add regression coverage. |
||
|
|
ae2be34fdc |
Merge pull request #471 from pbakaus/hook-skip-outside-project
fix: skip design-hook scans for files outside the resolved project root |
||
|
|
febce52e8d |
refactor: share the containment gate with hook-before-edit
hook-before-edit.mjs kept its own string-based isInsideProject; it now uses the shared isScanTargetInsideProject so all three hook passes apply one containment semantic, symlink canonicalization included. Because the before-edit hook gates proposed Writes whose target does not exist yet, canonicalPath now resolves the nearest existing ancestor and re-appends the remainder instead of falling back to the raw resolved path — a new file under a symlinked root compares equal to its canonical project. Written with AI assistance (Claude Code). Co-Authored-By: Claude Code <noreply@anthropic.com> |
||
|
|
ae03e9e09c |
fix: skip design-hook scans for files outside the resolved project root
The per-edit and Stop deep passes gated on sensitive paths, generated paths, extension, config ignores, and size, but never on containment. Any file the session touched outside the project (harness scratchpad dirs under the system temp root, sibling checkouts) was scanned and judged against THIS project's config and DESIGN.md palette, producing design-system findings that are wrong by construction. Both loops now check isScanTargetInsideProject() (audit reason: outside-project), matching the gate hook-before-edit.mjs already had. Paths are canonicalized so a symlinked root doesn't split the comparison. The Stop pass re-checks containment itself because caches written by older hook versions can still list out-of-project paths. Umbrella-dir launches (issue #305) are unaffected: their projectCwd resolves to the edited file's own project root, so containment holds. Written with AI assistance (Claude Code). Co-Authored-By: Claude Code <noreply@anthropic.com> |
||
|
|
99c4189788 |
Fix Google Fonts value suppression
AI-assisted change. |
||
|
|
47aff2e0be |
Fix Stop-hook loop: honor stop_hook_active per Claude Code contract
The Stop deep pass (runStopHook) never read the stop_hook_active field from the Claude Code Stop-hook event. When a prior fire kept the turn alive via hookSpecificOutput.additionalContext and the agent legitimately declined to act, the hook re-scanned and re-blocked every re-invocation until Claude Code's consecutive-block cap force-ended the turn (issue #400). Read stop_hook_active early in runStopHook, right after the event is parsed and before any scan, and exit 0 with no output when it is true. The prior fire already surfaced the findings; acting on them is the agent's call. Only Claude Code sends this field, so the strict === true is a no-op for other harnesses. runHook (PostToolUse) and hook-before-edit.mjs (PreToolUse) never receive the field, so they are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
270f4d20aa |
Make em-dash-overuse an advisory rule with browser parity
Em-dashes are used legitimately by humans, so em-dash-overuse fired far too often. Reclassify it as the first advisory-tier rule: detected, but never a failure. Engine - Add `advisory: true` to the rule metadata schema (em-dash-overuse is the first). findings.mjs stamps `advisory: true` on advisory findings so every consumer can partition without a registry lookup. Rule count stays 58. - Raise the firing threshold from a flat 5 dashes to two gates: an absolute floor of 8 and a density of about one dash per 500 characters of body text. A long article that uses a few em-dashes no longer trips; a short, dash-per-clause page still does. Entity decoding (mdash, numeric, hex) is unchanged. Thresholds live in shared/constants.mjs so every engine agrees. Browser parity - The browser bundle carried a registry entry but no logic, so the overlay and extension could never flag it. Add checkEmDashOveruse / checkEmDashOveruseDOM in rules/checks.mjs (reads rendered text, no entity decoding needed), wire it into the injected page-level pass, and carry the advisory flag through serializeFindings so the overlay/extension can render it with the mildest affordance. CLI - Advisory findings print under a separate dimmed "Advisory" section, are excluded from the failure count, and never change the exit code (an advisory-only scan exits 0). JSON keeps them with `"advisory": true`. `--no-advisory` suppresses them entirely. Hook - Advisory rules are skipped by default in both the per-edit and Stop deep-pass hooks, so the hook never nags about them. Opt in with `.impeccable/config.json` -> `detector.advisoryRules: "include"`. Tests - Fixture + threshold + browser-adapter coverage; advisory-skip default and opt-in for the hook; formatFindings partitioning. The em-dash-overuse stand for a deferred copy rule in the tier tests is swapped to marketing-buzzword. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
36e3c05ca7 |
Restore dice assignment, fusion, and the commitment counterweights
The ship40 concept pipeline had reversed the proven a-series mechanisms: the seed's roll decayed into a shortlist nomination that taste functions (model ranking, candidate floor, simulated user) then argmaxed into the safest card; the costume check returned as the Translation veto and carrier-removal test; and the 07-15 rewrite deleted the calibration, reflex-font lanes, color strategies, and commit-every-atom language that had held off the cream-editorial default since the alpha era. Five of six frozen craft directions converged on the same warm-paper family and both builders obeyed them. This lands the repair on top of the in-progress simplification: - new-work.md: the script assigns the build index again on both scopes; catalog challengers are fused (challenger supplies form and grammar, product supplies every fact, clarity wins conflicts) and weighed on the two proven axes only; attended runs present one fully committed direction with re-roll and an optional steer instead of a ranked lineup; the color-strategy picker, reflex-face list, saturated-look calibration, first-viewport thesis and memory test, commit-every-atom, scroll pacing, and prove-don't-claim return; the direction contract returns as five lean blocks audited by the separate-agent finish. - concept-seed.mjs: PROMOTED INDEX becomes ASSIGNED INDEX with build-assignment semantics; self re-roll only on named factual grounds. - craft-floor.md: hook-active sessions act on findings instead of re-auditing; the Refuse list is framed as category defaults the brief can earn; a closing commitment line keeps a ban list from being the last word before code. - codex.md / shape.md: contract references restored for flow coherence. Adopts the concurrent session's ceremony cuts, softened challenger instruction, seed SOURCE IDs and --candidate-count, detector-ownership fix, and the removal of the hook-side contract audit (the audit now belongs to the separate reviewer at finish). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
153b416f2e |
Move the slop defects back into the craft floor
The detector-blind slop review existed because the AI-tell rules had been stripped out of SKILL.md and nothing carried them. The floor is a better home: it loads after concept ideation and immediately before editing UI, which is the placement that made stripping them necessary in the first place. Models tread lightly when a ban list is present during ideation; by the time the floor loads, the direction is already committed. - Rename build-floor.md to craft-floor.md and restore the absolute bans (side-stripes, gradient text, glassmorphism, hero-metric, identical card grids, eyebrow-on-every-section, numbered markers, text overflow), the codex and gemini defect lists, and the reflexes no scanner catches. Rule ids match the ones the ablation catalog already knows. - Delete lib/slop-review.mjs and both injections. The Stop hook is now purely a mechanical pass and stays silent with nothing to report. - context.mjs replaces AI_SLOP_REVIEW_REQUIRED with the narrower MANUAL_DETECTOR_REQUIRED, emitted only when a session has no hook at all. A per-edit hook already covers the mechanical gap, and the floor covers the judgment one either way. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
d7d10277d1 |
Merge main into oneshot-v4, keeping the service layer split out
main still carries the site, so every `site/` path resolves to deleted. `tests/docs-integrity.test.js` goes with it (it imports the site's demo renderer), and `package.json` keeps main's `@anthropic-ai/sdk` bump while dropping `@google/genai` and `@paper-design/shaders`, which nothing in the product layer imports. Real code merges: - hook-lib: main's #391 cache fix (sync the remembered set to the live scan so fixed findings stop being named and a reintroduced one fires again) now runs on the immediate tier rather than the whole filtered set. Remembering a deferred finding the per-edit pass never reported would let the Stop deep pass dedupe it away. main's `maxFileBytes` ceiling, `cleanAcked` once-per-file ack, and template-extensions re-export all land alongside the tiering work. - live-browser: main's `hasParams` gate on the Tune badge, keeping this branch's `C.ink` badge text so it stays legible on kinpaku gold. - detect-text: both the block-level codex-grid-background scan and main's inset-stripe CSS check. - test-suites: union of both trigger sets and file lists, minus the site-only entries (`shiki-theme`, `docs-integrity`). - Two hook tests moved off deferred-tier rules (`overused-font`, `side-tab`) onto immediate-tier ones. They assert cache bookkeeping, which the per-edit pass only reaches for the immediate tier. Also drops the site waivers from `.impeccable/config.json` and stops `build:browser` recreating a stray `site/` tree just to write a bundle the other repo builds itself. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
d146d2084b |
Stop the design hook lying about findings it already reported (#391)
* Stop the design hook lying about findings it already reported
Three fixes, all aimed at the hook being trustworthy enough that an agent
keeps reading it.
1. The session cache was append-only, so the hook lied and then went blind.
`rememberFindings` unioned new keys into the remembered set and nothing ever
removed them, and the pending ack took its count from that set rather than
from the live scan. Fixing two of three findings produced:
Still has 3 finding(s) flagged earlier this session
(overused-font:1:inter, overused-font:2:roboto, overused-font:3:geist)
with roboto and geist already gone. Worse, a finding that was fixed and then
reintroduced was deduped against the stale memory and never re-reported, so
the hook was permanently blind to that regression for the rest of the session.
The cache now syncs to the complete current scan on every scan, so the count
shrinks as work lands and a reintroduced finding reads as fresh. Dedup within
a session still works, because it compares against the previous scan rather
than against all history. A detector failure leaves the remembered set alone
instead of recording an empty scan as truth.
2. The size ceiling, for generated files that do not live under dist/.
`GENERATED_PATH` covered dist, build, out, .next, .cache, coverage and
.min., but repos commit browser bundles and vendored detector copies next to
source. The hook was reading and scanning a 215KB generated bundle, and
reporting findings in it. Added `generated` as a path segment, matched with
separators on both sides so authored names such as generated-utils.ts and
CodeGenerator.tsx still get scanned, plus a `limits.maxFileBytes` ceiling
defaulting to 128KB. In this codebase authored files top out at 86KB while
the bundles start at 215KB, so the gap is comfortable.
3. The clean ack repeated on every clean edit.
It carries no finding, only the standing steer that a silent hook is not a
verdict on the design. That steer is worth saying, but not dozens of times
per session. It now fires once per file per session and reports
`clean-ack-deduped` in the audit log so suppressed noise stays visible. The
pending ack is deliberately untouched: it names real unresolved work, and the
comment explaining why it must repeat still holds.
Verified end-to-end against the built hook: three findings, fix two and the
count drops to one naming only the survivor, fix the last and it goes clean,
edit again and it stays silent, reintroduce and it fires as fresh.
Generated provider output is deliberately left out; the sync workflow owns it.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Address review: three clean-ack and audit bugs in the dedupe change
All three were introduced by this PR and all three are fair catches.
Quiet mode spent the ack (bugbot). A clean scan marked cleanAcked and
persisted it even when quiet suppressed all output, so a later non-quiet run
in the same session never got the steer. The quiet decision is now hoisted
above the scan loop and quiet leaves the ack unspent.
Multi-file events lost the ack (copilot). The first clean target became
cleanWinner unconditionally; if that file was already acked, cleanAckDeduped
went true and the `!cleanWinner` guard meant a later target that had never
been acked could never win. A raw apply_patch touching two files would drop
the second file's ack entirely. The loop now keeps looking for a target that
is actually owed an ack.
audit.bytes leaked across targets (copilot). It was set when a file was
skipped as too-large and never cleared, so in a multi-file event a later
emitted result carried the skipped file's byte count. Cleared per iteration.
The tests use a raw apply_patch payload rather than MultiEdit, because
MultiEdit in this harness is single-file ({ file_path, edits: [] }) and would
not have exercised the multi-target paths at all. Verified the three tests
fail against the pre-fix code and pass after, so they are not passing for the
wrong reason.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Address review: font-size waivers silently did nothing
Two more review findings, both real.
Specific-value font-size waivers were dead config (greptile). The rule emits an
ignoreValue, and the hook's own directive footer tells the agent to waive
value-specific findings with `hooks ignore-value <rule> <value>`, but
`design-system-font-size` was missing from the direct-value rule set in
`extractFindingIgnoreValue`. The extracted value came back empty, so any
waiver naming an actual size was compared against nothing and silently
dropped. Only the `*` wildcard worked, which is why the framework-viz waiver
earlier in this branch appeared to function.
Reproduced against the built hook: with a `0.82rem` waiver the finding still
fired; it now goes clean, while a waiver naming a different size correctly
still fires, so this is not over-matching.
Wrong audit skip reason (bugbot). In a mixed multi-target run, an earlier UI
file whose ack was already spent set `cleanAckDeduped`, and a later non-UI
clean file became the winner. The tail then reported `clean-ack-deduped` when
the honest reason was `non-ui-ack`. Audit-label only, no behavior change.
Reordered so the winner is described first and dedupe is reported only when it
is genuinely why nothing was emitted.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Mirror the font-size waiver fix into the CLI's config reader
Bugbot caught that the previous commit only fixed one of two copies.
`extractFindingIgnoreValue` exists twice, in skill/scripts/hook-lib.mjs and in
cli/lib/impeccable-config.mjs, and the direct-value rule list is duplicated in
both. Adding design-system-font-size to the hook alone meant the same
.impeccable/config.json filtered differently depending on the entry point: a
size waiver was honored by the hook and ignored by `npx impeccable detect`.
The two functions are otherwise byte-identical, so this restores parity rather
than changing CLI behavior independently. The new test notes the duplication so
the next person knows the pair has drifted once already.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix the audit byte-count leak properly, not just one scan order
My earlier fix cleared audit.bytes at the top of each iteration, which was
wrong twice over, and bugbot caught both.
The clear sat below the sensitive, generated, extension, ignore-file and
file-missing continues, so a later target exiting through any of those never
reached it and kept the oversized file's size while audit.file pointed
somewhere else. It also only handled the bundle-scanned-first order; when the
oversized file came last, the byte count was set after the emitting file had
already been decided and rode along on its audit entry regardless.
The root problem was keeping per-file state on the shared audit object. The
size is now held in a local and attached only when the oversized skip is the
run's actual outcome, so it cannot describe a file other than the one being
reported. Tests cover both scan orders, an early-continue target after the
skip, and the single-oversized-file case where the count should still appear.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
331540ddec |
Scope a single rule to a file with ignore-value "*" --file (#379)
* Scope a single rule to a file with ignore-value "*" --file
`ignore-file <glob>` was the only file-scoped escape the hook offered, and
it is far blunter than most findings justify: it silences every rule for
that path forever, including rules not written yet. A real UI surface with
one noisy rule had no proportionate option.
Add a file scope to `ignore-value`, so one rule can be turned off in
matching files while staying active everywhere else:
hooks ignore-value design-system-font-size "*" --file "src/widget.js"
- Refuse a bare `"*"` with no `--file`. Suppressing a rule project-wide is
`ignore-rule`'s job, and the error says so.
- Reject unknown `--flags` instead of folding them into the value.
`ignore-value overused-font Inter --shard` stored the value
"inter --shard", matched no finding, and reported success.
- Key dedup on the file scope too. The same rule/value legitimately
appears more than once with different scopes; the old rule+value key
silently overwrote the earlier entry.
- Keep normalizer key order (rule, value, files, createdAt, reason) in
step across both copies. Normalizing runs on every write, so emitting a
different order than what is on disk rewrites untouched entries.
- Lead with the narrow form in the hook's directive footer and hooks.md;
`ignore-file` is now documented as the whole-file-out-of-scope case.
Dogfoods it on skill/scripts/live-browser.js, where all 32 findings are
design-system-font-size: the overlay is injected over arbitrary host pages
and builds a self-contained UI, so DESIGN.md's ramp does not describe it.
The other rules stay live for that file.
Assisted-by: Claude Code
* Show the file scope in hooks status, and stop the wildcard error misdirecting
Two findings from Cursor.
status formatted every ignore value as rule=value and dropped files. Now
that the primary hooks path writes file-scoped `"*"` entries, that rendered
`design-system-font-size=*` — which reads as exactly the project-wide
wildcard this command refuses, the opposite of what is on disk. Print the
scope, matching the `rule=value [files]` shape `impeccable ignores list`
already uses. This repo's own config already carries several scoped
wildcards written through the CLI path, so status has been under-reporting
them.
The bare-wildcard refusal always pointed at `ignore-rule <rule>`. For
overused-font that command refuses on its own without --all-values, so the
guidance handed the user a second error. Name the flag for that rule.
Assisted-by: Claude Code
* Refuse an empty --file glob, and store multi-file scopes in canonical order
Two Copilot findings, both the silent-no-op class this PR exists to remove.
An empty glob was dropped by filter(Boolean). So
`ignore-value overused-font Inter --file=` reported "Added
overused-font=inter" and wrote an entry with no files: the user asked to
scope a rule to one file and silently got the project-wide suppression
instead — broader than what they asked for, reported as success. Refuse an
empty or whitespace glob on every form (--file, --file=, --files, --files=)
in both the hook-admin and CLI paths.
Multi-file scopes were deduped but not ordered, and the dedup key compares
the files array, so `--file b.css --file a.css` stored a second entry
distinct from `--file a.css --file b.css`. Sort at parse so storage is
canonical, and sort inside the key so entries already on disk in another
order still compare equal.
Assisted-by: Claude Code
* Sort files in every dedup key, not just two of the four
My previous commit sorted the file scope at parse time and inside
ignoreValueFilesKey, and stopped there. Cursor pointed out ignoreValueKey
(CLI) and ignoreValueEntryKey (hook-admin) still joined `files` in stored
order, so add/remove dedup missed any on-disk scope whose glob order
differed from the sorted argv form: a re-add duplicated the entry and a
remove silently failed.
Four functions hash `files`; I had fixed two. All four sort now. The
remaining `files.join(', ')` call sites are display, not keys.
Verified against a config seeded in non-sorted order, as an older client
would have written it: the re-add updates the existing entry rather than
duplicating it, and remove-value finds it. Test covers that shape.
Assisted-by: Claude Code
* Refuse a following flag as a --file glob
Cursor again, same class as the last two. requireGlob checked non-empty but
not whether the argv it consumed was itself a flag, so
`ignore-value design-system-font-size "*" --file --reason "why"` took
`--reason` as the scope, left "why" to fold into the value, stored
value="* why" files=["--reason"], and reported success. Garbage, announced
as done.
Refuse a glob starting with `--`, in both the hook-admin and CLI paths.
Assisted-by: Claude Code
|
||
|
|
2b1f36c43e |
Add concept world catalog and review workflow
AI-assisted: prepared by Codex at Paul's request. |
||
|
|
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. |
||
|
|
313b641361 |
hooks: direction-contract audit in the Stop deep pass
The skill's decide-then-build step opens the built HTML artifact with a DIRECTION CONTRACT comment (UNIQUE / NOT-TEMPLATE / OWN-WORLD / STORY / FIRST VIEWPORT / FORM). Until now nothing ever judged the finished build against that contract; the eval harness proved sample contracts promised radical compositions while the build shipped the standard template anyway. The Stop deep pass now extracts the leading contract comment from each session-touched HTML file (marker match in the first 200 chars, body capped at 1800 chars) and appends a contract-audit section after the detector findings: audit the render promise by promise, naming the two observed failure shapes (a promise not in the pixels; a contract whose own plan is the standard template wearing the concept's nouns). Zero extra API calls; the audit rides the existing single Stop emission and fires at most once per file per session via a contractAudited flag on the same session cache entry the finding dedupe uses. Ported from the eval harness reference implementation (extractDirectionContract / composeContractAuditMessage in impeccable-evals runner/workers/anthropic-native.ts). No hooks.json changes needed: Claude Code and Codex both already dispatch Stop to hook.mjs. Tests: 163 -> 179 in tests/hook.test.mjs (extraction unit coverage plus Stop-pass integration: present/absent/once-per-session/non-HTML/ malformed/oversized). hook-build 18/18, build:skills prose gate clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c3aba1e343 |
hooks: two-tier design hook — immediate per-edit rules + full-set Stop deep pass
Eval evidence showed the per-edit PostToolUse stream fires overwhelmingly
on copy-level rules (em-dash-overuse ~97x/session) and measurably makes
models more conservative, while a full-detector pass at completion is what
actually fixes contrast/padding/glow. Split the hook accordingly:
- Per-edit (PostToolUse) now surfaces only IMMEDIATE_TIER_RULES: broken
output (broken-image, text-overflow, clipped-overflow-container,
body-text-viewport-edge), objective contrast/legibility failures
(low-contrast, gray-on-color, tiny-text), single-property mechanical
slop (gradient-text, dark-glow), and design-system drift (the four
design-system-* rules, which compound if left uncorrected). Everything
else defers. Override with hook.perEditRules: "all" in
.impeccable/config.json. Tiering is off for Cursor/Copilot harnesses,
which have no Stop pass wired, so nothing gets silently dropped there.
- Stop deep pass (runStopHook): runs the FULL rule set over every UI file
touched this session (tracked via the existing hook.cache.json session
state; deferred-only edits now mark the file touched), dedupes against
everything already surfaced per-edit, honors ignore-rule/file/value and
inline disables, reuses the [impeccable@1] envelope, and no-ops fast
when no UI files were touched. Emits hookSpecificOutput
{ hookEventName: "Stop", additionalContext } per the Claude Code SDK
Stop contract (conversation continues so the model can act on it).
Second Stop fire is silent - deep-pass findings are remembered.
- Wiring: Stop entries (timeout 30) in plugin/hooks/hooks.json, the
.claude settings + .codex hooks manifests (transformers + hook-admin
repair path). Claude Code and Codex both dispatch a native Stop event;
Cursor's stop hook is inconsistently dispatched (pre-write gate stays)
and Copilot's agentStop/sessionEnd don't inject model context, so
neither gets a Stop entry - documented in reference/hooks.md.
- Tests: tiering split/override/harness gating, Stop dedupe + silent
no-touched-files + ignore machinery + kill switches; existing per-edit
tests moved to immediate-tier rule ids. 181 tests green; smoke-tested
the built dist skill end to end (glow surfaced per-edit, em-dash only
at Stop, second Stop silent).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
3e38e595c7 |
Add platform axis (web / ios / android / adaptive) (#269)
* Add a platform axis (web / ios / android / adaptive) to the skill Orthogonal to register: register decides whether design IS or SERVES the product; platform decides the delivery target and which native conventions apply. Set `## Platform` in PRODUCT.md; a missing field defaults to `web`, so legacy projects are unaffected. - extractPlatform() in skill/scripts/context.mjs (mirrors extractRegister); the CLI appends a NEXT STEP directive to read the native reference(s). `adaptive` (Flutter / RN / KMP shipping both iOS and Android) loads both ios.md and android.md. - New reference/ios.md (Apple HIG distilled) and reference/android.md (Material 3 distilled); reference/web.md is a thin pointer. The native refs frame register's role as narrow: platform conformance is the bar, brand lives in the expressive layer the platform gives you, never by breaking the rails. - Setup step 5 loads the native reference(s) when platform is native. Live mode and the detect CLI stay web-only, gated off ios/android/adaptive. - init asks platform right after register; adapt/audit/animate/layout carry short platform divergence notes; all secondary spots thread `adaptive`. - a11y stays in audit.md (loading it at design time makes output timid), so the native refs carry no Accessibility section; audit.md's Platform section owns native a11y. - Tests: extractPlatform unit coverage + skill-behavior scenario 10 (PRODUCT.md platform ios -> agent loads ios.md). Source-first: only skill/, scripts/, tests/, CLAUDE.md, NOTICE.md, the changelog and version are committed; the sync workflow regenerates the provider trees and ./plugin on merge. ios.md / android.md are distilled from the MIT-licensed ehmo/platform-design-skills; attribution in NOTICE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address review: gate web tools on native platforms, drop version churn Maintainer-review fixes applied with AI assistance (Claude Code), on top of the rebased platform-axis commit: - Design hook (post-edit and Cursor pre-edit) now resolves the project platform via loadContext + extractPlatform and skips its web rule scan for ios / android / adaptive projects, so React Native / Flutter code never draws web-shaped findings (new hook-lib resolveProjectPlatform / isNativePlatform helpers, covered by unit and subprocess tests). - context.mjs CLI warns on an unrecognized ## Platform value (e.g. a toolchain name like `flutter`) instead of silently defaulting to web; extractRegister / extractPlatform now share extractSectionValue. - Removed reference/web.md: nothing loaded it; CLAUDE.md carries the "web has no extra rulebook" explanation. - init.md: skip live-mode config (Step 6) for native platforms; note the per-app PRODUCT.md pattern for repos shipping web + native. - android.md: Material-everywhere apps that also ship on iPhone still owe iOS OS guarantees (safe areas, Reduce Motion, edge-swipe back). - ios.md: reworded a design-time line that framed Dynamic Type as an accessibility check (a11y stays owned by audit.md). - Renumbered the new skill-behavior scenario to 14 after main's 10-13; updated CLAUDE.md scenario list; added android + unrecognized-value CLI test cases. - No version or changelog changes: versioning happens at release time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Tighten platform reference prose Editorial pass on the platform-axis text, applied with AI assistance (Claude Code) under maintainer direction: - ios.md / android.md rewritten to house style: single-line paragraphs (no hard wraps), one-sentence scope intro, deduplicated intro/slop-test, register-compression down to two sentences. In-file attribution paragraphs removed (NOTICE.md owns attribution); "read on top of the register reference" cruft removed (SKILL step 5 and the context.mjs directive already say it). Bans sections dropped: they restated the rules above them; the two additive items (tab-bar overload, hover-dependent affordances) folded into rules. ~40% smaller each. - Sub-command Platform sections (adapt, audit, animate, layout), SKILL step 5, init.md platform prose, and the context.mjs directive trimmed the same way. Build (prose validators, counts) and both test runners green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Treat an empty PRODUCT.md section as absent, not the next heading Copilot review catch: extractSectionValue read the next `## ...` heading as the section value when a field was left empty, which made the CLI warn "value `## Product Purpose` is not recognized". Stop at the next heading and return null instead. Regression tests for extractPlatform, extractRegister, and the CLI warning path. Applied with AI assistance (Claude Code) under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Only read a token list of both native targets as adaptive Bugbot catch: after the exact platform tokens failed, any Platform line containing the words ios and android was classified adaptive, so negated or explanatory prose ("web only, not ios or android") silently loaded both native refs and skipped the hook, with no warning. The combo parse now accepts only list separators and the two platform words; anything else falls through to the CLI's unrecognized-value WARNING. Regression tests added. Applied with AI assistance (Claude Code) under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com> |
||
|
|
95b67ffa83 |
Add configurable detector extensions for server-side templates (#347)
* Add configurable detector extensions for server-side templates (#316) Co-authored-by: Cursor <cursoragent@cursor.com> * Use imperative voice for detector.extensions guidance in hooks.md Co-authored-by: Cursor <cursoragent@cursor.com> * Route html-engine extensions through detectHtml in the Cursor pre-write gate Co-authored-by: Cursor <cursoragent@cursor.com> * Prefer the longest matching suffix in matchConfiguredExtension 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> |
||
|
|
751ec31dd6 |
Fix: stop the design hook from creating .impeccable/ in unrelated projects (#346)
The PostToolUse hook was writing hook.cache.json after every edit, even when nothing was scanned or recorded. Gate the persist to earned writes only, and key the cache to the edited file's project root when the session starts from an umbrella directory. Fixes #344, #305 Co-authored-by: Abdul Wahab <abdulwahab@Abduls-MacBook-Pro-2.local> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
858b9bbea6 | Improve hook false-positive handling | ||
|
|
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> |
||
|
|
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> |