开源 Agent 套件 ECC 的多语言规则库怎么落到你的项目上

2026-07-29

本文基于 ECC 仓库 commit 591ab5c(2026-07-29)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/affaan-m/ECC 最新代码与文档为准。

ECC 的 rules 目录里最值钱的不是那些规则条文,而是每个语言规则文件开头那几行 paths 声明——它把「这条规则对哪些文件生效」写成了可核对的 glob,而不是留给模型自己揣摩。 大部分人第一次打开这个目录,会顺着 coding-style.md 往下读条文,读完觉得「这些我都懂」,然后关掉。真正决定它在你项目里有没有用的,是分层方式、作用域声明,和技术栈到规则的那条映射链路。

站内已有的 /learn/cursor-rules-zuijia-shijian//learn/claude-md-zenme-xie/ 讲的是规则本身该怎么写、写多长、冲突时怎么排优先级这一类通用方法论;本篇不重复那些,只盯住 ECC 这一个具体仓库,看它把这套方法论落成了什么样的目录、什么样的配置文件、什么样的安装动作,以及这些落法各自的代价。

一、这块解决的是哪个具体麻烦

编码 Agent 的规则文件有个很现实的困境:写得通用一点,就只能说「保持代码可读」这种谁都没法反驳也没法执行的话;写得具体一点,比如「错误必须用 fmt.Errorf 包一层」,那它在你的 Python 目录里就是噪声,白白占着上下文,还可能把模型带偏。

ECC 的处理方式是把这两种规则物理分开。rules/common/ 只放语言无关的原则,README 里明确说这一层「没有语言相关的代码示例」;具体到某个语言、某个框架的写法,一律落到同名的语言目录里,并且每个语言文件开头都有一句指回通用层的引用。以 Go 的编码风格文件为例,它的开头长这样:

---
paths:
  - "**/*.go"
  - "**/go.mod"
  - "**/go.sum"
---
# Go Coding Style

> This file extends [common/coding-style.md](../common/coding-style.md) with Go specific content.

两件事同时发生了。一是继承关系被写进正文,读的人(和模型)知道这份文件不是孤立的,上面还有一层;二是作用域被写进 frontmatter,那三条 glob 就是这份规则的边界。rules/common/ 下的文件则没有这个 paths 段——通用层默认对所有文件生效,这个差别是刻意的。

冲突怎么办?rules/README.md 里给了明确的优先级:语言规则压过通用规则,具体覆盖一般。它拿的类比是 CSS 优先级和 .gitignore 的匹配顺序。文档里举的例子是通用层把不可变更新当默认原则,而 Go 目录可以覆写成「Go 的惯用写法是用指针接收者改结构体」。这个取向值得留意:它承认通用原则会和语言惯例打架,并且选择让语言惯例赢。

二、目录是怎么铺的

rules/README.md 里画了一棵目录树,列出了 common/ 加上 typescript、angular、vue、nuxt、python、golang、web、react-native、swift、php、ruby、arkts 这些目录。但你把仓库拉下来 ls rules/ 会发现实际目录比这棵树多——cpp、csharp、dart、fsharp、java、kotlin、perl、react、rust 这些目录都在盘上,只是没被写进那棵树。同样,README 把 common/ 的内容列成八个文件,实际盘上还多出 code-review.mddevelopment-workflow.md 两个。README 末尾提到通用规则里会用一句「Language note」标记出可被覆写的条目,我在 rules/common/ 下没有搜到这个标记的实际出现。

这类文档与代码的漂移在高频迭代的项目里很常见,说它不严重也行,说它是个坑也对——你不能拿 README 的目录树当接口契约用,得以实际目录为准

语言目录的文件组成相当整齐,绝大多数是 coding-style.mdhooks.mdpatterns.mdsecurity.mdtesting.md 这五件套。少数目录会加料:python/ 多一个 fastapi.mdweb/ 多了 design-quality.mdperformance.mdreact-native/ 多了 accessibility.mdperformance.mdproduction-readiness.md

这里有个命名坑,仓库自己已经在文件里挑明了。rules/<lang>/hooks.md 在多数语言下指的是编码 Agent 的工具执行钩子——Go 那份写的就是在 ~/.claude/settings.json 里配 PostToolUse,编辑完 .go 文件自动跑 gofmt、go vet、staticcheck。但 rules/react/hooks.md 讲的是 React 的 useStateuseEffect 这类前端 hooks,文件顶部专门加了一句提醒,说它不是 Claude Code 的 hooks 运行时,之所以叫这个名字只是为了跟仓库里 rules/<lang>/hooks.md 的统一命名对齐。钩子机制本身的取舍,可以对照 /learn/claude-code-hooks/ 看。

三、技术栈是怎么被映射过去的

规则铺好了,谁来决定你这个项目该装哪几层?答案在 config/project-stack-mappings.json。这个文件自己的 description 字段写得很直白:把项目指示文件映射到 ECC 的技能、规则、钩子和默认命令,供 /project-init 自动配置项目使用。

它的结构是一个 stacks 数组,每个条目包含 idnameindicatorsrulesskillscommandspermissionsindicators 是探测依据,支持两种形态:只给 file 表示文件存在即命中,给 filecontains 表示还要文件内容包含某个字符串。Next.js 那条是个典型(下面是节选,原条目的 commands 还有 test/format/dev 三组,permissions.allow 也更长):

{
  "id": "nextjs",
  "name": "Next.js",
  "indicators": [
    { "file": "next.config.*" },
    { "file": "package.json", "contains": "\"next\":" }
  ],
  "rules": ["common", "typescript", "web"],
  "skills": ["coding-standards", "frontend-patterns", "backend-patterns", "tdd-workflow", "verification-loop"],
  "commands": {
    "build": ["npm run build", "npx next build"],
    "lint": ["npx next lint", "npx eslint ."]
  },
  "permissions": {
    "allow": ["npx next *", "npx eslint", "npm test", "npm run *"],
    "deny": ["npm publish"]
  }
}

几个设计取向能从这张表里直接读出来。commands 给的是候选列表而不是单个命令,同一个动作列了多种可能的跑法,探测方要自己挑能用的那个;Spring Boot 这条靠 pom.xml 里包含 spring-boot 来跟普通 Java 项目区分;permissions 里的 deny 明显是按「发布类动作」划的线——TypeScript 栈禁 npm publish,Rust 栈禁 cargo publish,Ruby 栈禁 gem push,Python 栈禁的是 pip install --user *。这种「允许构建测试、禁掉对外发布」的切法,跟最小权限的一般思路是一致的,展开可以看 /learn/agent-zuixiao-quanxian-sheji/

映射表和规则目录之间不是严丝合缝的。Ruby 那条的 rules 只写了 ["common"],尽管 rules/ruby/ 目录是存在的;Docker 那条的 rules 干脆是空数组,只给了 docker-patternsdeployment-patterns 两个技能。另一边,rules/ 下的 angular、vue、nuxt、arkts、react-native、fsharp 这些目录,在 stacks 数组里找不到对应条目。这张表覆盖的是「常见项目结构能自动认出来的部分」,不是规则目录的全集,剩下的靠你自己指定。

消费这张表的是 commands/project-init.md。这个命令文档里的安全约束写得比较克制:默认走 dry-run,用户明确批准前不改 CLAUDE.md、settings、rules、skills 和安装状态;已有 CLAUDE.md.claude/settings.local.json 时要提合并方案而不是覆盖;生成的权限要贴着探测到的构建测试工具走,别给宽泛的 shell 权限。它的输出契约要求列出探测证据、目标 harness、用过的 dry-run 命令、批准后要跑的 apply 命令、会被创建或改动的文件,以及各类告警。真正干活的是 scripts/install-plan.jsscripts/install-apply.js

组成部分它负责什么仓库位置你什么时候会碰到它
通用规则层语言无关的原则,不带语言代码示例rules/common/任何项目接入都会带上
语言/框架规则层覆写通用层,给具体工具与写法rules/golang/rules/react/你的技术栈命中时
作用域声明frontmatter 里的 paths glob各语言规则文件顶部判断某条规则会不会作用到某个文件
技术栈映射表指示文件 → 规则/技能/命令/权限config/project-stack-mappings.json跑初始化命令、或想手动挑装什么时
初始化命令说明探测栈、出 dry-run 计划、要审批commands/project-init.md接入一个新项目的第一步
安装运行时把文件真正铺到目标位置install.shscripts/install-apply.js落盘那一刻
模块清单定义 rules-core 到底拷哪些路径manifests/install-modules.json想搞清楚「我装了什么」时
规则蒸馏技能从技能里提炼跨领域原则回灌规则skills/rules-distill/规则维护期、装了新技能之后

四、规则太多怎么裁

这一节是我觉得最容易被误解的地方,因为文档和代码给出的印象不一样。

rules/README.md 的安装章节让人以为语言参数能裁剪规则:跑 ./install.sh typescript 装通用层加 TypeScript,跑 ./install.sh typescript python 装两套。但 install.sh 本身只有很短的一段 shell,作用是解析出仓库根目录、必要时补装依赖,然后把参数原样交给 scripts/install-apply.js。而在模块清单 manifests/install-modules.json 里,规则是作为一个整体模块存在的(节选,省去了 targetsdependencies 两个字段):

{
  "id": "rules-core",
  "kind": "rules",
  "description": "Shared and language rules for supported harness targets.",
  "paths": ["rules"],
  "defaultInstall": true,
  "cost": "light",
  "stability": "stable"
}

paths 是整个 rules 目录,defaultInstall 为真。换句话说,走这条路时语言参数并不会把 rules/ 削成子集——它影响的是附带装哪些技能模块。scripts/lib/install-manifests.js 里有两张表,一张把语言别名归一化,一张决定每个语言额外挂哪些技能模块。别名表长这样(节选):

const LEGACY_LANGUAGE_ALIAS_TO_CANONICAL = Object.freeze({
  golang: 'go',
  javascript: 'typescript',
  kotlin: 'java',
  rails: 'ruby',
  harmonyos: 'arkts',
});

顺着这张表读还能发现一个具体的对不上:rules/README.md 的安装示例里写了 ./install.sh angular./install.sh vue./install.sh nuxt./install.sh web./install.sh react-native,而别名表里没有这几个名字,代码在遇到表外的语言时会抛「Unknown legacy language」并把可选值列出来。真要用,照报错里列出的名字来,别照 README 抄。

所以「怎么裁」实际上有三个层次,按代价从小到大排:

裁在作用域上。规则文件铺在磁盘上不等于每次都进上下文,paths 那几行 glob 就是天然的裁剪器。你的仓库里没有 .go 文件,Go 那套规则的 glob 就不会命中。这是零成本的裁法,前提是承载它的 harness 真的按 glob 来筛。这也是为什么上下文预算要单独算账,参见 /learn/agent-shangxiawen-yusuan/

裁在安装范围上。别走整包默认安装,用模块或技能粒度的参数。安装器的帮助文本里除了传统的语言参数,还提供了按 profile、按 modules、按 skills 三种指定方式,以及 --config 读取项目里的安装意图文件。想要精确控制装什么,这几条比语言参数直接。

裁在目录上。手工安装就是这条路:只拷你要的那几个目录过去。rules/README.md 在这里给了一条硬警告——必须整目录拷贝,不要用 /* 打平。原因是通用目录和语言目录里存在同名文件(五件套的名字是重合的),打平之后语言文件会盖掉通用文件,并且语言文件里那些 ../common/ 的相对引用会全部断掉。README 也提醒不要往扁平的包级目录里塞,会跟非 ECC 的规则包撞车,它给的是一个带命名空间的位置:用户级放 ~/.claude/rules/ecc,项目级放 .claude/rules/ecc。安装器帮助文本里对默认目标的描述与此一致,另外还留了 CLAUDE_RULES_DIR 这个环境变量可以覆盖规则目录。

五、边界与代价:它明确不管的事

规则和技能的分工是硬的。 rules/README.md 把话说死了:规则定义的是标准、约定、检查清单,是「做什么」;技能提供的是具体任务的可操作参考材料,是「怎么做」。所以你别指望在规则文件里读到完整的实现方案,它会把你指到 skills/ 下面去。这个切分的代价是,两边容易各自漂移——仓库里专门有个 skills/rules-distill 技能来做「扫描已装技能、把反复出现的跨领域原则蒸馏回规则文件」的定期维护,它的脚本默认扫 ~/.claude/rules,跳过 _archived/ 目录,也支持用 RULES_DISTILL_DIR 改扫描位置。需要专门造一个技能来对齐两边,本身就说明这个分工是有维护成本的。

它往你的机器里写文件。 用户级安装的落点是家目录下的配置目录,语言参数那条路径默认还会带上 agents、commands、hooks 运行时、平台配置等一批模块,不只是规则。规则里推荐的 PostToolUse 钩子意味着你每次编辑文件后会自动触发格式化和静态检查工具,映射表里的 permissions.allow 会把一批命令放进允许列表。这些都是实打实的行为改变,装之前值得先看 dry-run 输出,而不是先跑再说。/project-init 的文档默认 dry-run,是个合理的取向,但真正落盘的还是 apply 那一步,审批责任在你。

多 harness 支持是有损的。 安装器支持的目标不止一个,除了默认的用户级和项目级安装,还有 cursor、codex、gemini、opencode、antigravity、codebuddy、joycode、qwen、zed、hermes、kimi、openclaw 这些。但帮助文本里对其中几个目标的描述用的词是「flattened rules」——规则会被打平后装进去。结合上一节那条「不要打平」的警告,你能推断出的结论是:在这些目标上,分层结构和 ../common/ 相对引用不会以原样保留。跨工具用同一套规则时,别默认行为一致。规则作用域和工作区的隔离问题可以对照 /learn/agent-gongzuoqu-geli/

它不管规则内容对不对。 优先级机制只解决「谁覆盖谁」,不解决「哪条更正确」。RULES.md 里那份仓库自用的元规则(比如必须先写测试再实现、不许在输出里带 API key 和绝对路径、不许绕过安全检查和校验钩子)是给贡献者和 Agent 的行为约束,跟你项目里的技术判断是两回事。规则库能做的是把约定摆到模型面前,摆完之后模型照不照做、照做的效果如何,得靠钩子和评审去兜。

六、上手与避坑清单

先看目录,别先看 README 的树。 会踩是因为 README 的目录树和 common 文件列表都落后于实际盘上的内容,你按树去找 rules/rust/ 会以为没有。避法是拉下仓库直接列目录,把实际目录名当准。

语言参数不等于规则子集。 会踩是因为安装文档的写法容易让人以为 ./install.sh python 只会铺 Python 相关规则,于是以为「装了就干净」。实际规则模块的 paths 是整个目录且默认安装,语言参数影响的是附带的技能模块。避法是先加 --dry-run(安装器有这个开关,也支持 --json 输出机器可读的计划),把要动的文件看一遍再决定。

手工拷贝绝不能打平。 会踩是因为 cp -r rules/common/* dest/ 这种写法看着更「干净」,但通用目录和语言目录里 coding-style.mdtesting.md 这些文件名是重合的,打平后语言文件会盖掉通用文件,../common/ 的相对链接也会集体失效。避法是照 README 的写法整目录拷,落在带 ecc 命名空间的位置上,别塞进扁平的包级目录里跟别人的规则包混住。

别把 rules/react/hooks.md 当钩子配置读。 会踩是因为同名文件在其他语言目录下确实是 Agent 钩子的配置建议,形成了错误的预期。避法是打开文件看第一段——那份文件顶部有一句显式声明,说它讲的是 React hooks 而非钩子运行时。反过来,你要找钩子配置就去 rules/common/hooks.md 和各语言的对应文件。

给映射表加自己的栈之前,先确认规则目录存在。 会踩是因为 stacks 数组里的 rules 字段写的是目录名,写一个盘上没有的名字不会在编辑器里报错。避法是加条目时对着 rules/ 的实际目录名逐个核,另外注意某些既有条目(比如 Ruby 只声明了通用层)是有意为之还是遗留,别照着抄结论。

扩语言按仓库给的模板走。 rules/README.md 的「Adding a New Language」一节写清了四步:建目录、按五件套加文件、每个文件开头加一行指回通用层的 extends 声明、能引用已有技能就引用。会踩是因为很多人只加文件不加那行声明,结果继承关系断了,读的人不知道上面还有一层。非语言的领域目录(像 web/)也按同样的分层模式走,前提是可复用的领域内容足够多到值得单独立一个。


把这套东西看完,你要做的判断其实只有三个:你的技术栈在映射表里有没有对应条目、你打算装到哪个目标上、你能不能接受它默认铺进来的那批东西。三个都清楚了再动手,装完的东西你才管得住。

想继续往下看,按这个顺序读仓库文件效率最高:先 rules/README.md 建立结构认知,再挑一个你熟的语言目录把五件套通读一遍看它的行文粒度,然后 config/project-stack-mappings.json 找到你的栈,最后 commands/project-init.md 看它准备怎么动你的项目。真要落地,从 dry-run 开始。

本文属于 ECC 开源 Agent 套件专题(共 40 篇,从选择性安装一直拆到内部机制)。同一批还写了另一种取向的项目——不给你资产、只给你纪律的 superpowers 方法论专题

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。