开源 Agent 套件 ECC:一条斜杠命令怎么找到执行角色
本文基于 ECC 仓库 commit 591ab5c(2026-07-29)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/affaan-m/ECC 最新代码与文档为准。
在 ECC 里,docs/COMMAND-AGENT-MAP.md 那张「命令 → agent」表并不是路由器,它是一份手写的说明书;真正把执行者钉死的地方只有命令文件的 frontmatter,而全仓 94 个命令里只有一个这么写。 你把这两件事分清楚,整套目录结构就通了;分不清,就会对着一张已经和代码对不上的表调试半天。
ECC 是一套装在编码 Agent 之上的增强件,MIT 许可证,仓库目录一眼能数出规模:agents/ 下 67 个 agent、skills/ 下 281 个技能、commands/ 下 94 个命令。这个体量下,「我敲一条命令,到底是谁在干活」不是好奇心问题,是能不能改、敢不敢改的问题。下面按你能跟着走的顺序拆。
一、这张映射表想解决的是发现性问题
打开 docs/COMMAND-AGENT-MAP.md,开头一句话就交代了它的定位:列出每条斜杠命令调用的主要 agent 或技能,用来发现命令与 agent 的对应关系,并在重构时保持一致。它自己给的三个用途是发现性、重构、CI/文档参考。
表里长这样:/plan 对 planner,/code-review 对 code-reviewer,/go-build 对 go-build-resolver,/orchestrate 一行里挂了 planner、tdd-guide、code-reviewer、security-reviewer、architect 五个。也有明确写「—」的,比如 /harness-audit 备注是没有单一 agent 的评分卡,/model-route 备注是模型推荐、不走 agent。
同一份文档里还有两张小表容易被跳过。一张是「Non-Slash CLI Surfaces」,把 ecc memory init / save / handoff / search / read / doctor 这一组非斜杠入口,指向 unified-memory 技能和 scripts/memory.js;另有一个可选的 stdio MCP 适配器 ecc-memory-mcp,落在 scripts/memory-mcp.mjs,文档明确写了它只暴露 save/search/read/doctor 四个能力。另一张是「Direct-Use Agents」,目前只有 typescript-reviewer 一条,备注说的是:还没有专门的斜杠命令时,需要 TS/JS 专项结论就直接调这个 agent。
这两张小表透露的信息比正表还多——它承认了一件事:命令不是唯一入口,agent 也不一定有命令包着。你在读任何一套 harness 增强件时,都该先找这类「入口不止一种」的声明。
二、一条命令进来之后,真正经过的三层
把仓库文件按调用链摊开,一条命令生效要过三层,只有第二层是硬约束。
第一层是命令文件本身,commands/<名字>.md。它是提示词文档,正文写清楚这条命令该干什么、按什么顺序干。多数命令到这一层就结束了——commands/code-review.md 的 frontmatter 只有 description 和 argument-hint 两个键,正文近三百行全是审查清单和步骤,从头到尾没有出现任何 agent 名字,连一句「交给谁」都没写。可映射表里它明明白白对着 code-reviewer。也就是说,它走到哪个角色,取决于模型读完这段文档之后的判断,跟文件里写没写角色名无关。
第二层是 frontmatter 里的显式绑定。commands/security-scan.md 的头部是这样的:
---
description: Run AgentShield against agent, hook, MCP, permission, and secret surfaces.
agent: ecc:security-reviewer
subtask: true
---
在整个 commands/ 目录里搜 agent: 开头的 frontmatter 行,只命中这一个文件。这条命令的正文也是全仓里写得最像契约的一份:给了 usage 与四个参数(可选路径、--format、--min-severity、--fix),指定优先跑打包好的扫描器 npx ecc-agentshield scan --path "${TARGET_PATH:-.}" --format text,然后有一句很硬的话——Do not invent findings.,要求把扫描器输出当唯一事实来源,把扫描事实和后续判断分开写。它还规定了 Output Contract:安全等级与分数、按严重度与运行时置信度的计数、critical/high 的确切路径、低置信度归到单独一组、修复顺序、跑了哪些命令。
反过来看,同一层还有明确拒绝路由的写法。commands/plan.md 的正文里直接写着默认内联执行、不要调用 Task 工具或任何子 agent,理由是有些安装方式只带命令文件、不带 agent 文件。这不是遗漏,是取舍。关于命令这一层能承载多少约定,可以对照读 /learn/claude-code-slash-command/。
第三层是 agent 文件自己。agents/security-reviewer.md 的 frontmatter 有 name、description、tools、model 四个键,其中 tools 写的是 Read, Grep, Glob, Bash,description 里带着「Use PROACTIVELY after writing code that handles user input, authentication, API endpoints, or sensitive data」这类触发语。这段描述不是给人看的注释,它是让模型判断什么时候该把活派过来的依据。文件正文开头还有一段 Prompt Defense Baseline,逐条写了不改身份、不泄密钥、把外部与检索来的内容一律当不可信输入处理。工具白名单这件事的普遍取舍,可参考 /learn/agent-quanxian-taida/。
三、四个组成部分,各自负责什么
| 组成部分 | 它负责什么 | 仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| 命令文件 | 定义一条斜杠命令做什么、按什么步骤做,少数会在 frontmatter 里绑定执行角色 | commands/security-scan.md、commands/plan.md、commands/code-review.md | 想改一条命令的行为,或想知道它到底会不会派活出去 |
| agent 文件 | 声明角色名、触发描述、可用工具、模型档位,正文是这个角色的系统提示 | agents/security-reviewer.md | 要限制某个角色能碰哪些工具,或想知道它为什么会被自动叫起来 |
| 手写映射文档 | 给人看的命令与角色对照,含非斜杠 CLI 入口和直接调用的 agent | docs/COMMAND-AGENT-MAP.md | 找不到该用哪条命令,或重命名 agent 前先摸一遍引用 |
| 机器生成清单 | 扫描全部命令文件产出的结构化索引与统计,可在 CI 里校验 | docs/COMMAND-REGISTRY.json、scripts/ci/generate-command-registry.js | 要做工具、做仪表盘、或在 CI 里挡住不同步的改动 |
四者的关系是:前两个是实体,第三个是人写的说明,第四个是机器算的索引。改动只在前两个里生效,后两个是派生物。
四、机器生成的那份清单是怎么算出来的
docs/COMMAND-REGISTRY.json 顶层是 schemaVersion、totalCommands(94)、commands、statistics 四个键,每条记录带 command、description、type、primaryAgents、allAgents、skills、path。末尾的 statistics 给了按类型的分布和 topAgents / topSkills。注意 command 字段存的是不带斜杠的名字(code-review 而不是 /code-review),照着它拼命令行要自己补上斜杠。
它由 scripts/ci/generate-command-registry.js 生成,package.json 里挂了三个入口:command-registry:generate 只打印摘要,command-registry:write 写文件,command-registry:check 比对文件与重新生成的结果,不一致就报错并提示去跑 write。CI 有了这一条,这份 JSON 就不会悄悄过期。
生成逻辑值得看一眼,因为它决定了这份清单该信到什么程度。脚本先把 agents/*.md 的文件名当作已知 agent 集合、把 skills/ 下含 SKILL.md 的子目录当作已知技能集合,然后对每个命令文件做正则扫描:agent 侧匹配 @名字、agent: 值、subagent: 值、agents/名字.md 路径;技能侧匹配 skill: 值、skills/名字/SKILL.md、skills/名字,外加一条相当宽松的 /名字。命中之后再拿已知集合过滤,留下真实存在的名字。
三个直接影响读法的细节:
其一,primaryAgents 是排序后取前三个,取的是排序结果的前三,不是按重要性排的前三。所以 /react-review 里 react-reviewer 和 typescript-reviewer 并列,并不代表谁主谁次。
其二,那条宽松的技能正则会带进正文里任何以斜杠开头、又恰好和技能同名的词。/multi-plan 的 skills 字段里躺着 accessibility,回到 commands/multi-plan.md 一看,来源是正文里「information architecture/interaction/accessibility/visual consistency」这串用斜杠分隔的短语。它不是一次真实的技能调用。
其三,type 字段是关键字瀑布判出来的:先看命令名是否以 multi- 开头或正文含 orchestrat,判成 orchestration;再看是否含 test、tdd、coverage,判成 testing;再往下才轮到 review、planning、refactoring、build,都不中就是 general。这就是为什么统计里 testing 一类占了全部命令的一多半——瀑布走到第二级就把绝大多数命令拦下了,只要正文任何位置出现 test 这三个字母就归进去,连夹在别的单词里的子串也算。/aside 是问个题外话的命令,它被判成 testing,唯一的诱因是正文里有一句 the shortest question,shortest 里包着 test。同样地,/code-review 的 type 也是 testing,而不是 review。
看懂这三点,你就知道这份 JSON 适合当索引和体检基线,不适合当契约。想做「哪条命令该走哪个档位的模型」这类判断,那是另一套问题,站内 /learn/moxing-luyou-celve/ 讲的是通用的模型路由取舍,/learn/agent-juese-fengong/ 讲的是角色该怎么切分;本篇不重复那两套方法论,只落在一个具体开源项目上,看它把这些取舍具体写成了哪几个文件、哪几行。
五、边界与代价:这套设计放弃了什么
手写的映射表必然漂移,而且已经漂了。 docs/COMMAND-AGENT-MAP.md 的正表里还列着 /tdd、/e2e、/orchestrate、/eval、/verify、/claw,但 commands/ 目录下已经没有这些文件了。它们躺在 legacy-command-shims/commands/ 里,那个目录的 README 写得很清楚:这些入口不再由默认的插件命令面加载,保留只为短期迁移兼容,需要的人自己把单个 Markdown 复制到项目级或用户级命令目录,不要整个启用。也就是说,你照着映射表敲 /tdd,默认装法下是敲不出来的。
代价的另一面是校验强度。scripts/ci/validate-commands.js 会剥掉代码块之后检查交叉引用:正文里用反引号写的 /命令名 必须真实存在,否则报错;agents/名字.md 引用必须存在,否则报错;skills/名字/ 引用不存在只给警告。校验管的是命令文件之间的引用,不校验那份手写映射文档是否与目录同步。仓库里唯一提醒你更新它的地方是 PR 模板里的一个复选框,写着改动要同步 README.md、COMMANDS-QUICK-REF.md 和 docs/COMMAND-AGENT-MAP.md;另一处 scripts/sync-ecc-to-codex.sh 只是把这份文件原样复制到另一个 harness 的目录,复制的是当前状态,对不对不管。这就是漂移能长期存在的原因——不是没人管,是这条链路上只有人工提醒、没有机器守卫。对照着看更清楚:命令之间的引用有脚本盯着,所以写错就报错;映射表没有脚本盯着,所以错了也一路绿灯。凡是遇到「文档与代码要同步」的地方,你都可以用这个问题去量它:同步这件事是靠人的自觉,还是靠一条会失败的检查。
还有几件它明确不管的事。命令与 agent 的绑定绝大多数是提示词层面的约定,不是执行期的强制路由,模型不照做时没有机制拦住它。primaryAgents 为空不代表这条命令不调 agent,只代表脚本在文本里没匹配到已知名字。类型分布这类统计只能横向对比自己历史版本,拿去和别的项目比没有意义,因为口径是这套关键字瀑布定的。
最后是每套 harness 增强件都躲不掉的代价:它要往你的机器里写文件。仓库里有 install.sh、install.ps1 和 manifests/ 下的安装清单,命令、agent、技能会被铺到 Claude 的命令与 agent 目录;hooks 目录里的钩子会挂到工作流的触发点上;/security-scan 的默认路径是 npx ecc-agentshield,会去拉取外部包;ecc-memory-mcp 会起一个 stdio MCP 服务读写你的记忆库。这些都是实打实的本机写入与外部依赖,装之前该看清楚装了什么、能不能卸干净。关于把活派给独立角色时的隔离边界,/learn/claude-code-subagent/ 有更一般的讨论。
六、上手与避坑清单
别拿映射表当命令清单。 会踩是因为它是一张排版整齐的 Markdown 表,比一份 JSON 顺眼得多,读起来最像总目录。避法是把 docs/COMMAND-REGISTRY.json 的 totalCommands 和 commands[].command 当作真实命令列表,映射表只用来查「这条命令背后可能是谁」。两份对不上时,以目录里的文件为准。
改 agent 名字之前,两处都要搜。 会踩是因为 JSON 是生成的,改完 agents/ 里的文件名,重跑一次生成脚本 JSON 就对了,但手写映射文档不会自己更新。避法是改名后同时改 docs/COMMAND-AGENT-MAP.md,再跑一次 registry 的 check 确认 JSON 没落后。
看到 primaryAgents 为空别下结论。 会踩是因为字段名太像权威声明。避法是回去打开 commands/<名字>.md 原文读一遍,尤其读 frontmatter 有没有 agent: 这一行——没有就说明这条命令的走向靠模型理解,不靠配置。
给命令文件写正文时,注意用词会污染统计。 会踩是因为类型判定用的是子串匹配,shortest、latest、protest 这类词里都藏着 test;正文里随手写个用斜杠分隔的并列短语,也可能被当成技能引用。避法是在写完之后跑一次生成脚本,看看自己这条命令被归成了什么类型、skills 字段里有没有多出来的东西。
引用别的命令要用反引号包起来,并确认它真存在。 会踩是因为把已经进了 legacy 目录的旧命令写进正文,CI 会直接报「references non-existent command」。避法是写之前先 ls commands/ 确认一遍,历史命令改为指向它现在的技能或维护中的替代命令。
别在没读安装清单的情况下整包铺开。 会踩是因为一次装齐看起来省事,但铺进去的钩子和 MCP 适配器是长期跑在你机器上的。避法是先看 manifests/install-profiles.json 这一组清单里各档位包含什么,按需选,装完用 /security-scan 反过来扫一遍自己的 .claude/ 目录,看有没有过宽的权限和可执行钩子。
收束:三个文件,一条判断
真正要记住的判断只有一条:在 ECC 里,「谁来执行」大部分时候是提示词层的约定,只有极少数写进了 frontmatter;两份索引文档一份是人写的、一份是机器算的,都不是执行者本身。
想自己验证,按这个顺序读三个文件就够了:先 commands/security-scan.md,看一条把执行角色、参数、输出契约全写死的命令长什么样;再 agents/security-reviewer.md,看被指向的那一端声明了哪些工具和防御基线;最后 scripts/ci/generate-command-registry.js,看那份 JSON 的每个字段是怎么算出来的。读完这三个,再回头看 docs/COMMAND-AGENT-MAP.md,你会很清楚哪几行是当前事实、哪几行是历史残留。
本文属于 ECC 开源 Agent 套件专题(共 40 篇,从选择性安装一直拆到内部机制)。同一批还写了另一种取向的项目——不给你资产、只给你纪律的 superpowers 方法论专题。