开源 Agent 套件 ECC 的选择性安装:三层清单怎么挑最小集合
本文基于 ECC 仓库 commit 591ab5c(2026-07-29)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/affaan-m/ECC 最新代码与文档为准。
这套东西真正的设计重点不是它带了多少内容,而是它把「装什么」变成了一份机器可解析、你可以逐项否决的清单。 你如果只把它当成一个把大堆 markdown 拷进 ~/.claude 的脚本,那基本上等于放弃了它一半的能力。仓库里 manifests/ 下的三个 JSON 文件才是这套安装体系的正体,安装脚本只是把这三份清单解析开、算出依赖、投影到某个具体工具的目录结构里。
先把定位说清楚。站内已有的 Claude Code 教程 讲的是这个编码工具本身怎么用,Agent 工作区隔离 讲的是隔离作用域的通用方法论,两篇都是方法层面的。本篇不重复这些,只盯着 ECC 这一个具体的开源项目,看它把「按需安装」这件事落到了什么数据结构、什么命令、什么失败信息上。方法论谁都会讲,能被 git clone 下来逐行核对的实现才是可验证的。
一、它先要解决的问题:装进去的东西不是躺着的
ECC 仓库里 agents 目录有 67 个 agent 定义,skills 目录有 281 个技能,commands 目录有 94 个命令。这些内容如果一股脑落进你的 harness 配置根目录,占的那点磁盘完全不值一提,麻烦在别的地方:技能和命令是会被扫描、被列举、被塞进模型可见范围的,命令名和技能名会互相撞车,hook 脚本会在每次会话事件上真的执行。装得越多,你越难说清某次异常行为是你自己的规则引起的还是套件里某条规则引起的。
docs/SELECTIVE-INSTALL-ARCHITECTURE.md 开头就把目标写死了:要的不只是「安装时少复制几个文件」,而是一套能确定性地回答五件事的安装系统 —— 请求了什么、解析成了什么、实际拷贝或生成了什么、做了哪些针对目标工具的转换、哪些产物归 ECC 所有因而后续可以安全地移除或修复。这五问决定了它必须先有清单,再有安装动作,而不是反过来。
这个取向是对它自己前一代安装方式的纠正。同一份文档在检讨现状时写得很直白:早期的安装脚本是语言优先的,目标分支堆在 shell 里,安装逻辑在 shell 分支中重复,于是每加一个新目标就得再写一堆特例。现在的 install.sh 只有几十行,做的事情是解析被 npm bin 符号链接遮住的真实包根目录、在 Git Bash 环境下用 cygpath 把 POSIX 路径转成 Windows 路径,然后 exec node 把参数原样交给 scripts/install-apply.js。安装语义一行都不在 shell 里。
二、三层分别控制什么
三层的职责边界很清楚:profile 表达意图,module 定义内容与依赖,component 提供人话入口。
profile 层在 manifests/install-profiles.json,每个 profile 就是一句描述加一个 module 列表,不含任何路径、不含任何目标工具逻辑。仓库里目前有 minimal、opencode、core、developer、security、research、full 这几个。它们之间的差别值得看具体内容而不是看名字:minimal 的描述里明确写了「no hook runtime」,core 相比它多了 hooks-runtime,developer 在 core 基础上加了 framework-language、database、orchestration,full 则把当前所有已分类的模块都列了进去。
"minimal": {
"description": "Low-context Claude Code setup with rules, agents, commands, platform configs, and quality workflow support, but no hook runtime.",
"modules": [
"rules-core",
"agents-core",
"commands-core",
"platform-configs",
"workflow-quality"
]
}
module 层在 manifests/install-modules.json,这是真正带路径的一层。每个模块的字段是 id、kind、description、paths、targets、dependencies、defaultInstall、cost、stability。paths 是仓库内的相对路径列表,targets 是这个模块支持哪些 harness,dependencies 是模块间的硬依赖,cost 用 light/medium/heavy 标注体量,stability 区分 stable 和 beta。
这一层有两个细节值得留意。一是模块不只装内容,也装脚本:hooks-runtime 的 paths 是 hooks、scripts/hooks、scripts/lib,commands-core 除了 commands 还带上了 scripts/harness-audit.js 和 scripts/skills-health.js。二是依赖链有真实深度,machine-learning 的 dependencies 是 framework-language、workflow-quality、database、devops-infra、security 五个,你点名要它,实际会拉进来一大片。
component 层在 manifests/install-components.json,是给人用的选择入口。ID 有固定格式,schema 里的 pattern 限死了前缀只能是 baseline、lang、framework、capability、agent、skill、locale 之一,冒号后面跟小写短横线命名。字段只有 id、family、description、modules,也就是说 component 本身不带任何路径,它就是一层命名映射。
{
"id": "baseline:hooks",
"family": "baseline",
"description": "Hook runtime configs and hook helper scripts.",
"modules": [
"hooks-runtime"
]
}
component 到 module 大多是多对一,少数条目一次指向两个模块。lang:typescript、lang:python、lang:go、lang:java 这几个的 modules 字段全都是 framework-language,描述里也直说了「currently resolves through the shared framework-language module」。agent: 家族的条目一律指向 agents-core。这层设计的意义在下一节会讲到代价。
| 组成部分 | 它负责什么 | 仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| profile 清单 | 把常见意图打包成 module 列表,不含路径 | manifests/install-profiles.json | 第一次安装选 --profile 时 |
| module 清单 | 定义真实路径、支持的目标、依赖、体量与稳定度 | manifests/install-modules.json | 想知道某个 profile 到底会拷什么时 |
| component 清单 | 给用户一层可读的加减入口,映射到 module | manifests/install-components.json | 用 --with / --without 微调时 |
| 解析器 | 展开依赖、按目标过滤、处理排除、报冲突 | scripts/lib/install-manifests.js | 安装报错看不懂时 |
| 目标适配器 | 决定内容落到哪个目录、怎么改写路径 | scripts/lib/install-targets/ | 换 harness 或发现目录结构不对时 |
| 安装状态 | 记录本次请求、解析结果与实际操作 | schemas/install-state.schema.json | 跑 doctor / repair / uninstall 时 |
三、落盘之前先看计划
命令行入口的选项在 scripts/install-apply.js 的帮助文本里写得很全:--target 选 harness,--profile 或 --modules 二选一表达主体意图,--with 和 --without 用 component ID 加减,--skills 按技能目录 ID 点名,--locale 装翻译文档,--config 从文件读安装意图,--dry-run 只出计划不动文件,--json 输出机器可读结果。
--dry-run 的输出不是含糊的摘要。printHumanPlan 会依次打印 Mode、Target、Adapter、Install root、Install-state 路径、Profile、Included components、Excluded components、Requested modules、Selected modules,如果有跳过或排除的模块还会单列 Skipped modules 和 Excluded modules,最后逐条打印 源相对路径 -> 目标绝对路径。你在这一步就能看到每一个文件的落点。养成先 dry-run 的习惯,比装完再翻目录省事得多,这和 Claude Code 的 Plan Mode 的思路是一回事:先看意图产物,再授权变更。
真正落盘之后,安装状态会写到目标适配器指定的位置。以默认的 claude 目标为例,scripts/lib/install-targets/claude-home.js 里 rootSegments 是 .claude,installStatePathSegments 是 ecc 加 install-state.json,也就是 ~/.claude/ecc/install-state.json。同一个适配器还负责路径改写:仓库里的 rules/ 会落到目标根目录下的 rules/ecc/,而 skills/ 下的内容是平铺到 skills/ 的,因为 Claude Code 要求技能是该目录的直接子目录。README 里为此专门警告过:手工安装不要把技能嵌到 ~/.claude/skills/ecc/ 底下。想理解为什么技能目录结构这么敏感,可以对照读 Claude Code Skills。
有了这份状态文件,scripts/ecc.js 路由的几个生命周期命令才有依据:list-installed 只读状态,doctor 拿状态和当前文件系统对比找漂移,repair 从状态重建计划把缺的补回来,uninstall 只删状态里记录的 ECC 托管产物。README 也建议在重装之前先按这个顺序跑一遍。
四、边界与代价:这个设计放弃了什么
component 的粒度是标称的,不是实际的。 这是最容易误判的一点。你写 --with lang:go,期待拿到一小撮 Go 相关的技能,实际拿到的是整个 framework-language 模块 —— 那里面从 Android 架构、Angular、Django、Laravel 到 Kotlin、Rust、React 的一大串技能目录全在,六十多个目录一个都不少。agent:code-reviewer 同理,落地就是 agents-core 整体。真正做到「一个 component 只对一个专门模块」的是少数,比如 lang:swift 指向的是独立的 swift-apple,capability:database、capability:security 这类 capability 家族也各自对应一个模块;语言和框架家族则基本都汇到共享模块上。还有几个 component 一次牵动两个模块,framework:laravel、lang:ruby 这类的 modules 里除了 framework-language 还挂着 security,你按语言点名,安全技能族会跟着进来。清单的描述字段没有回避这件事,写的是当前经由共享模块解析,但你不点开看就会想当然。
依赖是硬校验,不是软提示。 解析函数在遇到「某模块依赖了一个被排除的模块」时会直接抛错,错误信息形如 Module X depends on excluded module Y,还会附上是被哪个 component 排掉的。如果你的排除项把所有请求模块都干掉了,会得到 Selection excludes every requested install module。这意味着 --without 不是一个可以随手乱按的开关,动之前最好先 dry-run 看一眼依赖。
装上骨架不等于能跑。 skill-unified-memory 这个模块的描述里明确写了,它需要另行安装的 ecc-universal CLI 运行时;workflow-quality 的 dependencies 又指向了它。也就是说你选任何一个包含 workflow-quality 的 profile,都会把这条技能一起带进来,但它的完整能力依赖仓库之外的东西。ito-compute 模块也标注了要通过另行安装的规范 CLI 才能工作。清单能保证文件到位,保证不了运行时到位。
架构文档自己承认了未完成的部分。 那份设计文档专门留了「Current limitation」一节,写的是:部分模块的目标侧合并与删除语义还停留在脚手架级别;遗留的兼容入口仍然指回 install.sh;npm 的发布面仍然偏宽。紧接着的现状检讨一节说得更狠 —— 生命周期行为依赖的是记录下来的底层操作,而不是稳定的模块语义,这对纯文件拷贝够用,一旦涉及合并、生成、删除这类行为就会变脆;uninstall、repair、doctor 虽然有了,但仍属早期形态。文档还列了 module 清单「还需要但尚未有」的字段,比如安装策略、归属、路径模式、冲突声明、发布归属。这些是作者的自我评估,不是我的推断,也正因为写在明面上,你在依赖 repair 之前就该知道它的适用边界。
它明确不管的事。 不管你的 harness 如何加载和排序这些内容 —— 那是各家工具自己的事,README 里对 Cursor 的 agent 加载行为就写了「可能随 Cursor 版本而变」;不管你原有规则和它带来的规则谁优先;不管内容本身是否适合你的团队。卸载也只回收状态文件里记录的托管产物,你自己在同一目录下写的东西它不碰,反过来说,你手工改过的托管文件会被 doctor 当成漂移。
要如实说的风险。 这类套件会往你的用户目录写文件,hooks-runtime 会挂上会话钩子并在事件触发时真的执行脚本,platform-configs 带的 mcp-configs 是 MCP 目录默认值,research-apis、social-distribution 这些技能族的用途本身就涉及外部服务。这些代价不该被「一条命令装好」的顺滑感盖过去。装 hook 之前,先读一遍 Claude Code Hooks 搞清楚钩子什么时候被触发,比装完再排查省事。README 还有一条明确警告:不要叠装,比如已经用插件方式装过之后再跑一次完整安装,技能、命令、钩子会重复。
五、上手与避坑清单
别拿 full 起步。 会踩是因为 full 的语义是「所有当前已分类的模块」,其中包含媒体生成、供应链领域、预测市场这类和绝大多数工程项目无关的技能族,而且其中若干模块的 stability 标的是 beta。避法是从 minimal 或 core 起步,缺什么再用 --with capability:xxx 加,README 给的示范就是 npx ecc install --profile minimal --target claude --with capability:machine-learning 这种形态。
别用 --without 去砍底座模块。 会踩是因为 baseline 家族的几个模块被大量模块作为依赖引用,你排掉它会直接触发上面那条依赖错误,而不是「静默少装点」。避法是优先换更小的 profile;确实要减,参考 README 里那条安全示范 ./install.sh --profile core --without baseline:hooks --target claude,之后想加回来用 ./install.sh --target claude --modules hooks-runtime。
别在 ecc-install.json 里写 skill: 或 agent: 前缀的条目。 会踩是因为 schemas/ecc-install-config.schema.json 里 include 和 exclude 的 pattern 只允许 baseline|lang|framework|capability 四个前缀,配置文件由 Ajv 校验,写别的会直接被判无效配置而不是被忽略。避法是这几类走命令行的 --with / --skills / --locale。另外注意配置文件是会被自动发现的:安装脚本在没有显式 --config 且没有传语言参数时,会去当前工作目录找 ecc-install.json,你如果在某个仓库里留过一份旧的,下次安装会被它悄悄影响。
想只要一两个技能就用 --skills。 会踩是因为 component 清单里手写的 skill: 条目只有十来条,对着 281 个技能来说不足一成,你在里面找不到目标技能就以为不支持。实际上清单加载时会扫描 skills 目录,为每个技能目录合成一个 skill:<目录名> 的 component 和对应的模块,--skills 传进来的值也会自动补上 skill: 前缀。所以 ./install.sh --target claude --skills tdd-workflow,security-review 这种写法是成立的。
换 target 之后别假定装到的东西一样。 会踩是因为过滤是双重的:模块自身的 targets 列表要包含该目标,目标适配器的 supportsModule 还要再点头,不通过的模块会被静默归入 Skipped 而不是报错。比如 orchestration 模块只对 claude、claude-project、codex、opencode 开放。还有一条是写死在解析器里的默认行为:以 opencode 为目标且没有显式指定 profile 或模块时,会套用同名 profile 并强制排除 hooks-runtime,同时打印一条提示告诉你用 ./install.sh --target opencode --modules hooks-runtime 显式加回。避法就是每换一个目标重新 dry-run 一次,只看 Selected 和 Skipped 两行。
别装完不记账。 会踩是因为几个月后你根本记不清当时选了哪个 profile、加了哪些 component,出问题时只能靠猜。避法是把 list-installed、doctor、repair 当成常规动作,重装之前先跑一遍;仓库还提供了 ecc consult 这个检索入口,README 里的用法是 npx ecc consult "security reviews" --target claude,它会返回匹配的 component、相关 profile 以及预览和安装命令。
挑最小集合的自检顺序
给自己挑一套的时候,按这个顺序问四句就够了:你的 harness 是哪个,先定 --target;你的主体意图是低上下文还是常规工程,在 minimal 和 core 之间定 profile;你确实需要的能力是哪一两块,用 --with capability: 或 --skills 点名;hook 运行时你现在要不要,不要就走 minimal,要就单独把 hooks-runtime 加回来。四句问完先 --dry-run --json 存一份计划,再真装。
接着该读哪个文件也很明确:从 manifests/install-profiles.json 开始,看你选的 profile 展开成哪些模块;跳到 manifests/install-modules.json 逐个查这些模块的 paths 和 dependencies;想知道加减入口有哪些就翻 manifests/install-components.json;对解析结果有疑问就去 scripts/lib/install-manifests.js 里读 resolveInstallPlan 那段递归,排除、跳过、循环依赖三种情况的处理都在那里;最后再看 docs/SELECTIVE-INSTALL-ARCHITECTURE.md,那份文档里作者对现状的批评比对成果的介绍更有信息量。项目采用 MIT 许可证,这些文件你都可以直接下来对着读。
本文属于 ECC 开源 Agent 套件专题(共 40 篇,从选择性安装一直拆到内部机制)。同一批还写了另一种取向的项目——不给你资产、只给你纪律的 superpowers 方法论专题。