Skip to content

Add design detector PostToolUse hook for Claude Code and Codex#170

Open
abdulwahabone wants to merge 19 commits into
pbakaus:mainfrom
abdulwahabone:feature/design-hook-impl
Open

Add design detector PostToolUse hook for Claude Code and Codex#170
abdulwahabone wants to merge 19 commits into
pbakaus:mainfrom
abdulwahabone:feature/design-hook-impl

Conversation

@abdulwahabone
Copy link
Copy Markdown
Contributor

@abdulwahabone abdulwahabone commented May 28, 2026

PREVIEW

Claude
image
Codex
Screenshot 2026-05-29 at 4 36 15 AM
Cursor
Screenshot 2026-05-30 at 4 13 09 AM

Summary

  • Ships a PostToolUse design hook that runs the slop detector after file edits and injects findings via hookSpecificOutput.additionalContext (never blocks the turn).
  • Adds SessionStart orientation hook (throttled, UI-project gated) for Claude Code and Codex plugin installs.
  • Codex fixes from manual testing: parse apply_patch file paths from tool_input.command (not file_path), and co-scan co-located/imported stylesheets when a JSX/TSX component is edited so App.jsx patches don't miss slop in styles.css.
  • Collapses to a single PostToolUse matcher (Edit|Write|MultiEdit|apply_patch on Claude; Edit|Write|apply_patch on Codex .agents) — git-status sweep removed after field testing showed noise > signal.
  • Build pipeline emits hooks/hooks.json for Claude, .agents, and marketplace plugin/; includes hook admin skill command, PRD, README install notes, and 50+ unit tests.

Anti-patterns detected

The hook scans .tsx, .jsx, .html, .vue, .svelte, .astro, .css, .scss, .less, .ts, and .js after each direct edit. It uses the same detector as npx impeccable detect:

  • Source files (everything except .html/.htm): regex + page-level analyzers via detectText.
  • HTML files: full static-html/jsdom pass via detectHtml (layout, contrast, typography, DOM structure).

Page-level rules (marked below) only fire when the file looks like a full page (<!doctype>, <html>, or <head>). For deeper coverage on rendered apps (dev-server URLs, browser layout), use /impeccable audit.

ID Name Category Source files HTML files
side-tab Side-tab accent border slop Yes Yes
border-accent-on-rounded Border accent on rounded element slop Yes Yes
overused-font Overused font slop Yes Yes
single-font Single font for everything slop Full page only Yes
flat-type-hierarchy Flat type hierarchy slop Full page only Yes
gradient-text Gradient text slop Yes Yes
ai-color-palette AI color palette slop Yes Yes
nested-cards Nested cards slop Yes
monotonous-spacing Monotonous spacing slop Full page only Yes
everything-centered Everything centered slop Full page only Yes
bounce-easing Bounce or elastic easing slop Yes Yes
dark-glow Dark mode with glowing accents slop Full page only Yes
icon-tile-stack Icon tile stacked above heading slop Yes
italic-serif-display Italic serif display headline slop Yes
hero-eyebrow-chip Hero eyebrow / pill chip slop Yes
repeated-section-kickers Repeated section kicker labels slop (advisory) Yes*
pure-black-white Pure black background quality Yes Yes
gray-on-color Gray text on colored background quality Yes Yes
low-contrast Low contrast text quality Yes
layout-transition Layout property animation quality Yes Yes
line-length Line length too long quality Yes
cramped-padding Cramped padding quality Yes
body-text-viewport-edge Body text touching viewport edge quality Yes
tight-leading Tight line height quality Yes
skipped-heading Skipped heading level quality Yes
justified-text Justified text quality Yes
tiny-text Tiny body text quality Yes
all-caps-body All-caps body text quality Yes
wide-tracking Wide letter spacing on body text quality Yes

* repeated-section-kickers is advisory severity — hidden by default (minSeverity: warning). Surface it with "minSeverity": "advisory" in .impeccable/hook.json.

29 rules total — 14 on source files (9 line-level + 5 page-level), all 29 on HTML. Component-level .tsx/.jsx edits get the regex slice; co-located stylesheet scanning helps catch CSS-side tells the component import pulls in.


Note

Medium Risk
Large new hook layer runs on every UI file edit and injects imperative model instructions; mitigated by non-blocking exits and path/sensitivity guards, but behavior and noise impact warrant careful review.

Overview
Adds an automatic design-detector hook that runs after agent file edits on UI-related paths and nudges the model with findings (always exit 0, never blocking edits).

Harness wiring: .agents / .claude PostToolUsehook.mjs; Cursor uses afterFileEdit (hook-after-edit.mjs queues emissions) plus stop (hook-stop.mjs, loop_limit: 1) because inline additional_context is unreliable there.

Core logic (hook-lib.mjs): resolves edited files (including Codex apply_patch paths and Cursor path), expands scans to imported/co-located stylesheets, runs the bundled detector, filters/dedupes via .impeccable/hook.json + session cache, and emits fresh / pending / clean / suppression messages with caps and sensitive-path skips.

Operator surface: new /impeccable hooks command (hook-admin.mjs, reference/hooks.md) for on/off/status/ignore/reset; skill routing updated. Detector file walker and regex pass now treat .sass like other CSS preprocessors.

Reviewed by Cursor Bugbot for commit 43d02d9. Bugbot is set up for automated code reviews on this repo. Configure here.

abdulwahabone and others added 9 commits May 28, 2026 09:21
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>
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>
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>
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>
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>
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>
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>
Resolve conflicts while keeping Cursor stop-hook followup, hook tests,
and hooks routing in SKILL.src.md. Adopt main's context-aware command
menu, SKILL.src.md source layout, Codex nested agents, and expanded test
suite.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abdulwahabone abdulwahabone marked this pull request as ready for review May 29, 2026 20:02
@abdulwahabone abdulwahabone requested a review from pbakaus as a code owner May 29, 2026 20:02
Comment thread .cursor/skills/impeccable/scripts/hook-after-edit.mjs
Comment thread .agents/skills/impeccable/scripts/hook-lib.mjs
Comment thread .agents/skills/impeccable/scripts/hook-lib.mjs
Comment thread .agents/skills/impeccable/scripts/hook-after-edit.mjs Outdated
Comment thread .cursor/skills/impeccable/scripts/hook-session-start.mjs Outdated
Comment thread .claude/skills/impeccable/scripts/hook-session-start.mjs Outdated
Comment thread .claude/skills/impeccable/scripts/hook-lib.mjs
Comment thread .claude/skills/impeccable/scripts/hook-after-edit.mjs
Comment thread .cursor/skills/impeccable/scripts/hook-after-edit.mjs
Comment thread .agents/skills/impeccable/scripts/hook-lib.mjs
Comment thread .agents/skills/impeccable/scripts/hook-lib.mjs Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c98962b. Configure here.

Comment thread .cursor/skills/impeccable/scripts/hook-stop.mjs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant