Claude Code 工程化最佳实践:从 CLAUDE.md 到 Hooks

13阅读10分钟

我把 Claude Code 引入团队日常开发已经大半年了。最初以为写一份详细的 CLAUDE.md 就够了,结果发现 Agent 在长会话里开始忽略关键规则、忘记测试命令、甚至在上下文膨胀后产出与项目风格不一致的代码。这些问题不是模型能力不够,而是工程化配套没有跟上。

这篇文章整理了我在这半年里踩过的坑和逐渐成型的实践,围绕四个核心问题展开:哪些知识需要长期保留,哪些行为必须被约束,哪些结果需要验证,以及上下文如何管理。

上下文窗口是稀缺资源

Claude Code 的上下文窗口上限为 100 万 tokens,但实际可用的「高质量区间」远没有看起来那么大。社区测量数据显示,当上下文使用率达到 40% 左右时,模型输出质量就开始出现可感知的下降;到 60% 时应该主动结束当前会话1。一个 500 行的 TypeScript 文件大约消耗 4000 tokens,一次详细回复占用 1500–3000 tokens——这意味着一个认真工作的会话大约 50 轮交互就会触及退化边界。

Anthropic 在 Claude Code 官方文档中明确了记忆系统的分层加载机制:全局 ~/.claude/CLAUDE.md、项目根目录 CLAUDE.md、子目录 CLAUDE.md、以及 .claude/rules/*.md 条件加载规则文件2。每一层都会在会话启动时注入上下文。如果所有层级加起来超过 2000 tokens,Agent 对规则的遵从率会整体下降——不是后面的规则被忽略,而是所有规则的有效率同时降低3

这让我意识到一个根本性的设计原则:输出质量是上下文质量的直接函数。每一次往上下文里塞信息,都要问自己这条信息是否值得占用有限的注意力预算。

CLAUDE.md 不是许愿池

最早的 CLAUDE.md 我写了 150 行,把编码风格偏好、架构愿景、临时需求和硬性约束混在一起。结果 Agent 在关键约束上的遵从率大约只有 35%,和我写「写出整洁代码」这种模糊指令时的遵从率差不多3

问题出在信息密度和分层策略上。经过几轮调整,我总结出一套有效的分层结构:

CLAUDE.md              # 项目级硬约束,控制在 60 行以内
CLAUDE.local.md        # 个人偏好,加入 .gitignore
.claude/rules/*.md     # 条件加载规则,按目录激活

反面案例:把一切塞进 CLAUDE.md

# 反面示例:CLAUDE.md 里的信息噪音
 
## 编码风格
- 使用单引号,不要双引号
- 不要尾分号
- 函数不超过 30 行
- 变量命名要有意义
- 注释要写清楚          # ← 模糊指令,Agent 无法执行
- 写出整洁的代码          # ← 更是如此
 
## 架构想法
- 未来可能迁移到微服务    # ← 临时愿景,不是当前约束
- 考虑用 event sourcing   # ← 应该放在 RFC 文档里
 
## 临时需求
- 这周先把搜索功能做完    # ← 任务级别,不属于项目记忆
- 记得改一下首页 banner    # ← 应该放在 Issue 里

这种写法有三个问题:模糊指令不产生可验证的行为变化;临时需求污染长期记忆;架构愿景让 Agent 在应该遵循现有模式时犹豫不决。

正面案例:分层后的 CLAUDE.md

# 项目协作约束
 
## 项目事实
- Monorepo: pnpm + Turborepo, Node.js 22+
- 主应用: apps/web (Next.js 16 App Router)
- 共享 UI: packages/ui
- Prompt 模板: packages/prompts
 
## 代码边界 [CRITICAL]
- 页面文件只做路由装配,复杂 UI 放 _components/ 或 features/
- API 请求只能通过 apps/src/api/ 模块进入
- 环境变量只能通过 apps/src/config/env/ 读取
- 单文件不超过 500 行,超出需先讨论拆分方案
 
## 禁止操作
- 不执行 git reset --hard / git push --force
- 不还原用户已有改动
- 不使用 class-based views
- 不在 tests/ 目录外写测试文件
 
## 验证命令
- 类型检查: pnpm typecheck
- 全量验证: pnpm verify
- 提交前必须运行 pnpm verify

这份 30 行的 CLAUDE.md 比之前 150 行的版本有效得多。关键改动是:每条规则都能回答「它阻止了哪类误判」;硬性约束和代码边界用标签区分优先级;临时需求全部移出,放入 Issue 跟踪。

CLAUDE.md 中的否定表达陷阱

一个容易忽略的问题是:否定句会把被否定的概念重新拉进模型的注意力范围。当 CLAUDE.md 里写「不要使用分号」时,模型反而更容易产出带分号的代码,因为「分号」这个概念被激活了。实测数据表明,将否定规则改写为正向等价表达后,违规率大约降低一半3

否定写法(容易触发违规)正向等价写法(推荐)
不要使用分号使用 ESLint no-semicolon 规则
不要用 any 类型使用 unknown + 类型收窄
不要写默认导出使用命名导出
不要直接读 process.env环境变量统一从 config/env 模块读取
不要写类组件使用函数组件 + Hooks

这个改写的核心逻辑是:把「不要做 X」转换成「做 Y 来替代 X」。模型更容易遵从正向指令,因为它不需要在生成过程中持续抑制一个已被激活的概念。

Hooks:把规则从建议变成执行

CLAUDE.md 里的规则本质上是「建议」——模型大概率会遵守,但不保证。当团队需要确定性保障时,Hooks 是更可靠的选择。Hooks 是在 Agent 生命周期特定节点执行的确定性 Shell 命令,它们运行在 Agent 循环之外,不受模型推理影响4

我按风险等级把 Hooks 分成三层:

流程图画布 · 115%
Mermaid 流程图加载中...

安全层 Hook 示例

这个 Hook 在每次命令执行前拦截危险操作,是投入产出比最高的一层:

// .claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            // 拦截不可逆 git 操作和危险删除命令
            // exit code 2 表示阻断,Agent 会收到拒绝通知
            "command": "jq -r '.tool_input.command' | grep -qE 'git reset --hard|git push --force|rm -rf /' && exit 2 || exit 0"
          }
        ]
      }
    ]
  }
}

质量层 Hook 示例

文件写入后自动运行格式化,比在 CLAUDE.md 里写「记得运行 Prettier」可靠得多:

// 反面:只在 CLAUDE.md 里写规则(不可靠)
// "记得每次编辑后运行 prettier"
// 问题:模型在长会话中经常忘记,且无法审计
 
// 正面:用 Hook 强制执行(确定性)
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            // 自动格式化被修改的 TypeScript 文件
            // 只在文件匹配 .ts/.tsx 时执行,避免无关文件触发
            "command": "jq -r '.tool_input.file_path' | grep -qE '\\.(ts|tsx)$' && npx prettier --write \"$(jq -r '.tool_input.file_path')\" || true"
          }
        ]
      }
    ]
  }
}
约束方式可靠性适用场景失败代价
CLAUDE.md 文字规则中(~89% 遵从率)编码风格、架构约定Agent 可能产出风格不一致的代码
Hooks 自动执行高(确定性执行)格式化、类型检查、危险命令拦截无——Hook 始终执行
settings.json 权限高(工具级阻断)工具白名单、命令审批配置错误可能阻断正常工作流
子代理隔离高(独立上下文)代码审查、大规模文件扫描子代理看不到主会话上下文

子代理:上下文隔离的核心武器

当任务可能产生大量文件读取和搜索操作时,把这部分工作交给子代理是保持主会话上下文干净的最有效手段。子代理拥有独立的上下文窗口、独立的工具白名单和独立的权限集,完成后只把最终结果返回给主会话5

我犯过的一个典型错误是让主代理直接做代码审查——它会读取几十个文件,搜索大量引用关系,等审查完成时主会话的上下文已经膨胀到退化区间,接下来再做的任何任务质量都会下降。

# 反面做法:在主会话中做代码审查
# 问题:几十个文件的读取和搜索全部留在主上下文
 
我: 帮我审查这个 PR 的变更,检查有没有安全和性能问题
Claude: [读取 30 个文件,搜索引用关系,分析 diff...]
我: 审查完了,现在帮我重构搜索模块
Claude: [上下文已被审查任务污染,重构质量下降]

正确的做法是定义一个专门的审查子代理,让它处理所有的文件读取和分析工作:

# .claude/agents/code-reviewer.md
---
name: code-reviewer
description: 专注代码审查的子代理,只返回审查结论
tools: Read, Grep, Glob, Bash
model: haiku
---
 
你是一名资深代码审查员。关注以下维度:
1. 正确性:逻辑是否符合需求
2. 安全性:是否有注入、越权、敏感信息泄露
3. 性能:是否有 N+1 查询、不必要的重渲染
4. 项目约定:是否遵循 CLAUDE.md 中的代码边界
 
只返回审查结论和具体问题列表,不需要返回文件内容。

主会话调用方式:

我: 用 code-reviewer 代理审查当前分支相对 main 的变更

审查过程中的所有文件读取、搜索操作都发生在子代理的上下文窗口里,主会话只收到一份精炼的审查报告。

子代理适用场景对比

场景适合子代理?原因
代码审查(读大量文件)读取和搜索操作不污染主上下文
大规模文件重构可并行处理,各自隔离
核心业务逻辑调试需要主会话的完整对话上下文
数据库 Schema 迁移任务独立,有明确的验证标准
多模块 API 集成测试每个模块可以独立验证

会话生命周期管理

一个会话应该有一个明确的作用域——「实现分页功能」或「排查鉴权 Token 刷新问题」。作用域完成后,会话就应该结束。在同一个会话里开始第二个任务,仅仅因为上下文预算还有剩余,是产出质量下降最常见的原因6

上下文退化有几个可观察的信号:

  • 重复建议:Agent 开始建议已经尝试过的方案
  • 风格漂移:产出的代码风格开始偏离项目约定
  • 幻觉增加:出现不存在的函数名或虚构的 API

当这些信号出现时,正确的做法是 /compact/clear,而不是在退化的上下文里继续推进。

# 反面做法:在退化上下文中硬撑
# 问题:Agent 已经出现重复建议,继续对话只会累积噪音
 
我: 帮我修复搜索分页的 bug
Claude: [尝试方案 A,失败]
Claude: [尝试方案 B,失败]
Claude: [再次建议方案 A——重复信号出现]
我: 再试试别的方案         # ← 上下文已经退化
Claude: [建议一个不存在的 API——幻觉信号出现]
# 正面做法:识别信号后主动重置
 
我: 帮我修复搜索分页的 bug
Claude: [尝试方案 A,失败]
Claude: [尝试方案 B,失败]
我: /clear
我: 搜索分页 bug 仍未解决。之前尝试了 A 和 B 都失败了。
    请从不同角度分析根因,重点关注 SQL OFFSET 计算逻辑。
Claude: [在干净的上下文中从新角度分析,找到真正根因]

两种做法的关键差异在于:重置后用 /clear + 简要摘要,我控制了哪些信息进入上下文,而不是让失败的尝试和修正过程继续占据注意力预算。

验证证据:让交付可审查

AI 生成代码的审查焦点应该从「这是不是 AI 写的」转向「这次变更有没有证据支撑」。我要求所有 Claude Code 的产出都必须附带验证证据,格式固定为三部分:变更范围、验证结果、剩余风险。

## 变更范围
- 修改了搜索请求入口 `apps/src/features/search/api.ts`
- 新增空结果兜底组件 `apps/src/features/search/EmptyState.tsx`
 
## 验证结果
- pnpm typecheck: 通过
- pnpm test --filter search: 12 个测试全部通过
- 本地浏览器访问 /search?q=test: 搜索框正常返回结果
- 本地浏览器访问 /search?q=xyznotfound: 空结果兜底正常展示
 
## 剩余风险
- 未验证生产 API 的限流场景(需要部署后验证)
- 后端 500 错误只做了前端 Toast 提示,没有重试机制

这份证据把 Reviewer 的注意力聚焦在需要人工判断的地方——剩余风险,而不是逐行检查 AI 写的代码是否合理。

落地检查清单

以下清单按实施阶段分组,建议从第一阶段的 4 项开始,逐步推进。

第一阶段:基础配置(第 1 天)

  • CLAUDE.md 控制在 60 行以内,只包含硬性约束和代码边界
  • 所有否定规则改写为正向等价表达
  • 临时需求和架构愿景移出 CLAUDE.md,放入 Issue 或 RFC
  • 确认验证命令(typecheck / test / lint)写入 CLAUDE.md

第二阶段:Hooks 防护(第 2-3 天)

  • 配置安全层 Hook,拦截 git reset --hardrm -rf 等不可逆操作
  • 配置质量层 Hook,文件写入后自动运行格式化
  • 确认 Hook 的 exit code 语义:0 通过,2 阻断
  • .claude/settings.json 中配置合理的工具权限白名单

第三阶段:子代理与上下文管理(第 1 周)

  • 为代码审查创建专用子代理,避免主上下文膨胀
  • 为大规模文件扫描创建专用子代理
  • 团队约定:单个会话只处理一个明确作用域的任务
  • 团队约定:出现重复建议或幻觉信号时主动 /clear

第四阶段:交付规范(第 2 周)

  • 定义固定的验证证据模板(变更范围 + 验证结果 + 剩余风险)
  • 所有 Claude Code 产出必须附带验证证据才能提交 Review
  • Review 关注点从「是否 AI 生成」转向「证据是否充分」
  • 每周回顾 CLAUDE.md,移除已不再适用的规则,补充新发现的误判模式

小结

Claude Code 的工程化不是一个一次性的配置任务,而是一个持续校准的过程。CLAUDE.md 的每一条规则都应该能回答「它减少了哪类误判」;每一个 Hook 都应该有明确的风险等级;每一次交付都应该附带可验证的证据。上下文窗口是稀缺资源,像管理内存一样管理它。

参考资料

Footnotes

  1. Context Management Strategies for Claude Code — Iceberg Lakehouse, 2026. 详细分析了上下文退化阈值和管理策略。

  2. Memory - Claude Code Docs — Anthropic 官方文档。说明了 CLAUDE.md 的分层加载机制。

  3. Claude Code Best Practices: A Developer's Guide (2026) — MCP Directory. 包含 CLAUDE.md 长度建议和否定表达陷阱的实测数据。 2 3

  4. Automate actions with hooks - Claude Code Docs — Anthropic 官方文档。Hooks 的完整生命周期事件和使用指南。

  5. Create custom subagents - Claude Code Docs — Anthropic 官方文档。子代理的定义方式和上下文隔离机制。

  6. Claude Code Guide 2026: Context Engineering & Planning — Generative Inc. 会话生命周期管理和四阶段工作流设计。

评论 0

0 / 1000