# 让代码库对智能体友好

> 智能体的上限，往往由代码库的可读性和反馈速度决定。本课讲一键可跑的脚本、小而快的反馈回路、类型系统、目录与命名、文档就近、AGENTS.md 与 CLAUDE.md 的分工，以及如何用「新员工测试」量化你的代码库。

- 网址：https://coding.chengyu.dev/learn/engineering/codebase-ready/
- 学习路径：AI 编程的工程方法
- 预计用时：30 分钟；核实日期：2026-10-25
- 官方来源：[Set up Claude Code in a monorepo or large codebase（Claude Code 官方文档）](https://code.claude.com/docs/en/large-codebases)，[How Claude remembers your project（Claude Code 官方文档，含 AGENTS.md）](https://code.claude.com/docs/en/memory)，[Best practices for Claude Code（Claude Code 官方文档）](https://code.claude.com/docs/en/best-practices)，[Manage costs effectively（Claude Code 官方文档，代码智能插件与 Hook 过滤）](https://code.claude.com/docs/en/costs)，[Explore the .claude directory（Claude Code 官方文档）](https://code.claude.com/docs/en/claude-directory)，[AGENTS.md 指南（OpenAI Codex 文档）](https://learn.chatgpt.com/docs/agent-configuration/agents-md)，[Rules（Cursor 文档）](https://cursor.com/docs/context/rules)，[Effective harnesses for long-running agents（Anthropic）](https://www.anthropic.com/research/effective-harnesses-for-long-running-agents)，[Effective context engineering for AI agents（Anthropic）](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents)

## 学习目标

- 能把「新同事入职」标准用在代码库上，找出让智能体反复出错的结构性原因
- 能设计一条命令跑完全部检查，并让反馈足够快、足够短
- 能用类型、目录、命名和就近文档降低智能体的探索成本
- 能在 AGENTS.md 与 CLAUDE.md 之间做合理分工，并为大型仓库做分层配置

同一个智能体，同一个模型，放进两个仓库，表现可以天差地别。A 仓库里，它读完说明文件，跑一条命令确认基线是绿的，改完再跑一遍，十分钟交付。B 仓库里，它不知道测试怎么跑，试了三种命令都失败，在一个四千行的 `utils.js` 里迷路，最后改了不该改的文件。

差别在代码库，不在智能体。我习惯用一个比喻：**智能体是一位每二十分钟入职一次、没有任何记忆的新同事。** 对新同事友好的仓库，对智能体也友好；而且智能体对「不友好」的容忍度更低，它不会去茶水间问人。

这一课把「友好」拆成可以动手改的几件事。

## 一、一条命令跑完所有检查

先问一个问题：一个陌生人 clone 下来，不问任何人，能不能在五分钟内知道**怎么跑、怎么测、怎么检查**？

智能体需要的是一个确定的入口。Anthropic 在长时任务的 harness 文章里，让初始化智能体专门生成 `init.sh`：它启动开发服务器，这样后续每个会话就不必再花 token 去琢磨如何跑起应用。你的仓库也该有：

```json
{
  "scripts": {
    "dev": "vite",
    "typecheck": "tsc --noEmit",
    "lint": "eslint .",
    "test": "vitest run",
    "verify": "npm run typecheck && npm run lint && npm test"
  }
}
```

要点：

- **`verify` 一条命令**，退出码 0 即通过。智能体不用记三条命令的顺序，Stop Hook 和 CI 也能共用它。
- **环境可复现**：提供 `.env.example`，依赖服务用 `docker compose up -d` 或开发容器起来。测试依赖「某人电脑上的数据库」，智能体永远修不好。
- **输出要克制**：成功时尽量安静，失败时直接指出哪个文件哪一行。官方成本文档里有个例子：用 Hook 把测试输出过滤成只剩失败项，上下文占用从几万 token 降到几百。
- **worktree 友好**：并行会话常用 git worktree，被忽略的文件（`.env`、本地配置）不会自动带过去。Claude Code 支持在 `.worktreeinclude` 里声明要复制的被忽略文件。

## 二、小而快的反馈回路

智能体的工作方式是「改一点，验证一下」。**反馈每慢十倍，它能尝试的次数就少十倍，或者你得等十倍的时间。**

| 层 | 目标耗时 | 做法 |
|---|---|---|
| 单个文件的测试 | 几秒 | 支持按文件、按用例名运行，并写进说明文件 |
| 类型检查 | 十几秒内 | 增量编译，必要时按包分别检查 |
| 完整 `verify` | 几分钟以内 | 并行、缓存；重量级测试分到独立命令 |
| 端到端 | 分钟级 | 只在任务收尾时跑 |

最有杀伤力的是**不稳定的测试**。一个偶发失败的测试，会让智能体无法区分「我改坏了」和「它本来就会抖」，于是要么反复重试浪费上下文，要么得出错误结论。对智能体友好的第一步，常常是先修掉最抖的那几个测试，或者隔离并标注它们。

## 三、类型系统：机器可检查的规格

类型是最便宜的验证，也是最好的导航。智能体改动一个函数签名，类型检查会立刻告诉它所有受影响的调用点，不需要靠 grep 碰运气。

- 开启严格模式（TypeScript 的 `strict`、Python 的 `pyright`/`mypy` 严格配置），先从新代码和核心模块做起。
- 在**系统边界**用运行时校验补上类型（如 zod、pydantic），因为外部输入不受类型系统保护。
- 对强类型语言，装上代码智能插件。官方文档指出，它让智能体通过语言服务器做「跳转到定义」「查找引用」，一次调用替代一轮 grep 加多个候选文件的阅读，改完代码后还会自动报告类型错误。
- 动态语言项目也不是没救：补类型注解、写清楚数据结构的 schema，本身就是在给智能体提供「别猜」的依据。

## 四、目录、命名与文件大小

Anthropic 在上下文工程的文章里提到，文件夹层级、命名约定、时间戳这些元数据，是智能体即时检索时判断相关性的线索。换句话说，**命名就是索引**。

- **可预测的结构**：同类东西放同一位置（`src/orders/`、`src/orders/refund.test.ts`），智能体新增功能时能凭已有模式推断落点。
- **可 grep 的名字**：避免 `utils2.js`、`helper.ts`、`data.py` 这种名字；函数名要独特到搜得出来，别让十个文件都有一个叫 `handle` 的函数。
- **隔离噪声**：生成物、构建产物、vendored 代码不要让智能体读。搜索默认遵循 `.gitignore`；已提交的生成代码，用 `Read` 拒绝规则挡掉，例如 `Read(./**/dist/**/*)`。
- **控制文件体量**：几千行的巨型文件读一次就吃掉一大块上下文。多大算大，没有标准，我的经验是超过一两千行就值得拆，但要按「职责」拆，不要为了行数硬切。

## 五、文档就近：写在被读到的地方

给智能体的文档，核心是**在它需要的时候出现，且不占不需要的时候的上下文**。

- **根目录一份，子目录各一份**。Claude Code 会加载工作目录及所有上级目录的 CLAUDE.md，子目录里的则在智能体读到该目录文件时按需加载。Codex 则是从仓库根目录一路向下合并到当前目录，每个目录最多取一个文件。两者思路相同：规则靠近代码。
- **写 why，不写 what**：代码已经说明了做了什么；说明文件应该记录那些读代码读不出来的东西，比如「这里不能用事务，因为……」「金额必须是整数分」。
- **架构决定记录（ADR）**：一个决定一篇短文，写背景、选择、被放弃的方案。智能体最容易「好心」重构掉你有意为之的设计，ADR 是最好的挡箭牌。
- **文档要有人维护**：过期的文档比没有更糟。官方建议把对说明文件的修改当成普通 PR 评审；也可以用 Stop Hook 让智能体在会话结束时提出说明文件的更新建议。

## 六、AGENTS.md 与 CLAUDE.md 怎么分工

多款工具各有自己的规则文件，维护多份会走样。当前的局面是：

| 工具 | 读什么 | 说明 |
|---|---|---|
| Claude Code | CLAUDE.md；仅当没有任何 CLAUDE.md 时才读 AGENTS.md（v2.1.277 起） | 两者都有时只读 CLAUDE.md，除非 CLAUDE.md 里用 `@AGENTS.md` 导入 |
| Codex | AGENTS.md（含 `~/.codex/AGENTS.md` 全局文件） | 默认合并上限 32 KiB；同目录可用 `AGENTS.override.md` 覆盖 |
| Cursor | AGENTS.md，或 `.cursor/rules/` 下的 `.mdc` 规则 | 官方建议单条规则不超过 500 行；规则可用 `globs` 按文件范围附加 |

我的建议是**以 AGENTS.md 为唯一正文**，其余文件做薄薄一层：

```markdown
<!-- CLAUDE.md -->
@AGENTS.md

## Claude Code 专属
- 改完一批代码后运行 `npm run verify`，通过后再汇报
- 压缩时保留已修改文件清单和最近一次失败的测试输出
```

这样 Codex、Cursor 读的是 AGENTS.md，Claude Code 通过导入读到同一份内容，只有 Claude 才有的约定放在 CLAUDE.md 里。注意导入的内容和主文件一样占用上下文，所以正文仍要精简；真正只有部分目录才需要的规则，放进子目录的说明文件或带 `paths:` 的规则里。

大型仓库还有几个官方提供的杠杆：从子目录启动智能体只会加载该目录及上级的说明文件；`claudeMdExcludes` 可以跳过无关团队的说明文件；`worktree.sparsePaths` 让并行会话只检出需要的目录。

## 七、护栏：把「文档里的规矩」变成机器检查

写在文档里的规则是建议，写进检查里的才是规则。每当评审里第三次出现同一条意见，就该把它自动化：

- 格式化与 import 排序：编辑后 Hook 或 pre-commit；
- 「不能直接改生成文件」：PreToolUse Hook 退出码 2 拦截，或权限规则；
- 架构边界（路由层不许直接写 SQL）：用 lint 规则或依赖检查工具；
- **让报错信息教智能体怎么改**：自定义 lint 规则的错误文案，写成「这里应调用 `queries/` 下的函数，见 docs/db.md」，智能体读到报错就知道该怎么修，比文档更可靠。

## 量一量：新员工测试

怎么知道你的仓库友不友好？别凭感觉，做个测试：开一个全新的会话，只允许它读仓库里已有的文档，让它从零开始跑通构建与测试，并实现一个很小的功能，记录它每一次卡壳。

**代码库友好度体检（新员工测试）**

```text
请扮演第一天入职的工程师，对本仓库做一次「新员工测试」。

规则：只能使用仓库里已有的 README、AGENTS.md、CLAUDE.md 和代码本身，不要问我任何问题，不要修改任何文件。

任务：
1. 弄清楚如何安装依赖、启动项目、运行单个测试、运行全部检查，把你最终成功的命令列出来；
2. 回答：新增一个同类的接口或模块，应该放在哪个目录，参考哪个现有实现；
3. 找出一个你认为最容易被改坏的地方，并说明仓库里有没有保护它的测试或文档。

每次卡壳（命令失败、文档与现实不符、找不到入口、不知道约定）都记录下来：发生在哪一步、浪费了多少次尝试、缺少什么信息。

最后输出一张「摩擦清单」，按修复成本从低到高排序，每项给出建议的修法（补一行文档、加一个脚本、修一个测试、拆一个文件）。
```

摩擦清单里的前五项，通常就能带来比换更贵的模型更明显的提升。做完修复再测一次，数字变化就是你的收益。

> 小测：一个仓库同时有 AGENTS.md 和 CLAUDE.md，但 CLAUDE.md 里没有任何导入语句。Claude Code 默认会读取哪些内容？

> 小测：时间有限，只能改善代码库的一件事来提升智能体的表现。下面哪一项通常收益最大？

- [ ] 为仓库添加一条 `verify`（或等价的一键检查命令），并写进 AGENTS.md
- [ ] 统计最近一个月偶发失败的测试，修复或隔离其中最严重的三个
- [ ] 把规则正文整合到 AGENTS.md，CLAUDE.md 只保留 `@AGENTS.md` 加少量 Claude 专属内容
- [ ] 为最复杂的一个子目录添加就近的说明文件，写下三条读代码读不出来的约定
- [ ] 用「新员工测试」提示词体检一次，按摩擦清单修复前三项后再测一次，比较卡壳次数

延伸阅读：[CLAUDE.md 与记忆](/learn/claude-code/claude-md/)、[Hooks 自动化](/learn/claude-code/hooks/)、[实战：搭建项目脚手架](/learn/ship/scaffold/)。最后一课把这些放到团队尺度：[团队落地](/learn/engineering/team-adoption/)。
