副本一致性:同一份 SKILL.md 在仓库里有几份

2026-08-10

在 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.md70
*/agent-harness/cli_anything/*/skills/SKILL.md67
unimol_tools/agent-harness/cli_anything/unimol_tools/SKILL.md1
*/agent-harness/skills/cli-anything-*/SKILL.md2
其余(cli-hub-matrix/ 下 5 份,加 cli-hub-meta-skillcodex-skillhermes-skillreasonix-skillmacrocli 各 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.mdcli-anything-3mfskills/cli-anything-threemf/
obs-studio/.../obs_studio/skills/SKILL.mdcli-anything-obs_studioskills/cli-anything-obs-studio/
slay_the_spire_ii/.../slay_the_spire_ii/skills/SKILL.mdcli-anything-sts2skills/cli-anything-slay-the-spire-ii/
musescore/.../musescore/skills/SKILL.mdmusescore,缺 cli-anything- 前缀skills/cli-anything-musescore/
quietshrink/.../quietshrink/skills/SKILL.mdquietshrink,缺 cli-anything- 前缀skills/cli-anything-quietshrink/

三、同一份内容在 skills/ 里存在两份。 skills/cli-anything-iterm2/SKILL.mdskills/cli-anything-iterm2-ctl/SKILL.md 都是 100 行,diff 只输出第 2 行的 name,正文完全相同。两个名字来自两处口径:运行时用别名把 iterm2_ctl 映射回 iterm2,banner 里展示的 skill id 是 cli-anything-iterm2cli-anything-plugin/repl_skin.py:130-132);而同步脚本按包名规范化,生成的是 cli-anything-iterm2-ctlsync_root_skills.py:13-23)。我们用这两个脚本的函数在本地文件树上模拟了一遍:68 个源生成的 68 个 id 全在 skills/ 里存在,反向多出来两个不由同步产生的目录——cli-anything-iterm2cli-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.jsonskill_mdnull 的一共 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.jsondocs/ 与各 agent-harness/ 下的源码整理,核对日 2026-08-10,对应仓库快照 39634a6。 本文内容为仓库源码与文档口径,我们没有安装或运行过其中任何一个 harness, 也没有在本机驱动过任何一款宿主软件,因此不涉及实际操控效果的任何描述。 这类 harness 会在本机执行外部程序与脚本,是否使用请结合自身环境评估。 注册表与 harness 内容随上游更新而变动,请以仓库最新内容为准。 安全相关做法请结合自身环境评估,本文不构成安全方案建议。

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