Old Coder:用证据报告审查代码,而非差异
代理先提交您批准的 SPEC,再生成可复现的 EVIDENCE 报告。了解固定 gauntlet、确切命令和数值,以及变异测试相比覆盖率新增了什么。
Old Coder 技能实际改变的内容
Old Coder 技能用文档审查替代代码审查。您的编码代理会先编写 SPEC,再编写任何代码。您批准该文档后,它才会开始实现。随后,它会通过一组固定的自动化检查运行自己的工作,这组检查称为 gauntlet,并向您提交一份 EVIDENCE 报告,其中包含确切的命令和实际数值。您只需阅读两份文档,不必查看差异内容。
这种取舍只有在这两份文档能够承载过去由差异内容承载的信任时才成立。SPEC 能够承载这种信任,是因为您在任何代码存在之前就批准了它,因此它不可能围绕代理已经编写的代码进行调整。EVIDENCE 报告能够承载这种信任,是因为其中的每个数值都来自您可以自行运行的命令,并且可以观察该命令生成相同的数值。如果其中任何一部分失去严谨性,您得到的就只是审查摘要,而不是审查本身。这比查看差异内容更糟,因为它会让人误以为工作已经完成。
该技能使用普通 Markdown 编写,因此适用于任何遵循书面指令的代理:Claude Code、Codex CLI、Cursor,或您自己的循环流程。它与Ponytail 懒惰资深开发者角色技能属于同一类。如果您不熟悉这种文件格式,什么是代理技能,以及代理如何加载技能介绍了相关机制。
SPEC 是你仍需作出的唯一决策
SPEC 是在代码存在之前编写的测试计划。技能文件要求其中包含 4 项内容。
- 具体场景:输入、预期输出、边界情况和错误情况。
divide(1, 0) raises ZeroDivisionError with message X,不能只写“能够处理错误输入”。 - 负向约束:明确哪些内容不得改变,例如现有测试和公共 API 签名。
- 设置计划:列出每个工具和每个新依赖,并用一行说明各自的用途。
- 绝对文件路径:这样无需查找即可打开文件。
设置计划最能暴露模型的知识截止时间,因为凭记忆提供的工具或固定版本可能已经过时一年。因此,最好为代理提供 自托管的 SearXNG 实例进行网页搜索,让它在提出版本建议前检查当前版本。
批准后提交。批准后仍可编辑的 SPEC 不能算作契约;提交记录可以让你之后检查所测量的证据是否针对你签署的内容。
这是留给你的唯一一个“是”或“否”决策,这既是重点,也是风险所在。应将其视为一次生产环境变更,因为它本身就是生产环境变更。如果你已经在代理操作前设置了 明确的审批门禁,那么 SPEC 审批可以直接纳入同一工作流。
EVIDENCE 报告:为数字附上执行命令
EVIDENCE 报告是你最后阅读的内容。这项技能要求将每个规范行为映射到用于验证它的测试,将每个测试层级与实际执行的命令和结果一并报告,所有数字都来自最后一次代码修改后的全新运行,并列出每个跳过的层级及其原因。形容词不是证据。“全部 41 个测试通过,覆盖 49/49 条语句”是结果。“测试充分”不是。
仓库中的示例报告 demo-rate-limiter/evidence.md 将其源代码状态标识为一个提交以及一个 sha256 树哈希。该行的重要性超出表面,因为它说明了生成这些数字的确切字节内容。没有这一行,报告可能会悄悄描述一个已经不存在的工作树。
三条反作弊规则可确保报告真实,技能文件将它们规定为绝对要求。不得通过削弱测试来使其通过:不得放宽断言,不得提高容差。不得在同一步中同时修改测试和实现来达到绿色,因为同时修改会掩盖究竟是哪一方存在错误。不得报告未实际运行的层级:“已跳过,无可用工具,改用手动变更”可以保持可信度,而捏造结果会破坏整个方案。
安装 skill,并固定已安装的提交
该仓库是 AmazingAng/old-coder,采用 MIT 许可证。其 README 通过 skills CLI 提供了一行安装命令:
npx skills add https://github.com/amazingang/old-coder该命令会安装运行时 main 所指向的内容,而 CLI 本身也在持续变化。确认文件的实际位置后,再判断 skill 是否已生效:
ls ~/.claude/skills/old-coder/您应看到 SKILL.md 和一个 references/ 目录。如果那里没有这些内容,说明 skill 不在 Claude Code 查找的位置。skills CLI 的旧版本会将文件写入 ~/.agents/skills/,但不会将结果链接到 ~/.claude/skills/。因此文件虽然存在于磁盘上,agent 却不会加载它。运行 npx skills@latest add ... 可以避免使用过时 CLI 导致的问题。
建议采用手动安装方式,因为这样可以记录实际安装的内容:
git clone https://github.com/AmazingAng/old-coder.git
cd old-coder
git checkout acc5a89
git rev-parse HEAD
mkdir -p ~/.claude/skills
cp -r skills/old-coder ~/.claude/skills/acc5a89 在 17 August 2026 是 main 的最新提交。请选择您自己的提交,并记录下来。该仓库仍在积极开发中,gauntlet 参考内容、模板和验证器协议已经在不同文件之间发生过移动。如果您的 EVIDENCE 报告没有说明评测所用的 skill 版本,就无法区分代码变更和规则变更。将该提交哈希存放在 SPEC 旁边,并与其约束的代码放在同一个仓库中。
对于不读取 ~/.claude/skills 的 agent,请将 skills/old-coder/SKILL.md 和 skills/old-coder/references/gauntlet.md 添加到其系统提示词或规则文件中。集成只需这些步骤。
铁人三项中运行的检查
铁人三项是一组分层检查。每次规格行为全部通过后运行一次。该技能包括以下检查:
- 完整测试套件,用于检测回归。不能出现新的失败;任何预先存在的失败都必须先记录为基线。
- 静态类型检查,以及 lint 和格式检查,用于发现整类错误和代码偏移。
- 变更行覆盖率。未达到阈值时必须以非零状态退出。
- 变异测试,用于发现不会验证任何内容的测试。
- 基于属性的测试,用于覆盖没人预想到的边界情况。
- 复杂度预算、实际程序的一次真实执行、供应链和密钥扫描,以及按随机顺序运行的测试套件健康检查。
- 根据任务风险选择的领域检查:并发压力测试、API 兼容性、回滚演练和延迟基准测试。
示例将这些检查接入一个脚本 demo-rate-limiter/tools/gauntlet.sh,并使用 requirements-dev.txt 中锁定的工具链:pytest、pytest-cov、coverage、hypothesis、mypy、ruff、pip-audit 和 pytest-randomly。每个工具都锁定到确切版本(截至 2026 年 8 月,pytest 9.1.1、ruff 0.16.0)。运行脚本:
cd demo-rate-limiter
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt -e .
./tools/gauntlet.sh脚本会为每个分层检查输出一个横幅,例如 === tests + coverage === 和 === mutation ===,最后输出 === gauntlet: all layers green ===。脚本在 set -e 下运行,因此第一个失败的检查会停止脚本,最后的横幅不会输出。看到最后的横幅即可完成验证:这表示上面的每个检查都以 0 状态退出。
脚本中有一个细节值得复制到您自己的实现中。覆盖率检查层的写法如下:
pytest -q --cov=ratelimiter --cov-report=term-missing --cov-fail-under=100如果没有 --cov-fail-under,pytest --cov 只会输出百分比,无论覆盖率下降到多低都会以 0 状态退出。这会导致该检查层在脚本中“默认通过”,而脚本第一行承诺在第一个失败检查处停止。无法失败的铁人三项检查层只是装饰。
覆盖率之外,变异测试增加了什么
覆盖率回答的是一个问题:测试套件是否执行了这一行。它无法回答你真正关心的问题:如果这一行有错误,是否会有断言失败。调用函数但不检查任何结果的测试,仍会报告它触及的每一行都达到 100% 覆盖率。覆盖率可以发现未测试的代码,但无法发现没有验证任何内容的测试。
变异测试直接回答第二个问题。它会有意修改代码,每次只改动一处,然后重新运行测试套件。如果测试套件失败,说明该变异体被杀死,也就是有断言正在验证该行为。如果测试套件仍然通过,说明该变异体存活:代码行确实运行了,但没有任何测试检查结果。
演示使用 tools/mutants.py 完成这一过程。它会将带编号的单处修改写入 src/ratelimiter/__init__.py,每次修改后运行 pytest,然后恢复文件。这里的修改模拟了人因疲劳可能犯的错误:>= 变成 >,或者丢弃返回值。
该运行器中的杀死规则,是许多自制变异测试脚本最容易出错的地方。只有 pytest 退出码为 1 时才算杀死,因为 1 表示测试已运行且至少有一项失败。退出码为 0 表示变异体存活。其他任何退出码,例如测试收集错误或根本没有收集到测试,都表示没有完成验证,不能计入结果。将“非零”都视为杀死的脚本,会把自身崩溃计为成功,而这个数字只会不断上升。
同一文件中还有两个需要如实说明的细节。变异体 M11 没有列出,因为它是等价变异体:在单调时钟下,只删除一个过期条目和删除所有过期条目会产生相同的可观察行为,因此任何测试都不可能杀死它。运行器会设置 PYTHONDONTWRITEBYTECODE=1,而测试流程会先删除所有 __pycache__,因为两个大小相同且在同一秒写入的变异体可能共享缓存的 .pyc,这样第二个变异体就会继承第一个变异体的判定结果。
正因为存在这一风险,测试流程会在真正的变异测试之前运行负向对照:
.venv/bin/python tools/mutants.py --negative-control
.venv/bin/python tools/mutants.py该对照会在固定修改时间下运行两个变异体:一个必须被杀死,另一个严格等价且必须存活。如果两个变异体都被判定为杀死,说明字节码缓存泄漏到了不同运行之间,报告中的所有杀死计数都会被高估。这正是本技能关于检查器的规则的具体体现。pytest 和 mypy 经过多年使用,已经验证了它们的失败行为。你上周编写的脚本还没有经过这种验证。因此,在脚本通过时信任它之前,先证明它能够失败,并将这一证明记录到 EVIDENCE 中。
The data behind this chart
[
{
"label": "Scenario tests",
"mutants_killed": 22,
"mutants_run": 22
},
{
"label": "Property tests",
"mutants_killed": 3,
"mutants_run": 22
}
]演示自身的 EVIDENCE 报告说明了为什么单个汇总数字仍会隐藏细节。场景测试套件杀死了 22 个变异体,共运行 22 个。针对相同变异体单独重新运行的基于属性的测试,杀死了 3 个。某个变异体被哪个测试首先触发失败,就归因于哪个测试,因此完美的总结果只能验证整个测试套件,无法说明其中任何一个测试层的情况。这些属性测试仍然有存在的价值,因为它们可以捕获没有被逐一枚举的输入形状。但它们并没有承担正确性验证的主要负担,而只有分别测量每一层,才能发现这一点。
在服务器上完整运行检查,不要在笔记本电脑上运行
EVIDENCE 报告声明某些命令产生了某些数值。只有其他人能够产生相同数值时,这项声明才可验证,而笔记本电脑是验证它的最差环境。你的 Python 是不同的补丁版本,PATH 携带了下一台机器上不会有的工具。变异测试会让情况更糟,因为它会针对每个变异体重新运行整个测试套件,因此演示中的列表本身就意味着额外运行 22 次测试套件。
将其放入 VPS 上的容器中。容器固定操作系统和解释器,固定版本的 requirements-dev.txt 固定工具,VPS 则提供一台不会同时运行浏览器的机器。
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-venv git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workdocker build -t gauntlet:24.04 .
docker run --rm -v "$PWD:/work" -w /work/demo-rate-limiter gauntlet:24.04 \
sh -c 'python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt -e . && ./tools/gauntlet.sh'成功运行会在 === gauntlet: all layers green === 结束。有两个步骤需要出站网络:使用 pip 安装固定版本的工具,以及 pip-audit 层;该层会通过漏洞服务检查依赖项。离线运行不会静默跳过该层,而是直接失败。这正是门禁应有的行为。
如果要在每次推送时运行同样的检查,同一个脚本就可以成为一个 CI 作业步骤。该仓库会在 GitHub Actions 上使用 Python 3.12,在 ubuntu-latest 运行自己的完整检查。将 runs-on 指向 self-hosted runner,作业就会改为在你的 VPS 上执行:
name: gauntlet
on:
push:
branches: [main]
jobs:
gauntlet:
runs-on: self-hosted
defaults:
run:
working-directory: demo-rate-limiter
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python -m venv .venv && .venv/bin/pip install -r requirements-dev.txt -e .
- run: ./tools/gauntlet.sh将触发条件限制为推送到你控制的分支。也不要让同时构建 fork 仓库拉取请求的 self-hosted runner 在你的服务器上使用 runner 的凭据运行陌生人的代码。同样的原则也适用于代理本身:为它提供可以直接销毁的一次性 VM,不要使用你的工作站。如果希望将完整检查结果发送到讨论变更的位置,请将其接入self-hosted PR 审查代理。
这种方法失效的情况
第一个问题在于结构本身,任何工具都无法消除它。gauntlet 会将 SPEC 中的约束转换为可执行的证据,但无法判断 SPEC 是否正确。如果批准了包含错误需求的 SPEC,得到的 EVIDENCE 报告仍会完美地描述错误的程序:覆盖率完整、所有 mutant 都被杀死、每一层检查都通过,但软件执行的功能并不是你需要的功能。通过不阅读差异而节省的每一小时,都应花在阅读 SPEC 上。
第二个问题是检查器。该仓库在自己的证据文件中如实记录了这一点。其独立验证协议运行了 6 轮,第 6 轮返回 failed;此后进行的修复从未重新验证,因此最终发布的状态没有经过端到端验证。Shell lint 层被记录为不可用,而不是通过。更早的轮次发现了真实的行为缺陷,以及一个不可靠的 mutation runner;这些问题当时隐藏在已经报告为通过的状态之后。gauntlet 显示为通过,并不代表它已经完成自我验证。
第三个问题是范围。手写的 mutant 列表只能覆盖有人想到要植入的错误,而从列表中删除的每个等价 mutant,都是你必须信任的一项判断。现成的 mutation 工具(mutmut、cosmic-ray、Stryker、PIT)会系统地生成 mutant。只要你的语言提供这类工具,默认应优先使用它们。
根据风险校准工作量
该技能定义了 3 个等级,并要求代理声明所选等级。
- Tier 1,简单任务:拼写错误、注释或配置值。运行完整测试套件并执行 lint,不新增测试,同时用一句话说明无需新增测试的原因。
- Tier 2,常规任务:错误修复或小型功能。执行完整流程。错误修复必须从能够复现错误的失败测试开始,这样今天的错误就会成为明天的回归测试。
- Tier 3,高风险任务:资金、身份验证、数据丢失、并发或公共 API。先建立失效模型,列出此更改可能造成损害的方式;为每种模式增加一层强化测试,然后执行完整流程,并运行属性测试、变异测试,以及一次使用恶意输入攻击实现的明确检查。
Tier 3 还包含一个实验步骤。让第二个代理在全新上下文中,仅查看任务契约、已批准的 SPEC 和源代码状态,然后在签署 EVIDENCE 之前尝试破坏已完成的工作。该代理不修改任何内容,只报告发现的问题,由人工评估结果。这样可以降低因共享任务上下文而产生的相关性,但无法降低因共享同一模型而产生的相关性。
如果您更希望构建符合这种形式的技能,而不是直接采用此技能,请参阅编写您自己的代理技能,其中介绍了文件布局以及决定代理何时加载该技能的 description 字段。
FAQ
变异测试能发现哪些代码覆盖率无法发现的问题?
覆盖率只能记录某一行是否执行过。它无法记录该行出错时是否有断言会失败。因此,测试即使调用了某个函数但不检查任何结果,仍可能报告完整覆盖率。变异测试会有意修改代码,每次只修改一处,然后重新运行测试套件。存活的变异体表示该行确实执行了,但没有任何测试验证结果。这就是为什么该技能将“永远不要追逐覆盖率数字”列为绝对规则,并指出变异测试是发现覆盖率造假的那一层。
我还需要阅读代理编写的代码吗?
按照此工作流,您应在编码前阅读 SPEC,在编码后阅读 EVIDENCE 报告,并通过重新运行报告中引用的命令进行抽查。查看差异变为可选步骤。问题在于,您的所有判断现在都集中在一份文档上,因为如果 SPEC 中的需求本身有误,它就会为您不想要的程序生成一套全绿的测试关卡。请将节省下来的时间用于检查 SPEC。
我可以在自己的服务器上的 CI 中运行 Old Coder 测试关卡吗?
可以,而且这正是更合适的运行位置。在容器内安装 requirements-dev.txt 中固定版本的工具链,并将项目的测试关卡脚本作为一个作业步骤运行。在 GitHub Actions 中设置 runs-on: self-hosted,并在您的 VPS 上注册一个 runner。仅将触发条件设置为您控制的分支上的 push,因为自行托管的 runner 如果构建来自 fork 的 pull request,就会使用 runner 的凭据执行不受信任的代码。
我应该安装哪个版本的技能?
固定一个版本。该仓库仍在积极开发,其参考文件已经拆分并移动,因此上个月生成的报告可能依据不同的规则进行评估。克隆仓库,检出特定 commit,将 skills/old-coder 复制到 ~/.claude/skills/,并将该 commit hash 记录在 SPEC 旁边。这样,证据发生变化就意味着代码发生了变化。