Skip to content

Repository files navigation

DS Guardrail — AI 协作设计系统护栏模板

实验台: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)

开箱安装(AI 自助)— 推荐

在新项目里启动 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


手动安装(Boss 自己装 / 调试用)— 4 步

前置条件

  • 项目用 ESLint v9+(flat config)
  • 项目用 Claude Code 协作
  • 项目有 Design System(React 组件库 / Tailwind tokens / CSS variables 都行)

Step 1: 粘贴 ESLint 规则

把 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 黑名单

Step 2: 复制两个 Hook 脚本 + 填项目配置区

把 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 会自动跳过那部分)。

Step 3: 配 Claude Code Hook

把 claude-settings.json.template 复制成 .claude/settings.json(去掉 .template 后缀)。

注意 .gitignore 陷阱:很多项目的 .gitignore 写了 .claude/,会忽略整个目录。 要让团队共享这个配置,改成:

.claude/settings.local.json   ← 只忽略个人配置,settings.json 解禁

Step 4: 给现有违规加豁免(可选,二选一)

跑一次 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 一直亮红,团队每次顺手清理几个,逐步清零。

Step 5: 验证

新建一个测试文件——必须放隔离的 sandbox 目录(比如 src/__sandbox__/), 不要污染业务路径。文件里故意写一个原生 <button> 和一个 hex 颜色。保存。

如果设置正确,AI 会立刻收到违规清单 + 自动修正。

测完立刻删 sandbox 目录。


已知坑 / Trade-off(必读,比代码更值钱)

⚠️ 环境前置(必读,否则模板不生效)

坑 E:Hook 跨目录不生效

  • 现象:Hook 看似配好了,但根本不触发——Write 文件后什么都没发生,stderr 没回反馈,AI 以为自己写的合规。所有违规全部漏过。
  • 已三次复现(2026-05-25 / 26 / 27):从 /Users/helin 启动 Claude Code → 项目级 .claude/settings.json 不加载 → Hook 静默失效。这是落地必栽的坑。
  • 解决方案:
    1. 启动 CC 前必须 cd <project-root>,不能从父目录起
    2. 启动后第一件事:Write 一个故意违规的小文件(比如 frontend/src/__sandbox__/_probe.tsx 里塞个 <button>),确认 stderr 收到 Hook 反馈
    3. 没收到反馈就停下排查——不验证就开工 = 等于没装 Hook

坑 1:MultiEdit 工具不一定可用

Hook 配置里 matcher: "Edit|Write|MultiEdit" 列了三个工具。MultiEdit 在某些 Claude Code 版本里没启用——不影响功能,Edit/Write 都正常工作。 未来 MultiEdit 启用了自动覆盖,不需要改配置。

坑 2:anchor id 误报

const anchor = '#abc'——这是个 DOM hash 路由 id,跟颜色没关系,但因为 abc 三个字母碰巧都是合法 hex 字符,会被规则错杀。

三种应对(按优先级):

  1. 推荐:业务代码避免用 '#xxx' 短 anchor id,改用 'section-xxx'
  2. 备选:那一行加 // eslint-disable-next-line no-restricted-syntax -- anchor id, not a color
  3. 进阶:把规则里 hex 正则改严,只抓 6 位(#[0-9a-fA-F]{6}\b)—— 代价是漏抓 #abc 这种短色

坑 3:disable header 文件成为"豁免区"

Step 4 偷懒路线给老文件加的 disable header 是文件级豁免——之后你编辑这些老文件、 加新代码,新违规也不会被拦。

这是 trade-off 不是 bug。两种路线在 Step 4 已经说过,模板里没法替你选。 原则:有历史债的项目用 A,新项目用 B。

坑 4:测试 sandbox 一定要放隔离目录

验证 hook 时新建的测试文件——别放进 src/pages/ / src/components/ 等业务路径。 真实业务目录每个文件都有语义归属,混入 demo 文件未来很难一眼挑出来。 推荐 src/__sandbox__/,测完立刻删。

坑 5:护栏是"写后兜底",不是"写前拦截"(已解决 P2 PreToolUse 重写)

历史:早期版本 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 抽到变量里就规避。


行为说明(不是 bug,是设计)

坑 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 条——尤其是 ⚠️ 环境前置(坑 E),不验证就开工 = 等于没装。

未来你(或你团队)拿这套模板去新项目,先读坑、再动手。

About

DS guardrail template — ESLint rules + Claude Code PreToolUse hooks. Drop-in for any React/TS project to make Claude Code self-correct toward your Design System.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages