前两篇讲 Claude Code 记忆体系时,.claude/rules/ 只是"记忆系统"这张大地图里的一个小角落------一段话、一张表就带过了。但它其实是一套值得单独拆解的机制:当项目规则从几十行长成几百行,当"前端规则"和"后端规则"开始互相打架,当你希望某条规则只在 Claude 碰后端代码时才出现------解决这些问题的,正是它。
本文只讲一件事:.claude/rules/ 到底怎么工作、为什么这么设计,以及怎么把它用对。
一、起点:CLAUDE.md 是一个文件,不是一套系统
CLAUDE.md 的加载逻辑很朴素:向上遍历目录树,把沿途所有 CLAUDE.md 全文拼接进上下文。这个模型有一个隐含假设------你的所有项目规则,不管相不相关,都值得每次会话全量加载。
小项目里这个假设成立。但项目一大就会浮现两个具体矛盾:
- 膨胀矛盾:前端规范、后端规范、测试规范、安全规范全部塞进一个文件,官方建议的 200 行上限很快被击穿,而"眼下用得上的" API 规则和"根本用不上的" React 规则占着同样的上下文权重。
- 无关性矛盾:你正在改一个 Python 脚本,却要让 Claude 每次都读一遍与之无关的 TypeScript 组件规范------这些 token 白白消耗,还可能引入指令噪音,增加规则之间互相矛盾的概率。
.claude/rules/ 的价值主张正是针对这两点:先拆分,再按需。拆分解决膨胀(一个主题一个文件,而不是一整块文本);按需解决无关性(通过路径匹配,只有 Claude 真正接触到相关文件时,规则才进入上下文)。这两个能力是独立的------拆分本身不减少 token,真正省 token 的能力来自第三节的路径作用域。
二、基础形态:目录即规则库
把 Markdown 文件放进项目的 .claude/rules/ 目录,每个文件对应一个主题:
bash
your-project/
├── .claude/
│ ├── CLAUDE.md # 主项目说明
│ └── rules/
│ ├── code-style.md # 代码风格
│ ├── testing.md # 测试约定
│ └── security.md # 安全要求
几个基础特性:
- 递归发现 :目录下所有
.md文件都会被找到,包括嵌套子目录,所以可以按团队结构组织成rules/frontend/react.md、rules/backend/api.md这样的层级。 - 一文件一主题:文件名要能望文生义,因为排查加载情况时,文件名往往是唯一线索。
- 不带
paths字段的规则文件等同无条件加载 ,加载优先级与.claude/CLAUDE.md完全相同。也就是说,把一份 CLAUDE.md 拆成十个 rules 文件,只要都不写paths,对上下文的实际消耗和拆分前一模一样------收益纯粹是组织结构上的可维护性,不是省 token。
三、核心设计:路径作用域规则
这是 .claude/rules/ 存在的真正理由。在文件头部加一段 YAML frontmatter,声明 paths 字段:
markdown
---
paths:
- "src/api/**/*.ts"
---
# API 开发规范
- 所有接口必须做输入校验
- 使用标准错误响应格式
- 补充 OpenAPI 文档注释
paths 字段是一个字符串数组,支持标准 glob 模式,也支持花括号扩展一次匹配多个后缀:
| 模式 | 匹配范围 |
|---|---|
**/*.ts |
任意目录下的所有 TypeScript 文件 |
src/**/* |
src/ 目录下的所有文件 |
*.md |
项目根目录下的 Markdown 文件 |
src/components/*.tsx |
特定目录下的 React 组件 |
src/**/*.{ts,tsx} |
同时匹配 .ts 和 .tsx |
触发机制:客户端确定性匹配,而不是模型的语义判断
理解这条规则的关键在于弄清楚谁在做加载判断、依据什么做判断。
路径作用域规则不是在"每次工具调用"时触发,而是在 Claude 实际读取、编辑到匹配路径的文件时触发------这个匹配是 Claude Code 客户端做的确定性 glob 比对:某次文件操作命中了模式,规则内容就被注入上下文;命中不了,这份规则就完全不占用 token,也不会进入模型的"视野"。整个过程模型不参与决策,它甚至意识不到"要不要加载这条规则"这件事存在。
这一点和 Skills 的加载机制形成了鲜明对比:Skills 的相关性判断是交给模型自己完成的------模型根据当前任务和技能描述,自主判断某个技能是否值得调用。而路径作用域规则从设计上刻意避开了这种语义判断,换成了纯粹的、可复现的文件系统事实匹配。
设计取舍:为什么绑的是文件路径,不是用户输入的语义
这是一个值得深究的设计决策,背后是两种触发信号可靠性上的根本差异。
路径匹配是确定性 的:一个文件要么命中 glob,要么不命中,可测试、可复现、没有歧义。绑定用户输入语义则要靠模型去判断"这句话是不是在说 API 相关的事",天然是概率性的,既可能漏判也可能误判。更现实的问题是用户的措辞和代码的实际归属经常脱节:用户可能只是说"把登录报错修一下",完全没提"API"或"TypeScript",但改动最终落在某个 API 目录下的文件里------如果触发条件绑的是"用户提到了相关字眼",这种情况会被直接漏掉;反过来,用户嘴上提到了某个技术词,但实际操作的是完全无关的文件,绑语义又可能被误触发。绑路径问的始终是一个客观事实,两种情况都不会出错。
这也顺带划清了 rules 和 Skills 的分工边界:Skills 本就承担着"判断任务语义相关性"的职责,rules 没必要重复做同一件事。把 rules 锚定在"文件系统事实"上,和 Skills 的"任务语义相关性"恰好构成了两条互不重叠的触发轴。再往底层看,glob 匹配是纯客户端的字符串比对,零推理开销、零额外 token 成本;如果换成语义判断,则意味着每一轮都要让模型多做一次相关性推理,既不确定又更贵。
frontmatter 是这套机制里最脆弱的一环
路径作用域规则的失败模式很特殊:它不报错,只是静默地不生效。规则文件安安静静地待在目录里,你以为它在工作,实际上从未进入过上下文------原因可能是 glob 模式写错了、frontmatter 格式不被当前版本正确解析,或者规则所在的作用域层本身就不支持条件加载。这种失败比"报错"更危险,因为它不会在任何地方留下痕迹提醒你。
因此,写完一条路径作用域规则后,不要假设"格式写对了就万事大吉"。稳妥的做法是:主动打开一个匹配路径下的文件,触发一次规则应该生效的场景,再去确认它是否真的进入了当次上下文。把"验证生效"当成写规则流程里不可省略的最后一步,比记住任何具体的版本号或语法细节都更可靠。
四、作用域与优先级:用户级与项目级如何叠加
.claude/rules/ 不止能放在项目内部,也支持用户级:
| 层级 | 位置 | 作用范围 | 加载顺序 |
|---|---|---|---|
| 用户级 | ~/.claude/rules/ |
你本机上的所有项目 | 先加载 |
| 项目级 | <项目根>/.claude/rules/ |
随仓库共享给团队 | 后加载 |
"用户级规则先于项目规则加载"背后的逻辑,和 CLAUDE.md 的分层哲学是一致的:离当前工作场景更具体的内容排在后面,在模型注意力上通常权重略高,所以项目规则实际上拥有比你个人全局偏好更高的话语权。适合放进用户级目录的是那些与具体项目无关、只关乎个人习惯的内容,比如你偏好的代码风格、你惯用的工作流程。
需要提醒的是:这只是一种软性倾向 ,官方从未承诺过严格的"后者覆盖前者"语义------所有指令都会被同时"看到"并综合权衡。规则之间如果真的互相矛盾,Claude 依然可能任选其一,不能指望加载顺序替你消除冲突。
五、跨项目复用:用符号链接共享一套规则
如果你在多个项目上维护统一的团队标准(比如统一的安全审查清单),没必要在每个仓库里都复制一份。.claude/rules/ 目录原生支持符号链接,链接会被正常解析和加载,循环链接也会被检测并妥善处理:
bash
# 链接一整个共享目录
ln -s ~/shared-claude-rules .claude/rules/shared
# 也可以只链接单个文件
ln -s ~/company-standards/security.md .claude/rules/security.md
这个能力和 CLAUDE.md 里的 @import 语法定位不同:@import 是把外部文件内容"钉"进当前文件的引用语法;符号链接则是在文件系统层面让多个项目物理共享同一份文件------改一处,所有链接到它的项目同步更新,不需要每个项目单独维护导入语句。在 Windows 上创建符号链接需要管理员权限或开启开发者模式,这是维护跨项目共享方案时需要提前考虑的限制。
六、Monorepo 治理:claudeMdExcludes 该配置在哪
在单一仓库容纳多个团队代码的场景下,向上遍历目录树会把其他团队的 CLAUDE.md 和 rules 一并收进上下文------它们可能与你正在做的事完全无关,纯粹浪费 token 甚至引入冲突指令。claudeMdExcludes 设置项可以按 glob 精确排除这些文件:
json
{
"claudeMdExcludes": [
"**/monorepo/CLAUDE.md",
"/path/to/monorepo/other-team/.claude/rules/**"
]
}
这个设置写在 settings.json / settings.local.json 里,支持四个层级,各自的适用场景不同:
| 层级 | 影响范围 | 适合的场景 |
|---|---|---|
| 用户级设置 | 只影响你本人,所有项目 | 你个人在所有仓库里都想排除某类无关文件 |
| 项目级设置(随仓库共享) | 进版本控制,全队共享 | 团队达成一致,长期排除某些路径,需要经过 review |
| 项目本地设置(不进版本控制) | 只影响你在这一个项目里 | 临时或个人化的排除,不希望影响其他成员 |
| 托管策略(组织级) | 全公司所有机器,个人无法覆盖 | IT / 管理员要统一排除某类路径 |
几个实操细节:
- 匹配是针对绝对路径的 glob 匹配,写排除规则时要确认路径写全了。
- 各层的排除数组是合并去重关系,不是覆盖关系------多层同时配置会叠加,不会互相打架。
- 唯一的例外是:托管策略自己的 CLAUDE.md 永远不能被任何层的
claudeMdExcludes排除,这是刻意设计,确保组织级底线规则任何人都绕不过去。
选层的经验法则很简单:只影响自己且是临时性的,放本地设置;团队达成共识要长期生效,放项目级共享设置并走 review;需要全公司统一执行,交给托管策略层。
七、边界:rules 不是万能容器
.claude/rules/ 解决的是"指令模块化 + 按路径懒加载"这一个具体问题,不该用它承担别的职责。放在一起对比,更容易看清每种机制该管什么:
| 机制 | 加载时机 | 谁来触发 | 适合放什么 |
|---|---|---|---|
| CLAUDE.md | 会话启动时全量加载 | 客户端,无条件 | 全项目通用的规范、架构决策 |
.claude/rules/(无 paths) |
会话启动时全量加载 | 客户端,无条件 | 效果等同 CLAUDE.md,仅用于拆分组织 |
.claude/rules/(带 paths) |
Claude 读取匹配路径文件时 | 客户端,按路径确定性触发 | 只对特定目录/文件类型生效的规范 |
| Skills | 被调用或判断相关时 | 模型自主判断相关性,或用户显式调用 | 多步骤操作流程 |
| Hooks | 固定生命周期事件 | 客户端强制执行,不依赖模型判断 | 必须每次都执行的硬性动作 |
这张表里最容易被混淆的一点是:rules(哪怕是路径作用域的)本质上仍然是"影响 Claude 的行为倾向",而不是"阻止 Claude 做某事"的硬约束。如果你的需求是"绝对不能让 Claude 碰某类敏感文件",不该写成一条规则寄希望于模型自觉遵守,而应该用权限拒绝规则或生命周期钩子做客户端强制拦截------规则文件管不住这种红线,它能做的只是提高模型遵循某种约定的概率。
八、调试:如何确认规则真的生效了
排查 .claude/rules/ 相关问题时,按下面顺序检查,能覆盖绝大多数情况:
- 先确认文件被发现和加载:列出当次会话已加载的全部记忆与规则文件。如果目标文件没出现在列表里,说明它压根没被发现或没被触发加载,不必急着怀疑规则内容本身写得对不对。
- 确认触发条件真的满足:如果是路径作用域规则,先检查是否真的让 Claude 接触过匹配路径的文件------它不是无条件加载的,不触碰匹配文件就永远不会出现。
- 怀疑 frontmatter 格式问题时,做最小化验证:去掉复杂的 glob 组合,先用一个最简单的模式单独测试是否生效,再逐步加回复杂度,定位到底是哪一部分格式导致了静默失败。
- 用生命周期日志辅助排查:如果客户端提供了"指令加载"相关的钩子或日志,善用它记录"哪些指令文件在何时、因为什么原因被加载",这比反复靠猜测验证更可靠。
- 排查规则之间的冲突:多个规则文件之间,或规则与 CLAUDE.md 之间如果给出矛盾的指令,模型可能任选其一------这不是加载失败,而是权重博弈的结果,需要靠人工审查消除冲突,而不是指望加载顺序替你决定一切。
结语
.claude/rules/ 本质上是给 CLAUDE.md 补上了两个它天生缺乏的能力:模块化组织 (一个主题一个文件,而不是一整块文本)和条件加载 (路径匹配触发,而不是无条件常驻)。理解它的关键在于分清两种形态------不带 paths 的规则就是"拆分过的 CLAUDE.md",图的是可维护性;带 paths 的规则才是真正的增量能力,图的是上下文效率。
而路径作用域规则的设计本身也传递出一个更普遍的原则:能用确定性的客观信号解决的触发判断,就不要交给概率性的语义判断去做。文件路径是稳定、可验证、零成本的信号;用户意图是模糊、需要推理、有成本的信号。Claude Code 把这两类判断分别交给了 rules(路径匹配)和 Skills(语义相关性),各司其职,而不是让一种机制包办所有触发场景。
用好 .claude/rules/,记住三条:小项目不必用,先用一份 CLAUDE.md;规则膨胀或出现明显的路径相关性时再拆分;拆分后凡是"只对某类文件生效"的部分,一定补上 paths,否则拆分本身不省一分 token。写完之后,养成主动验证生效与否的习惯,比记住任何具体的语法细节都更可靠。