CLAUDE.md 怎么写:从 /init 生成,到让 Claude Code 真的照着做
CLAUDE.md 是 Claude Code 每次开工都会读的项目说明。本站在 Express 的源码上实测:/init 生成了什么,加三条规矩后它照没照做,按路径生效的规则什么时候加载,AGENTS.md 怎么被读到。并整理了官方对写什么、不写什么、放在哪的说明。
本站已实测,测试日期:2026-10-11。
本页目录(11 节)
这篇教程解决一个问题:怎么让 Claude Code 不用你每次重复交代,就知道这个项目的规矩。 办法是在项目里放一份 CLAUDE.md。
每个会话开始时,Claude Code 的上下文都是空的,上一次聊过什么它不记得。CLAUDE.md 是它每次开工都会读的一份 Markdown 文件:测试怎么跑、代码怎么写、提交怎么提,写在这里就不用再说第二遍。
本站接着上一篇用的 Express 仓库,把生成、修改、验证这几步实际做了一遍。
| 实测环境 | |
|---|---|
| 日期 | 2026-10-11 |
| 版本 | Claude Code 2.1.296 |
| 系统 | Ubuntu 24.04 |
| 模型 | Opus 5.5 |
| 连接方式 | 通过酷站AI 中转站,不是直连官方 |
| 示例仓库 | Express 的源码,v5.3.0 |
1用 /init 生成一份
在项目目录里启动 claude,输入:
/init
它跑了三条命令去读 package.json、lib/、测试、lint 配置和 CI 配置,42 秒后在项目根目录写出一份 51 行的 CLAUDE.md。开头是这样的:
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is the Express.js framework itself (v5.x, CommonJS, Node.js >= 18), not an app built on it.
## Commands
- `npm test` — run the full mocha suite (`test/` and `test/acceptance/`) with `--check-leaks` and `test/support/env` preloaded
- `npm run lint` / `npm run lint:fix` — ESLint over the whole repo, including `examples/`
- `npm run test-cov` — coverage report via nyc (HTML in `coverage/`)
- Single test file: `npx mocha --require test/support/env --check-leaks test/req.subdomains.js`
- Single test by name: add `--grep "should return an array"` to the command above
Always include `--require test/support/env` when running mocha directly. It sets `NODE_ENV=test` and silences
`express`/`body-parser` deprecation warnings, and some tests depend on those settings.
后面还有三节:Architecture(架构)、Tests(测试怎么组织)、Conventions(约定)。其中有些内容是读代码才知道的,比如:
- 路由、请求体解析、静态文件这些大部分在依赖包里,不在
lib/里,“这些地方的 bug 多半该去上游修”。 - lint 规则禁止直接用全局的
Buffer。 - CI 会测 Node 18 到 26,所以不要用 Node 18 不支持的语法。
三点观察:
- 它生成的是英文。 我们只输入了
/init,没有说用什么语言。 - 官方的定位是“起步版”。 文档说
/init会分析代码库,写出它发现的构建命令、测试说明和项目约定;之后要靠你补上它自己发现不了的内容。已经有CLAUDE.md时,/init是提出改进建议,不会覆盖。 - 架构那一节写得偏细。 按下面官方的标准,“读代码就能知道的东西”其实不该放进来。这份文件拿来就能用,但值得删一删。
2确认它被读到了
官方给的检查办法是在会话里运行 /context,看 Memory files 那一栏。
这里有个容易踩的地方。刚生成完,在同一个会话里看 /context,是没有这一栏的,我们试了。CLAUDE.md 是在会话启动时加载的,要退出重开一个会话才看得到:
Memory files · /memory
└ CLAUDE.md: 2.4k tokens
2.4k 是它占的上下文(这个数是加了下一步那三条规矩之后量的)。每个会话都要付一次,所以文件不是越长越好。
3加上你自己的规矩,看它照不照做
/init 写的是它能从代码里发现的东西。你的工作习惯它发现不了,要自己加。我们在文件末尾加了三条:
## 本项目的工作约定
- 回答和总结一律用中文。
- 改完 `lib/` 下的文件,先跑 `npm run lint`,再只跑对应的那一个测试文件,不跑全量的 `npm test`。
- 提交说明用英文,一行,不超过 72 个字符,以 `fix:`、`docs:` 或 `test:` 开头。
重开一个会话,给一个小任务:
给 lib/request.js 里 req.subdomains 上面那段注释补一句,说明 hostname 是 IP 地址时会怎样。改完提交。
屏幕上的记录(有删节):
● Update(lib/request.js)
⎿ Added 3 lines
379 + * If the hostname is an IP address, it is not split on dots and is treated
380 + * as a single part, so req.subdomains is `[]` unless "subdomain offset" is 0.
● Bash(npm run lint 2>&1 | tail -5 && npx mocha --require test/support/env --check-leaks test/req.subdomains.js 2>&1 | tail -5)
● Bash(git add lib/request.js && git commit -q -m "docs: note req.subdomains behavior for IP hostnames…)
⎿ 1fa3b2b docs: note req.subdomains behavior for IP hostnames
● 我在 lib/request.js 里 req.subdomains 上方的注释补了一段,说明 hostname 是 IP 地址时的行为……
npm run lint 没有报错,test/req.subdomains.js 的 11 个测试全部通过。
三条都照做了:
| 我们写的规矩 | 它实际做的 |
|---|---|
| 用中文回答 | 总结是中文 |
| 先 lint,再只跑对应的测试文件 | npm run lint,然后只跑了 test/req.subdomains.js |
| 提交说明:英文、一行、72 字符以内、带前缀 | docs: note req.subdomains behavior for IP hostnames |
第二条的效果可以和上一篇对比:没有这条规矩时,它修完 bug 把 1261 个测试全跑了一遍;有了之后只跑了那一个文件。
要注意,这只是一次的结果。官方文档说得很直接:CLAUDE.md 的内容是当作上下文交给 Claude 的,不是强制执行的配置。它会读、会尽量照做,但不保证每次都严格遵守,含糊的和互相冲突的规则尤其如此。
写什么,不写什么
官方的标准是一句话:对每一行问一下,删掉它会不会让 Claude 犯错?不会就删。 文件臃肿了,真正要紧的那几条反而会被它忽略。
| 该写 | 不该写 |
|---|---|
| 它猜不到的命令 | 读代码就能知道的东西 |
| 和默认习惯不一样的代码风格 | 它本来就懂的语言惯例 |
| 测试怎么跑、用哪个测试工具 | 详细的 API 文档(给链接就行) |
| 仓库的规矩:分支怎么命名、PR 怎么提 | 经常变的信息 |
| 这个项目特有的架构决定 | 长篇的解释和教程 |
| 开发环境的特殊之处,比如必须设的环境变量 | 逐个文件介绍代码库 |
| 常见的坑、不明显的行为 | “写干净的代码”这类不用说的话 |
什么时候往里加。 官方列了四种信号:
- Claude 第二次犯同一个错。
- 代码审查时发现了它本该知道的事。
- 你在对话里打了一句上次就打过的更正。
- 新来的同事也需要知道这件事才能干活。
怎么写才管用。 规则要具体到可以检查:
| 含糊的写法 | 具体的写法 |
|---|---|
| 把代码格式化好 | 用 2 个空格缩进 |
| 改完要测试 | 提交前运行 npm test |
| 文件要放整齐 | API 的处理函数放在 src/api/handlers/ |
另外几条:
- 每个文件控制在 200 行以内。 更长的文件占更多上下文,遵守的程度也会下降。
- 用标题和列表组织,不要写成大段文字。
- 不要自相矛盾。 两条规则冲突时,它可能随便挑一条。
- 某一条总被跳过时,只给那一条加上 IMPORTANT 这样的强调。 每条都强调,就等于都没强调。
- 提交进 git,让团队一起维护。
想让它帮你查问题,可以在会话里运行 /doctor prompt-audit。它会找出过时的、互相矛盾的、引用了不存在的文件或命令的指令,给出修改建议,你同意之后才会改。这个命令我们没有实测。
不该写进 CLAUDE.md 的两类东西
- 只在某些时候才用到的步骤和知识,写成 skill。skill 是用到时才加载的,不会占每个会话的上下文。写法见Claude Code skills 怎么写。
- 必须每次都执行的动作,比如每次提交前、每次改完文件后一定要跑的检查,写成 hook。hook 是在固定时机执行的命令,不管 Claude 怎么决定都会跑。
放在哪:四个位置
CLAUDE.md 可以放在几个地方,作用范围不同:
| 范围 | 位置 | 用来放什么 | 谁看得到 |
|---|---|---|---|
| 组织 | Linux 上是 /etc/claude-code/CLAUDE.md,macOS 和 Windows 另有路径 |
公司的编码规范和安全要求,由 IT 管 | 组织里所有人 |
| 个人 | ~/.claude/CLAUDE.md |
你在所有项目里都适用的偏好 | 只有你 |
| 项目 | ./CLAUDE.md 或 ./.claude/CLAUDE.md |
团队共用的项目说明 | 通过 git 共享给团队 |
| 项目里的个人部分 | ./CLAUDE.local.md |
只有你自己用的项目设置,要加进 .gitignore |
只有你 |
加载的规则,文档里有三点值得知道:
- 这些文件是拼在一起的,不是互相覆盖。 从文件系统的根一路到你启动的目录,离你启动的位置越近的越靠后读。
- 上级目录里的会在启动时加载;子目录里的是用到才加载,Claude 读了那个子目录里的文件之后才会读它。
- 可以引入别的文件:在
CLAUDE.md里写@路径,比如@docs/git-instructions.md。引入的文件也在启动时加载,所以它能让文件更好整理,但省不了上下文。
在会话里输入 /memory,可以看到当前读了哪些文件,并直接打开编辑:
Memory
❯ Auto-memory true
❯ Project instructions Checked in at ./CLAUDE.md
User instructions Saved in ~/.claude/CLAUDE.md
Open auto-memory folder
只对一部分文件生效的规则
有些规矩只和一部分文件有关,比如测试文件怎么写。把它们都塞进 CLAUDE.md,每个会话都要为它们付上下文。办法是放进 .claude/rules/ 目录,并在文件头部用 paths 写明对哪些文件生效。
我们建了一个 .claude/rules/tests.md:
---
paths:
- "test/**/*.js"
---
# 测试文件的规矩
- 新增用例的描述以 `should` 开头,和现有用例保持一致。
- 只用 `node:assert` 和 `supertest`,不要引入新的断言库。
然后让它去处理一个测试文件:
看一下 test/req.subdomains.js 里有没有覆盖“hostname 是带端口的 IPv4 地址”的情况。没有的话补一个用例,不用提交。
它一读那个测试文件,屏幕上就多出一行:
Read 1 file (ctrl+o to expand)
⎿ Loaded .claude/rules/tests.md
接着它补的用例是 it('should return an array with the whole IPv4 with port', ...),描述以 should 开头,没有引入新的库,12 个测试全过。
两个细节:
- 这条规则不在
/context的 Memory files 里。 那一栏列的是启动时加载的文件;按路径生效的规则是用到才加载的,看的是上面那行Loaded。 - 没写
paths的规则文件会在启动时加载,和.claude/CLAUDE.md同等对待。
paths 用的是 glob 写法:**/*.ts 匹配任何目录下的 TypeScript 文件,src/**/* 匹配 src/ 下的所有文件,src/**/*.{ts,tsx} 可以一次匹配两种扩展名。
项目里已经有 AGENTS.md
如果仓库已经为别的编程工具写了 AGENTS.md,不需要再抄一份。按官方文档,从 2.1.277 版起 Claude Code 默认这样处理:
| 仓库里有 | Claude 读什么 |
|---|---|
只有 AGENTS.md |
读 AGENTS.md |
AGENTS.md 和 CLAUDE.md 都有 |
只读 CLAUDE.md |
CLAUDE.md 里引入了 AGENTS.md |
读 CLAUDE.md,AGENTS.md 的内容通过引入带进来 |
第一种我们试了:在一个只有 AGENTS.md 的目录里启动,/context 显示的是
Memory files · /memory
└ AGENTS.md: 71 tokens
有一个坑文档专门提到:CLAUDE.local.md 也算数。 在一个靠 AGENTS.md 的项目里,你为了放点个人设置加了一个 CLAUDE.local.md,Claude 就不再读 AGENTS.md 了。想两个都读,在会话里输入 /config,把 Project instructions 改成 claude-md-and-agents-md。
它自己记的笔记:自动记忆
除了你写的 CLAUDE.md,Claude Code 还会自己记笔记,叫自动记忆(auto memory),默认是开着的。
| CLAUDE.md | 自动记忆 | |
|---|---|---|
| 谁写 | 你 | Claude |
| 内容 | 指令和规则 | 它学到的东西:你的偏好、你纠正过它的地方、从代码里看不出来的项目背景 |
| 存在哪 | 项目里,可以提交 | 本机的 ~/.claude/projects/<项目>/memory/,不跨机器 |
| 加载多少 | 全部 | 索引文件 MEMORY.md 的前 200 行或前 25KB |
它不是每个会话都记。文档说它会判断一条信息以后用不用得上,并且跳过能从代码里看出来的内容,也跳过 CLAUDE.md 里已经写了的内容。
我们的实测符合这个说法:前后几个会话做完,自动记忆的目录建出来了,但里面是空的。我们没有纠正过它什么,也没有说过自己的偏好。
要关掉它,在 /memory 里切换,或者在项目的设置里写 "autoMemoryEnabled": false。
它不照做的时候怎么查
按官方文档的排查顺序:
- 先确认它读到了。 运行
/context,看 Memory files。不在里面,它就看不到。 - 看位置对不对。 文件是不是在会被加载的位置;子目录里的
CLAUDE.md要等它读了那个目录的文件才加载。 - 把规则写具体。
- 找有没有互相冲突的规则,包括不同位置的几份
CLAUDE.md和.claude/rules/之间。 - 看是不是和 Claude Code 自带的说明撞了。 比如你写了提交和 PR 的规矩,而它自己也有一套内置的;可以用
includeGitInstructions关掉内置的,用attribution改署名。
如果一条规矩是“必须在某个时刻执行”,不要再改措辞了,把它写成 hook。
这一趟花了多少
生成之后的那个会话,做了两个小任务,/cost 显示:
Total cost: $0.2942
Total duration (API): 45s
Usage by model:
claude-opus-5-5: 1.2k input, 2.2k output, 223.3k cache read, 40.2k cache write ($0.2942)
这是 Claude Code 按官方价格估算的数字,不是中转站实际扣的金额。
这次实测没有覆盖到的
- 规则的遵守情况只看了一次。 三条规矩这一次都照做了,不代表每次都会。
- 组织级的
CLAUDE.md、CLAUDE.local.md、@引入没有实测,按官方文档写的。 /doctor prompt-audit没有实测。- 官方文档说只有
AGENTS.md时,会话里会出现一行提示,写明加载了哪个文件。我们启动后没有发消息就退出了,没有看到这一行,只在/context里确认了它被加载。