在 .claude/rules/ 放项目规则,可以按端再分成 backend/、frontend/。写不写 paths 决定它什么时候进上下文:不写 paths,它每次会话开始就加载,和 CLAUDE.md 一样占位置;写了 paths,Claude 读到匹配的文件时才加载。个人习惯放 ~/.claude/rules/,不提交。
会在三种情况下打开这一页:某条约束只对后端或前端成立,要下沉到目录层 → 看「写不写 paths 决定加载时机」,写完补 paths;规则写了但行为没变 → 看「规则不改行为时的四种现象」;从 Cursor 或 TraeCode 迁规则文件过来 → 看「规则文件在三个工具里的位置与字段」。
最容易写错的是把「不写 paths 的规则」当成按需加载——它其实每次都加载,等于又往 CLAUDE.md 里加了一节。另一个高频错误:paths 只写一层通配(backend/*.go),实际文件在 backend/internal/db/ 下,规则永远命不中。写 paths 就用 ** 跨多级目录,写完开一轮新对话实测一次。
写的这条规则,说得出它在哪一次会话里被加载,以及它命中的那个文件长什么样吗?
答「不能」,它要么每次都占位置,要么一次都不加载。要么给它补 paths,要么把这句话上提到 CLAUDE.md。
规则只有两种状态:每次会话都进上下文,或命中某个文件才进。选哪种,取决于这句话对谁成立,不取决于它有多重要。
| 写法 | 什么时候加载 | 代价 |
|---|---|---|
不写 paths | 每次会话开始就加载 | 和 CLAUDE.md 一样占每一次对话的位置 |
写了 paths | Claude 读到匹配的文件时才加载 | 本轮没碰到匹配文件就不加载,别指望它约束别处 |
放在 ~/.claude/rules/ | 本机所有项目生效 | 不提交,不进小组共用的上下文 |
它和 CLAUDE.md 的分工只有一句话:整仓成立、每次改动都成立的短句放根文件;只对某一端或某一类文件成立的句子放规则,并写 paths。根文件写过的句子,规则文件不要再抄。
scope_id)写进 backend.md 并写 paths: backend/**/*.go。改完开一轮新对话,让助手读一次后端文件验证。
paths 的规则当垃圾桶,塞进「代码要写好」这类无法核对的句子;或把根文件里已有的数据范围再抄一份到规则里,让同一条被读两遍。
边界:paths 匹配用 glob,支持 ** 跨多级目录。一条约定对前后端都成立时,它不该出现在 backend.md 或 frontend.md——把它放回 CLAUDE.md。
三处的目录名、前置元数据字段名、不带范围时的默认行为都不同。迁移规则文件前先在这张表里对一遍三处写法。
| 项 | Claude Code | Cursor | TraeCode |
|---|---|---|---|
| 项目位置 | .claude/rules/(可再分 backend/、frontend/) | .cursor/rules/*.mdc | .trae/rules/*.md |
| 个人位置 | ~/.claude/rules/ | 无对等位置 | ~/.trae-cn/user_rules |
| 限定文件范围 | 前置元数据 paths:(glob 列表) | globs: 加 alwaysApply: false | alwaysApply: false 加 globs:(逗号分隔多个通配符) |
| 不带范围时 | 每次会话开始就加载,和仓库说明一样占上下文 | alwaysApply: true 时始终生效 | 创建时选定生效方式,由 TraeCode 改 alwaysApply |
| 手动点名 | 无对等写法 | 无对等写法 | 对话里用 #Rule 点名 |
| 只写 description 的规则 | 无此形态 | 会和技能的 description 抢「什么时候想起」;Cursor 视其为可迁到技能 | 同左,本课不用 |
| 篇幅参考 | 无硬性数字,按「一条一件事」拆 | 一份规则宜 50 行以内 | 无硬性数字 |
| 改完怎么验证 | 开一轮新对话再试 | 开一轮新对话再试 | 开一轮新对话再试 |
迁完实测一次:把 .cursor/rules/backend.mdc 复制成 .claude/rules/backend.md,把 globs: 改成 paths:。开一轮新对话,让助手读一次后端文件,看这句话有没有出现在它的回复里。
前三份里,两份是该写的,一份是不该写的。示例按一个「按登录用户隔离数据」的项目写,根 CLAUDE.md 已经写了数据范围,下面的规则只补某一端才会做错的事实。
paths 用 ** 跨多级目录,匹配 backend/internal/db/query.go 这类文件。改完开一轮新对话,让助手读一次这个文件验证。
--- paths: - "backend/**/*.go" --- 数据库是 MySQL。主键沿用现有脚本,所属范围列名为 scope_id,不要改成 UUID。 上传文件放在 UPLOAD_DIR,文件字节不写入 SQL。 若使用向量库,点的 id 与数据库切片 id 相同。 改查询或检索时,测试覆盖两件事:当前所属范围读得到,其他所属范围读不到。
同一份规则文件里可以列多条 paths,命中其中一条即加载。
--- paths: - "frontend/src/**/*.ts" - "frontend/src/**/*.tsx" --- 只通过后端接口访问数据。 不要在浏览器里直连 MySQL 或 Qdrant。 数据范围来自登录会话,不要用地址栏里的 id 切换范围。 密钥和网关地址不要打进前端包。
这份不写 paths,还把只对后端成立的句子塞了进来。结果是改 README 的对话也会读到「范围列名是 scope_id」,每次会话都被占一层位置。
--- --- 代码要写得好,注意数据安全。 数据库是 MySQL,所属范围列名为 scope_id。 上传文件放在 UPLOAD_DIR。
反例的修法:删掉「代码要写得好,注意数据安全」(无法核对),把后两句移进 backend.md 并加 paths: backend/**/*.go,global.md 整个删除。
规则不生效很少报错,多数是匹配不到、还没加载、或被别处覆盖。按「现象 → 原因 → 修法」对号入座,改完开一轮新对话再判断。
| 现象 | 原因 | 修法 |
|---|---|---|
backend.md 写了 paths,改后端代码时这条规则却从没生效 | paths 写成 backend/*.go 只匹配一层,实际文件在 backend/internal/db/ 下 | 改成 backend/**/*.go;开一轮新对话,让助手读一次后端文件再验证 |
| 改了规则文件,当前这一轮对话里没任何变化 | 带 paths 的规则在会话中命中文件时才注入,已经开始的对话不回溯 | 开一轮新对话再试,不要在旧会话里判断规则有没有生效 |
同一条约束在 CLAUDE.md 和规则里各一份,改了一处另一处忘改 | 抄了一遍,同一条被读两遍 | 跑 grep -n "scope_id" CLAUDE.md .claude/rules/*.md,同一约束出现两处即违规,只留一处 |
把个人调试习惯写进 .claude/rules/ 并提交,小组每个人的助手都开始照做 | 项目规则是小组共用的,一提交就对所有人生效 | 移到 ~/.claude/rules/,并把它从仓库删除 |