# CLAUDE.md 与记忆：写好项目说明

> 每次开新会话都要重新交代「用 pnpm、接口统一包装返回值」？这一课讲清 CLAUDE.md 各层级的位置和加载顺序、/init 与 @ 导入的用法、自动记忆的分工，以及怎样写出一份短而有效的项目说明。

- 网址：https://coding.chengyu.dev/learn/claude-code/claude-md/
- 学习路径：Claude Code 精通
- 预计用时：18 分钟；核实日期：2026-10-25
- 官方来源：[How Claude remembers your project（Claude Code 官方文档）](https://code.claude.com/docs/en/memory)，[Best practices for Claude Code（Claude Code 官方文档）](https://code.claude.com/docs/en/best-practices)，[Extend Claude Code（Claude Code 官方文档）](https://code.claude.com/docs/en/features-overview)，[Commands（Claude Code 官方文档）](https://code.claude.com/docs/en/commands)

## 学习目标

- 能说出用户级、项目级、本地级 CLAUDE.md 的位置和加载顺序
- 能用 /init 生成初稿，并按「删掉会不会出错」的标准精简
- 能用 @ 导入和 .claude/rules/ 组织较长的说明
- 能分清哪些内容该写进 CLAUDE.md，哪些该交给自动记忆、Skill 或 hook

上一课说过，每个会话都从空白的上下文开始。于是很多人陷入一个循环：周一告诉 Claude「我们用 pnpm，不要用 npm」，周二它又跑了 `npm install`；你提醒「后端返回值统一用 `Result` 包一层」，第二天新会话里它又直接返回了裸对象。

问题不在模型记性差，而在于你没给它一个每次都会读的地方。这个地方就是 CLAUDE.md。

## 两套记忆：你写的和它记的

Claude Code 有两种跨会话保留信息的机制，每次会话开始时都会加载：

| | CLAUDE.md | 自动记忆（auto memory） |
|---|---|---|
| 谁来写 | 你 | Claude 自己 |
| 内容 | 指令和规则 | 它从你的纠正、偏好中学到的东西 |
| 存在哪 | 项目、用户、组织各层级 | `~/.claude/projects/<项目>/memory/`，同一仓库的各 worktree 共用 |
| 加载多少 | 会话启动时加载的文件全部内容 | 索引 `MEMORY.md` 的前 200 行或 25KB |

自动记忆默认开启。你对它说「记住，跑 API 测试前要先起本地 Redis」，它就会存进自动记忆；你说「把这条加进 CLAUDE.md」，它才会去改 CLAUDE.md。输入 `/memory` 可以浏览、编辑这两类文件，也能开关自动记忆。

有一点必须清楚：**这两者都只是上下文，不是强制配置**。CLAUDE.md 的内容会作为一条消息交给模型，它会尽力遵守，但不保证。真正必须执行的规则，比如「绝不能改 `.env`」，要交给权限规则或 hook（见第 3 课和第 7 课[Hooks 自动化](/learn/claude-code/hooks/)）。

## 文件放在哪里，按什么顺序加载

CLAUDE.md 可以放在多个位置，作用范围不同：

| 层级 | 位置 | 用途 | 和谁共享 |
|---|---|---|---|
| 组织策略 | macOS：`/Library/Application Support/ClaudeCode/CLAUDE.md`；Linux 和 WSL：`/etc/claude-code/CLAUDE.md`；Windows：`C:\Program Files\ClaudeCode\CLAUDE.md` | 公司统一的规范、合规提醒 | 本机所有用户 |
| 用户级 | `~/.claude/CLAUDE.md` | 你个人在所有项目里的偏好 | 只有你 |
| 项目级 | `./CLAUDE.md` 或 `./.claude/CLAUDE.md` | 团队共享的项目约定，提交到 git | 团队成员 |
| 本地级 | `./CLAUDE.local.md` | 你在这个项目里的私人设置，加入 `.gitignore` | 只有你 |

加载规则可以记成三句话：

1. **向上全部加载。** 在 `foo/bar/` 启动，`foo/bar/` 和 `foo/` 以及更上层目录里的 CLAUDE.md、CLAUDE.local.md 都会在启动时读入。
2. **拼接而不是覆盖。** 所有找到的文件依次放进上下文，顺序是从文件系统根目录往下到你的工作目录，所以离启动目录越近的越晚被读到；同一目录里，CLAUDE.local.md 排在 CLAUDE.md 之后。整体看，组织策略最先，然后是用户级，再然后是项目级。
3. **子目录按需加载。** 工作目录下面子目录里的 CLAUDE.md 不在启动时加载，而是等 Claude 读写那个子目录里的文件时才加载。

如果仓库里已经有给其他编码工具写的 `AGENTS.md`，而工作目录及以上都没有 CLAUDE.md 或 CLAUDE.local.md，新版 Claude Code（v2.1.277 起）会直接读 `AGENTS.md`。两者都有时，默认只读 CLAUDE.md；想共用一份，可以在 CLAUDE.md 第一行写 `@AGENTS.md` 导入它。

想确认哪些文件真的被加载了，运行 `/context`，看 **Memory files** 下面的列表。

> 小测：你在 monorepo 的 apps/admin/ 目录下启动 Claude Code。仓库根目录和 apps/admin/ 里各有一个 CLAUDE.md，内容里对缩进的要求不一样。会发生什么？

## 用 /init 起个头

在项目根目录启动 Claude Code，输入 `/init`。它会分析代码库，生成一份包含构建命令、测试方法和它发现的项目约定的初稿；如果已经有 CLAUDE.md，它会给出改进建议而不是覆盖。项目里有 `.cursorrules`、`.cursor/rules/` 或 `.github/copilot-instructions.md` 时，`/init` 也会把其中相关的部分吸收进来。

想要更细致的流程，可以在启动前设置环境变量 `CLAUDE_CODE_NEW_INIT=1`。这时 `/init` 会先问你要配置哪些内容（CLAUDE.md、Skill、hook），用子智能体探索代码库，追问它拿不准的地方，最后给出一份可以审阅的方案再写文件。

`/init` 生成的只是起点。它能从代码里推断出来的东西，恰恰是最不需要写的；真正有价值的是它推断不出来的部分，需要你补上。

## 什么该写，什么不该写

官方给了一个很好用的判断标准：对每一行都问自己，**删掉它，Claude 会不会因此出错？** 不会，就删。

| 应该写 | 不该写 |
|---|---|
| 它猜不到的命令：`pnpm dev:server`、`make seed` | 读代码就能知道的东西，如目录结构、依赖列表 |
| 和语言默认习惯不同的代码风格 | 语言通用规范，如「变量名要有意义」 |
| 测试怎么跑、优先用哪个测试命令 | 详细的 API 文档（放链接或做成 Skill） |
| 分支命名、提交信息格式等协作约定 | 经常变化的信息 |
| 项目特有的架构决策和原因 | 长篇教程和背景介绍 |
| 开发环境的坑：必需的环境变量、本地服务 | 逐个文件的说明 |
| 不明显的陷阱：哪个目录不能动、哪里有历史包袱 | 「写干净的代码」这类空话 |

写法上，规则要具体到能验证：「用 2 空格缩进」比「格式要规范」好，「提交前运行 `pnpm lint`」比「注意质量」好。

## 一个好的 CLAUDE.md 示例

假设你在做一个后台管理系统：前端 Vue 3，后端 NestJS，pnpm monorepo。一份合格的 CLAUDE.md 大概是这样，不到 40 行：

```markdown
# 项目：商城后台（pnpm monorepo）

## 常用命令
- 安装依赖：`pnpm install`（不要用 npm 或 yarn）
- 启动：`pnpm dev:admin`（前端 5173 端口）、`pnpm dev:server`（后端 3000 端口）
- 只测一个模块：`pnpm --filter server test -- order`，不要每次跑全量测试
- 提交前必须通过：`pnpm lint && pnpm typecheck`

## 代码约定
- 后端接口统一返回 `Result.ok(data)` 或 `Result.fail(code, msg)`，不要直接返回实体
- 金额一律用「分」存整数，前端展示时再除以 100
- 前端组件用 `<script setup lang="ts">`，状态管理用 Pinia，不引入 Vuex
- 注释和提交信息用中文；提交格式：`feat(order): 新增退款审核`

## 不要碰
- `apps/server/src/migrations/` 下已有的迁移文件只能新增，不能修改
- `packages/legacy-sdk/` 是对接旧 ERP 的历史代码，改动前先问我

## 环境的坑
- 本地 MySQL 必须是 utf8mb4，否则商品名里的表情符号会写入失败
- 支付回调在本地无法触发，用 `pnpm mock:pay` 模拟

## Compact Instructions
- 压缩上下文时，保留本次会话改过的文件列表和用过的测试命令

<!-- 维护者备注：这条注释会在加载前被去掉，不占上下文 -->
```

几个值得学的地方：命令写全，不让它猜；每条约定都能检查；「不要碰」写清边界；最后的 Compact Instructions 一节是官方建议的写法，告诉 Claude 在上下文被压缩时优先保留什么。块级 HTML 注释会在注入上下文之前被去掉，适合给人看的备注。

## @ 导入：组织长文件，但不省上下文

CLAUDE.md 里可以用 `@路径` 引入其他文件，比如 `参见 @README.md 和 @docs/git-flow.md`。规则如下：

- 相对路径以**写导入的那个文件**所在目录为基准，不是以工作目录为基准；
- 被导入的文件还能继续导入，最多嵌套 4 层；
- 写在行内代码或代码块里的 `@` 不会被导入，想单纯提到一个路径就用反引号包起来；
- 路径里有空格时，在空格前加反斜杠；
- 项目级文件导入工作目录之外的文件（比如 `@~/.claude/my-notes.md`）时，第一次会弹窗让你确认，防止别人提交的文件偷偷引入你机器上的内容。

要特别注意：**导入只帮你整理文件，不减少上下文消耗**，被导入的内容会和 CLAUDE.md 一起全部加载。真正能省上下文的是两种做法：

- **路径规则**：把规则放进 `.claude/rules/` 下的 `.md` 文件，在开头的前置信息里写 `paths`（如 `src/api/**/*.ts`），只有 Claude 处理匹配的文件时才加载；
- **Skill**：只在需要时加载的流程和参考资料，第 5 课[Skill 入门](/learn/claude-code/skills/)细讲。

## 保持精简

官方建议每个 CLAUDE.md 控制在 **200 行以内**，越长越占上下文，遵守程度也越差。如果你发现 Claude 明明有规则却总是不照做，很可能是文件太长、规则被淹没了；如果它总问 CLAUDE.md 里已经写过的问题，可能是措辞有歧义。

几个维护习惯：

- 同样的错误出现第二次，再把规则加进去，不要预先写一堆；
- 某一条反复被忽略，只给这一条加「IMPORTANT」，全部加粗等于都没加粗；
- 定期运行 `/doctor`，它会建议删掉能从代码推断出的内容；`/doctor prompt-audit` 则会检查过时、矛盾或引用了不存在文件的指令，只出报告，你同意后才改。

> 小测：你的 CLAUDE.md 已经 400 行，你把其中 250 行挪到 docs/conventions.md，再在 CLAUDE.md 里写一行 @docs/conventions.md。对上下文占用有什么影响？

## 动手：让 Claude 帮你审一遍

**精简并补全 CLAUDE.md**

```text
请审查本项目的 CLAUDE.md（如果没有，先运行等同于 /init 的分析生成初稿，但不要写入文件）。

按下面的标准逐条检查，输出一张表格，列出「保留 / 删除 / 改写」和理由：
1. 删掉这一条，你会不会因此犯错？不会就建议删除；
2. 这条内容能否从代码、package.json 或配置文件里直接看出来？能就建议删除；
3. 规则是否具体到可以验证？不够具体就给出改写后的版本；
4. 是否只和某个目录有关？是的话建议移到 .claude/rules/ 并写好 paths。

最后补充你在阅读代码时发现、但文件里没写的坑（比如必需的环境变量、不能改的目录）。先给我看方案，我确认后再修改文件。
```

- [ ] 在一个真实项目里运行 `/init`，读一遍生成的初稿
- [ ] 用上面的提示词审查一遍，把文件压到 200 行以内
- [ ] 新建 `CLAUDE.local.md` 写一条只对自己有用的偏好，并确认它在 `.gitignore` 里
- [ ] 运行 `/context`，在 Memory files 里确认文件都被加载了
- [ ] 运行 `/memory`，看看自动记忆里 Claude 已经替你记了什么
