工程手记

77 个规则文件——一层通用规则不够用

通用规则会自相矛盾——Go 的指针接收器和"始终返回新对象"直接冲突,而后者对 TypeScript 是对的。这是分层的起因。

建立规则系统时面临一个基础问题:通用规范和语言专用规范如何共存。

我采用的方案是分层存储:一份通用规则作为基础,11 个语言特定目录各自扩展,再加一份中文翻译层。77 个文件分布在 13 个目录里,但不是简单地重复 13 遍相同内容——每一层有明确的职责和覆盖关系。

问题:单层平铺的陷阱

最直观的做法是把所有规则写进一个大文件。但这样做有三个问题。

首先是冗余。通用的编码原则(比如测试覆盖率 80%、禁止硬编码密钥)对所有语言都适用,不需要重复写 11 遍。

其次是维护成本。一处修改要同步到 11 个地方,漏掉一个就产生不一致。

第三是可迁移性差。选择采用 TypeScript 时不需要关心 Perl 的规则,把无关的内容混在一起反而增加认知负担。

方案:三层分组

我把 77 个文件按职责分成三层。

第一层是通用规则(common 目录,10 个文件)。这里放所有语言都适用的原则:不可变性、文件大小上限(800 行)、错误处理策略、测试覆盖率(80%)、代码审查标准。这些是基线,整个体系的基座。

第二层是语言特定规则(11 个语言目录,55 个文件)。每个目录对应一门语言或语言族群(cpp 处理 C/C++,typescript 覆盖 TypeScript 和 JavaScript)。语言特定目录里的 5 个文件(coding-style、testing、patterns、hooks、security)与通用层同名,但内容是该语言的方言。

第三层是翻译层(zh 目录,11 个文件)。这是通用规则的中文版本,与 common 目录的内容完全对应(多一份 README),满足中文环境的表述习惯。

数字加起来:10(common)+ 55(11×5)+ 11(zh)+ 1(rules/README.md)= 77 个文件。

关键设计:特定覆盖通用

分层的核心机制是优先级约束。语言特定的规则可以覆盖通用规则。

这个模型来自 CSS 特异性和 .gitignore 的优先级机制。不是"多份规则随意叠加",而是"特定优先,无特定则用通用默认"。

具体例子:common/coding-style.md 有一条铁律——"始终返回新对象,禁止原地修改"。这对 TypeScript、Python、Rust 等语言是对的,因为不可变数据模式防止隐藏的副作用。

但 Go 不同。惯用的 Go 代码使用指针接收器进行结构体变更——这是语言习惯,也是性能优化的标准实践。所以 golang/coding-style.md 在文件开头就声明:

This file extends common/coding-style.md with Go specific content.

然后针对可变性做出例外说明。规则读者看 Go 项目时会先读通用层,再读语言层,遇到冲突时语言层赢。

陷阱:安装时的覆盖风险

分层的好处显而易见,但也有一个隐藏的陷阱。

假设用这种方式安装规则:

cp rules/* ~/.claude/rules/

看似简洁,实际破坏了整个结构。因为通用目录和语言目录里存在同名文件(都有 coding-style.md、testing.md 等),展开通配符时语言特定文件会直接覆盖通用规则。README.md 里的 ../common/ 相对引用也会断掉。

README 的安装指南明确了正确做法:

# 安装通用规则(所有项目必需)
cp -r rules/common ~/.claude/rules/common

# 安装语言特定规则
cp -r rules/typescript ~/.claude/rules/typescript
cp -r rules/python ~/.claude/rules/python
# ...其他语言

不用通配符,逐个目录复制。这样保留了目录结构,相对引用才能生效。这是"踩过才知道"的约束——第一次装时很容易掉进去。

显式标注可覆盖项

但通用规则和语言规则之间哪些会冲突、哪些是兼容的,仅靠约定不够。

我在 README 里加了一个机制:Language note 标记

在 common 目录里,可能被语言层覆盖的条目会标注一句:

Language note: This rule may be overridden by language-specific rules for languages where this pattern is not idiomatic.

比如 common/coding-style.md 的不可变性原则就带了这个标记。读规则的人一看到它,就知道"这条原则大多数语言都遵守,但可能有例外"。对应的语言目录里如果有覆盖,就形成了一个清晰的对话。

反过来,语言特定文件在覆盖时也要声明来源:

Idiomatic Go uses pointer receivers for struct mutation — see common/coding-style.md for the general principle, but Go-idiomatic mutation is preferred here.

这样做的好处是可搜索。grep 一下 "Language note",所有潜在冲突点一目了然。新增语言时也知道哪些条目是"容易冲突的"。

规则与 Skill 的分工

整套体系里还有一个角色是 skills——这个 2026 年 03 月的时间点还没有具体内容。

但分工边界已经定好了:规则定标准,Skill 定做法

规则说"测试覆盖率必须 80%"是标准。Skill 说"怎样用 pytest 构造 fixtures,怎样用 mock 隔离依赖"是方法。规则说"禁止硬编码密钥"是底线。Skill 说"把密钥存进 .env,用 dotenv 库加载"是工具链实现。

规则通常是范围更广的原则和检查清单——应用到多个技术栈,形成同一个文化基准线。Skill 是特定任务的深入指南,可能只给某一个语言或框架用。

规则层的文件相对稳定(改一次要同步到 13 个目录有成本,所以需要重大理由)。Skill 可以快速迭代(技术动作变了,Skill 立即补)。

小结

77 个文件分成通用层、11 个语言目录和一套中文译本,看似复杂,实际是在规模和一致性之间的权衡。

关键的三个设计决策是:

  1. 特定优先——语言规则可以覆盖通用规则,类似 CSS 特异性的模型
  2. 结构保护——禁用通配符安装,逐目录复制,保留相对引用的完整性
  3. 冲突可见——用 Language note 标记潜在的覆盖点,避免隐藏的假设

这套结构一旦理解清楚,就成为一个快速导航点:看 common 知道所有项目的基线,看具体语言目录知道专项微调,看标记知道哪里有例外。

星野的头像

星野 XINGYE

一个人维护 AI 平台的工程师。这里记录 63 篇复盘:18 份故障档案、OTA、架构演进与工作流。