代码语言
选择后全站课程跟随

CLAUDE.md:为什么只是建议

4,031 字 · 预计阅读 12 分钟

前情提要

  • CLAUDE.md 有好几层:用户级的 ~/.claude/CLAUDE.md、项目根目录的 CLAUDE.md、不提交到仓库的 CLAUDE.local.md,还有子目录里的
  • 多层 CLAUDE.md 不是互相覆盖,而是拼接在一起;离你启动位置越近的,越晚进入上下文
  • 可以用 @路径 把内容拆到别的文件里;.claude/rules/ 下的规则文件可以用 paths 限定只在碰到某些文件时生效
  • CLAUDE.md 是「建议」,不是「强制」,有小概率不遵守;用 /context 能看到加载了哪些

关于 CLAUDE.md,有两个常见的困惑:

  1. 我在里面写了「必须」「绝对不要」,它为什么还是偶尔不听?
  2. 用户级、项目级、本地级好几份,到底谁先谁后、谁说了算?

这一节去源码里找答案。主要看两个文件:src/utils/claudemd.ts(找文件、读文件、拼文字)和 src/context.ts(交给循环)。

整个过程分四步:找 → 读 → 拼 → 放。


一、找:从根目录一路往下走

CLAUDE.md 从目录路径上逐级寻找

找文件的函数叫 getMemoryFiles(),简化之后是这样:

typescript
// src/utils/claudemd.ts → getMemoryFiles()(简化)
result.push(组织级 CLAUDE.md 和规则)                 // 1. IT 统一下发的
result.push(~/.claude/CLAUDE.md, ~/.claude/rules/*.md)   // 2. 用户级

const dirs = []                                      // 3. 先收集「当前目录 → 根目录」的每一层……
for (let dir = cwd; dir !== 根目录; dir = 上一级(dir)) dirs.push(dir)

for (const dir of dirs.reverse()) {                  // ……再反过来,从根目录往下走到当前目录
  result.push(dir/CLAUDE.md, dir/.claude/CLAUDE.md)
  result.push(dir/.claude/rules/*.md)                //    只要不带 paths 的
  result.push(dir/CLAUDE.local.md)
}
result.push(MEMORY.md)                               // 4. 自动记忆的索引

「离你启动位置越近的,越晚进入上下文」,就是 dirs.reverse() 这一行。为什么要倒过来?文件开头的注释给了答案:

文件按优先级从低到高加载:越晚加载的优先级越高,模型对它们的注意力也越多。

所以顺序是刻意安排的:组织级最先,然后是你的全局习惯,然后从外层目录往里走,最后是你启动的那个目录。同一层里 CLAUDE.local.md 排在 CLAUDE.md 后面,你的个人偏好也比团队规范更晚出现。

还有一个细节:result 就是一个数组,所有文件都往里 push。整个函数里没有任何「后面的替换前面的」的逻辑。两层规则打架时,代码不会帮你裁决,只能交给模型自己挑。

越具体的规则越靠后、越受重视;所有文件只拼接,不覆盖。


二、读:进上下文之前的四道处理

读入项目规则前需整理并过滤

每个文件读进来,要先过 parseMemoryFileContent() 这一关。有四件你可能不知道的事。

1. paths 被取出来

frontmatter 里如果有 paths,这份规则就变成「条件规则」,启动时不加载,等 Claude 碰到匹配的文件再说。

2. HTML 注释被删掉

块级的 <!-- ... --> 注释会在进入上下文之前被删掉,代码块里的注释保留。

这是一个很实用的小技巧:给人看的说明写成 HTML 注释,不占模型的上下文。

markdown
<!-- 这条规则是 2026-09 线上事故之后加的,详见 docs/incident-0912.md -->
- 改数据库迁移文件之前,先跟我确认

模型只看到第二行,维护的人能看到第一行。

3. @ 引入的六条规矩

用 @路径 可以把 CLAUDE.md 拆到别的文件里。源码里有几条文档没细说的规矩:

规矩 源码里的写法
最多嵌套 5 层 MAX_INCLUDE_DEPTH = 5
只引入文本文件 有一份很长的扩展名白名单(.md、.json、.ts、.py……),图片、PDF 会被跳过
找不到文件不报错 注释原文:Non-existent files are silently ignored
代码块里的 @ 不算 只在普通文本里识别 @路径
引入项目外的文件要你批准一次 用户级的 ~/.claude/CLAUDE.md 例外,可以随便引
同一个文件不会加载两次 防止循环引用

第三条最值得注意:路径写错了,Claude Code 不会提醒你。 所以引入之后,一定要用 /context 看一眼 Memory files 里有没有它。

4. 太长只警告,不截断

单个文件超过 4 万字符(MAX_MEMORY_CHARACTER_COUNT = 40000),界面上会提示 Large CLAUDE.md file detected,但内容照样全部加载。

官方建议的「200 行以内」是写作建议,代码不会帮你砍。真正会被截断的是自动记忆的索引 MEMORY.md:超过 200 行或 25KB 的部分直接截掉。

HTML 注释白送你一块「只给人看」的地方;@ 引入写错了不会报错,要自己检查。


三、拼:变成一段什么样的文字

所有文件读完,getClaudeMds() 把它们拼成一大段文字:

本文是会员内容

全文 4,031 字,已试读约 28%。 开通专业版可阅读全文,并解锁全部课程与后续更新。

开通专业版

支持 7 天无理由退款 · 早鸟期前 500 名自动升级永久卡