# 问题 修复 文件 1 前端构建失败(引号错误) size="small type=" → size="small" type=" PosterHistoryPage.vue 2 migrate_014 ORM vs 缺失列 全部改为原始 SQL,不再引用 ORM 模型 migrate_014.py 3 cleanup 字段名错误 output_path → ppt_path cleanup.py 4 文案生成 case 越权 添加 case.user_id != user_id 校验 poster/service.py 5 存储路径未接通持久化卷 全部改用 get_storage_root()(默认 /app/api/storage/insurance) config.py, ppt/routes.py, poster/service.py, poster/tasks.py 高风险问题修复 # 问题 修复 文件 6 migrate_019 rollback 撤销成功字段 每个 ALTER 后立即 commit,失败只回滚当前语句 migrate_019.py 7 迁移锁 Windows 不兼容 + 句柄未持久化 全局变量保存锁句柄,支持 Windows msvcrt api/insurance/db/__init__.py 8 PDF 校验异常时放行 异常返回 False(文件损坏) security.py 9 健康检查始终返回成功 缺少关键资源时返回 503 + missing 列表 poster/routes.py 10 短密钥掩码泄露原值 ≤4 字符返回 **** ppt_admin_service.py 11 设置无键名白名单 添加 _ALLOWED_SETTING_KEYS 白名单 ppt_admin_service.py 12 容器重启任务永久 stuck 添加 recover_stale_tasks() 启动恢复函数 poster/tasks.py, ppt/parse_worker.py
12 KiB
/impeccable hooks
Manage the design detector hook for the current project.
The hook runs the impeccable design detector on direct file edits to design-relevant files (.tsx, .jsx, .html, .vue, .svelte, .astro, .css, .scss, .sass, .less, .ts, .js). Claude Code, Codex, and GitHub Copilot use a post-tool-use hook and push a short system reminder into the agent's context after the edit; findings get a correction prompt, pending issues get a re-nudge, and clean UI-ish files get a short ack unless quiet mode is on (hook.quiet in config). Plain .ts and .js files are still scanned, but stay quiet unless the detector finds something. Cursor uses preToolUse to block bad proposed writes before they land and stays silent when it allows a clean write.
The detector rules run in two tiers. The per-edit hook surfaces only the immediate tier: mechanical, unambiguous problems worth interrupting an edit for, such as broken images, overflowing or clipped content, contrast and legibility failures, gradient text, glow shadows, and design-system drift. Everything else (copy cadence, palette and typography taste, layout rhythm) is deferred to a deep pass on the Stop hook event, which runs the full rule set over every UI file touched in the session and surfaces the remaining findings once, deduplicated against what the per-edit pass already reported. A session with nothing left to report stops silently. Set hook.perEditRules to "all" in .impeccable/config.json to restore the full rule set on every edit. The Stop deep pass is wired for Claude Code and Codex, which both dispatch a native Stop hook event. Cursor does not get one (its stop hook is not consistently dispatched; the pre-write gate covers it), and GitHub Copilot's stop-style events do not feed context back to the model, so they keep the full detector per edit.
Every hook is a mechanical pass. The reflexes no scanner catches live in craft-floor.md, which the skill loads before it edits UI, so they apply whether or not a hook is wired. A session with no automatic hook gets one MANUAL_DETECTOR_REQUIRED directive from context.mjs asking for a single detector run at the end.
This command toggles the hook per project by editing .impeccable/config.json (the unified Impeccable config; hook runtime settings live under its hook key, and shared detector ignores live under detector). Per-developer overrides, including the install consent decision (hook.consent) the CLI records, live in the gitignored .impeccable/config.local.json. Set hook.enabled: false to turn the hook off, hook.quiet: true to silence the clean/pending acks, or hook.auditLog to a file path for an NDJSON log. The legacy IMPECCABLE_HOOK_DISABLED, IMPECCABLE_HOOK_QUIET, and IMPECCABLE_HOOK_LOG env vars are still honored and override these config values when set.
Declare server-side template extensions under detector.extensions when the project uses Blade, Twig, ERB, or Handlebars files; the hook skips them otherwise because they sit outside the built-in extension list. One entry per extension, { "ext": ".blade.php", "engine": "html" }. engine picks the analyzer (html for markup templates, text for JS/TS/CSS-like files) and defaults to html. Match against the end of the filename, so double extensions like .blade.php and .html.erb work. Config only adds extensions; the built-in list always applies.
Manual npx impeccable detect scans use the same project filter config by default: detector.ignoreRules, detector.ignoreFiles, detector.ignoreValues, and detector.designSystem.enabled. hook.enabled only controls automatic hook execution, not manual CLI scans. Use npx impeccable detect --no-config ... for a raw detector run that ignores project config/context. Use npx impeccable ignores ... for direct CLI CRUD on the same detector ignores.
Supported harnesses: Claude Code (.claude/settings.local.json in the project, which is gitignored so the hook stays machine-local; a hook you move into the shared settings.json is honored in place too), Codex (.codex/hooks.json in the project), Cursor (.cursor/hooks.json in the project), and GitHub Copilot (.github/hooks/impeccable.json in the project, a team-shared committed file that both the Copilot CLI and the cloud agent read). For the Copilot CLI, repo-level hooks fire once .github/hooks/impeccable.json is committed to the repository's default branch.
On Cursor, preToolUse checks proposed Write/Edit/Shell write content and denies only when the real detector finds an issue. The denial message is visible to the agent as the tool error, so the agent can reconsider before the bad write lands.
Routing
The first argument is the action. Defaults to status.
| Action | What it does |
|---|---|
status |
Print current state, shared/local config paths, ignored rules / files / values, env override. |
on |
Set enabled: true in .impeccable/config.json, record local hook consent as accepted, and install/repair provider hook manifests when the skill is installed. |
off |
Set enabled: false in .impeccable/config.json. |
ignore-rule <id> |
Append <id> to detector.ignoreRules; for overused-font, requires --all-values. Suppresses the rule across the whole project. |
ignore-file <glob> |
Append <glob> to detector.ignoreFiles. Suppresses every rule for matching files. |
ignore-value <id> <value> [--shared] [--reason "..."] |
Append a rule/value suppression to shared .impeccable/config.json. |
ignore-value <id> <value> --local [--reason "..."] |
Append a private rule/value suppression to .impeccable/config.local.json. |
ignore-value <id> "*" --file <glob> [--file <glob>...] |
Turn one rule off in matching files only, leaving it active everywhere else. Repeat --file, or use --file=<glob> / --files=<glob>. A bare "*" with no --file is refused: use ignore-rule <id> if you really mean project-wide. |
reset |
Delete the project config, dedup cache, and Cursor pending queue. |
Flow
-
Resolve the action from the user's argument. If no action was given, default to
status. -
Invoke the admin script and pass the user's output through verbatim:
node .claude/skills/impeccable/scripts/hook-admin.mjs <action> [args...] -
If
<action>isoff, follow up with a one-line note: "Done. New edits will not trigger the design hook in this project until you run/impeccable hooks on." -
If
<action>ison, follow up with: "Done. The design hook will fire after the next Edit/Write/MultiEdit on a UI file." -
If
<action>isignore-value,ignore-file, orignore-rule, just print the script output. The default scope is shared.impeccable/config.json; add--localonly when the user explicitly asks for a private exception. -
If
<action>isstatus, just print the script output. Do not add commentary unless the user asked a follow-up question.
Intentional findings
The hook itself never writes ignore config. Persist an exception only after the user explicitly confirms the flagged issue is intentional, and always go through hook-admin.mjs.
Prefer the narrowest exception:
- If the finding line shows an exact
ignore-valuecommand, run that command. This writes shared.impeccable/config.jsonby default. - For value-specific findings such as
overused-fontandbounce-easing, useignore-valuewhen the user confirms the specific value. Do not useignore-rule overused-fontfor a specific font. - If the finding has no value-specific command, such as
side-tab, scope that one rule to the file:ignore-value <id> "*" --file <path>. Runnpx impeccable detect <path>first to see what actually fires there. - Reach for
ignore-file <path>only when the whole file is out of scope for design review: a fixture, a generated artifact, a deliberate slop demo. It silences every rule for that file permanently, including rules that have not been written yet. A real UI surface with one noisy rule wants the file-scoped value ignore above. - Use
ignore-rule <id>only when the user asks to suppress that whole rule across the project. For broad overused-font suppression, useignore-rule overused-font --all-valuesonly when the user asks to ignore overused fonts generally. - Prefer config ignores (the commands above) by default; they keep suppressions in one reviewable place. Reach for an inline comment only when the waiver must travel with a single file that leaves the repo (a generated/exported standalone document, an emailed HTML file). The supported marker is
impeccable-disable <rule>(whole file) orimpeccable-disable-line/impeccable-disable-next-line(one line), in any comment syntax, with an optional reason after:or--. The detector honors it by default;--no-inline-ignoresor--no-configbypasses it.
Example value-specific exception:
node .claude/skills/impeccable/scripts/hook-admin.mjs ignore-value overused-font Inter --shared --reason "User confirmed Inter is intentional"
Example intentional motion exception:
node .claude/skills/impeccable/scripts/hook-admin.mjs ignore-value bounce-easing bounce-ball --shared --reason "User confirmed ball bounce animation is intentional"
Example whole-rule font exception:
node .claude/skills/impeccable/scripts/hook-admin.mjs ignore-rule overused-font --all-values --reason "User asked to ignore overused fonts generally"
Example one-rule-in-one-file exception, for a file that is still worth reviewing for everything else:
node .claude/skills/impeccable/scripts/hook-admin.mjs ignore-value design-system-font-size "*" --file "src/overlay/widget.js" --reason "Injected widget builds its own type scale; DESIGN.md's ramp describes the site"
Example whole-file exception, for a file that is out of scope entirely:
node .claude/skills/impeccable/scripts/hook-admin.mjs ignore-file "src/legacy/Card.tsx"
Constraints
- Never modify
.impeccable/config.jsonor.impeccable/config.local.jsonby hand from this command. Always go throughhook-admin.mjsso writes stay validated and the file shape stays consistent. One exception:detector.extensionshas no admin action, so when the user asks to cover a template stack, edit that one field in.impeccable/config.jsondirectly and leave the rest of the file untouched. - Do not edit the hook scripts themselves (
hook.mjs,hook-lib.mjs,hook-before-edit.mjs) from this flow. Those are skill plumbing. - Cursor can block a proposed write when the detector finds a real issue. Claude Code, Codex, and GitHub Copilot do not block the edit; they emit a post-edit reminder instead. Disabling stops both blocking and reminders.
- The hook is bundled with the Impeccable skill and installed through project-local manifests:
.claude/settings.local.json,.codex/hooks.json,.cursor/hooks.json, and.github/hooks/impeccable.json. On Codex, the user must approve the hook via/hooksthe first time. On Cursor, confirm hooks are enabled under Settings -> Hooks. On GitHub Copilot, the CLI loads.github/hooks/impeccable.jsononce it is committed to the repository's default branch, and the cloud agent reads it from the repo directly.
Failure modes
- If
.impeccable/config.jsonor.impeccable/config.local.jsonis unreadable or malformed, the hook ignores that file and uses the remaining valid config/defaults.hook-admin.mjs statuswill show malformed files as ignored. - If the user asks to "disable the hook" globally, lead with
/impeccable hooks off(persistent for this project; writeshook.enabled: falseto config). The legacyIMPECCABLE_HOOK_DISABLED=1env var also works as a one-shot override that follows the shell.