一部分规矩只跟某些文件有关:Python 的类型注解要求,对 tsx 文件毫无意义。 把它们写成带路径条件的规则,改到哪个文件才加载哪几条。 演示里五个改动共加载 995 token, 全塞根目录是 3625——差在三倍半,而且差的这部分全是无关内容。
上一节讲根目录那份文件怎么写,这一节讲怎么把不该常驻的那部分拆出去。 它和上一节是同一个原则的两个层次:能晚加载的就别早加载。
一、规则为什么需要条件
一个中型仓库里,语言往往不止一种:后端 Python、前端 TypeScript、脚本 shell、迁移 SQL、 文档 markdown。 每一类文件有自己的写法要求。 Python 要写类型注解,SQL 要写注释说明索引意图,markdown 要注意链接格式。 这些要求如果全写进根目录的 CLAUDE.md,结果就是:改一个 Python 文件的时候, 上下文里躺着五条跟它无关的规矩。
条件化规则就是给每条规矩加一个适用范围:只有碰到的文件在范围内,才把它读进来。
二、条件怎么写
条件是一个路径匹配式,写法跟 gitignore 里那套差不多:星号匹配一段,两星匹配任意层, 问号匹配单个字符。 规则本身还是 markdown,条件写在文件头的元数据里。 所以一条规则就是一个文件,文件名叫什么不重要,重要的是那个条件。 拿演示里的六条举例:python 那条管所有 py 文件,react 那条管 tsx 文件,api 那条只管 src/api 下面, tests 那条只在测试目录里生效。
五个改动跑下来,各自命中的条数一眼能看出来:改 py 文件命中两条,改 tsx 只命中一条, 改 md 只命中一条。
三、三倍半的差距是怎么来的
按条件加载,五次改动合计 995 token。六条全常驻,五次改动就是 3625。 这个对比有点粗糙——真实场景里一次改动往往不是五次,而是一次会话几十次工具调用。 每一次调用都要带着当时的全部常驻内容。 所以这个倍数不是上限,是下限。会话越长,差距越大。
比 token 更值得注意的是「噪声」。 六条规则里五条跟当前文件无关,它们挤在上下文里,会让真正相关的那一条不显眼。 这跟人在噪声里看文件是一个道理:屏幕上有五段无关的说明,你会漏掉第六段。
四、写宽还是写窄
条件写宽(比如所有的 py 文件),命中多,省得少,但不会漏。 条件写窄(比如只匹配 src/services 下),省得多,但容易漏——新建一个目录,规矩就不生效了。 这条线上没有最优解,只有一个判断标准:这条规则漏掉的代价,比它白带上的代价大不大。
安全类的规则宁可写宽。比如「不要改 migrations 下已提交的脚本」,带上没坏处,漏了会出事。 风格类的规则可以写窄。比如「组件文件名用大驼峰」,漏了最多是风格不统一,不会把东西弄坏。 一个常见错误是全都写窄:图省 token,结果一堆规则从来不生效,而你以为它们在管着。
五、规则和技能的分界线
带条件的规则和技能(第 3 章)看着很像:都是一份 markdown,都是「用得上才读」。 但它们的触发方式完全不同。 规则是按文件路径触发:你改了哪个文件,匹配的规则就自动进来。 它不看你在干什么,只看你碰到了什么。 技能是按任务意图触发:模型读描述,判断这次任务用不用得上。 它看你问的是什么,不看你要改哪个文件。
所以判据很清楚:这条内容如果是「写这类文件时不许做什么」,做成规则; 如果是「遇到这类任务该按哪几步走」,做成技能。 举个例:「所有 py 文件必须有类型注解」——写 py 文件时的约束,做成规则。 「部署前要核对迁移、环境变量、回滚方案」——一个流程,跟改哪个文件无关,做成技能。
两者也会配合:技能里可以写「按 src/api 下的规则检查返回格式」,让流程去引用约束。 约束集合在一处,流程分在另一处。
六、和 CLAUDE.md 怎么分工
根目录的 CLAUDE.md 放「跟文件类型无关」的东西:命令、目录总则、禁区。 带条件的规则放「跟文件类型或路径有关」的东西:某个语言的写法、某个模块的约定。 两者是叠加的(上一节的规则),所以内容不要重复。重复了就会出现「同一个主题两处不同说法」的隐患。
一个简单的检查:把根目录和所有规则的标题列出来,看有没有两个标题在讲同一件事。
七、动手 Demo:改了五个文件,每次到底该加载哪几条规则
六条规则,各带一个路径条件,各占一份 token。 五个改动分别落在不同的路径上:api 的 py、ui 的 tsx、tests 的 py、docs 的 md、core 的 py。 逐个算命中了哪几条、多少 token,再跟「六条全塞根目录」比一遍。
def hit(glob, path):
g = glob.replace(u"**/", u"") # 先去掉任意层前缀
segs = g.split(u"/")
ps = path.split(u"/")
if len(segs) == 1: # 只写了文件名:比后缀
return path.endswith(segs[0][1:])
return tail_match(segs, ps) # 否则从后往前逐段比
实跑输出:
--------------------------------------------------------------------
改了五个文件,每次到底该加载哪几条规则
--------------------------------------------------------------------
① 六条规则,各带一个路径条件
--------------------------------------------------------------------
| 规则 | 命中条件 | token |
|---|---|---:|
| python | 任意层的 py 文件 | 210 |
| react | 任意层的 tsx 文件 | 130 |
| api | src/api 下面任意层 | 95 |
| tests | 任意 tests 目录下 | 80 |
| docs | 任意层的 md 文件 | 60 |
| sql | 任意层的 sql 文件 | 150 |
磁盘上是六个文件,条件写在文件头上,长这样:
[配置文件原样 · 本沙箱按同一套 glob 规则真跑]
| ---
| globs: "**/*.py"
| ---
|
| # Python 约定
| - 类型注解一律写全。
| - 不要用 print 调试,用 logging。
上例是 python 那一条。改哪个文件,就只有命中的那几条进上下文。
② 五个改动,各命中几条
--------------------------------------------------------------------
| 改的文件 | 命中 | 条数 | token |
|---|---|---|---|
| src/api/user.py | python、api | 2 | 305 |
| src/ui/Button.tsx | react | 1 | 130 |
| tests/test_user.py | python、tests | 2 | 290 |
| docs/install.md | docs | 1 | 60 |
| src/core/util.py | python | 1 | 210 |
五次改动合计加载 995 token。
③ 换成全塞根目录
--------------------------------------------------------------------
| 做法 | 五次改动合计 |
|---|---:|
| 按条件加载 | 995 |
| 六条全塞根目录 | 3625 |
六条全常驻,五次改动就是 3625 token;按条件加载是 995。
(全塞时同一份规则每轮都在,五次改动就要算五遍。)
! 而且全塞的代价不止 token:六条规则里五条跟当前文件无关,它们挤在上下文里,
会让真正相关的那一条变模糊。
④ 条件写窄一点,还是宽一点
--------------------------------------------------------------------
python 那条的条件是「任意层的 py 文件」,仓库里八百多个 py 文件全命中。
tests 那条限定在 tests 目录下面,只在测试文件上生效。
条件越宽,命中越多,省下的越少;条件越窄,越容易漏。
这条线上没有最优解,只有一个判断标准:这条规则漏掉的代价,比它白带上的代价大不大。
安全类的规则(比如「不要改 migrations」)宁可写宽,带上没坏处;
风格类的规则可以写窄,漏了也不出事。
①六条规则,各自的适用面一目了然。 注意 token 那一列:规则本身不大,问题从来不是单条的大小,是「几条一起常驻」。 配置的写法就是一段元数据加一段正文,条件写在元数据里。这段本沙箱按同一套匹配规则真跑了。 ②五个改动,各自命中的条数不同。改 src/api/user.py 命中两条(python 加 api),改 tsx 只命中一条。 注意 src/core/util.py 只命中 python 一条——它不是 api 目录,也不是测试。 这就是「条件写对了」的样子。 ③全塞根目录是 3625,按条件是 995,差三倍半。 这个倍数还是保守的:真实会话里工具调用几十次,每一次都带着当时的全部常驻内容,差距会更大。 ④第四段讲宽窄权衡。这里没有正确答案,只有「漏掉的代价」这一个判据。 安全类写宽,风格类写窄——这条分界线比任何参数都好用。
30 秒实验
- 再加一条规则「所有 json 文件必须四空格缩进」,条件写任意层的 json。 重跑第 ② 步,看有没有改动命中它。
- 把 api 那条的条件从「src/api 下面任意层」改成「src/api 这一层」,重跑第 ② 步。 src/api/v1/user.py 还命中吗?
- 假设一次会话有 30 次工具调用,按第 ③ 步的两种做法各算一遍总 token。差距是多少倍?
常见误区
- 把带路径条件的规则全写成无条件。等于回到「全塞根目录」,条件白设了。
- 条件全写窄,图省 token。结果一堆规则从来不生效,而你以为它们在管着。
- 规则内容和 CLAUDE.md 重复。叠加之后同一个主题出现两个说法,就是一处隐患。
- 以为只有「按文件类型」一种条件。按目录、按项目、按测试还是源码,都是同一套机制。
- 不看命中情况就上线。写完不知道哪些改动会命中它,等于没配。
自测三问(点开对答案)
1. 什么内容适合写成带条件的规则,什么内容该留在根目录? —— 跟文件类型或路径有关的(某语言的写法、某模块的约定)写成条件规则; 跟文件类型无关的(命令、目录总则、禁区)留在根目录。 2. 条件写宽和写窄,各有什么风险? —— 写宽:命中多,省得少,但不会漏。 写窄:省得多,但新建目录或改结构后容易漏,而且你不知道它漏了。 3. 判断宽窄用哪条标准? —— 看这条规则漏掉的代价。安全类的宁可写宽(漏了出事),风格类的可以写窄(漏了只是不统一)。规矩会写了,下一节把它们拼成三份能直接用的项目配置,并且互相检查一遍。
- ☐ (本篇完,下一篇 2.5 实战:3 种典型项目配置)
相关笔记
- 写作范式:根目录那份怎么写
- 五层记忆:叠加与就近原则
- 三种项目配置:三种项目的成套配置
- 🐎 Claude Code 实战 · 索引