design-review — Separate Evaluator for Design-Skill Output
Why this skill exists · 为什么独立出来
EN — Two rules carried over from Anthropic's harness design for long-running apps and the GAN paradigm:
- The agent doing the work praises its own work. The 9 design skills (apple / anthropic / ember / sage / glass / eclat / lectern / atelier / primer) are generators — they know the style, they can produce HTML, and they will naturally rate their own output favourably.
- An independent, skeptical evaluator is the real lever. This skill imports nothing from any generator. Its rules, scripts, known-bugs list, and critic agents live in one shared pool — the nine styles pay the same quality floor.
ZH — 两条原则来自 Anthropic harness-design 和 GAN 范式:(1) 做事的
agent 倾向自评过高;(2) 独立持怀疑的 evaluator 才是真正的杠杆。9 个
design skill 是 generator,会给自己打高分;design-review 脚本、规则、
已知 bug 清单 不属于任何一个风格,9 种风格共用同一套工艺底线。
检查模型 · The gate model(全仓唯一定义)
这一节是全仓"几道检查"的唯一出处。 其它任何文件说到检查数量,以这里为准、 引用这里,不要自己另报一个数(2026-08 之前全仓有三套互相矛盾的数法, 计数检查追着改了四轮 —— 根因就是没有唯一定义)。EN — this section is the single source of truth for the repo's gate counts; every other surface cites it instead of asserting its own number.
五道机械检查(bin/design-review 默认全跑,按序):
verify.py— 结构(静态)visual-audit.mjs— 渲染(Playwright)axe-audit.mjs— 可达性(axe-core;阻断规则四条:color-contrast、 link-name、aria-prohibited-attr、svg-img-alt。全仓两主题实测 0 违规 —— glass light 那 84 处欠账已于 2026-08-27 在 CSS token 层还清,见 known-bugs §6.6)interaction-audit.mjs— 点开之后才存在的那些状态(2026-09-05 加)screenshot.mjs— 全页截图(只产物;评判它的是人眼)
第四道在补什么(为什么前三道漏得掉)
前三道判的是同一个静止画面:verify 读源码,visual-audit 量首帧, axe 跑加载后的 DOM。于是标签页点了没反应、手风琴展开后文字读不清、 按钮一点就抛异常 —— 三道全绿照样发布。
第四道逐个操作可交互元素(button / summary / [role=tab] / [aria-expanded] /
th[aria-sort] / input / select / 页内锚点),每点一次判五件事:
| 判定 | 级别 |
|---|---|
| 这一下点出了控制台报错或未捕获异常 | error |
| 这一下点出了新的 axe 违规(点前没有、点后才有,四条阻断规则内) | error |
声明了 data-inert-by-design 却真的改了页面(声明过期) |
error |
| 点完页面没有任何可测变化 —— 死控件,或长得像能点的装饰 | warn |
| 页内锚点指向的 id 在本页不存在 | warn |
| 点开后新露出的内容压在别的内容上 | warn |
已经激活的控件不算死:带 aria-current / aria-selected="true" /
aria-pressed="true",或 class 里有 active / selected / current 的,
点它本来就不该有反应。故意不接线的控件写
data-inert-by-design="理由" —— 跟 data-allow-overlap 同一个约定,
必须写理由,声明才是可复核的而不是一个静音开关;而且一旦它真的动了,这一条会报 error。
判「这个元素读者看得见吗」用浏览器自带的 checkVisibility(),不是手写那套判断。
收起的 <details> 不会给内容设 display:none —— 它保留一个真实的
640×122 盒子且 visibility:visible,这正是 known-bugs §1.40 那批幻觉重叠的根因。
代价:每个控件一次页面加载,一页 17-21 秒(前三道合计约 9 秒)。
同形状的控件只测前两个(--per-shape),八条一模一样的 FAQ 手风琴不必测八遍。
要跳过用 --no-interact,但默认是开的 —— 选配的检查等于没有检查。
自检:skills/design-review/scripts/interaction_selftest.sh —— 7 个故意做坏的测试页,
10 项检查,每一类判定的正反两面都测。
让它不靠记性:编辑触发的 hook
五道跑一页要 17-25 秒,因为其中三道要开浏览器。没人会在每次改动后都付这个代价, 于是实际情况是发布前才想起来跑一次 —— 而那正是结构问题最难改、可访问性问题最可能 带着上线的时刻。
verify.py 是唯一不需要浏览器的一道,0.05 秒,所以它可以每次写 HTML 都跑。
hooks/design-gate/ 两个 hook:
post-edit.sh(PostToolUse)—— 每次写 HTML 跑一遍verify.py,不过就把结果 送回给模型;同时把这一版的内容哈希记进.design-gate/pending.tsv。 路径不是.html、或者这个项目没声明过它在意设计,立刻退出不作声。 「这一页归谁管」看的是项目自己放的DESIGN.md,不是「检查器源码在哪」 —— 第一版按后者找,于是只有 sky-skills 自己能命中,而用这套东西的下游项目一个都收不到, 偏偏最容易忘记跑检查的正是它们。verify.py的位置从钩子脚本自身路径反推, 跟被编辑的文件在哪毫无关系(见hooks/design-gate/owner.sh)。stop.sh(Stop)—— 回合结束时,报出哈希在pending.tsv里、 却不在passed.tsv里的文件。bin/design-review五道全过才写这条回执, 带--no-interact或--no-axe的部分运行不写(部分运行的回执是假的)。 改文件会改哈希,回执自动失效,不用谁记得作废它。
它只拦一次,然后清账。 一个"条件不满足就一直不放行"的 Stop hook, 在条件根本达不到的时候会一直不放行,记录里就是 hook 和模型互相重复八轮 直到被强制放行。所以这个只拦一次,把待办清空,然后闭嘴 —— 目的是让漏掉这件事被看见,不是强制执行。
装:hooks/design-gate/install.sh(先 --dry-run 看它要改什么)。
单次关掉:DESIGN_GATE_HOOK=off。自检 hooks/design-gate/selftest.sh,21 项。
项目自己的决定:DESIGN.md
九套语言是通用的,一个用它们的项目却有自己的决定:写哪一套、哪些提示看过了并且 决定就这样、这一页到底想让谁看懂什么。这些今天没有落点,于是每次跑都在重新争 同样的 warning,而口味评审判的是一个虚构记事本应用的样张,不是这个项目的意图。
在页面旁边(或往上任一层)放一个 DESIGN.md:
---
skill: anthropic
monolingual: false
waivers:
- check: visual-audit:figure-no-caption
reason: 这里的图就是章节头,配图注等于把上面的 h2 再说一遍
until: 2026-12-31
---
# 这个项目的设计决定
...给口味评审看的散文...
| 字段 | 作用 |
|---|---|
skill: |
写哪一套语言。不写就靠样式表链接自动探测,而还没写完的页面探测不出来 |
monolingual: |
这个项目的页面只写一种自然语言。双语规则(§G)是本仓发布双语站点才有的义务,别人没答应过 —— 不写这行,它 docs/ 下每一页每次都失败,直到有人想起来手打 --allow-monolingual |
waivers: |
降级,不隐藏 —— error 变 warn,那条发现照样打出来,理由跟在旁边 |
| 散文部分 | 原样喂给 critic / multi-critic。样张是声音的标尺,它是目的的标尺 |
三条硬规矩,都有检查:
- 理由必填且要写成句。这是豁免可复核与静音开关的分界。
check必须是真实存在的检查 id。id 是从visual-audit.mjs源码里现读的, 所以上游改名会在这里报错,而不是让豁免安静地什么都不做。until过期就校验失败,逼你重新决定一次。 另外豁免了一条这一页根本没触发的检查也会被报出来 —— 那行字在骗下一个读者。
monolingual: 不是豁免。豁免说的是「这条发现我们看过了,决定就这样」,它降级不隐藏;
monolingual: 说的是「那条规则不是冲我们来的」—— 两句话不是一个意思,所以是两个字段。
DESIGN.md 写坏了整轮不跑,在第一道之前就停 —— 它决定检查怎么做,
半读半猜比直接拒绝更坏。模板见 references/DESIGN.md.template,
自检 scripts/design_md_selftest.sh,36 项。--no-design-md 可以忽略它。
五道之外,按需叠加,不计入"五道":
pixel-gate.mjs(--pixel)— 可选的第六道机械检查,像素回归比对已提交基线- LLM critic(
--criticsolo /--multi-critic4 专家)— 口味评审。 critic 不是第五道机械检查,它在机械检查之外 - 人眼看截图 — 机械检查是必要条件,不是充分条件
(上面这行注释是机器可读形态,scripts/count-check.py 解析它做全仓计数判定。
改模型 = 改这一节 + 让 count-check 的探针继续过。)
What ships today · 当前交付
| 组件 | 状态 | 实体 |
|---|---|---|
| Gate 1 · structural verify | shipped | scripts/verify.py(8 类 check + 双语强制 + --allow-monolingual 豁免) |
| Gate 2 · rendered visual-audit | shipped | scripts/visual-audit.mjs(86 条 known-bugs 里能机器化的那些) |
| Gate 3 · accessibility axe-audit | shipped (2026-08-14) | scripts/axe-audit.mjs(axe-core;四条阻断规则;当时清账每页只量一个主题,glass light 仍有欠账 —— known-bugs §6.6) |
| Gate 4 · full-page screenshot | shipped | scripts/screenshot.mjs(Playwright · 绝对路径 + file:// 通用) |
| 口味评审(五道之外)· solo critic | shipped | .claude/agents/design-critic.md |
| 口味评审(五道之外)· multi-critic(4 专家) | shipped (2026-04-22) | .claude/agents/design-{composition,copy,illustration,brand}-critic.md 权重 25/25/20/30 |
| Learning-loop · 回灌成规则 | shipped (2026-04-22) | .claude/agents/design-learner.md + scripts/learning-loop.mjs |
| Cross-repo 入口 | shipped | ~/.claude/skills/design-review/dr-cli --repo=<仓> --skill=<名> <html> |
| 参考库 canonical | 实时计数见 ~/.claude/skills/design-review/dr-cli --coverage(扩库中) |
~/.claude/skills/<style>-design/references/canonical/ |
Entry points · 入口
一条命令跑完(推荐)
~/.claude/skills/design-review/dr-cli [--repo=<path>] [--skill=<name>] [--css=<path>]... \
[--critic | --multi-critic] [--learn] [--allow-monolingual] \
<html> [...]
--critic/--multi-critic跑 LLM 口味评审(五道机械检查之外;solo 或 4 专家并行)--learn跑完把 verdict 喂learning-loop.mjs产出design-learnerprompt--allow-monolingual对内部单语 memo 豁免双语强制(issue #2)--audit <dir>整树批量检查;加--discover只列出树里所有.html(分目录,不跑检查),确认没有藏在子目录里漏检的页 —— 详见下面「多页交付」- 第二视口几何复查默认开:Gate 2 主跑(1440)后,把 overlap/overflow 几类在更窄视口(默认 1024)再跑一遍,宽度相关的碰撞作 warn 标
[at Npx](known-bugs §1.34)。--viewport2=WxH调宽度、--no-second-viewport关掉 - 任一步 exit 非 0 整体 fail,截图存
--out=或默认<repo>/shots/
本仓用例:
~/.claude/skills/design-review/dr-cli index.html docs/HARNESS-ROADMAP.html
~/.claude/skills/design-review/dr-cli --multi-critic demos/gated-dual-clone/index.html
跨仓用例(例:engram):
~/.claude/skills/design-review/dr-cli --repo=/path/to/engram --skill=anthropic \
--css=/path/to/engram/docs/assets/app.css \
docs/en/index.html
多页交付:发布前整树批量检查(防漏页)
逐页跑 dr-cli <file> 是写每一页时的常规做法,但多页 deck 发布前要整树跑一遍,否则放在子目录里的页(_demos/ 样例、复制进来的报告产物等)很容易忘了过检查,带着真 bug 上线也没人发现。
# 整树批量检查(verify + visual-audit,递归子目录,一份汇总报告)
~/.claude/skills/design-review/dr-cli --audit <deck-dir>
# 只列出会被检查到的页(分目录,不跑检查)—— 先确认没有漏页
~/.claude/skills/design-review/dr-cli --audit --discover <deck-dir>
--audit <dir>递归走子目录,_demos/这类装内容的下划线目录也会进(纯资源目录_assets/无 html 自动跳过)。逐页检查只覆盖你显式传的那一个文件,--audit <dir>是发布前的兜底。--audit --discover <dir>先把树里所有.html按目录列出来(不跑检查),一眼看清有没有藏在子目录里没被检查的页。
分别调用(调试用)
# 1) 结构检查(静态)
python3 skills/design-review/scripts/verify.py \
[--skill=<name>] [--css=<path>]... [--allow-monolingual] <html> [...]
# 2) 视觉检查(Playwright 渲染) · 加 --ignore-intentional 消掉 brand-intentional 噪音
node skills/design-review/scripts/visual-audit.mjs \
[--ignore-intentional] <html>
# 3) 可达性检查(axe-core · --strict 让阻断规则真的阻断)
node skills/design-review/scripts/axe-audit.mjs --strict <html>
# 4) 肉眼检查(全页截图)
node skills/design-review/scripts/screenshot.mjs <html> shot.png
# 5) 口味评审(五道之外)· solo / multi critic
# 在 Claude Code 里:
Task(subagent_type="design-critic", ...) # 单专家
# or 4 并行:
Task(subagent_type="design-composition-critic", ...)
Task(subagent_type="design-copy-critic", ...)
Task(subagent_type="design-illustration-critic", ...)
Task(subagent_type="design-brand-critic", ...)
# 6) 回灌成规则(critic 发现 → known-bugs + 机器 check)
node skills/design-review/scripts/learning-loop.mjs \
--verdict=<path/to/verdict.json> # 产出 design-learner prompt
# 然后在 Claude Code 里 Task(subagent_type="design-learner", ...)
任一步 exit 非 0 = 没完成。修完再跑。
每道检查覆盖的 bug 类
| 检查 | 抓哪些 | 依赖 |
|---|---|---|
Gate 1 verify.py |
占位符(文档页 <pre>/<code> 块自动剥除,不误报)、BEM modifier-only、未定义 class(union: 默认 skill CSS + HTML link + --css)、<svg> 不平衡、hero 容器用错、container --mod 未与 base 同列(BEM base-less 错)、公开页缺双语(lang-toggle + lang-en/zh) |
Python 标准库 |
Gate 2 visual-audit.mjs |
WCAG contrast < 4.5、hero 框图渲染 < 900px、SVG <text> 实际像素 < 9px、多列网格孤儿卡、SVG <text> 重叠、SVG 文字 fill 和承载 shape RGB 距离 < 40、多 h1 / heading 跳级 / 无 alt img / 无文本 a、brand 色在 top region 占比 < 0.4%、cross-skill-smell(别扮成另一个 skill)、hollow-card §10b、asymmetric-first-col-hero §10c、svg-foreign-hex、figure 无 figcaption、Fraunces/Newsreader 等非本 skill 字体、italic 滥用 —— 共 26 类,每类对应 known-bugs.md 1 行 |
playwright |
Gate 3 axe-audit.mjs |
axe-core 可达性:color-contrast、link-name、aria-prohibited-attr、svg-img-alt 阻断,其余只报告 | playwright + axe-core |
Gate 4 screenshot.mjs |
只产物不评审 —— 给人看的 | playwright |
口味评审(五道之外)solo design-critic |
整页口味(构图 + 文案 + 插画 + 品牌)一位通才评审 | Task() subagent |
| 口味评审(五道之外)multi-critic × 4 | 构图 / 文案 / 插画 / 品牌 四位专家独立 fresh-context · 权重 25/25/20/30 聚合 | Task() × 4 + 聚合 |
具体清单在:
references/known-bugs.md(84 条,每条写 Reader sees / Why / Defense)references/cross-skill-rules.md(9 种风格共通工艺底线 · 有 §G 双语规则 + §I 卡片分组规则)references/dos-and-donts.md(每 skill 下的风格特定反例)
Learning-loop · 回灌成规则的流程
目标:同一类 bug 不被抓两次。
- Critic 抓到新问题 →
learning-loop.mjs --verdict=<json>解析 verdict - 调
design-learnersubagent → 产出 paste-ready 三件套:known-bugs.md新行visual-audit.mjs新机器 check(若能渲染时扫到)<skill>-design/references/dos-and-donts.md新行(若 style-specific)
- 人审 paste → commit
closes #N
首次实战(2026-04-22):13 条原始 issue → 5 类新 bug → 3 新 known-bugs(1.17/1.18/1.19)+ 1 新机器 check(figure-no-caption)+ 8 条 dos-and-donts。
Roadmap · 对应 HARNESS-ROADMAP
| Phase | 组件 | 状态 |
|---|---|---|
| 0 · 独立脚本 | verify / visual-audit / screenshot | done |
| 1 · 多风格 known-bugs 库 | 实时计数见 --facts --list |
done · 扩展中 |
| 2a · canonical 参考库 | 计数见 --coverage |
next · 每 session 1-2 张递进 |
| 3 · generator self-diff | 强制自评 note | 部分完成 |
| 5 · multi-critic | 4 专家并行 + 聚合 | done (2026-04-22) |
6 · /design-loop 编排 |
planner → gen → review → critic × 3 轮 | Future(依赖 Phase 01 planner) |
| 7 · learning-loop | design-learner + learning-loop.mjs |
done (2026-04-22) |
| 8 · library-grower | 5 张优秀产物 → 自动蒸馏新 canonical | Future(等 10+ 真实页数据) |
| 10 · facts 检查 | facts.mjs —— 展示页宣称的数字 vs 磁盘真值,不符即非 0 退出;外加中英配对:同一处声明的两半必须说同一个数 |
done (2026-07-30 · 配对 2026-09-08) |
facts 是两问,不是一问。 前一问「页面说的跟磁盘对不对得上」靠一份手写措辞清单
(facts.mjs 里那批正则),它只认有人想到过要写的写法。2026-09-08 实测这不是理论
缺口:它报了 18 处过期数字,全改完之后说 clean,同一批页面上还剩 17 处 ——
漏掉的那些长一个样:同一行里英文那半改了,中文那半没改,而且方向不固定(另一页反过来)。
所以加了第二问:同一处声明的中英两半必须说同一个数。 不认措辞,只问两边一不一致。 它跟前一问抓的是两类东西 —— 页面可以自己跟自己一致但整体是旧的 —— 谁也替代不了谁。
规则窄得有理由,是量出来的(本仓 2667 处配对):只比数字集合 → 50 处不同、47 处是好的;
再要求两边数字个数相同 → 6 处、3 处是好的;再要求每侧不超过 4 个数字 → 3 处、
0 处是好的。个数相同这条挡掉的是「英文写单词中文写数字」(Four scripts / 4 个脚本),
占原始差异的 44/50;上限 4 这条的理由是一句计数声明活在标题、图注或数据标签里,
带一两个数字,而本仓唯一超过 4 的是三段 45 个数字的变更记录 —— 2 和 45 之间没有
任何东西,阈值是画在空隙里的,不是拍的。代价说清楚:长段落里的过期数字这一问看不见,
那是前一问的活。自检 scripts/facts_selftest.sh,20 项。
生命周期规则
design-review 发现一个 不在 known-bugs.md 里 的新问题 → 必须补:
known-bugs.md追加 1 行,写清 Reader sees / Why / Defense- 能静态扫 → 加 check 到
verify.py - 能渲染时扫 → 加 check 到
visual-audit.mjs - Style-specific → 同步更新对应
<skill>-design/dos-and-donts.md
repo 级硬规则:同一类问题不能被抓两次。
Files · 文件
~/.claude/skills/design-review/dr-cli— 一条命令跑完 5 道检查 + 可选--multi-critic/--learnscripts/verify.py— Gate 1 结构 checkscripts/visual-audit.mjs— Gate 2 渲染 check(51 项)scripts/axe-audit.mjs— Gate 3 可达性 check(axe-core)scripts/screenshot.mjs— Gate 4 全页截图scripts/count-check.py— 全仓计数判定(承载短语 vs 磁盘真值 + 检查模型)scripts/learning-loop.mjs— 组件 07 · critic verdict → design-learner promptreferences/known-bugs.md— 84 条 bug 大全references/cross-skill-rules.md— 9 种风格共通规则(含 §G 双语 / §I 卡片分组)references/canonical/README.md— canonical 参考库说明 + 扩库流程
Reference
- Anthropic · harness design for long-running apps — "separate doing from judging"
- 同仓 pair:
skills/gated-dual-clone-audit/(第 2 个独立 evaluator) - 指南:
docs/HARNESS-ROADMAP.html(9 组件路线图)