副本一致性:同一份 SKILL.md 在仓库里有几份
在 CLI-Anything 里改一份 SKILL.md 之前,得先搞清楚你改的是不是「那一份」。这个仓库的 SKILL.md 不是一个软件一份,而是同一份内容按规则落到两个位置,再由一个同步脚本把深处那份刷回根目录。副本一多,就会出现两种典型的误判:一种是你改了一份,下次同步被覆盖掉;另一种是你拿 md5 一比,发现一大半对不上,以为副本全漂了。
我们采集时(2026-08-10,对应仓库快照 39634a6)把这件事从头数了一遍。结论有点反直觉:skills/ 与包内副本之间「字节不同」的那一大批,语义上一个字都没差;而真正落后了一个大版本的副本,恰恰不在被比对的那批里。
先数清楚:150 份 SKILL.md 分布在哪
统计口径是 find . -name 'SKILL.md' -not -path './.git/*' | wc -l,我们采集时得到 150。而带 agent-harness/ 的顶层目录只有 69 个。150 份的落点分成五类:
| 位置形态 | 份数 |
|---|---|
skills/*/SKILL.md | 70 |
*/agent-harness/cli_anything/*/skills/SKILL.md | 67 |
unimol_tools/agent-harness/cli_anything/unimol_tools/SKILL.md | 1 |
*/agent-harness/skills/cli-anything-*/SKILL.md | 2 |
其余(cli-hub-matrix/ 下 5 份,加 cli-hub-meta-skill、codex-skill、hermes-skill、reasonix-skill、macrocli 各 1 份) | 10 |
这张表要这么读:第一行是仓库根的规范副本(canonical),第二行是包内的兼容副本(compatibility copy),第三、第四行是两种不符合常规形态的落点,第五行则不是某个软件的 harness skill。
skills/ 下 70 个子目录里,69 个以 cli-anything- 开头,多出来的那一个是 skills/cli-hub-meta-skill/——它是目录发现型的 meta skill,不对应任何一个软件。所以 70 减 69 的那个 1,不是某个 harness 多写了一份。
两份副本是怎么来的
第一条链路是生成。生成器在 cli-anything-plugin/skill_generator.py(586 行),它一次写两份:规范副本落到 <repo_root>/skills/<skill_name>/SKILL.md,兼容副本落到 <agent-harness>/cli_anything/<software>/skills/SKILL.md,两份内容一致(skill_generator.py:535-552)。
第二条链路是反向同步。.github/scripts/sync_root_skills.py(93 行)的文档字符串写的是 Sync repo-root skills/ from harness-local SKILL.md files.,也就是以包内那份为源、把仓库根那份刷成镜像。它的源发现只有两条 glob:*/agent-harness/cli_anything/*/skills/SKILL.md 与 */agent-harness/cli_anything/*/SKILL.md(:60-61)。我们用 Python 读取该脚本、在本地文件树上调用它的 _discover_sources(),得到 68 个源(这不是 CI 实跑结果,我们没有运行过仓库里的任何脚本与测试)。
关键在写入那一步:脚本会把 frontmatter 里第一个 name:(含其后的缩进续行)整体改写成 name: "<skill_id>"(:26-55)。而模板产出的写法是折叠标量 name: >- 换行加两格缩进的值(cli-anything-plugin/templates/SKILL.md.template:2-3)。两条链路对同一个字段用了两种 YAML 写法——这就是下一节那个数字的来源。
反直觉的那一处:43 对「不同」,正文差异是 0
我们把 68 个包内源与其仓库根对应文件逐对做了 md5 比较:完全相同 20 对,内容不同 43 对,另有 5 个的包内 name 值与根 skill id 对不上(这 5 个下节单说)。
43/68 对不上,看数字像是副本管理失控了。但把这 43 对拆开看就完全是另一回事:分别比较 yaml.safe_load 解析后的 frontmatter 与正文字节,正文差异 0 个、frontmatter 解析值差异 0 个。43 对全部只是 YAML 序列化风格的差异——包内是模板产出的 name: >- 折叠写法,根目录侧是同步脚本改写的 name: "cli-anything-x"。
最直观的例子是 gimp:
diff skills/cli-anything-gimp/SKILL.md gimp/agent-harness/cli_anything/gimp/skills/SKILL.md
我们采集时这条命令只输出第 2 行的那一处差异。
这件事对使用者的意义很实际:在这个仓库里用 md5 或 cmp -s 当「副本漂移」的告警,会稳定产生 43 次噪声。要判断两份副本是不是真的分叉了,得比解析后的值和正文,而不是比字节。反过来,如果你自己维护类似的双副本结构,把生成端与同步端的序列化写法统一掉,这 43 个假阳性本来是不必存在的。
顺带记一句文档口径:skills/README.md:22-24 把包内那份称作 compatibility copy,而实际 43/68 对与仓库根不字节相同(尽管只差 name 的写法)。文档写的是一种,仓库里的状态是另一种,这是两个可以各自核对的事实,我们只陈述到这里。
真正有内容的差异,在被比对的范围之外
md5 那一轮比的是 68 个「在 glob 范围内」的源。范围之外还有几处,差异是实打实的。
一、两份孤儿副本落在 agent-harness/skills/ 下。 同步脚本的两条 glob 只扫 cli_anything/ 路径,这两份不在其中:
firefly-iii/agent-harness/skills/cli-anything-firefly-iii/SKILL.md是 281 行、version: "1.0.0";而skills/cli-anything-firefly-iii/SKILL.md是 709 行、version: "2.0.0",且与深层的包内副本字节相同。也就是说这份孤儿落后了一个大版本号。openrefine/agent-harness/skills/cli-anything-openrefine/SKILL.md只有 10 行,正文自称This compatibility copy mirrors skills/cli-anything-openrefine/SKILL.md at the standalone output root.(:9),而仓库根与深层包内副本都是 72 行。
CI 这边,.github/workflows/check-root-skills.yml(35 行)的触发路径是 */agent-harness/**、skills/** 与两个脚本本身,唯一步骤是跑 python3 .github/scripts/validate_root_skills.py(这个校验脚本的源发现口径与同步脚本是不是同一套,我们没有核实)。同步脚本那两条 glob 的范围与这两份文件的位置对不上,这是可以各自核对的两个事实,我们不推断更多。
二、5 个包内副本的 name 与仓库根的 skill id 对不上。 同步脚本会在镜像里改写 name,源文件仍是原值,因此这几处只在包内那侧可见:
包内文件(第 2 行的 name 值) | 仓库根目录 |
|---|---|
3MF/.../threemf/skills/SKILL.md 写 cli-anything-3mf | skills/cli-anything-threemf/ |
obs-studio/.../obs_studio/skills/SKILL.md 写 cli-anything-obs_studio | skills/cli-anything-obs-studio/ |
slay_the_spire_ii/.../slay_the_spire_ii/skills/SKILL.md 写 cli-anything-sts2 | skills/cli-anything-slay-the-spire-ii/ |
musescore/.../musescore/skills/SKILL.md 写 musescore,缺 cli-anything- 前缀 | skills/cli-anything-musescore/ |
quietshrink/.../quietshrink/skills/SKILL.md 写 quietshrink,缺 cli-anything- 前缀 | skills/cli-anything-quietshrink/ |
三、同一份内容在 skills/ 里存在两份。 skills/cli-anything-iterm2/SKILL.md 与 skills/cli-anything-iterm2-ctl/SKILL.md 都是 100 行,diff 只输出第 2 行的 name,正文完全相同。两个名字来自两处口径:运行时用别名把 iterm2_ctl 映射回 iterm2,banner 里展示的 skill id 是 cli-anything-iterm2(cli-anything-plugin/repl_skin.py:130-132);而同步脚本按包名规范化,生成的是 cli-anything-iterm2-ctl(sync_root_skills.py:13-23)。我们用这两个脚本的函数在本地文件树上模拟了一遍:68 个源生成的 68 个 id 全在 skills/ 里存在,反向多出来两个不由同步产生的目录——cli-anything-iterm2 与 cli-hub-meta-skill。
四、一份路径少了一层。 unimol_tools 的包内副本在 unimol_tools/agent-harness/cli_anything/unimol_tools/SKILL.md,比其余 67 份少一层 skills/,靠同步脚本的第二条 glob 兜住。
五、有 harness 一份都没有。 sketch 目录下 find sketch -iname '*skill*' 无输出,registry.json 里 sketch 的 skill_md 也是 null。注册表里有条目,不等于这个 harness 的配件是齐的——registry.json 里 skill_md 为 null 的一共 5 条(adguardhome、comfyui、mermaid、sketch、clibrowser),其中前三个在 skills/ 下其实有目录 —— 注册表字段与目录状态对不上,这是两个可以各自核对的事实。
校验脚本怎么判定,以及该改哪一份
.github/scripts/validate_root_skills.py(114 行)的文档字符串是 Validate that deep harness SKILL.md files are mirrored in repo-root skills/.,它把问题分成两类(:44-71):
- clobber:镜像里有源里没有的行。含义是重跑同步会把这些行删掉,必须回源修改。报错文案原文包含
You edited a GENERATED file.(:81),最多列出 10 行差异,多的以… and N more line(s)折叠(:91-94)。 - stale:源领先于镜像,重跑一次同步即可修复。
同步脚本自己也在注释里写明覆盖是破坏性的、「the mirror may hold edits the source lacks」,并对每次覆盖打印 overwrite: 行以免静默丢失(sync_root_skills.py:80-85)。
按这套语义,改动的落点是清楚的:改包内那份源,别改仓库根的镜像。至于那两份不在 glob 里的孤儿副本、以及 sketch 这种一份都没有的情况,脚本给不出提示,得靠人自己记住位置。
你可以自己跑一遍的核对动作
下面这些都是从文件系统数出来的,clone 下来就能复现:
find . -name 'SKILL.md' -not -path './.git/*' | wc -l
find skills -name 'SKILL.md' | wc -l
find . -maxdepth 2 -mindepth 2 -type d -name 'agent-harness' | wc -l
diff skills/cli-anything-iterm2/SKILL.md skills/cli-anything-iterm2-ctl/SKILL.md
我们采集时这四条依次得到 150、70、69,以及只有第 2 行的一处差异。至于那两份孤儿副本,第一节的分类里已经给了形态 */agent-harness/skills/cli-anything-*/SKILL.md,全仓共 2 份,就是 firefly-iii 与 openrefine 这两处。
要判断某一对副本是不是真的分叉,别停在 md5sum 上:先看 diff 输出的行数,如果只有 name 那一行,那就是序列化写法差异;如果正文也有差异,或者版本号对不上(像 firefly-iii 那对 281 行对 709 行),才是需要处理的情况。
最后两句提醒与本篇的技术内容同样重要:这些 harness 是要在本机调起真实软件的,它们会执行外部程序与脚本,而且包装桌面软件的那一类需要你自行安装上游应用(README.md:236)。注册表里有 79 条,不代表这 79 个装上就能用;SKILL.md 齐不齐只是齐件度的其中一项,我们没有安装或运行过其中任何一个 harness,给不出可用性结论。
本文依据 CLI-Anything 官方仓库(github.com/HKUDS/CLI-Anything)的 README、registry.json、
docs/ 与各 agent-harness/ 下的源码整理,核对日 2026-08-10,对应仓库快照 39634a6。
本文内容为仓库源码与文档口径,我们没有安装或运行过其中任何一个 harness,
也没有在本机驱动过任何一款宿主软件,因此不涉及实际操控效果的任何描述。
这类 harness 会在本机执行外部程序与脚本,是否使用请结合自身环境评估。
注册表与 harness 内容随上游更新而变动,请以仓库最新内容为准。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。