首页
/ superpowers 评测基础设施迁移:将 drill 合规基准提升为 evals/ 的完整工程实践

superpowers 评测基础设施迁移:将 drill 合规基准提升为 evals/ 的完整工程实践

2026-09-06 12:30:47作者:彭桢灵Jeremy

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 条非目标,边界划分非常清晰:

目标

  1. evals/ 成为 superpowers 中规范的评测主干 —— 完整 drill 源码、场景、fixtures、prompts、backend 配置与测试;
  2. 已被逐文件验证为 100% 被 drill 场景覆盖的 tests/ bash 测试被删除,其余保留;
  3. tests/(插件基础设施测试)与 evals/(LLM 行为评测)的分工是有意义且被文档化的;
  4. 顶层文档(README.mdCLAUDE.mddocs/testing.md)指向正确的位置;
  5. 独立的 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 + 冒烟 + 子代理)

拷贝后的验证分四层,逐层递进:

  1. 文件清单 diff:在 drill 侧用 find 排除所有 excluded 路径后生成文件清单,在 superpowers 侧对 evals/ 生成同样格式的清单,diff 两条清单,期望零输出;
  2. 逐文件 SHA-256:对 drill 侧清单中每个文件计算 shasum -a 256,与 evals/ 下对应文件比对,任何 MISMATCH 都要报告;
  3. 冒烟检查uv sync 装依赖成功(只证明可安装,不是行为测试);uv run drill list 能列出场景名(此时可能报缺 SUPERPOWERS_ROOT 的错,属预期,下一任务修复);
  4. 子代理校验:派发一个 general-purpose 子代理,提示词要求其逐条检查并报告 PASS/FAIL:
    • lift commit message 是否记录了 git rev-parse HEAD 报出的 SHA;
    • evals/ 下是否不存在任何被排除路径;
    • drill 中每个非排除文件在 evals/ 是否有 SHA-256 一致的对应文件,且 evals/ 无多余文件;
    • pyproject.tomluv.lockscenarios/*.yamlbackends/*.yamlsetup_helpers/*.pydrill/*.pyprompts/*.mdfixtures/bin/docs/ 是否齐全。

子代理报 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.pyPROJECT_ROOT 解析到 evals/,其父目录正是仓库根——即 SUPERPOWERS_ROOT 应取的值。

计划任务本身是标准的 TDD 四步:

  1. 先写失败测试(追加到 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"
  1. 运行并观察失败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' 失败;
  2. 实现 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()
  1. 运行并观察通过,然后提交。

关键细节在调用时机:_set_superpowers_root_default() 在模块 import 时、紧跟 load_dotenv() 之后执行,从而保证所有读取方都能看到该值——engine.pysetup.py 在前后置钩子里直接 os.environ["SUPERPOWERS_ROOT"],YAML 插值在 backend 加载时读 os.environ。设计文档的 YAML 替换审计确认了这一契约:只有 5 个 claude*.yaml 会在 args 中把 ${SUPERPOWERS_ROOT} 插值进 --plugin-dir 参数;codex.yamlgemini.yaml 只是在 required_env 里列了它(供 engine.py/setup.pyos.environ 读取)。

Task 6–8:新环境契约落到 YAML 与文档

Task 6:backend YAML 收缩 required_env

  • evals/backends/codex.yamlrequired_env[OPENAI_API_KEY, SUPERPOWERS_ROOT] 收缩为 [OPENAI_API_KEY]
  • evals/backends/gemini.yamlrequired_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:从新位置做行为验证

这一步的原则是“真实验证,不是只看代码”:

  1. unset SUPERPOWERS_ROOT 后跑 drill 全量 pytest(unset 是故意的,确保测的是 helper 而不是继承的环境变量);
  2. unset SUPERPOWERS_ROOTuv run drill list,期望列出场景且不再报缺变量;
  3. 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.shrun-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.shrun-extended-multiturn-test.sh 无场景覆盖多轮铺垫 保留除非补场景
run-test.shrun-all.sh(explicit-skill-requests 下) n/a(runner) 保留
tests/subagent-driven-dev/go-fractals/svelte-todo/ sdd-go-fractals.yamlsdd-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.shrun-skill-tests.shanalyze-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.shrun-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.sh vs mid-conversation-skill-invocation.yaml 派发子代理;SAFE TO DELETE 才删,其余(haiku 专属、多轮、brainstorming/systematic-debugging prompt)无 drill 覆盖,保留。
  • 10cgo-fractals/svelte-todo/ 是含 design.mdplan.mdscaffold.sh 的完整 fixture 目录,先确认 drill 侧 fixtures/ 下有对等内容(fixture 对等),再逐对派发子代理(bash 侧“测试”即 scaffold.sh 及 runner,drill 侧为对应 sdd-*.yaml)。
  • 10d/10e/10f:三个 claude-code 测试各对应一个候选场景(spec-reviewer-catches-planted-flawscode-review-catches-planted-bugsworktree-creation-under-pressure),统一走子代理门。
  • 10gtest-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.
  • 10htest-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:陈旧引用清理(活引用更新,历史引用只加注不改写)

删除完成后的收尾清理分两类,处理原则截然不同:

  1. 生成删除路径清单
git diff --name-only --diff-filter=D dev..HEAD | sort > /tmp/deleted-paths.txt
  1. 全仓搜索活引用:对每个被删路径,grep -rln 限定 *.md/*.yml/*.yaml/*.sh/*.json,排除 node_modules/.venv/evals/.git/
  2. 分类处置
命中位置 处置
docs/testing.mdREADME.md(Contributing) 更新——活文档在介绍该测试
CLAUDE.mdGEMINI.mdAGENTS.md 引用了就更新
.github/workflows/*.yml 更新——CI 不应再跑已删测试
scripts/*.opencode/INSTALL.md.codex-plugin/INSTALL.mdlefthook.yml 引用了就更新
RELEASE-NOTES.mddocs/superpowers/plans/*.md 只加注不改写(带日期的历史工件)
  1. 活引用要么删除、要么替换为指向 drill 场景的指针(如 “see evals/scenarios/triggering-test-driven-development.yaml”);历史工件在每个文件的首个命中处加一段引用块注释(说明这些 bash 测试已于 2026-05-06 提升为 drill 场景,引用作为该文档所描述工作的历史快照保留),不改原引用文字
  2. 再派一个子代理做第二遍 scrub,只报告(file:line + 一句判断),不修改文件;所有报告命中处理完才提交。

Task 12–13:顶层文档定稿与回归门

Task 12:docs/testing.md 拆分 + 顶层指针

计划给出了 docs/testing.md 的目标全文结构——两段式开篇(tests/ vs evals/ 各一句定义)、## Plugin tests 节列出 tests/ 各目录职责与运行方式(各目录 run-*.shnpm 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:重跑冒烟(回归门)

删除与文档改动全部完成后,三项回归验证:

  1. unset SUPERPOWERS_ROOTuv run pytest(drill 自身套件);
  2. unset SUPERPOWERS_ROOT 后再跑 drill run triggering-test-driven-development -b claude,期望 1 passed, 0 failed, 0 errors
  3. 存活插件测试回归: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 项:

  1. rsync 排除是否真的排除了(find evals -name '.git' -type d 应无输出);
  2. lift commit message 指向的 SHA 是否是 obra/drill 中真实存在的提交;
  3. SUPERPOWERS_ROOT helper 在未设置时是否正确默认(cd evals && unset SUPERPOWERS_ROOT && uv run drill list);
  4. 每个被删 bash 测试对应的 drill 场景是否真的验证了 bash 测试断言的内容(抽读场景 YAML);
  5. docs/.github/scripts/lefthook.yml 是否还有指向已删路径的活引用;
  6. drill pytest 套件是否已适配新环境变量契约并通过;
  7. 路径改动后冒烟场景是否真的跑过;
  8. 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 new evals/ 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 的全量删除路径一致。

方法论要点

把这次迁移从“一次仓库整理”抽象出来,可复用为任何“外部评测框架并入仓库 + 被替代测试安全退役”的通用流程:

  1. SHA 锚定 + 逐文件 SHA-256:verbatim 拷贝可审计、可回溯,排除项逐条 find 验证;
  2. 环境自举:利用目录相对关系(PROJECT_ROOT.parent)把外部依赖的环境变量变为自动默认值,保留 override 通道,用 TDD 锁住行为;
  3. 删除门:任何测试删除都由独立子代理做逐断言匹配表(bash 断言 ↔ verify.assertions/verify.criteria),保守原则(不确定即 UNMATCHED),默认保留;保留项加头注释显性化覆盖差距;
  4. 引用清理双轨制:活引用更新、历史工件加注不改写,保持文档时间线诚实;
  5. 行为验证优先于代码审查:路径/环境改动必须用便宜场景端到端跑通,且 unset 环境变量以排除继承干扰;
  6. 对抗式终审 + 结构化 PR:双代理并行找茬(负分机制抑制误报)、备选方案拒绝理由入 PR 正文、合并后行动项(归档)随 PR 落档。

这套“拷贝校验门 → 契约迁移(TDD)→ 逐文件删除门 → 引用清理 → 行为回归 → 对抗审查”的管线,与 superpowers 自身 subagent-driven-development 的纪律一脉相承,也是该仓库计划文档反复采用的标准执行范式。

登录后查看全文
热门项目推荐
相关项目推荐