实验台:fundhub-data-registry-1(2026-05-26 验证完成)
适用对象:设计师 / 轻技术背景的产品负责人——你用 Claude Code 跟 AI 协作生成 UI,
不想(也没时间)逐行审 AI 的 DS 合规性。
没有这套护栏时:
你跟 AI 说"做一个登录表单"。AI 写完。你打开发现:原生 <button>、几个 hex 颜色、
inline style ——这些都没走你的 Design System。你被迫当代码警察,挨个挑违规、要求改、再审。
装了护栏后:
你跟 AI 说"做一个登录表单"。AI 写完——但它没机会把违规版本交给你看。 AI 一保存文件,护栏立刻在 AI 自己眼前列出违规清单。AI 看到清单、自己改对、再交给你。
你的角色变了——只审"AI 改对之后的合规版本",把精力放在设计判断上 (信息层级、布局节奏),不再用眼睛盯 hex 颜色。
你的 DS 规矩 ──→ ESLint 规则 ┐
│
AI 保存 .ts/.tsx 文件 │
│ ├─→ 违规清单
↓ │
Claude Code Hook ──→ 跑 ESLint ┘ │
↓
AI 看到清单 ←─── stderr 返回给 AI
│
↓
AI 自动改对
- ESLint 规则 = 你定的 DS 规矩(什么写法禁止)
- Hook 脚本 = "门卫",AI 每次准备写文件时(PreToolUse)先跑 ESLint,违规版根本不落盘
- 反馈环 = 违规清单 → AI 看到 → 自动改对
护栏只是 门卫,不是 老师。它能说"这里错了",但不能告诉 AI "应该改成什么"。
AI 要能正确修复,必须懂你的 DS——所以本模板配套必须有一份给 AI 看的 DS 速查表 (什么颜色对应哪个 token、什么场景用哪个组件)。否则护栏只会反复骂 AI"不准用 hex", AI 不知道改成啥。
DS 速查表的具体形式(推荐放进项目根的 CLAUDE.md 或 DESIGN.md):
- Token 映射表:
#34694f→text-accent/#fffdf8→bg-surface… - 组件对照表:原生
<button>→<Button>from@/components/ds/ 原生<dialog>→<Modal>… - 间距 / 排版 token 列表
| 文件 | 干什么用 |
|---|---|
README.md |
你正在看的这份 |
eslint-rules.snippet.js |
ESLint 规则片段(6 条 syntax + 1 块 import 黑名单),粘贴到新项目的 eslint.config.js |
ds-guardrail-hook.mjs |
DS 护栏 hook(PreToolUse Edit/Write/MultiEdit,写前拦截违规版不落盘),放进新项目的 scripts/ 目录 |
deps-guard-hook.mjs |
依赖锁死 hook(PreToolUse Bash),拦 AI 偷偷 npm install 第三方包,放进 scripts/ 目录 |
claude-settings.json.template |
Claude Code 设置模板(含上面两个 hook 的注册),重命名后放进 .claude/settings.json |
apply-disable-headers.mjs |
一次性给老文件加豁免注释的脚本(Step 4 用) |
install.md |
AI 自助安装剧本(开箱用,AI 读了自动派 subagent 探测 + 装 + 验证) |
install-prompt.md |
你在新项目里复制粘贴用的 prompt(5 行内引向 install.md) |
在新项目里启动 Claude Code,给它一句话:
装 ds-guardrail。用 curl 拿剧本(WebFetch 会摘要不要用):
curl -s https://raw.githubusercontent.com/zerohe2001/ds-guardrail-template/main/install.md
按里面执行。
为什么提 curl:WebFetch 会把长 markdown 摘要,AI 拿摘要执行装不全。curl 拉 raw 原文。
完整剧本(给 AI 看):install.md
你装完要审什么(给你看):install-prompt.md
- 项目用 ESLint v9+(flat config)
- 项目用 Claude Code 协作
- 项目有 Design System(React 组件库 / Tailwind tokens / CSS variables 都行)
把 eslint-rules.snippet.js 里的代码粘贴到你项目的 eslint.config.js。
文件里有完整注释和粘贴位置示意。两条 ESLint 规则:
no-restricted-syntax—— 6 条规则(原生<button>/<dialog>/ hex / rgba / Tailwind 任意值 hex / inline color&background)no-restricted-imports—— 9 个第三方 UI 库 + styled-components 黑名单
把 ds-guardrail-hook.mjs 和 deps-guard-hook.mjs 复制到你项目的 scripts/ 目录。
第二个 hook 的作用:拦 AI 偷偷 npm install antd 装第三方 UI 库。bare npm install(同步现有 deps)和升级现有包都放行;只拦"装新包"。打开它的"项目自定义区",确认 PACKAGE_JSON_PATHS 指向你项目所有 package.json 路径(monorepo 列多个)。
回到 ds-guardrail-hook.mjs 的配置:
打开文件顶部"项目自定义区",填 4 段(模板和项目数据解耦——核心代码通用,项目特化全在这里):
// 1. 前端代码目录名
const SRC_DIR_NAME = 'frontend';
// 2. 你 DS 组件库的 import 路径(AI 看到反馈就知道从哪 import)
const DS_COMPONENTS_IMPORT = '@/components/ds';
// 3. DS 速查表路径(fix hint 末尾会列出,让 AI 知道去哪查完整对照)
const DS_REFERENCE_PATHS = ['CLAUDE.md', 'DESIGN.md'];
// 4. 常用 hex → DS class 对照(5-10 条最常用,内联进反馈,AI 不用 Read 文件就能直接改)
const COMMON_TOKEN_MAP = [
'#34694f → text-accent / bg-accent',
'#fffdf8 → bg-canvas',
// 加你项目实际用的常见颜色对照
];填完之后 AI 收到的护栏反馈会带项目特化信息——不只说"用 DS token",而是直接告诉 AI 用哪个 class 名。
如果你前端代码直接放项目根(没有子目录),SRC_DIR_NAME 改成 '.'。
其他三段任何一段留空也可以(fix hint 会自动跳过那部分)。
把 claude-settings.json.template 复制成 .claude/settings.json(去掉 .template 后缀)。
注意 .gitignore 陷阱:很多项目的 .gitignore 写了 .claude/,会忽略整个目录。
要让团队共享这个配置,改成:
.claude/settings.local.json ← 只忽略个人配置,settings.json 解禁
跑一次 ESLint 你会看到一堆"存量违规"。两条路:
A 偷懒路线(推荐给有历史债的项目):
node /Users/helin/Desktop/ds-guardrail-template/apply-disable-headers.mjs这个脚本会跑一次 ESLint,给所有有违规的文件顶部加一行
/* eslint-disable no-restricted-syntax -- pre-existing DS guardrail */。
代价:这些老文件变成"豁免区",未来编辑它们加新代码也不会被拦(见坑 3)。
B 严格路线(推荐给新项目):
跳过这步。让 ESLint 一直亮红,团队每次顺手清理几个,逐步清零。
新建一个测试文件——必须放隔离的 sandbox 目录(比如 src/__sandbox__/),
不要污染业务路径。文件里故意写一个原生 <button> 和一个 hex 颜色。保存。
如果设置正确,AI 会立刻收到违规清单 + 自动修正。
测完立刻删 sandbox 目录。
坑 E:Hook 跨目录不生效
- 现象:Hook 看似配好了,但根本不触发——Write 文件后什么都没发生,stderr 没回反馈,AI 以为自己写的合规。所有违规全部漏过。
- 已三次复现(2026-05-25 / 26 / 27):从
/Users/helin启动 Claude Code → 项目级.claude/settings.json不加载 → Hook 静默失效。这是落地必栽的坑。 - 解决方案:
- 启动 CC 前必须
cd <project-root>,不能从父目录起 - 启动后第一件事:Write 一个故意违规的小文件(比如
frontend/src/__sandbox__/_probe.tsx里塞个<button>),确认 stderr 收到 Hook 反馈 - 没收到反馈就停下排查——不验证就开工 = 等于没装 Hook
- 启动 CC 前必须
Hook 配置里 matcher: "Edit|Write|MultiEdit" 列了三个工具。MultiEdit 在某些
Claude Code 版本里没启用——不影响功能,Edit/Write 都正常工作。
未来 MultiEdit 启用了自动覆盖,不需要改配置。
const anchor = '#abc'——这是个 DOM hash 路由 id,跟颜色没关系,但因为
abc 三个字母碰巧都是合法 hex 字符,会被规则错杀。
三种应对(按优先级):
- 推荐:业务代码避免用
'#xxx'短 anchor id,改用'section-xxx' - 备选:那一行加
// eslint-disable-next-line no-restricted-syntax -- anchor id, not a color - 进阶:把规则里 hex 正则改严,只抓 6 位(
#[0-9a-fA-F]{6}\b)—— 代价是漏抓#abc这种短色
Step 4 偷懒路线给老文件加的 disable header 是文件级豁免——之后你编辑这些老文件、 加新代码,新违规也不会被拦。
这是 trade-off 不是 bug。两种路线在 Step 4 已经说过,模板里没法替你选。 原则:有历史债的项目用 A,新项目用 B。
验证 hook 时新建的测试文件——别放进 src/pages/ / src/components/ 等业务路径。
真实业务目录每个文件都有语义归属,混入 demo 文件未来很难一眼挑出来。
推荐 src/__sandbox__/,测完立刻删。
历史:早期版本 hook 用 PostToolUse,AI 保存文件之后才检查,意味着违规版本会先真的落盘一会儿,git status 会看到中间态。
现状:hook 改写为 PreToolUse + ESLint stdin 模式——AI 调 Write/Edit 时 hook 在工具执行前先拿到 "将要写入的内容" pipe 给 ESLint,违规 → exit 2 直接取消工具调用,文件根本不落盘。git status 永远干净。
新增的小注意点(替代坑 5):hook 采用"保守失败"原则——任何内部错误(stdin 解析失败 / Edit 的 old_string 找不到 / ESLint 自身崩溃)一律 exit 0 放行。理由是 hook bug 不应该卡住 AI 的正常工作;代价是极少数情况下违规版可能溜过去。调试时可以把 safeExit0() 改成打印错误 + exit 2,能看到哪一步出错。
以下三种写法规则抓不到,AI 学会规避后会绕过护栏。短期不补规则(成本高、收益低),但要知道边界。
坑 B:模板字符串 hex 不被覆盖
const css = `color: #34694f` 不会被抓。规则只匹配 string Literal 节点,TemplateLiteral 走另一条 AST 路径。CSS-in-JS 模板字符串塞 hex 会完全合法通过。
坑 C:React.createElement('button', ...) 不被覆盖
规则只看 JSX <button>(JSXOpeningElement),命令式 React.createElement('button', ...) 走 CallExpression,不进 selector。现代 React 几乎不用 createElement,但要知道有这个出口。
坑 D:spread variable 的 style 不被覆盖
const s = { color: 'red' }; <div style={s}> 不会被抓。规则只匹配 style={{...}} 字面对象表达式,style={Identifier} 看不到变量内部。这是规则覆盖最大的盲区——AI 把 style 抽到变量里就规避。
坑 A:多规则叠加时反馈可能不清晰
同一字面量可被多条规则同时报告。例:style={{ background: '#fff' }} 中 '#fff' 既触发 Rule 3(inline background)也触发 Rule 2(hex 字面量),同一位置出现 2 条反馈。对 AI 不困扰(修法殊途同归),但 reviewer 看 stderr 可能误以为是重复——这是非互斥规则集的正常行为。
| 场景 | 验证状态 | 备注 |
|---|---|---|
| Write 新文件含违规 | ✓ 触发护栏 | 5 处违规精确到行列 |
| Edit 改既有文件加违规 | ✓ 触发护栏 | |
| MultiEdit 批量改 | ⚠ 跳过 | 本次 Claude Code 环境没启用 MultiEdit |
| 合法写法误报检查 | ⚠ 部分误报 | 仅 anchor id '#xxx' 误报,其他都正确放过 |
| disable header 文件新加违规 | ✓ 静默通过 | 符合预期(trade-off,见坑 3) |
| AI 自动修正闭环 | ✓ 跑通 | hex → token、<button> → <Button>、删 inline style 都正确 |
短期(你哪天觉得不爽就改):
- 修 hex 误报:把规则里 hex 正则改严,只抓 6 位
- 加规则:原生
<dialog>/rgba()字面量 / Tailwind 任意值text-[13px]等
中期:
- 加一份 DS 速查表(token 映射 + 组件对照),放进 CLAUDE.md
- 配 ratchet 机制:违规计数只能减不能增,CI 强制
长期:
把 hook 改成 PreToolUse + lint stdin(写前拦截,不落盘)——技术上复杂,避免坑 5(已完成 P2)- 加 stylelint 堵
.css/.scss文件里的 hex(如果项目用 CSS module) - 修 deps-guard 的 commit-message false positive:当前 hook 把 Bash 命令字符串里的
npm install <pkg>子串都当真命令;写 commit message 含这种字面量会触发 hook。绕开:git commit -F <file>。彻底修:让 hook 识别 heredoc / 引号上下文(工程量大)。 - 把模板打成 npm 库(仅当 5+ 项目同时用 + 团队多人维护时才有 ROI)
模板里 5 个文件 + 这份 README,组合起来约 200 行代码。
真正的价值不是代码,是上面"已知坑"那 10 条——尤其是
未来你(或你团队)拿这套模板去新项目,先读坑、再动手。