copy-audit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Copy Audit

副本审计

A plugin exists in more places than anyone tracks: the repo that publishes it, the runtime cache, copies vendored into other repos, and per-agent skill stores. They drift silently, and every signal that looks like proof is not one.
Run the audit, read it, and repair what it names. Never repair by guessing.
插件的存在位置远超人们的追踪范围:发布它的仓库、运行时缓存、被 vendored 到其他仓库的副本,以及每个 Agent 的 Skill 存储。它们会悄然偏离,所有看似能证明状态的信号都不可靠。
运行审计工具,查看报告,修复它指出的问题。切勿凭猜测进行修复。

Step 1 - run the audit

步骤1 - 运行审计

Run the checker with the plugin's name. Redirect to a file and check the bare command's exit code; a pipe reports the last command's status, which turns a failed run into an apparent success.
Use the form for the shell you are actually in - the exit-code idioms are not interchangeable, and reading the wrong one makes a refusal that measured nothing look like a clean report.
PowerShell -
$?
is a boolean here (
True
/
False
, never
2
), and redirecting a native command's stderr sets it to
$false
even on exit 0, so read
$LASTEXITCODE
and do not merge the streams:
python ${CLAUDE_PLUGIN_ROOT}/scripts/check_plugin_copies.py --plugin NAME > audit.txt
Write-Output "EXIT=$LASTEXITCODE"
bash / POSIX sh:
python ${CLAUDE_PLUGIN_ROOT}/scripts/check_plugin_copies.py --plugin NAME > audit.txt 2>&1
echo "EXIT=$?"
Exit 2 means it refused to run. That is a result, not a failure: read the blocker it names, clear it, and run again. Exit 1 comes only with
--strict
, and means the run produced findings.
使用插件名称运行检查工具。将输出重定向到文件,并检查裸命令的退出码;管道会报告最后一个命令的状态,这会将失败的运行伪装成看似成功的结果。
请使用你实际使用的Shell对应的格式——退出码的用法不可互换,若读取错误的退出码,会让未执行任何检测的拒绝操作看起来像是一份干净的报告。
PowerShell - 此处
$?
是布尔值(
True
/
False
,绝不会是
2
),重定向原生命令的stderr即使在退出码为0时也会将其设为
$false
,因此请读取
$LASTEXITCODE
,不要合并流:
python ${CLAUDE_PLUGIN_ROOT}/scripts/check_plugin_copies.py --plugin NAME > audit.txt
Write-Output "EXIT=$LASTEXITCODE"
bash / POSIX sh:
python ${CLAUDE_PLUGIN_ROOT}/scripts/check_plugin_copies.py --plugin NAME > audit.txt 2>&1
echo "EXIT=$?"
退出码2表示工具拒绝运行。这是一个结果,而非失败:请阅读它指出的阻碍因素,清除后重新运行。退出码1仅在使用
--strict
参数时出现,表示运行结果发现了问题。

Step 2 - if it refuses, clear the blocker first

步骤2 - 若工具拒绝运行,先清除阻碍因素

The audit refuses when the source is not a trustworthy baseline - uncommitted changes under the plugin, or a branch holding commits the checked-out branch lacks. Grading copies against a stale source reports them current when they merely match an obsolete source, which is worse than no report.
Commit or merge what it names, then re-run. Use
--allow-dirty-source
only when you have already accepted the baseline; the report is then stamped ungraded and must not be quoted as proof of anything.
当源文件并非可信基准时,审计工具会拒绝运行——比如插件目录下存在未提交的变更,或者当前检出的分支缺少其他分支的提交。以过时的源文件为基准评估副本,会将仅匹配过时源文件的副本判定为当前版本,这比没有报告更糟糕。
提交或合并它指出的变更,然后重新运行。仅当你已接受该基准时才使用
--allow-dirty-source
参数;此时报告会标记为“未分级”,不得作为任何事项的证明引用。

Step 3 - read the verdicts

步骤3 - 查看判定结果

There are exactly three:
  • IN SYNC
    - the CR-normalized bytes match. Nothing to do.
  • STALE
    - a real copy of ours (provenance confirmed by content, not just by name), and behind. Repair it.
  • UNRELATED
    - the same name, but a different lineage - provenance was never confirmed. Not ours. Leave it alone and do not report it as drift.
Each row shows the line overlap the call was decided on, so a borderline verdict is visible rather than hidden inside a number. A verdict needs enough lines to mean anything: below ten distinct non-blank lines on the smaller side the copy grades
UNRELATED
whatever the overlap says, because a stub matches
---
and its own
name:
by construction.
What was compared: only
skills/<name>/SKILL.md
. A copy whose
references/*.md
or
scripts/*
drifted still reads
IN SYNC
, and in this repo
references/
is load-bearing. If your question is about a reference file, this audit does not answer it - diff those files directly.
判定结果共有三种:
  • IN SYNC
    - CR标准化后的字节匹配。无需操作。
  • STALE
    - 经内容(而非仅名称)确认是我们的真实副本,但已过时。需要修复。
  • UNRELATED
    - 名称相同,但来源不同——从未确认过出处。不属于我们的副本。无需处理,也不要将其报告为偏离。
每一行都会显示判定所依据的行重叠率,因此边界判定结果是可见的,而非隐藏在数字中。判定需要足够多的行数才有意义:较小文件中不同非空白行少于10行时,无论重叠率如何,副本都会被判定为
UNRELATED
,因为存根文件按构造会匹配
---
及其自身的
name:
字段。
对比内容说明: 仅对比
skills/<name>/SKILL.md
文件。若副本的
references/*.md
或
scripts/*
文件发生偏离,仍会被判定为
IN SYNC
,而在本仓库中
references/
目录是至关重要的。如果你要查询的是引用文件,本审计工具无法回答——请直接对比这些文件。

Step 4 - rows that are NOT graded, and rows that are findings

步骤4 - 未分级的行与发现的问题

A row either carries a verdict or says why it was not graded. Every not-graded row prints its reason, and the summary prints one count per distinct reason - so an excluded copy is visibly excluded, never a missing row. The two reasons today:
  • A cache version directory other than the graded one - only one cache version is graded. That is the version the install manifest claims when its directory exists; when it does not, the highest version directory actually present is graded instead, because that is what loads.
  • A dated backup snapshot under the Claude home's
    backups
    directory. A dated backup is supposed to be behind; grading it would tell you to "edit and commit" a directory that usually is not even a repo.
Two report lines are findings in their own right, and
--strict
exits 1 on them even when nothing is stale:
  • FINDING: the install manifest claims version X at ... but that directory does NOT exist
    - the headline failure this tool exists to expose.
  • FINDING: the install manifest records no entry for <plugin>@<marketplace>
    • nothing claims the plugin is installed at all.
N directories could not be read
is also printed when the scan hit a directory it could not enter. A copy inside one of those is missing from the report, not absent from the machine.
每一行要么带有判定结果,要么说明未分级的原因。所有未分级的行都会打印原因,摘要部分会按不同原因分别统计数量——因此被排除的副本会明确显示为已排除,不会凭空消失。目前有两种未分级的原因:
  • 缓存版本目录(非分级版本) - 仅对一个缓存版本进行分级。即安装清单声明的版本对应的目录(若存在);若该目录不存在,则对实际存在的最高版本目录进行分级,因为这是会被加载的版本。
  • Claude主目录下
    backups
    中的日期备份快照
    - 日期备份本就应该是过时的;对其进行分级会要求你“编辑并提交”一个通常甚至不是仓库的目录。
有两行报告本身就是发现的问题,即使没有过时副本,使用
--strict
参数时也会以退出码1结束:
  • FINDING: the install manifest claims version X at ... but that directory does NOT exist
    - 这是本工具旨在揭露的核心故障。
  • FINDING: the install manifest records no entry for <plugin>@<marketplace>
    - 没有任何记录表明该插件已安装。
当扫描遇到无法进入的目录时,会打印
N directories could not be read
。这些目录内的副本不会出现在报告中,但仍存在于本机上。

Step 5 - repair, per role

步骤5 - 按角色进行修复

Every
STALE
row carries its own repair. The role decides it:
  • cache - never hand-patch it. The runtime maintains this snapshot. Edit the source and let the next session refresh it. A patched cache reports success while the real source stays old, which is the single most expensive mistake in this area.
  • vendored - what to do depends on whether a
    .git
    exists above the copy:
    • if one does, the copy is git-tracked by that project: edit it in that repo and commit there. Copying a file in would leave their tree dirty.
    • if none does, there is no repo behind it to keep in sync with: edit it in place.
  • agent-store - both stores an
    npx skills
    install writes: the central store under the agents home, and the per-agent copy under the Claude home's
    skills
    directory. Reinstall for the agents that read the store. A skills
    update
    short-circuits on the source hash without checking the copy, so an update alone never repairs a drifted copy - and editing the per-agent copy in place leaves the central store drifted and is clobbered by the next install.
  • worktree - another branch's checkout. Merge or rebase that branch; do not edit its files to match.
每一行
STALE
结果都对应相应的修复方式,具体取决于副本的角色:
  • 缓存 - 绝不要手动修补。运行时会维护此快照。编辑源文件,让下一次会话刷新缓存。修补缓存会显示成功,但实际源文件仍为旧版本,这是该领域代价最高的错误之一。
  • vendored副本 - 操作方式取决于副本上方是否存在
    .git
    目录:
    • 若存在,则该副本由对应项目的git追踪:在该仓库中编辑并提交。直接复制文件会导致其工作树变脏。
    • 若不存在,则没有对应的仓库需要保持同步:直接在原地编辑。
  • Agent存储 -
    npx skills
    安装会写入两个存储:Agent主目录下的中央存储,以及Claude主目录下
    skills
    目录中的每个Agent专属副本。为读取该存储的Agent重新安装。Skill的
    update
    操作会根据源哈希直接短路,不会检查副本,因此仅执行更新永远无法修复偏离的副本——而直接编辑Agent专属副本会导致中央存储偏离,并会被下一次安装覆盖。
  • 工作树 - 另一个分支的检出版本。合并或变基该分支;不要编辑其文件以匹配当前版本。

Step 6 - prove it landed

步骤6 - 验证变更已生效

Re-run the audit. Do not accept any of these as proof instead:
  • the install manifest naming the new version - it can name a version, an install path and a commit while the directory was never created
  • "I restarted" - that says nothing about a directory outside the load path
  • two version numbers matching - that proves the numbers match, not the bytes
The only proof is the hash, which is what re-running takes.
重新运行审计工具。切勿将以下内容作为验证依据:
  • 安装清单中命名的新版本——它可以指定版本、安装路径和提交记录,但对应的目录可能从未创建
  • “我重启了”——这无法说明加载路径外的目录状态
  • 两个版本号匹配——这仅能证明版本号一致,而非字节内容一致
唯一的证明是哈希值,而重新运行审计工具就是获取哈希值的方式。

What this skill will not do

本Skill不支持的操作

It does not write to any copy. The correct action differs per role and two of the roles must never be written to at all, so the audit reports and a person repairs.
它不会对任何副本执行写入操作。正确的操作因角色而异,且其中两个角色绝对不能被写入,因此审计工具仅生成报告,由人工进行修复。