superpowers 评测基础设施迁移:将 drill 合规基准提升为 evals/ 的完整工程实践
superpowers 的 skill 行为评测曾依赖一个独立的 drill 仓库(驱动真实 tmux 会话、由 LLM actor 与 LLM verifier 判定的 skill 合规基准)。本文基于迁移实施计划 2026-05-06-lift-drill-into-evals.md 及其配套设计文档 2026-05-06-lift-drill-into-evals-design.md,完整拆解这次迁移的目标与边界、目录结构重构、15 个任务的逐步执行细节(rsync 拷贝校验、环境默认值自举、逐文件子代理删除门)、以及收尾的对抗式审查与 PR 规范,读完可以掌握一套“把外部评测框架并入仓库、同时安全清理被替代测试”的可复用工程方法论。
背景与动机:drill 已是事实上的评测主干
drill 是一个 Python skill-compliance 基准(独立仓库 obra/drill),其工作方式按设计文档描述为:驱动真实的 tmux 会话、运行一个 LLM actor 充当模拟用户、再对产生的会话记录运行 LLM verifier,按场景(scenario)输出 pass/fail。它支持 Claude Code、Codex、Gemini CLI,以及(按近期提交)OpenCode 与 Copilot CLI。
在设计文档(设计文档 Background 一节)中给出了两个关键前提:
- 迁移动能已经存在:drill 仓库中的 PRI-1397 提交系列已经把约 22 个 superpowers bash 测试“提升”为 drill 场景;最近一次 superpowers 提交(
a2292c5)明确删除了一个冗余 bash 测试,提交信息写明 “replaced by drill behavioral coverage”。 - 痛点:drill 作为兄弟仓库存在,要求贡献者克隆两份 checkout 并手动设置
SUPERPOWERS_ROOT环境变量,评测基础设施对贡献者不够可发现。
本次迁移要做的三件事:把 drill 原样搬入 superpowers 仓库的 evals/ 目录;在每个 bash 测试逐文件验证其断言被 drill 场景 100% 覆盖后删除冗余 bash 测试;更新顶层文档让贡献者落在新的结构上。
目标与非目标
设计文档给出了 5 条目标与 5 条非目标,边界划分非常清晰:
目标
evals/成为 superpowers 中规范的评测主干 —— 完整 drill 源码、场景、fixtures、prompts、backend 配置与测试;- 已被逐文件验证为 100% 被 drill 场景覆盖的
tests/bash 测试被删除,其余保留; tests/(插件基础设施测试)与evals/(LLM 行为评测)的分工是有意义且被文档化的;- 顶层文档(
README.md、CLAUDE.md、docs/testing.md)指向正确的位置; - 独立的 drill 仓库在本 PR 中不受影响,归档是合并后的独立手动步骤。
非目标
| 非目标 | 说明 |
|---|---|
| CI 集成 | 仅手动运行。后续自然方向是分层(PR 跑快子集、每晚全量),但需要 API 预算决策、CI secrets、含 tmux + node + python + 各 CLI 的 runner 镜像,均超范围 |
| 场景与 skill 同目录 | 场景集中放在 evals/scenarios/;未来若按 skill 拆分只是路径重命名,YAML 格式不变 |
| 重命名 Python 包 | 目录叫 evals/,但 Python 包保留 drill 名字以控制 diff 规模,在 evals/README.md 中说明 |
| 归档 drill 仓库 | 本 PR 不碰 obra/drill;合并后手动归档(只读 + README 指向 obra/superpowers/evals/) |
搬运 analyze-token-usage.py |
是实用工具不是测试代码,可日后单独迁移 |
迁移后的目标结构:tests/ 与 evals/ 的语义分工
设计文档给出迁移后的完整目录树,其核心是让两个目录各司其职:
superpowers/
evals/ ← NEW (full drill copy)
pyproject.toml (Python 3.11, uv-managed)
uv.lock
.gitignore (drill's own; results/, .venv/, .env)
README.md (was drill's README; install instructions updated)
CLAUDE.md (was drill's CLAUDE.md; paths updated)
docs/
design.md (drill's design — preserved verbatim)
manual-testing.md
pressure-and-red-testing.md
drill/ (Python package; name kept; cli, engine, actor, verifier, etc.)
backends/ (claude-*.yaml, codex.yaml, gemini.yaml)
scenarios/ (32+ YAML scenarios)
setup_helpers/ (15 Python helpers: create_base_repo, sdd_*, spec_*, worktree, etc.)
fixtures/ (template-repo, sdd-go-fractals, sdd-svelte-todo)
prompts/ (actor.md, verifier.md)
bin/ (assertion helper scripts: tool-called, tool-count, etc.)
tests/ (drill's own pytest suite)
tests/ ← bash tests preserved by default
brainstorm-server/ ← KEEP (node tests for brainstorm-server JS code)
opencode/ ← KEEP (plugin loading tests)
codex-plugin-sync/ ← KEEP (sync verification)
claude-code/ ← MOSTLY KEEP — see deletion gate
explicit-skill-requests/ ← KEEP unless verified replaced
skill-triggering/ ← KEEP unless verified replaced
subagent-driven-dev/ ← KEEP unless verified replaced
docs/
testing.md ← UPDATED (split into "Plugin tests" + "Skill behavior evals")
README.md ← small Contributing-section pointer to evals/
CLAUDE.md ← one-line "Eval harness lives at evals/" pointer
语义分工只有一句话:
tests/—— 插件的非 LLM 代码是否工作?brainstorm-server JS 的单元/集成测试、OpenCode 插件加载、codex-plugin-sync 同步验证。bash + node + python。evals/—— agent 在真实 LLM 会话中是否行为正确?drill 场景,actor + verifier 架构,纯 Python,跑真实 tmux 会话。
工具链为 Python 3.11 + uv(沿用 drill 既有工具链,不做变更),加上 rsync、bash、git。
实施计划 Task 1–4:分支、SHA 锚定与带排除项的 verbatim 拷贝
整个计划是“单 PR 对 dev、新分支 f/evals-lift”,15 个任务、每步一个原子提交。执行者被要求按 subagent-driven-development 或 executing-plans 技能逐任务执行,步骤用 checkbox 语法跟踪。
Task 1:从 dev 拉分支
四步操作,每步都有明确期望输出:
git status --short # 期望:空(或仅未跟踪的 .opencode/package-lock.json)
git fetch origin dev:dev
git checkout -b f/evals-lift dev
git log --oneline -1 # 期望:以 origin/dev 指向的提交开头
Task 2:锚定 drill 源 SHA
在拷贝前,先记录 drill 仓库的 HEAD SHA 作为溯源锚点:
cd /path/to/drill
DRILL_SHA=$(git rev-parse HEAD)
echo "$DRILL_SHA"
同时用 git status --short 验证 drill 工作树是干净的——计划特别强调:如果 drill 有未提交内容,必须停下报告,否则 SHA 锚定失去意义(记录的是无法复现的状态)。
Task 3:rsync 拷贝到 evals/(带显式排除项)
这是整个迁移中最大的单个提交,之所以选 rsync 而不是 cp -r,就是为了显式排除项:
rsync -a \
--exclude=.git \
--exclude=.venv \
--exclude=results \
--exclude=.env \
--exclude=__pycache__ \
--exclude='*.egg-info' \
--exclude=.private-journal \
--exclude='*.pyc' \
/path/to/drill/ \
evals/
随后逐项验证排除生效(每条命令都期望无输出):
find evals -name '.git' -type d
find evals -name '.venv' -type d
find evals -name 'results' -type d
find evals -name '.env'
find evals -name '__pycache__' -type d
find evals -name '*.egg-info' -type d
任何一条返回路径,都必须手工清除后再继续。提交信息模板把 SHA 写进 commit message 做溯源:
Lift drill into evals/ at $DRILL_SHA
rsync of obra/drill@$DRILL_SHA into superpowers/evals/, excluding
.git/, .venv/, results/, .env/, __pycache__/, *.egg-info/,
.private-journal/.
The drill repo is unaffected by this commit; archival is a separate
manual step after this PR merges.
Source SHA recorded in this commit message for provenance.
提交前还有防御性写法 : "${DRILL_SHA:?Set DRILL_SHA from Task 2 before committing}",确保没有 SHA 时提交直接失败。
Task 4:拷贝校验(文件清单 diff + 逐文件 SHA-256 + 冒烟 + 子代理)
拷贝后的验证分四层,逐层递进:
- 文件清单 diff:在 drill 侧用
find排除所有 excluded 路径后生成文件清单,在 superpowers 侧对evals/生成同样格式的清单,diff两条清单,期望零输出; - 逐文件 SHA-256:对 drill 侧清单中每个文件计算
shasum -a 256,与evals/下对应文件比对,任何 MISMATCH 都要报告; - 冒烟检查:
uv sync装依赖成功(只证明可安装,不是行为测试);uv run drill list能列出场景名(此时可能报缺SUPERPOWERS_ROOT的错,属预期,下一任务修复); - 子代理校验:派发一个 general-purpose 子代理,提示词要求其逐条检查并报告 PASS/FAIL:
- lift commit message 是否记录了
git rev-parse HEAD报出的 SHA; evals/下是否不存在任何被排除路径;- drill 中每个非排除文件在
evals/是否有 SHA-256 一致的对应文件,且evals/无多余文件; pyproject.toml、uv.lock、scenarios/*.yaml、backends/*.yaml、setup_helpers/*.py、drill/*.py、prompts/*.md、fixtures/、bin/、docs/是否齐全。
- lift commit message 是否记录了
子代理报 FAIL 时,先修根因(删泄漏文件、重新 rsync 等)再继续。
Task 5:SUPERPOWERS_ROOT 环境默认值自举(TDD 流程)
drill 历史上要求贡献者手动 export SUPERPOWERS_ROOT 指向 superpowers checkout。搬入 evals/ 后,evals/ 的父目录就是 superpowers 仓库根,这个值可以自动给出。设计文档在“Concrete path/config edits”一节已预先验证过路径解析:drill/cli.py 定义 PROJECT_ROOT = Path(__file__).parent.parent,迁移后 cli.py 位于 evals/drill/cli.py,PROJECT_ROOT 解析到 evals/,其父目录正是仓库根——即 SUPERPOWERS_ROOT 应取的值。
计划任务本身是标准的 TDD 四步:
- 先写失败测试(追加到
evals/tests/test_cli.py):
def test_set_superpowers_root_default_when_unset(monkeypatch, tmp_path):
"""When SUPERPOWERS_ROOT is unset, helper sets it to PROJECT_ROOT.parent."""
monkeypatch.delenv("SUPERPOWERS_ROOT", raising=False)
from drill.cli import _set_superpowers_root_default, PROJECT_ROOT
_set_superpowers_root_default()
import os
assert os.environ["SUPERPOWERS_ROOT"] == str(PROJECT_ROOT.parent)
def test_set_superpowers_root_default_respects_existing(monkeypatch):
"""When SUPERPOWERS_ROOT is already set, helper does not override."""
monkeypatch.setenv("SUPERPOWERS_ROOT", "/custom/path")
from drill.cli import _set_superpowers_root_default
_set_superpowers_root_default()
import os
assert os.environ["SUPERPOWERS_ROOT"] == "/custom/path"
- 运行并观察失败:
uv run pytest tests/test_cli.py -k set_superpowers_root_default -v,期望 2 个测试以AttributeError: module 'drill.cli' has no attribute '_set_superpowers_root_default'失败; - 实现 helper(替换
evals/drill/cli.py头部):
"""Drill CLI: run, compare, list."""
from __future__ import annotations
import os
import secrets
from pathlib import Path
import click
from dotenv import load_dotenv
PROJECT_ROOT: Path = Path(__file__).parent.parent
load_dotenv(PROJECT_ROOT / ".env")
def _set_superpowers_root_default() -> None:
"""Default SUPERPOWERS_ROOT to the parent of evals/ if not already set.
Drill historically required contributors to export SUPERPOWERS_ROOT
pointing at the superpowers checkout. After lifting drill into
superpowers/evals/, the parent of PROJECT_ROOT is always the
superpowers root, so we can supply this default automatically.
Existing SUPERPOWERS_ROOT environment values are respected as overrides.
"""
os.environ.setdefault("SUPERPOWERS_ROOT", str(PROJECT_ROOT.parent))
_set_superpowers_root_default()
- 运行并观察通过,然后提交。
关键细节在调用时机:_set_superpowers_root_default() 在模块 import 时、紧跟 load_dotenv() 之后执行,从而保证所有读取方都能看到该值——engine.py 与 setup.py 在前后置钩子里直接 os.environ["SUPERPOWERS_ROOT"],YAML 插值在 backend 加载时读 os.environ。设计文档的 YAML 替换审计确认了这一契约:只有 5 个 claude*.yaml 会在 args 中把 ${SUPERPOWERS_ROOT} 插值进 --plugin-dir 参数;codex.yaml 与 gemini.yaml 只是在 required_env 里列了它(供 engine.py/setup.py 的 os.environ 读取)。
Task 6–8:新环境契约落到 YAML 与文档
Task 6:backend YAML 收缩 required_env
evals/backends/codex.yaml:required_env从[OPENAI_API_KEY, SUPERPOWERS_ROOT]收缩为[OPENAI_API_KEY];evals/backends/gemini.yaml:required_env改为空列表[](保留字段写空列表而不是删字段,避免 YAML schema 校验出问题);- 5 个
claude*.yaml不动:它们的--plugin-dir插值需要这个变量,保留required_env也维持向后兼容(该变量同时仍是 override 通道)。
改完跑 uv run pytest -x 确认无破坏。
Task 7:drill 自身 pytest 套件适配新契约
若 Task 6 暴露出 evals/tests/test_backend.py 中关于 required_env 成员资格的断言失败,则按“从有到无”改写断言:
# Before:
def test_codex_requires_superpowers_root():
backend = load_backend("codex")
assert "SUPERPOWERS_ROOT" in backend.required_env
# After:
def test_codex_does_not_require_superpowers_root():
"""codex.yaml dropped SUPERPOWERS_ROOT from required_env;
the cli.py helper supplies the default."""
backend = load_backend("codex")
assert "SUPERPOWERS_ROOT" not in backend.required_env
无失败则跳过提交、直接进入下一任务。
Task 8:evals/README.md 与 evals/CLAUDE.md 去掉手动配置步骤
两处都从“必须 export SUPERPOWERS_ROOT”改为:
export ANTHROPIC_API_KEY=sk-...
并附一句说明:SUPERPOWERS_ROOT 默认取 evals/ 的父目录(superpowers 仓库根),只有当你要对另一份 superpowers checkout 跑 drill 时才需要显式设置。
Task 9:从新位置做行为验证
这一步的原则是“真实验证,不是只看代码”:
unset SUPERPOWERS_ROOT后跑 drill 全量 pytest(unset是故意的,确保测的是 helper 而不是继承的环境变量);unset SUPERPOWERS_ROOT后uv run drill list,期望列出场景且不再报缺变量;- source 本地
.env拿到ANTHROPIC_API_KEY后,跑一个便宜场景:
uv run drill run triggering-test-driven-development -b claude 2>&1 | tail -3
期望 claude: 1 passed, 0 failed, 0 errors。计划还给出了调试兜底:若失败,最可能是路径默认值改动,可临时在 helper 调用后加一行 print(os.environ["SUPERPOWERS_ROOT"]) 确认 helper 是否真正生效。
Task 10:bash 测试删除阶段——逐文件子代理门 + 覆盖度映射
这是整个迁移中最体现方法论的部分:每个候选删除文件都有自己独立的“子代理验证 + 提交”。每个候选文件的固定流程是:读 bash 测试 → 读候选 drill 场景 YAML → 用统一提示模板派发子代理 → 子代理输出逐断言匹配表 → 全部断言有匹配才删除提交,任何一条不匹配就停下升级、不删。
子代理提示词模板(所有删除共用):
You are gating a bash test deletion. The bash test is allegedly
covered by a drill scenario; your job is to verify that claim.
BASH TEST: <paste full contents of bash test>
DRILL SCENARIO: <paste full contents of drill scenario YAML>
Output a markdown table with columns: BASH ASSERTION, DRILL CHECK,
STATUS. List EVERY assertion the bash test makes (every grep, every
[ ], every test command, every PASS/FAIL emit). For each, find a
matching drill check (in verify.assertions or verify.criteria) or
mark as UNMATCHED.
After the table, output "VERDICT: SAFE TO DELETE" if every bash
assertion has a match, otherwise "VERDICT: KEEP — N unmatched
assertions". Be conservative: if you are uncertain about a match,
mark as UNMATCHED.
设计文档中的“临时覆盖度映射”(tentative coverage map)是这一阶段的输入,逐文件标注了预期结论(删除候选还是保留):
| Bash 测试 | 声称的 drill 替代 | 覆盖状态 |
|---|---|---|
tests/skill-triggering/prompts/*(6 个 prompt 文件) |
triggering-*.yaml(6 个场景) |
候选——删前逐 prompt 验证 |
tests/skill-triggering/run-test.sh、run-all.sh |
n/a(runner 不是测试) | 保留——runner 脚本 |
tests/explicit-skill-requests/prompts/please-use-brainstorming.txt |
无明确对应 | 大概率保留,除非补 drill 场景 |
tests/explicit-skill-requests/prompts/use-systematic-debugging.txt |
无明确对应 | 大概率保留 |
tests/explicit-skill-requests/run-claude-describes-sdd.sh |
部分 → mid-conversation-skill-invocation.yaml |
候选——逐脚本验证 |
tests/explicit-skill-requests/run-haiku-test.sh |
无场景覆盖 Haiku 专属行为 | 保留 |
run-multiturn-test.sh、run-extended-multiturn-test.sh |
无场景覆盖多轮铺垫 | 保留除非补场景 |
run-test.sh、run-all.sh(explicit-skill-requests 下) |
n/a(runner) | 保留 |
tests/subagent-driven-dev/go-fractals/、svelte-todo/ |
sdd-go-fractals.yaml、sdd-svelte-todo.yaml |
候选——含真实“测试套件通过”断言,删前验证 |
tests/claude-code/test-document-review-system.sh |
spec-reviewer-catches-planted-flaws.yaml |
候选——删前验证 |
tests/claude-code/test-requesting-code-review.sh |
code-review-catches-planted-bugs.yaml |
候选——删前验证 |
tests/claude-code/test-subagent-driven-development-integration.sh |
sdd-rejects-extra-features.yaml(仅 YAGNI 子集) |
部分——bash 还断言 ≥3 commits / npm test 通过 / 跑 analyze-token-usage.py;场景断言 forbidden-exports + reviewer-as-gate,两者大体不相交,几乎必然保留 + 扩展 drill 场景 |
tests/claude-code/test-subagent-driven-development.sh |
meta/描述型测试(让 agent 描述 SDD);无场景覆盖描述类断言 | 保留除非补场景 |
tests/claude-code/test-worktree-native-preference.sh |
worktree-creation-under-pressure.yaml |
候选——删前验证 |
test-helpers.sh、run-skill-tests.sh、analyze-token-usage.py |
n/a(工具库) | 保留 |
Task 10a:skill-triggering 的 6 个 prompt 文件
这 6 个 prompt 文件本身没有断言——它们只是 bash runner 的输入,断言由 runner 脚本做。所以子代理验证的是“prompt 内容与 drill 场景 turns[].intent 描述是否一致”。映射关系:
| Prompt | Drill 场景 |
|---|---|
| dispatching-parallel-agents.txt | triggering-dispatching-parallel-agents.yaml |
| executing-plans.txt | triggering-executing-plans.yaml |
| requesting-code-review.txt | triggering-requesting-code-review.yaml |
| systematic-debugging.txt | triggering-systematic-debugging.yaml |
| test-driven-development.txt | triggering-test-driven-development.yaml |
| writing-plans.txt | triggering-writing-plans.yaml |
6 个全部 SAFE TO DELETE 才批量 git rm;任何一个是 KEEP,该文件留下、其余可继续。删完后检查 runner 是否成为孤儿:若 prompts/ 目录清空,run-test.sh 与 run-all.sh 一并删除;否则保留 runner。提交信息模板中写明“子代理验证确认每个 prompt 的 intent 与对应 drill 场景的 turns[].intent 一致,drill 场景为规范源”。
Task 10b–10h:explicit-skill-requests 与 claude-code 的逐文件处置
- 10b:先
for f in *.sh prompts/*.txt; do head -30; done通读确认,只对run-claude-describes-sdd.shvsmid-conversation-skill-invocation.yaml派发子代理;SAFE TO DELETE 才删,其余(haiku 专属、多轮、brainstorming/systematic-debugging prompt)无 drill 覆盖,保留。 - 10c:
go-fractals/、svelte-todo/是含design.md、plan.md、scaffold.sh的完整 fixture 目录,先确认 drill 侧fixtures/下有对等内容(fixture 对等),再逐对派发子代理(bash 侧“测试”即scaffold.sh及 runner,drill 侧为对应sdd-*.yaml)。 - 10d/10e/10f:三个 claude-code 测试各对应一个候选场景(
spec-reviewer-catches-planted-flaws、code-review-catches-planted-bugs、worktree-creation-under-pressure),统一走子代理门。 - 10g:
test-subagent-driven-development-integration.sh预期结论是 KEEP,但仍然要派发子代理做一次比对——目的是把差距显性化。若判 KEEP,就在文件头加注释记录:
# Drill coverage: sdd-rejects-extra-features.yaml covers the YAGNI
# enforcement (forbidden exports + reviewer-as-gate). This bash test
# additionally asserts: ≥3 task commits, npm test passes, token
# analysis runs. Keep until those assertions are added to drill or
# explicitly retired.
- 10h:
test-subagent-driven-development.sh是“让 agent 描述 SDD”的 meta 测试,drill 场景测行为不测描述,无覆盖,直接保留并注释:
# No drill coverage: this test asks the agent to *describe* SDD
# (asserts that asked-about skills can be summarized correctly).
# Drill scenarios test behavior, not description. Kept.
注意 10g/10h 的“保留 + 注释”本身就是产出:它让后来者一眼看清哪些断言在 drill 之外、为什么这些 bash 测试还活着。
Task 11:陈旧引用清理(活引用更新,历史引用只加注不改写)
删除完成后的收尾清理分两类,处理原则截然不同:
- 生成删除路径清单:
git diff --name-only --diff-filter=D dev..HEAD | sort > /tmp/deleted-paths.txt
- 全仓搜索活引用:对每个被删路径,
grep -rln限定*.md/*.yml/*.yaml/*.sh/*.json,排除node_modules/、.venv/、evals/、.git/; - 分类处置:
| 命中位置 | 处置 |
|---|---|
docs/testing.md、README.md(Contributing) |
更新——活文档在介绍该测试 |
CLAUDE.md、GEMINI.md、AGENTS.md |
引用了就更新 |
.github/workflows/*.yml |
更新——CI 不应再跑已删测试 |
scripts/*、.opencode/INSTALL.md、.codex-plugin/INSTALL.md、lefthook.yml |
引用了就更新 |
RELEASE-NOTES.md、docs/superpowers/plans/*.md |
只加注不改写(带日期的历史工件) |
- 活引用要么删除、要么替换为指向 drill 场景的指针(如 “see
evals/scenarios/triggering-test-driven-development.yaml”);历史工件在每个文件的首个命中处加一段引用块注释(说明这些 bash 测试已于 2026-05-06 提升为 drill 场景,引用作为该文档所描述工作的历史快照保留),不改原引用文字; - 再派一个子代理做第二遍 scrub,只报告(file:line + 一句判断),不修改文件;所有报告命中处理完才提交。
Task 12–13:顶层文档定稿与回归门
Task 12:docs/testing.md 拆分 + 顶层指针
计划给出了 docs/testing.md 的目标全文结构——两段式开篇(tests/ vs evals/ 各一句定义)、## Plugin tests 节列出 tests/ 各目录职责与运行方式(各目录 run-*.sh 或 npm test)、## Skill behavior evals 节给出 quick start:
cd evals
uv sync
export ANTHROPIC_API_KEY=sk-...
uv run drill run triggering-test-driven-development -b claude
并明确说明:drill 场景慢(每个 3–30+ 分钟)、跑真实 LLM 会话、当前不进 CI,自然后续是分层模型(PR 快子集 + 每晚全量 + 按需)。同一任务还包含:CLAUDE.md 加 “Eval harness” 一节指向 evals/;README.md Contributing 节加一行指向 evals/;顶层 .gitignore 追加 evals/results/、evals/.venv/、evals/.env(belt-and-suspenders,evals/.gitignore 已本地覆盖,根级条目服务于不递归读嵌套 ignore 的工具)。
另外,设计文档为 evals/lefthook.yml(drill 自带的 pre-commit: uv run ruff check && uv run ty check)定了实现期决策:选简单方案——文档化贡献者手动 cd evals && lefthook run pre-commit,不改 superpowers 顶层工作流。.env 则留在 evals/.env(gitignored)。
Task 13:重跑冒烟(回归门)
删除与文档改动全部完成后,三项回归验证:
unset SUPERPOWERS_ROOT后uv run pytest(drill 自身套件);unset SUPERPOWERS_ROOT后再跑drill run triggering-test-driven-development -b claude,期望1 passed, 0 failed, 0 errors;- 存活插件测试回归:
tests/brainstorm-server/的 node 测试node server.test.js,期望Results: 25 passed, 0 failed。
若 FAIL,按“删除/清理/文档阶段的提交”二分定位。
Task 14–15:对抗式审查与 PR 规范
Task 14:双代理对抗式最终审查
先固化审查材料(git log --oneline dev..HEAD + git diff dev..HEAD --stat),然后并行派两个子代理,同一提示词、对抗式框架(“5 points to whoever finds the most legitimate issues”),要求重点核查 8 项:
- rsync 排除是否真的排除了(
find evals -name '.git' -type d应无输出); - lift commit message 指向的 SHA 是否是
obra/drill中真实存在的提交; SUPERPOWERS_ROOThelper 在未设置时是否正确默认(cd evals && unset SUPERPOWERS_ROOT && uv run drill list);- 每个被删 bash 测试对应的 drill 场景是否真的验证了 bash 测试断言的内容(抽读场景 YAML);
docs/、.github/、scripts/、lefthook.yml是否还有指向已删路径的活引用;- drill pytest 套件是否已适配新环境变量契约并通过;
- 路径改动后冒烟场景是否真的跑过;
- drill 仓库是否未被改动(
cd ../drill && git status)。
提示词还要求“先验证再断言:声称 X 坏了,先去磁盘上确认;自信的错误结论负分”。报告格式为编号列表,每条带 severity(critical/important/minor/nitpick)与一句 file:line 说明。每条成立的意见单独提交修复,修复后重跑 Task 13 冒烟,最后按成立意见数宣布胜者(误报倒扣分)。
Task 15:推送并开 PR
gh pr create --base dev --head f/evals-lift,PR 正文按仓库既有模板组织,要点:
- 问题陈述:drill 已是 de facto 评测主干(PRI-1397 系列 +
a2292c5的先例),但兄弟仓库形态要求双克隆 + 手动SUPERPOWERS_ROOT,本 PR 完成迁移; - 变更清单:rsync 提升(带 SHA)、
_set_superpowers_root_default()helper、codex/gemini 的required_env收缩、逐文件子代理门控的 bash 测试删除、docs/testing.md拆分、README/CLAUDE 指针; - 备选方案与拒绝理由:
| 备选 | 拒绝理由 |
|---|---|
| vendor 拷贝 + 同步脚本(drill 继续独立演进) | 分叉风险;单一事实源胜出 |
| git subtree merge(历史入树) | superpowers 历史膨胀 50+ 提交,merge 提交难看,subtree 运维沉重 |
| 保持兄弟仓库只改文档 | 没解决可发现性问题 |
- 合并后行动项:归档
obra/drill(只读 + README 指向新位置); - 环境声明:Claude Code 本地安装 + 指定模型;drill pytest 通过、
triggering-test-driven-development从新位置端到端通过(大规模 sweep 按规范中“CI 后置”策略留给发布节奏跑)。
收尾验证清单
计划末尾给出 10 项终验清单,可作任何类似迁移的验收模板:
- [ ]
git log --oneline dev..HEAD提交顺序符合预期 - [ ] lift commit message 记录了源 SHA
- [ ]
find evals -name '.git' -type d无输出 - [ ]
cd evals && unset SUPERPOWERS_ROOT && uv run pytest通过 - [ ]
cd evals && unset SUPERPOWERS_ROOT && uv run drill list返回场景 - [ ]
cd evals && unset SUPERPOWERS_ROOT && uv run drill run triggering-test-driven-development -b claude通过 - [ ]
tests/brainstorm-server/server.test.js仍通过(非 LLM 测试回归门) - [ ]
git diff dev..HEAD docs/superpowers/plans/... RELEASE-NOTES.md只有注释、没有路径改写 - [ ]
cd ../drill && git log --oneline -1显示 drill 与 lift 提交记录的源 SHA 一致(未被改动) - [ ] PR 正文列出合并后的归档行动项
当前仓库中该计划的落地结果
当前仓库内容印证了这次迁移的落地:
- docs/testing.md 已按 Task 12 拆分为 “Plugin tests” + “Skill behavior evals” 两节,quick start 仍为
cd evals && uv sync && uv run drill run triggering-test-driven-development -b claude; - README.md 中 skill 行为测试指向
evals/的 drill 评测主干(并注明其来自superpowers-evals克隆——相对计划中“verbatim 拷贝 + 归档”的方案,落地时演化为指向独立评测仓库的 submodule 形态,属文档化了的实现期决策); - RELEASE-NOTES.md 记录 “skill-behavior testing moved out of
tests/into a newevals/submodule built on 'drill'”,并在历史段落保留了“引用保留为 dated artifacts”的注释块——正是 Task 11 “历史引用只加注不改写”原则的产物; - CLAUDE.md 含 “Eval harness” 一节(约 L102)指向
evals/; - 被保留下来的 bash 测试仍在库中,例如 tests/explicit-skill-requests/run-test.sh(隔离 HOME、按 prompt 文件验证显式点名 skill 的触发行为)与 tests/claude-code/test-worktree-native-preference.sh——符合“未 100% 覆盖即保留”的默认策略;
- 而
tests/skill-triggering/目录已不在当前仓库顶层目录中,与 Task 10a 的全量删除路径一致。
方法论要点
把这次迁移从“一次仓库整理”抽象出来,可复用为任何“外部评测框架并入仓库 + 被替代测试安全退役”的通用流程:
- SHA 锚定 + 逐文件 SHA-256:verbatim 拷贝可审计、可回溯,排除项逐条
find验证; - 环境自举:利用目录相对关系(
PROJECT_ROOT.parent)把外部依赖的环境变量变为自动默认值,保留 override 通道,用 TDD 锁住行为; - 删除门:任何测试删除都由独立子代理做逐断言匹配表(bash 断言 ↔
verify.assertions/verify.criteria),保守原则(不确定即 UNMATCHED),默认保留;保留项加头注释显性化覆盖差距; - 引用清理双轨制:活引用更新、历史工件加注不改写,保持文档时间线诚实;
- 行为验证优先于代码审查:路径/环境改动必须用便宜场景端到端跑通,且
unset环境变量以排除继承干扰; - 对抗式终审 + 结构化 PR:双代理并行找茬(负分机制抑制误报)、备选方案拒绝理由入 PR 正文、合并后行动项(归档)随 PR 落档。
这套“拷贝校验门 → 契约迁移(TDD)→ 逐文件删除门 → 引用清理 → 行为回归 → 对抗审查”的管线,与 superpowers 自身 subagent-driven-development 的纪律一脉相承,也是该仓库计划文档反复采用的标准执行范式。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00