别把 Claude Code 当聊天框:一套「确定性工程」落地手册

写在前面

我用 Claude Code 做生产项目一年多,走过一段典型弯路:一开始以为瓶颈在模型,不断换更强的模型,该出的 bug 还是出。后来我把注意力从"让模型更聪明"转到"让约束更硬",提升比换三次模型加起来都明显。

原因不复杂:Claude Code 不是问答系统,而是概率性的执行系统 ------同一个需求,每次产物都不一样。你要做的是把概率压缩成确定性。我把它拆成三层:

层次 回答的问题 手段 强制力
规则层 它知不知道项目规矩 CLAUDE.md、.claude/rules/、Skills、Commands 弱(建议)
分工层 有没有人出来唱反调 SubAgent、工具白名单 中(结构约束)
反馈层 "改好了"三个字谁来验证 Hooks、MCP、Checkpoint、验收脚本 强(运行时强制)

规则层管"愿意怎么做",分工层管"被允许怎么做",反馈层管"做不到就走不掉"。 只有第三层是真正的强制力。


一、它为什么会"越用越难用"

裸用时你会稳定撞上三堵墙------不是"有时候发生",而是必然随时间发生。

上下文漂移。 规划与中途的约定只存在于对话历史里,历史被压缩后"刚才说好用 A 方案"就丢了,它按重新推断的方案继续写,而你毫无察觉。

自我宽容。 同一个 Agent 先写代码再自己审查,等于让考生给自己阅卷。

口头验收。 "改好了"的真实命中率取决于它有多想收尾,而不是代码的真实状态。

共同点:缺的不是模型能力,是工程约束。


二、规则层:把口头约定写成文件

CLAUDE.md 不是备忘录,是编译期约束 :每一条都必须可被判定------要么通过,要么违反。大多数人的版本是「注意事项」下挂三条------高质量、规范、最佳实践,这类内容对模型的约束力接近于零,因为它无法判定自己是否违反了"高质量"。

判断标准只有一条:能不能被 grep 出来、被脚本检查? 不能,就重写:

markdown 复制代码
# Project: acme-api

## 这个仓库是什么
acme-api 是对外提供订单查询的 Node.js 服务。上游网关,下游 MySQL + Redis。

## 必须遵守(改代码前先读这段)
- 运行时:Node.js 20 LTS + TypeScript 5.x **ESM**,禁止出现 `require()`
- 包管理:只用 pnpm。`npm install` / `yarn add` 一律视为错误
- 目录约定:`src/routes/` 只放路由声明,**业务逻辑下沉到 `src/services/`**
- 错误码:必须引用 `ErrorCode` 枚举,**不得**手写 `res.status(400).json({...})`
- 鉴权:新增 HTTP 端点必须挂 `requireAuth()`;确需放开写 `allowAnonymous('原因')`

## 不要做的事
- 不改 `src/legacy/**`;不改测试断言去迁就实现;不做计划外重构

## 验收标准(必须同时满足)
1. `pnpm typecheck` 通过  2. `pnpm test` 全绿  3. `pnpm lint` 无 error
4. 接口类改动必须用真实请求打过一次,贴出响应

一个观察:禁止条款的效果明显好过倡导条款 ------有没有挂 requireAuth(),grep 一下就知道;符不符合"高质量",谁也判不了。

超过两百行后,每条规则被"注意到"的概率明显下降。按领域拆到 .claude/rules/,用 frontmatter 的 globs 限定生效范围。规则加载量直接影响模型表现,这不是文档洁癖。

再往上是两个容易混淆的机制:

  • Skill (.claude/skills/<name>/SKILL.md)**自动触发**,管"做事的规矩"。核心是 frontmatter 的 description,写得含糊这个 Skill 就等于不存在。我的 api-guard` 跑一张六项检查清单(鉴权、schema、统一包装、幂等、埋点、测试)。其中「需你决策」一段是我每个 Skill 都保留的设计------让 Skill 学会承认自己不知道,否则它会用看似合理的猜测把业务语义堵上。
  • Slash Command (.claude/commands/xxx.md)手动 / 调用 ,管"做事的顺序"。最常用的 ship.md:只读调研 → 出方案(停下等我确认)→ 实现 → 验证(真实执行并贴结果)→ 自审。

三、分工层:让批判与创造分离

规则是静态的,代码质量问题却是动态的。靠结构解决:让 critic 和 author 不处于同一个上下文。

SubAgent 就是 .claude/agents/ 下的 Markdown,frontmatter 定义身份与能力边界,子 Agent 在隔离上下文里运行------这是整个设计的核心。三个角色足矣:

角色 模型定位 tools 关键约束
planner 最强档 Read, Grep, Glob, WebSearch 只给计划不给代码;说不清的列成问题清单反问
coder 中档 Read, Write, Edit, Bash, Grep, Glob 计划的作者不是你;只改计划内文件
reviewer 小档(换系列) Read, Grep, Glob, Bash(pnpm test:*, pnpm typecheck, pnpm lint) 只读;每条问题带行号 + 置信度 + 后果

reviewer 的提示词关键在最后两行:

markdown 复制代码
## 输出要求
每条问题必须带三样东西,缺一不可:
- 文件 + 行号
- 置信度 0-100
- **具体后果**:会导致什么。不接受"这里可能有点问题"这种废话

**置信度低于 70 的一律不输出。** 宁可漏,不要吵。

三个容易被忽略的决策:reviewer 必须只读 ------能改代码的 reviewer 会自己动手,从"审查者"退化成"第二个 coder";reviewer 用小模型反而更好 ------同模型自我审查的最大问题是过度认同 ,换个不同系列的小模型能打破这种共鸣,还便宜;置信度阈值比评分更重要 ------AI 审查让人放弃使用的原因不是漏报,是误报淹没有效信息,报 30 条、28 条是废话等于报 0 条。

编成一条 /pipeline:调 @planner 拆解(拿到待确认问题后转问我,不许替我回答 )→ 交给 @coder 实现 → 调 @reviewer 独立验证(让它真的跑命令 )→ 高于 70 分的问题交回 @coder 修复,最多两轮。需要说明的是,这种编排的可靠性来自 prompt 写得够硬,不是引擎级保证。要更强的确定性,得上 Hook。


四、反馈层:把"改好了"翻译成"跑过了"

三层里最重要、也是最少人做的一层。前面所有东西本质上都是提示 ,模型可以在某次采样里忘了遵守;只有 Hook 是确定会执行的------挂在工具调用事件上,由运行时触发,不由模型决定。

rules 是"它应该这样做",hooks 是"它不得不这样做"。

最有用的事件是 Stop:每当 Claude 想结束回合,先跑你的脚本 。退出码 0 放行,2 拦截并把 stderr 原样喂回给 Claude------这不是"报错",是"把反馈传回去",它读到后会接着干活。

json 复制代码
{
  "permissions": {
    "allow": ["Read", "Edit", "Write", "Glob", "Grep", "Bash(pnpm:*)"],
    "deny": ["Bash(rm:*)", "Bash(git push:*)", "Read(./.env)", "Read(./.env.*)"]
  },
  "hooks": {
    "PostToolUse": [
      { "matcher": "Write|Edit",
        "hooks": [{ "type": "command", "command": "pnpm typecheck 2>&1 | tail -15" }] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "node scripts/verify-app.mjs" }] }
    ]
  }
}

四个要点:git push 必须禁掉------让 Agent 拥有推送权限是迟早出事的决定;Bash(pnpm:*) 是白名单粒度控制,比 Bash(*) 安全得多;PostToolUse 防错误累积,Stop 防提前收工;个人偏好放 .claude/settings.local.json 并加进 .gitignore,团队规则放 settings.json 提交 Git。

verify-app.mjs 四步:跑 typecheck / test / lint 收集失败 → git diff --name-only HEAD 取改动文件 → 检查 src/routes/ 下有没有 requireAuth 或 allowAnonymous → 任一失败写 stderr 并 process.exit(2)。

三个 pnpm 命令不是重点,最后那段项目专属检查才是 ------它把"新增端点必须挂鉴权"从软约定变成会拦截的硬约束 。这就是确定性工程的本质:把软约定翻译成可执行检查,交给运行时强制执行。

MCP 解决另一半问题------"它能看到什么":常驻 context7(治过时的 API 写法)、playwright(自己开浏览器验证)、filesystem(看见项目全貌)、数据库 MCP(查真实数据)。

三个坑:数据库一律只读账号 (给一个能 DROP TABLE 的连接串,性质等同于把生产库 root 密码贴出来);filesystem 路径必须绝对 ;改完配置必须重启。


五、实战:给一个 Express 服务做鉴权收口

选它是因为很典型:改动范围中等、涉及安全、容易出隐蔽 bug。

起点。 每个路由都自己写 jwt 校验:缺 header 返回 401 { msg: 'no auth' },jwt.verify 失败返回 403 { msg: 'bad token' }------另一个路由同样情况却返回 401。参数校验写成 res.status(500).json({ msg: 'amount?' }),退款逻辑没有任何幂等保护。更要命的是,同样的逻辑在另外两个路由里是另外两个版本。

规划。 planner 会 grep 出全部 jwt.verify 调用点。真正的价值是它会反问 :三个路由角色要求不同,是否需要按角色收口;能否一次性替换;token 过期与非法是否返回同一个错误(返回不同可能泄漏用户是否存在)。这三个问题我不回答它就不该动手。

抽出中间件。 目标是一支 requireAuth(...roles),四个刻意取舍:

ini 复制代码
export const requireAuth =
  (...roles: string[]): RequestHandler =>
  (req, _res, next) => {
    try {
      const token = parseBearer(req.headers.authorization);
      const payload = jwt.verify(token, secret) as JwtPayload & { roles?: string[] };
      if (!payload.sub) throw new AppError(ErrorCode.UNAUTHORIZED, 'token 缺少 sub');

      const user: AuthUser = { sub: payload.sub, roles: payload.roles ?? [] };
      if (roles.length && !roles.some(r => user.roles.includes(r))) {
        throw new AppError(ErrorCode.FORBIDDEN, `需要以下任一角色:${roles.join(', ')}`);
      }
      req.user = user;
      next();
    } catch (err) {
      next(err); // 统一交给 error handler,中间件自己不写响应
    }
  };

① 中间件自己不写响应 ,错误统一走 next(err),"错误长什么样"只有一个地方定义,且只对 status >= 500 打 error 日志。② JWT_SECRET 缺失时启动即失败 。③ token 过期与非法返回同一错误 ,避免通过错误信息枚举用户是否存在。④ 职责单一,只做认证。

覆盖失败路径。 不止 happy path,六条:无 Authorization 头 → 401;scheme 写成 Basic → 401;token 过期 → 401 且响应体结构与无 token 时完全一致 (这条防的不是 bug 是信息泄漏);角色不足 → 403;缺 amount → 400 而非 500;相同幂等键重复提交 → 两次 refundId 相同。第三条只有 reviewer 视角想得到。

验收并固化到 CI。 别问"你确定没问题吗",直接下指令:「用 playwright 走一遍登录,验证 401 时前端跳登录页而不是白屏,把截图给我。」从"我说通过了"到"截图在这儿",这是反馈层想要的全部效果。最后把同一份 verify-app.mjs 放进 CI------本地 Hook 可以绕过,CI 绕不过。

复盘。 退款要不要幂等、幂等键什么粒度、能否一次性切过去------这三件业务语义的事它猜不中。流程能保证"已知的正确被稳定执行",不能保证"未知的语义被正确猜中"。


结语

这套东西真正改变的不是"代码写得快不快",而是你敢不敢把活交给它。裸用 Claude Code,你只能让它做随时能检查的小事;配好三层之后,你才敢让它做那种要跑二十分钟但你敢去接杯咖啡的任务。

规则层管"愿意怎么做",分工层管"被允许怎么做",反馈层才管"做不到就走不掉"。


相关推荐
AI视频工具指南1 小时前
怎么用 AI 做一两分钟的小故事视频?
人工智能·aigc·音视频
自律懒人1 小时前
英伟达三款开源 Agent 模型横评:120B 旗舰、60B 压缩、74B 塞进 24GB
ai编程
从小白到软工3 小时前
4-3 项目-学生成绩管理系统(上)
c语言·开发语言·数据结构·经验分享·ai编程
ZzT4 小时前
context-mode 拆解:98% 是怎么算出来的,装进 Claude Code 实际省多少
ai编程·claude
Dawson Zhu4 小时前
大模型拒答率与错误率平衡术:五步构建可落地的安全与体验最优解
人工智能·语言模型·架构·aigc·agi
tz_zs4 小时前
【AI编程】 防止AI编程中的膨胀
ai编程
feiyu_gao4 小时前
可验证的 AI:如何让 Agent 用代码自证正确
aigc·ai编程·vibecoding
深频率4 小时前
1M上下文、5元千次:DeepSeek把搜索也拿去卖了
aigc
用户842558130874 小时前
同一篇稿子发到 6 个平台,为什么只有这里的图片全裂了?
ai编程
用户842558130874 小时前
把「一文多发」做成脚本流水线:CDP、滑块、二维码、WAF、限流,这些坑我替你踩完了
ai编程