☰
impeccable 设计检测 Hook 完全指南:为 Claude Code、Codex、Cursor、Grok 与 GitHub Copilot 配置逐次编辑的 UI 质量守门员
2026/9/29 21:17:27 网站建设 项目流程

impeccable 设计检测 Hook 完全指南:为 Claude Code、Codex、Cursor、Grok 与 GitHub Copilot 配置逐次编辑的 UI 质量守门员

【免费下载链接】impeccableThe design language that makes your AI harness better at design.项目地址: https://gitcode.com/GitHub_Trending/im/impeccable

Impeccable 的设计检测 Hook(design detector hook)是随 Impeccable 技能分发的自动化设计审查管道:每当 Agent 直接编辑.tsx、.jsx、.html、.vue、.svelte、.astro、.css等设计相关文件时,它都会在后台运行设计检测器,把机械性、无歧义的设计问题(破图、溢出裁剪、对比度不足、渐变文字、发光阴影、设计系统漂移)以系统提示的形式推回 Agent 上下文。本文基于 .opencode/skills/impeccable/reference/hooks.md 展开,结合仓库中的 Rust 实现(crates/hook、crates/foundation),系统讲解 Hook 的双层规则机制、五类 AI 编码助手的接入差异、.impeccable/config.json完整配置、impeccable hooks管理命令、以及 findings 的三分法分诊流程——读完即可在自己的项目里启用、调优并正确消化检测结果。

Hook 是什么:一次编辑后的机械设计检查

Impeccable 的 Hook 是一个"纯机械"的自动化检查器("Every hook is a mechanical pass")。它不评判品味、不评估排版节奏,只拦截那些客观、无歧义、值得打断一次编辑的问题。它关注的不是"设计好不好看",而是"这次改动是否产生了可证伪的设计缺陷"。

从实现看,Hook 逻辑由三个独立入口组成(Rust 原生实现,对应 JS 时代的hook.mjs/hook-lib.mjs/hook-admin.mjs):

  • crates/hook/src/hook.rs:run_hook(PostToolUse 逐次编辑通道)与run_stop_hook(Stop 深度通道),总是以退出码 0 结束,stdout 要么是单个 JSON 文档,要么为空;
  • crates/hook/src/before_edit.rs:hook-before-edit,即 Cursor 的preToolUse写入闸门,同样总是退出 0,输出恰好一个 JSON 文档({"permission":"allow"}或 deny 载荷);
  • crates/hook/src/admin.rs:impeccable hooks管理命令(status/on/off/ignore-rule/ignore-file/ignore-value/reset)。

扫描的目标扩展名由常量ALLOWED_EXTS定义(见 crates/hook/src/hook_lib.rs):

pub const ALLOWED_EXTS: &[&str] = &[ ".tsx", ".jsx", ".html", ".htm", ".vue", ".svelte", ".astro", ".css", ".scss", ".sass", ".less", ".ts", ".js", ];

各助手的接入形态差异

同一个检测器,在不同 AI 编码助手里以不同形态工作(这是阅读本文最容易混淆、也最需要先建立认知的部分):

助手接入事件行为
Claude CodePostToolUse编辑后向上下文推入一段简短系统提醒;findings 给修正提示,待处理问题给再提示,干净 UI 文件给简短确认(除非hook.quiet)
CodexPostToolUse同上;首次需通过/hooks批准
GitHub CopilotpostToolUse同上;因 Copilot 的 stop 类事件无法把上下文反馈给模型,保持每次编辑都跑完整检测器
CursorpreToolUse在写入落地前检查拟写内容,只有真实检测到问题时才拒绝(deny);放行干净写入时保持沉默
Grok BuildPostToolUse+StopPostToolUse 只标记被触碰的文件(Grok 会丢弃该 stdout,不要指望逐次编辑提醒),findings 在 Stop 事件的additionalContext上浮出

值得注意的两个助手特例:

  • Cursor的拒绝消息对 Agent 可见为工具错误("The denial message is visible to the agent as the tool error"),Agent 可以在坏写入落地前重新考虑。
  • Grok在end_turn之后还会触发一次只读的 Stop(reason: "shutdown"),必须跳过它、只扫描end_turn——这正是 crates/hook/src/hook.rs 中run_stop_hook对reason != "end_turn"直接skipped: "stop-reason"返回的原因。

双层规则机制:逐次编辑层与 Stop 深度层

Hook 的规则分为两层(two tiers):

  1. 逐次编辑层(immediate tier):只在编辑时上浮"立即可修"的问题——机械、无歧义、值得为它中断一次编辑的缺陷。
  2. Stop 深度层(deep pass):其余一切(文案节奏、调色板与字体品味、布局节奏)全部推迟到会话结束的Stop事件,在那里对本次会话触碰过的每个UI 文件跑完整规则集,并与逐次编辑层已报告的 findings 去重后一次性上浮。

"本次会话没剩下任何可报告内容"时,Stop 深度通道静默结束——对应 crates/hook/src/hook.rs 中fresh_groups.is_empty()时的skipped: "stop-clean"。

immediate tier 具体包含哪些规则

immediate tier 的规则清单被抽到crates/foundation/src/registry.rs的IMMEDIATE_TIER_RULES常量(之所以放在 foundation 而不是 hook crate,是因为 hook crate 是 native-only,而 wasm 消费者需要同一份列表,见 crates/foundation/src/registry.rs):

pub const IMMEDIATE_TIER_RULES: &[&str] = &[ // 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, trivial to fix at the edit site. "gradient-text", "dark-glow", // Design-system drift compounds if not corrected at edit time. "design-system-font", "design-system-color", "design-system-radius", "design-system-font-size", ];

分类逻辑一目了然:破图/溢出/裁剪/视口越界这类坏输出、对比度/灰度字/过小字号这类客观可读性失败、渐变文字/深色发光这类单属性机械瑕疵、以及设计系统字体/颜色/圆角/字号漂移这类越拖越难修的系统偏差。规则分组与设计意图完全对应源码注释:"Every one of them is mechanical, unambiguous, and cheap to correct at the edit site."

恢复"每次编辑跑全部规则"

如果希望每次编辑都跑完整规则集(放弃双层分层),在.impeccable/config.json中设置:

{ "hook": { "perEditRules": "all" } }

perEditRules接受"all"或"immediate"(默认值),解析逻辑见 crates/hook/src/hook_lib.rs。run_hook中per_edit_tiering_active(&config, harness)决定是否把 findings 切成(immediate, deferred)两堆,deferred 部分仅touch_file记入缓存留给 Stop 通道(见 crates/hook/src/hook.rs)。

另外,哪些助手会收到 Stop 深度通道:Claude Code、Codex、Grok Build 会分发原生Stop事件,所以有深度通道;Cursor 的 stop hook 不会被稳定分发(由 pre-write 闸门覆盖),GitHub Copilot 的 stop 类事件无法把上下文反馈给模型,所以这两家保持逐次编辑跑完整检测器。

配置中心:.impeccable/config.json与config.local.json

Hook 是按项目(per project)开关的。所有运行时配置都落在统一的 Impeccable 配置里:

  • .impeccable/config.json(项目共享):Hook 运行时设置放在hook键下,共享的检测器忽略项放在detector键下;
  • .impeccable/config.local.json(开发者私有,gitignored):开发者级覆盖,包括 CLI 记录的安装同意决策hook.consent。

从源码看,read_config依次读取config.json再读config.local.json,后者覆盖前者(见 crates/hook/src/hook_lib.rs);HookConfig::default()给出了完整的默认值(见 crates/hook/src/hook_lib.rs)。

hook键的完整参数

键类型默认值作用
enabledbooltrue设为false关闭 Hook
quietboolfalse设为true静默干净/待处理确认(clean/pending acks)
auditLogstring无指向一个 NDJSON 日志文件路径
perEditRulesstring"immediate""all"或"immediate",控制逐次编辑层规则集
advisoryRulesstring"exclude""include"或"exclude",是否纳入 advisory 类规则
limits.maxFindingsnumber5单次输出的最大 findings 数
limits.maxCharsnumber8000单次输出的最大字符数(UTF-16)
limits.maxFileBytesnumber131072超过该字节数的文件跳过扫描

一个最小但完整的启用配置示例:

{ "hook": { "enabled": true, "quiet": false, "perEditRules": "immediate", "limits": { "maxFindings": 5, "maxChars": 8000, "maxFileBytes": 131072 } }, "detector": { "designSystem": { "enabled": true }, "ignoreRules": [], "ignoreFiles": [], "ignoreValues": [] } }

注意limits.maxFileBytes的上限检查在 crates/hook/src/hook.rs:超过时记录skipped: "too-large"并附带bytes审计字段。默认 128 KB 意味着超大文件不会被逐次扫描。

环境变量覆盖(legacy)

以下 legacy 环境变量仍然有效,设置时优先于配置值:

  • IMPECCABLE_HOOK_DISABLED=1:一次性关闭(跟随 shell 作用域);
  • IMPECCABLE_HOOK_QUIET=1:等价于hook.quiet: true;
  • IMPECCABLE_HOOK_LOG=<path>:等价于hook.auditLog。

run_hook中环境变量检查先于配置读取:IMPECCABLE_HOOK_DISABLED为 truthy 时直接返回skipped: "env-disabled"(见 crates/hook/src/hook.rs)。truthy 判定接受1|true|yes|on(大小写不敏感,见 crates/hook/src/hook_lib.rs)。

为 Blade / Twig / ERB / Handlebars 声明模板扩展名

内置扩展名列表之外的服务端模板栈(Blade、Twig、ERB、Handlebars)默认会被跳过。声明方式是在detector.extensions下逐个添加:

{ "detector": { "extensions": [ { "ext": ".blade.php", "engine": "html" }, { "ext": ".html.erb", "engine": "html" } ] } }
  • 每个扩展一条记录,engine选择分析器:html用于标记模板,text用于 JS/TS/CSS 类文件,默认html;
  • 匹配规则是按文件名结尾匹配(match against the end of the filename),所以.blade.php、.html.erb这类双扩展名可用;
  • 配置只做追加,内置列表始终生效。

实现细节在 crates/hook/src/hook_lib.rs:normalize_extension_entries会为缺ext的裸字符串自动补点前缀,match_configured_extension对每个 entry 做name.ends_with(ext)并选择最长匹配。detector.extensions没有对应的 admin 动作,属于唯一允许直接手改的字段(见下文"约束")。

手动 CLI 扫描与 Hook 的关系

手动npx impeccable detect扫描默认使用同一套项目过滤配置:detector.ignoreRules、detector.ignoreFiles、detector.ignoreValues、detector.designSystem.enabled。但hook.enabled只控制自动 Hook 执行,不影响手动 CLI 扫描。

  • npx impeccable detect --no-config ...:忽略项目配置/上下文,跑一次"裸"检测;
  • npx impeccable ignores ...:在 CLI 上直接对同样的 detector ignores 做增删改查(CRUD)。

各助手 Hook 清单的安装位置

支持的五类助手,Hook manifest 都是项目本地文件,通过impeccable hooks on安装/修复:

助手Manifest 路径备注
Claude Code.claude/settings.local.jsongitignored,机器本地;移入共享的settings.json也会被就地识别
Codex.codex/hooks.json首次需通过/hooks批准
Cursor.cursor/hooks.json需在 Settings -> Hooks 确认已启用
Grok Build.grok/hooks/impeccable.json需要/hooks-trust或--trust
GitHub Copilot.github/hooks/impeccable.json团队共享的已提交文件,Copilot CLI 与云 Agent 都读它;CLI 在文件提交到仓库默认分支后生效

这些 manifest 的内容由 crates/hook/src/admin.rs 的HOOK_MANIFEST_TARGETS定义,例如 Claude Code 的 manifest 同时注册PostToolUse(matcherEdit|Write,5 秒超时)和Stop(30 秒超时,状态消息 "Design deep pass");Cursor 的 manifest 只注册preToolUse,调用impeccable hook-before-edit。

impeccable hooks管理命令:动作路由表

第一条参数是动作,缺省为status。完整动作表:

动作作用
status打印当前状态、共享/本地配置路径、被忽略的规则/文件/值、环境变量覆盖
on在.impeccable/config.json写enabled: true,在本地配置记录 hook consent 为 accepted,并在技能已安装时安装/修复各助手的 manifest
off在.impeccable/config.json写enabled: false
ignore-rule <id>向detector.ignoreRules追加<id>;对overused-font必须加--all-values;在整个项目范围内抑制该规则
ignore-file <glob>向detector.ignoreFiles追加<glob>;对匹配文件抑制每一条规则
ignore-value <id> <value> [--shared] [--reason "..."]向共享.impeccable/config.json追加规则/值抑制
ignore-value <id> <value> --local [--reason "..."]向.impeccable/config.local.json追加私有规则/值抑制
ignore-value <id> "*" --file <glob> [--file <glob>...]只在匹配文件中关闭某一条规则,其他地方保持激活;--file可重复,也支持--file=<glob>/--files=<glob>;裸"*"不带--file会被拒绝
reset删除项目配置、去重缓存与 Cursor 待处理队列,并从on安装过的每个 provider manifest 中移除 Hook 条目(已提交的 Copilot 文件也在内;on从不写共享settings.json,所以它不会被碰)

调用形式(脚本位于 .opencode/skills/impeccable/scripts/impeccable):

.impeccable/skills/impeccable/scripts/impeccable hooks <action> [args...]

从实现看,动作分派在 crates/hook/src/admin.rs:run解析首参(缺省"status"),未知动作打印合法动作列表并返回 1;on内部调用set_enabled(rt, cwd, true)——写共享配置、写consent: "accepted"到本地配置、再repair_hook_manifests(对已存在的 skill 目录逐个合并/修复/备份 manifest);reset则删除配置、缓存、pending 队列,并对每个 manifest 做prune_impeccable_hook_from_manifest清理。

几个值得注意的参数行为:

  • ignore-rule overused-font不带--all-values会直接报错,提示改用ignore-value overused-font <font>或补--all-values(见 crates/hook/src/admin.rs);
  • ignore-value的裸"*"不带--file会被拒绝并提示改用ignore-rule(见 crates/hook/src/admin.rs);
  • ignore-file不接受--reason,因为detector.ignoreFiles只存 glob;
  • ignore-value会拒绝"惰性精确条目"——若该规则根本无法产出该值,则报错提示改用文件级抑制(见 crates/hook/src/admin.rs)。

status输出格式由 crates/hook/src/admin.rs 生成:显示 state、shared/local 文件路径(含(malformed; ignored)标注)、ignoreRules/ignoreFiles/ignoreValues、maxFindings/maxChars、IMPECCABLE_HOOK_DISABLED覆盖状态与缓存文件路径。

命令流(Flow):技能如何转发给 admin 脚本

  1. 从用户参数解析动作;未给动作时默认status;
  2. 调用 admin 脚本并把用户输出原样透传:
    .opencode/skills/impeccable/scripts/impeccable hooks <action> [args...]
  3. 若动作是off,追加一行提示:"Done. New edits will not trigger the design hook in this project until you run/impeccable hooks on."
  4. 若动作是on,追加:"Done. The design hook will fire after the next Edit/Write on a UI file."
  5. 若动作是ignore-value/ignore-file/ignore-rule,只打印脚本输出;默认作用域是共享的.impeccable/config.json,只有用户明确要求私有例外时才加--local。
  6. 若动作是status,只打印脚本输出,用户没追问就不要加注释。

Findings 三分法分诊(Triage)

Hook 本身从不直接写忽略配置——所有例外都必须经由impeccable hooks。每个 finding 都归入三种结局之一:

  1. 真实设计问题(Real design problem):修掉它。永远不要为了跳过修复或强行放行被拦截的写入而添加 ignore。
  2. 有把握的误报或合规例外(Confident false positive or sanctioned exception):自行持久化最窄的 ignore,并在回复中披露。判据是你能说出名字的证据:有意的 demo/fixture、坏设计的文档化、字面意义或领域适配的动效(如弹跳的球)、或用户已确认的选择。把证据写进--reason,格式为"<who decided: evidence>";只有用户确实确认过才写 "user confirmed"。
  3. 不确定(Unsure):保留 finding,用一行问题问用户。只问一次——一行问题的成本远低于 Hook 在每次后续编辑时重新触发。

自助(self-serve)止步于ignore-value;ignore-file与ignore-rule抑制面太大,不能凭自己的判断添加,必须先问用户。

如何选择最窄的例外

  • 若 finding 行给出了ignore-value <rule> <value>组合,就把它原样传给impeccable hooks ignore-value并带上--reason。默认写入共享.impeccable/config.json;

  • 对值特定的 finding(如overused-font、bounce-easing),用ignore-value抑制具体值,不要用ignore-rule overused-font去抑制一个具体字体;

  • 若 finding 没有值特定命令(如side-tab),把该规则限定到文件:ignore-value <id> "*" --file <path>。先跑npx impeccable detect <path>看实际触发什么;

  • 只有当整个文件都不在设计审查范围内(fixture、生成产物、故意的 slop demo)时才用ignore-file <path>。它会永久静默该文件的每一条规则——包括还没写出来的规则。一个只有一条吵闹规则的真实 UI 表面,应该用上面的文件级值抑制;

  • 只有用户要求在整个项目抑制整条规则时,才用ignore-rule <id>。对广泛的字体抑制,仅在用户要求"一般性忽略 overused 字体"时使用ignore-rule overused-font --all-values;

  • 默认优先使用配置式 ignore(上述命令),它们把抑制项集中在一个可审查的位置。只有当豁免必须跟随单个文件离开仓库(生成的/导出的独立文档、邮件发送的 HTML 文件)时,才使用内联注释。支持的内联标记:

    • impeccable-disable <rule>(整文件)
    • impeccable-disable-line/impeccable-disable-next-line(单行)

    任意注释语法均可,冒号或--后跟可选理由。检测器默认识别它们;--no-inline-ignores或--no-config会绕过。

完整命令示例

值特定例外(有把握的误报,证据具名):

.impeccable/skills/impeccable/scripts/impeccable hooks ignore-value overused-font Inter --shared --reason "User confirmed Inter is intentional"

自助例外(字面意义动效,证据具名):

.impeccable/skills/impeccable/scripts/impeccable hooks ignore-value bounce-easing bounce-ball --shared --reason "Agent: literal ball-bounce animation, bounce easing is the subject"

整规则字体例外(用户要求):

.impeccable/skills/impeccable/scripts/impeccable hooks ignore-rule overused-font --all-values --reason "User asked to ignore overused fonts generally"

单规则单文件例外(文件仍值得做其他审查):

.impeccable/skills/impeccable/scripts/impeccable hooks 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"

整文件例外(文件完全超出范围):

.impeccable/skills/impeccable/scripts/impeccable hooks ignore-file "src/legacy/Card.tsx"

约束(Constraints)

  • 绝不允许从这个命令手动修改.impeccable/config.json或.impeccable/config.local.json。一切写入都走impeccable hooks,保证写入经过校验、文件形状保持一致。唯一例外:detector.extensions没有 admin 动作,用户要求覆盖某个模板栈时,直接编辑 config.json 中的这一个字段,其余字段保持不动;
  • 不要从本流程编辑impeccable hook/impeccable hook-before-edit背后的 launcher 或二进制——那是技能管道(skill plumbing);
  • Cursor 会在检测到真实问题时阻止一次拟写入;Claude Code、Codex、GitHub Copilot 不阻止编辑,改为发出事后提醒。禁用 Hook 会同时停止阻止与提醒;
  • Hook 随 Impeccable 技能捆绑,通过项目本地 manifest 安装:.claude/settings.local.json、.codex/hooks.json、.cursor/hooks.json、.github/hooks/impeccable.json;
  • Codex 首次使用需经/hooks批准;Cursor 需在 Settings -> Hooks 确认启用;GitHub Copilot CLI 在.github/hooks/impeccable.json提交到仓库默认分支后加载,云 Agent 直接从仓库读取。

失败模式(Failure Modes)

  • 若.impeccable/config.json或config.local.json不可读或格式损坏(malformed),Hook 忽略该文件,使用其余有效配置/默认值;impeccable hooks status会把损坏文件标注为(malformed; ignored)(实现见 crates/hook/src/admin.rs)。
  • 若用户要求"全局禁用 Hook",先执行/impeccable hooks off(对当前项目持久化,写入hook.enabled: false);legacy 环境变量IMPECCABLE_HOOK_DISABLED=1也可作为跟随 shell 的一次性覆盖。

扩展阅读:更深一层的实现细节

  • 会话去重缓存:.impeccable/hook.cache.json记录每个会话(最多 8 个会话)下每个文件的 editCount 与已报告 findings;同一 finding 不会重复轰炸 Agent。同一文件在一次会话内编辑超过EDIT_COUNT_THRESHOLD(6 次)后进入抑制(suppressed)状态,输出抑制通知(见 crates/hook/src/hook_lib.rs)。Stop 通道最多扫描STOP_MAX_FILES(20)个文件(crates/hook/src/hook_lib.rs)。
  • Stop 归属判定(stop baseline):stop_baseline.rs只在工具结果里有**已验证的首次 Edit/Write 前像(preimage)**时才建立基线,并只用于纯文本检测器;DOM 与设计系统 findings 依赖其他文件,一律标为[attribution unknown](见 crates/hook/src/stop_baseline.rs)。这会直接影响 Stop 深度通道的输出标注:[new]表示会话新增的债务,[attribution unknown]表示可能早于本会话。
  • Cursor 防循环机制:hook-before-edit对同一文件 + 同一 finding 签名累计cursorDenials,超过 6 次后放行写入但附警告("to avoid a loop"),见 crates/hook/src/before_edit.rs。
  • 安全与生成物过滤:Hook 会跳过敏感路径(.env、.git、密钥/凭据文件等)与生成路径(node_modules、dist/build/out/.next、.min.*、.d.ts、lock 文件等),正则见 crates/hook/src/hook_lib.rs。

结语:Hook 与技能的互补

最后回到文档的开篇立场:每个 Hook 都是机械检查,扫描器永远抓不到的"反射式"(reflex)准则沉淀在 craft-floor.md 中——该文件由技能在编辑 UI 之前加载,因此无论是否接入 Hook 都生效。而没有自动 Hook 的会话,会从impeccable context收到一条MANUAL_DETECTOR_REQUIRED指令,要求会话结束时手动跑一次检测器。

也就是说:Hook 解决的是"编辑当下"的即时反馈与"会话收尾"的债务清点,而 craft-floor 解决的是"编辑之前"的准则内化。三者(逐次编辑层、Stop 深度层、craft-floor 准则)合起来,构成了 Impeccable 对 AI 编码助手设计质量的三重保障。

【免费下载链接】impeccableThe design language that makes your AI harness better at design.项目地址: https://gitcode.com/GitHub_Trending/im/impeccable

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询