死磕claudecode -.claude/rules:CLAUDE.md 臃肿了,就把规则拆成按路径加载的小块

📅 2026/7/11 5:43:15
死磕claudecode -.claude/rules:CLAUDE.md 臃肿了,就把规则拆成按路径加载的小块
TL;DRCLAUDE.md每次会话全量加载规则多了就变成噪声。.claude/rules/目录里的 Markdown 文件可以按路径匹配按需加载——编辑前端时只加载前端规则改数据库时只加载数据库规则。问题一个 monorepo 里同时存在 React 组件、API 路由、Prisma schema、Terraform 配置、集成测试。把所有规则塞进根目录CLAUDE.md两个问题必然出现.claude/rules/解决这两个问题只有路径匹配的规则才进入上下文。这不是简单的性能优化而是一种架构决策——把全量广播的规则系统变成按需订阅的规则系统。Rules 系统架构加载优先级Claude Code 的上下文来源有三层按优先级排列CLAUDE.md项目根目录 ↓ 每次会话始终加载 .claude/rules/*.md路径匹配 ↓ 仅匹配当前编辑路径的文件才加载 auto memory会话记忆 ↓ 跨会话累积由 Claude Code 自己管理关键区别CLAUDE.md是无条件加载的全局上下文.claude/rules/是条件加载的局部上下文。理解这个区别很重要——如果你把一条规则放进了 CLAUDE.md等于告诉 Claude Code “这条规则对仓库里的每一个文件都适用”。如果它实际上只对某些目录有意义就应该放进 rules。文件格式每个 rule 文件是标准 Markdown顶部用 YAML frontmatter 声明匹配规则--- name: rule-name# 可选用于调试识别description: 一句话描述# 可选帮助维护者理解用途paths:# 必填glob 模式列表-匹配路径1/**-匹配路径2/**---## Markdown 正文具体规则内容...paths字段支持标准 glob 模式。当 Claude Code 编辑的文件路径匹配任一 glob 时该规则文件的完整内容进入上下文。不匹配则完全不可见。这意味着一条规则如果 paths 写得不够精确可能会匹配到不该匹配的文件反过来如果写得太窄该加载的规则又会被遗漏。glob 模式的设计是整个 rules 系统最需要仔细对待的部分。一个常见的错误是在 paths 里写**匹配所有文件——这等于把 rules 文件变成了第二个 CLAUDE.md完全失去了按需加载的意义。paths 应该尽可能具体只覆盖规则真正适用的范围。Token 成本模型假设CLAUDE.md占 1500 token每个 rule 文件平均 200-400 token。在一次典型的编辑任务中始终加载CLAUDE.md ≈1500token 按需加载rules匹配2-3 个 ≈600token ----------------------------------------------- 总上下文成本 ≈2100token如果全部规则都放进CLAUDE.md可能是 4000 token。规则越多拆分的收益越大。这个成本计算在实践中很关键。一个 10 人团队的 monorepo如果每个子系统前端、后端、移动端、基础设施、数据管道各贡献 5-8 条规则全局 CLAUDE.md 很容易膨胀到 3000-5000 token。而如果拆成 rules一次前端编辑任务的规则上下文可能只有 600 token。差别是数量级的。真实 Rules 配置示例示例 AReact 组件规则--- name: react-component-rules description: React 组件开发规范 paths: -packages/ui/src/components/**-apps/web/src/components/**---## 组件规则- 只用functioncomponent禁止 class component - Props 用 TypeScript interface 定义单独文件导出不内联type- 测试文件与组件同目录ComponentName.test.tsx - className 合并用 cn()from /lib/utils不用模板字符串拼接 - 图标统一从 lucide-react 导入不引入自定义 SVG - 颜色使用 tailwind.config.ts 的 theme token禁止硬编码 hex 值 - 组件文件内导出只保留默认导出和一个命名导出如有这个规则只在编辑packages/ui/src/components/或apps/web/src/components/下的文件时加载。编辑 API 路由或数据库 schema 时这些规则完全不存在于上下文中。注意这个例子的 paths 写法——它同时匹配两个不同应用下的组件目录这在 monorepo 中很常见多个应用共享同一套组件规范但各自的组件目录可能不在同一个 packages 下。另一个值得注意的细节是规则的措辞方式。每条规则都是明确的、可验证的指令“禁止 class component而不是尽量用 function component”“从 lucide-react 导入而不是推荐使用图标库”。模糊的规则对 Claude Code 来说等于没有规则——它会按自己的理解来执行结果往往不是你期望的。示例 BAPI 路由规则--- name: api-route-rules description: API 路由处理器开发规范 paths: -src/routes/**-apps/api/src/routes/**---## API 路由规则- 所有输入用 zod schema 校验不信任任何 request body - 返回值统一用 ApiResponseT类型包装 - 禁止在路由处理器中直接调用 prisma client必须走 repository 层 - 错误处理统一通过 errorHandler middleware不单独 try-catch - 新增路由必须同时添加集成测试test/integration/ - HTTP 方法语义GET 不修改状态POST 创建资源PATCH 部分更新 - 路由文件按资源分组单文件不超过150行示例 C数据库迁移规则--- name: prisma-migration-rules description: Prisma schema 和数据库迁移规范 paths: -packages/db/prisma/**-packages/db/migrations/**---## 数据库规则- schema 变更必须生成迁移文件禁止手动编辑 SQL - 新增 model 必须 map 到 snake_case 表名 - 所有 DateTime 字段默认 now()不依赖应用层 - 枚举类型用 Prisma enum不用字符串字段模拟 - 删除字段必须分两步先标记 deprecated下个版本再移除 - 迁移文件一旦合并到 main禁止修改只能追加新迁移 - 涉及数据迁移时必须写 reversible 迁移示例 D测试文件规则--- name: test-rules description: 测试文件编写规范 paths: -**/*.test.ts-**/*.test.tsx-**/*.spec.ts-**/*.spec.tsx-tests/**---## 测试规则- describe 块用中文描述场景it 块用英文描述行为 - 每个test文件顶部注释说明测试覆盖的核心场景 - mock 放在 describe 块外部beforeEach 内设置具体返回值 - 不 mock 被测模块本身的函数只 mock 外部依赖 - 异步测试统一用 async/await不用 done()回调 - 测试数据用 factory 函数生成不硬编码 fixture - 集成测试需要真实数据库连接不用内存 SQLite 替代Monorepo Rules 分层策略一个典型的 monorepo按职责拆分 rules.claude/rules/ ├── typescript-general.md# 所有 .ts/.tsx 文件├── react-component.md# React 组件目录├── api-route.md# API 路由目录├── prisma-model.md# Prisma schema 和迁移├── test-rules.md# 测试文件├── infra-prod.md# 生产环境基础设施└── docs.md# 文档文件每个文件的 frontmatter 和核心内容!-- typescript-general.md ----- name: typescript-general description: TypeScript 通用规范 paths: -**/*.ts-**/*.tsx---## TypeScript 规范- strict 模式禁止 any显式 any 必须加 ts-expect-error 注释说明原因 -import排序react → 第三方库 → / 别名 → 相对路径 - 导出用命名导出默认导出只用于页面组件 - 工具函数纯函数优先副作用函数必须声明返回类型!-- infra-prod.md ----- name: infra-production-rules description: 生产环境基础设施变更规范 paths: -infra/production/**-terraform/prod/**-k8s/prod/**---## 生产环境规则- 所有变更必须经过 plan 审查禁止直接 apply - 安全组变更必须明确标注端口、协议、来源 CIDR - 资源命名遵循{project}-{env}-{service}-{resource-type}格式 - 环境变量引用 vault 路径不硬编码任何值 - 变更后必须验证健康检查端点返回200!-- docs.md ----- name: documentation-rules description: 文档编写规范 paths: -docs/**-**/*.md---## 文档规则- 中文为主技术术语保留英文 - 代码块标注语言类型 - 文件顶部有标题和一句话摘要 - 链接使用相对路径不硬编码域名分层原则拆分的依据是编辑场景的独立性。当你编辑一个 React 组件时不需要知道数据库迁移规范当你改 Terraform 配置时不需要看测试策略。每个编辑场景只激活 2-3 个规则文件这是健康的粒度。具体操作时先问自己一个问题团队里负责前端的人和负责基础设施的人是不是同一批人如果不是他们需要的规则几乎完全不重叠这正是拆分的最强信号。如果团队很小、所有人都做所有事情规则拆分的紧迫性就低一些——但仍然值得做因为上下文噪声对 Claude Code 的影响是客观存在的跟团队规模无关。另一个实用的判断方法是看规则的失效条件。一条规则如果在前端目录里不适用比如所有 DateTime 字段默认 now()这就说明它不该出现在全局上下文里而应该用 rules 限制在特定路径。Rules vs CLAUDE.md 决策矩阵|规则类型|放 CLAUDE.md|放 .claude/rules/|原因|| — | — | — | — ||项目构建和测试命令|✓| |每次会话都需要知道||全局架构说明|✓| |影响所有编辑决策||代码风格通用要求|✓| |简短且全局适用||按目录的编码风格| |✓|只在编辑该目录时才有意义||按文件类型的规则| |✓|只在编辑该类型时才加载||安全边界概述|✓| |团队所有人必须知道||安全边界具体限制| |✓|具体到目录的防护规则||测试策略|✓| |影响所有功能开发||测试具体要求| |✓|按文件类型单测/集成/e2e不同||团队工作流约定|✓| |跨目录的全局流程||第三方库使用偏好| |✓|只在使用该库的文件中加载||CI/CD 配置规范| |✓|只在编辑 CI 配置时加载|核心判断标准这条规则是否在 80% 以上的编辑任务中都需要是 → CLAUDE.md。否 → rules。规则冲突诊断场景一两个 rules 文件匹配同一路径且指令矛盾假设typescript-general.md和react-component.md都匹配apps/web/src/components/Button.tsxtypescript-general.md →导出用命名导出避免默认导出react-component.md →页面组件用默认导出Claude Code 会同时加载两条规则看到矛盾指令。它的行为不可预测——可能选第一条可能选第二条可能在同一个文件里两种都用。诊断方法手动检查规则的路径覆盖是否有交集。在终端执行# 列出所有 rule 文件的 paths 字段forfin.claude/rules/*.md;doecho$(basename$f)sed-n#x27;/^---$/,/^---$/p#x27; $f | grep -A 10 #x27;paths:#x27;echodone修复方法调整 glob 使交集为空或者在交集区域统一措辞。比如上面两条可以改为typescript-general.md →工具函数用命名导出react-component.md →React 组件用默认导出场景二CLAUDE.md 和 rule 文件矛盾CLAUDE.md →所有新代码都用 TypeScriptlegacy-rules.mdpaths:scripts/legacy/**→用 JavaScript不引入类型这种情况下 CLAUDE.md 作为全局规则优先级更高但 rule 文件的局部规则是故意例外。Claude Code 面对这种矛盾时通常局部规则会覆盖全局——这正是 rules 存在的意义。最佳实践在 CLAUDE.md 中主动声明例外消除歧义## Working Rules- 所有新代码用 TypeScriptscripts/legacy/ 除外见规则文件场景三匹配规则过多编辑一个文件时加载了 5 个以上规则文件说明粒度太细或路径划分有问题。用下面的命令模拟某个路径会加载哪些规则# 检查编辑 src/routes/users.ts 会加载哪些规则TARGETsrc/routes/users.tsforfin.claude/rules/*.md;doPATHS$(sed-n#x27;/^---$/,/^---$/p#x27; $f | sed -n #x27;/^paths:/,/^[^ ]/p#x27; | grep #x27; - #x27; | sed #x27;s/.*\(.*\).*/\1/#x27;)forpin$PATHS;do# 简单 glob 匹配检查ifls$p2/dev/null|grep-q$(basename$TARGET);thenechoMATCH:$(basename$f)→$pfidonedone粒度决策矩阵|粒度|适用场景|Token 成本|维护成本|典型规则数|| — | — | — | — | — ||按目录|monorepo 不同包各包技术栈独立|低每次 1-2 个|低按目录自然划分|4-8 个||按文件类型|同一目录下多种文件类型共存|中每次 2-3 个|中glob 需要仔细设计|5-10 个||按功能模块|大型服务按业务领域拆分|中每次 2-4 个|中模块边界需要定期审查|6-12 个||按文件名|特殊文件需要特殊处理如 docker-compose.yml|高可能匹配过多|高文件名模式难维护|按需 1-3 个|推荐起点按目录划分。这是最自然的边界也最容易验证路径不重叠。随着项目复杂度增长再在目录基础上叠加文件类型规则。实际操作中最常见的组合策略是目录 文件类型双层叠加。比如一个按目录划分的react-component.md负责components/**下的所有规则再叠加一个按文件类型划分的typescript-general.md负责**/*.tsx的通用 TypeScript 规则。两层规则的内容要刻意避免重叠——文件类型规则只管语法和类型层面的事情import 排序、禁止 any、导出风格目录规则管架构和业务层面的事情用什么组件模式、怎么处理样式、测试放哪里。这种分层方式让规则各管各的不容易冲突。失败案例规则膨胀导致上下文溢出场景一个 8 人团队在 monorepo 中创建了 20 个 rules 文件。拆分依据很合理React 规则、TypeScript 规则、测试规则、UI 包规则、组件规则、样式规则、API 规则、数据库规则、迁移规则、基础设施规则、CI 规则、文档规则、legacy 代码规则、性能规则、安全规则……当开发者编辑packages/ui/src/components/Button.tsx时同时加载了 5 个规则文件react-component.md ≈350token 匹配 components/** typescript-general.md ≈280token 匹配 **/*.tsx test-rules.md ≈300token 匹配 **/*.test.tsx ← Button.test.tsx 在同目录 ui-package-rules.md ≈250token 匹配 packages/ui/** style-rules.md ≈200token 匹配 **/*.css components/**总计 1380 token 的规则上下文加上CLAUDE.md的 1800 token光规则就消耗了 3180 token。一次编辑任务的总上下文预算大约 8000-12000 token规则占了 25-40%。更严重的是react-component.md和ui-package-rules.md有重叠内容都提到了组件导出风格typescript-general.md和react-component.md对默认导出的态度不完全一致。Claude Code 在生成代码时出现了不一致的行为有时用命名导出有时用默认导出。根因这个案例的教训是rules 系统的价值不在于拆得有多细而在于每次编辑时加载的规则恰好够用且不冲突。拆分是手段不是目的。修复合并为 6 个边界清晰的规则文件.claude/rules/ ├── typescript-general.md# 所有 .ts/.tsx纯语法和类型规则├── react-component.md# components/**组件特定规则含样式├── api-route.md# routes/**API 规则├── database.md# prisma/** migrations/**数据库规则├── test-rules.md# **/*.test.* tests/**测试规则└── infra.md# infra/** terraform/** k8s/**基础设施规则每个文件的 paths 互不交叉编辑一个文件最多加载 2-3 个规则文件类型 目录职责总规则 token 控制在 600-900 以内。验证方法合并后在编辑几个典型文件时用/context命令查看实际加载了哪些规则确认编辑 packages/ui/src/components/Button.tsx → 加载typescript-general.md react-component.md → 规则 token≈630→ ✅ 编辑 src/routes/users.ts → 加载typescript-general.md api-route.md → 规则 token≈580→ ✅ 编辑 packages/db/prisma/schema.prisma → 加载database.md → 规则 token≈300→ ✅落地检查清单从 CLAUDE.md 拆出第一批 rules 时逐项确认• [ ] 每个 rule 文件的 paths 覆盖范围与其他文件无交叉• [ ] 一次典型编辑任务加载的规则文件不超过 3 个• [ ] 没有跨文件的矛盾指令用诊断脚本或手动检查• [ ] 每次任务的规则 token 总成本 1000总上下文 2000 含 CLAUDE.md• [ ] CLAUDE.md 中主动声明了 exceptions“某些目录另有规则”• [ ] 团队成员知道去哪里改规则——不会有人绕过 rules 直接在 CLAUDE.md 里加新条目• [ ] 规则文件有 name 和 description方便ls .claude/rules/时快速理解• [ ] 新人加入团队时能通过读 rules 文件名和 description 快速了解各目录的编码规范最后一条经常被忽略但很重要rules 文件不只是给 Claude Code 读的它也是团队知识的载体。文件名和 description 写得清楚新人在浏览.claude/rules/目录时就能理解这个仓库的不同区域有哪些不同的规矩。这是一种低成本的知识传递方式。与其他篇的关系•04-CLAUDE.mdrules 是从 CLAUDE.md 中拆出来的。先写好 CLAUDE.md全局规则再按冲突或膨胀信号拆出 rules。•03-项目地图项目地图定义了目录职责rules 的 paths 划分应该与目录职责对齐。•10-渐进式披露rules 本身就是一种渐进式披露——只在需要时加载需要的上下文。如果单个 rule 文件超过 60 行考虑它是否应该拆分或使用渐进式披露策略引用外部模板。权衡路径规则的核心权衡是维护点增加。每多一个 rule 文件就多一个需要同步更新的地方。拆分之前先确认信号• CLAUDE.md 超过 120 行• 不同技术栈的规则开始互相矛盾• Claude Code 在某个目录下反复犯另一个目录才会犯的错误• 团队有人开始抱怨Claude Code 总是不按我们这个包的规矩来以上信号出现两个以上就值得拆。否则保持单个 CLAUDE.md 更简单。