首页
/ superpowers SDD 计划级隔离工作区:跨计划账本冲突的 RED→GREEN 实证评估

superpowers SDD 计划级隔离工作区:跨计划账本冲突的 RED→GREEN 实证评估

2026-09-04 11:32:17作者:平淮齐Percy

本文以 superpowers 仓库中 SDD(Subagent-Driven Development)"计划级工作区"(plan-scoped workspace)改动的评估结果文档为主体,完整还原该评估的场景设计、fixture 迭代、RED/GREEN 分组结果与成本分析,并结合仓库中的 sdd-workspace 脚本SKILL.mdtest-sdd-workspace.sh 等实现,说明"账本身份行 + 每计划独立目录"这一机制如何在结构上消除陈旧账本被误当作自身进度、导致跳过任务序列的失败模式。读完本文,你将掌握一套面向"流程文档改动"的 RED→GREEN 压力评估方法,以及 SDD 持久进度(durable progress)机制的实际行为边界。

一、背景:SDD 持久进度工作区为何需要"计划身份"

SDD 技能的核心理念是:控制器的对话记忆无法穿越上下文压缩(compaction),进度必须落盘。在 SKILL.md 的 Durable Progress 一节中,技能明确要求"把进度记录在账本文件里,而不只是 todo",因为真实会话中丢失位置的控制器曾把整个已完成任务序列重新派发——这是观察到的最昂贵的失败。

早期版本的持久进度工作区位于扁平路径 .superpowers/sdd/,所有工件(progress.mdtask-N-brief.mdtask-N-report.md)都只按裸任务编号命名,没有任何计划身份。其原始启动指令是:

At skill start, check for a ledger: cat "$(git rev-parse --show-toplevel)/.superpowers/sdd/progress.md". Tasks listed there as complete are DONE — do not re-dispatch them; resume at the first task not marked complete.

这段指令的问题在于:同一工作树里执行后续计划(follow-up plan)的新会话,会把上一个计划的账本读成自己的进度,按字面语义就会跳过任务;而且工作区从来不会被删除,陈旧状态会无限期累积。该设计文档(2026-07-06-sdd-plan-scoped-workspace.md)还记录了在 serf 仓库(2026-06-22 → 2026-07-05)中观察到的三类结构性事故:

  • 跨计划冲突靠临时手段绕过cc-plugin-marketplaces worktree 在三个计划间累积了 68 个文件;P2 控制器被迫发明 progress-p2.mdp2-task-N-report.md 旁路命名来避开 P1 的账本,P2 的 brief 在默认路径上静默覆盖了 P1 的,还留下一份废弃的 progress-p3.md 残桩;
  • git 污染反复发生:SDD 临时文件被提交过,需要两次清理提交(8305e340dc966261a5);三个工件至今仍被 serf 的 main 跟踪,其中包括一份在别的机器上写出的报告,如今会在每个新 worktree 中"显形";
  • 自愈式 .gitignore 只在脚本运行时写入:手工追加账本的控制器(实际观察到的行为)从不创建它,而 gitignore 对已跟踪文件无能为力。

由此得出的根因结论是:身份在数据中无处安放,正确性依赖一个没有触发器的清理。任何仅靠"计划结束时清理"的方案,恰好在账本本应存活的崩溃/压缩场景下失效。身份必须是结构性的(structural)。

对应到本次改动(评估文档中 GREEN 组所测的"this branch"),机制由三部分组成:

  1. 每计划一个目录:工作区变为 .superpowers/sdd/<plan-slug>/,slug 是计划文件名去掉 .md 的后缀(计划文件名按惯例已是带日期的 kebab-case)。不同计划的工件从结构上不再可能冲突;一个陈旧的兄弟目录是惰性的,因为没有任何指令指向它;
  2. 账本自带身份行progress.md 创建时首行必须是 # SDD ledger — plan: <计划文件路径>,用来兜底那些不跑脚本、手写账本的控制器,以及覆盖旧扁平路径上的遗留残骸;
  3. 工作区生命周期终点:最终全分支评审干净且修复波合并后、移交给 finishing-a-development-branch 之前,控制器删除自己计划的工作区目录;兄弟目录永不触碰。

二、评估方法与两个压力场景

评估采用 writing-skills 技能定义的 RED→GREEN 压力测试法——"测试技能就是把 TDD 应用到流程文档上":先跑无新文本的基线(RED,观察代理如何决策),再测新文本(GREEN,验证行为改变),每份回复由人逐字通读并手工打分。方法说明:

  • 日期:2026-07-06(在 2026-07-06 因 RED 基线未能复现"盲目采纳陈旧账本"而重新划定范围,并在维护者 Jesse 签字后执行);
  • 样本:每个臂 5 个全新 sonnet 子代理(subagent_type 为 general-purpose),压缩-恢复(compaction-resume)话术框架,每份回复全文通读并手工评分;
  • 被测输出:控制器的"恢复决策"(resume decision)——不实际派发实现者,只要求控制器恢复到"知道自己执行状态"的那一点后停止并报告。

两个场景共用同一个 fixture 仓库结构,模拟一个"plan A 已完整执行完毕、控制器在压缩后恢复执行后续 plan B"的项目:

S1 — 来自不同计划的陈旧账本。 fixture 仓库中,plan A(docs/plans/2026-07-01-widget-backend.md,5 个任务)已执行完成;被测控制器恢复执行 plan B(docs/plans/2026-07-06-widget-export.md,同样 5 个任务),plan B 尚未实现任何任务。GREEN 臂使用 scoped 布局——升级后的最坏情况:旧扁平账本 .superpowers/sdd/progress.md 带有 plan A 的五行 "complete (review clean)" 且没有身份头,外加 plan A 自己已完成的计划级工作区 .superpowers/sdd/2026-07-01-widget-backend/progress.md(首行是命名 plan A 的身份行),而 plan B 没有工作区。正确控制器应在不采纳任何陈旧工件的前提下,从 plan B 的 Task 1 起步。

S2 — 同计划恢复(回归控制)。 同一项目,但 plan B 的 Task 1–2 是真实实现的:代码满足各任务规格、已提交(feat(export): export data modelfeat(export): csv serializer)且被账本如实记录为 complete。正确控制器应识别 Task 1–2 已完成并派发 Task 3。S2 的 RED 控制臂使用 flat 布局——账本在发布格式的 .superpowers/sdd/progress.md(无身份行);GREEN 臂使用 scoped 布局——账本在 .superpowers/sdd/2026-07-06-widget-export/progress.md,首行为 # SDD ledger — plan: docs/plans/2026-07-06-widget-export.md

三、RED 轮次展示了什么——以及没有展示什么

对发布版(改动前)SKILL.md 文本共跑了三轮 RED:v1 和 v2 使用新会话框架,然后是一个压缩-恢复框架的探针轮(probe),后者显式激活了发布版技能自己的 "After compaction, trust the ledger and git log over your own recollection" 指令。共 25 次重复(5 × 5 单元:v1 S1、v1 S2、v2 S1、v2 S2、probe S1),每次重复用一个全新 sonnet 子代理,每份回复全文通读。

核心发现:25/25 的控制器重复都拒绝把账本当作跳过工作的许可。 三轮中全部 15 个 S1 重复都正确识别出外来的、属于其他计划的账本,并让自己的计划从 Task 1 起步;另外 10 个(v1 S2 和 v2 S2)拒绝了名义上属于自己计划的账本——5 个是因为 fixture v1 的占位哈希让账本不可验证,5 个是因为 fixture v2 引用的提交虽然真实且确属控制器自己的计划,但包含与 "review clean" 声称相矛盾的非功能桩代码。在任何框架、任何单元下,没有任何一个重复采纳虚假完成声称并跳过真实工作。最初假设的失败模式——盲目采纳外来陈旧账本——没有复现。

因此,可复现的基线危害不是错误率,而是:

(a) 每次恢复都要付出取证式消歧税。 在探针轮(最接近真实崩溃/压缩恢复的框架,且 "trust the ledger" 指令处于激活状态)中,每个重复仍然要花真实的工具调用来"证明这份账本不是自己的"之后才能做任何事:每次重复分别 7、13、9、10、6 次工具调用(均值 9.0)。

(b) 设计文档中记录的结构性事故(serf 仓库,即上文第一节列出的跨计划冲突、被覆盖的 brief、git 污染与三次清理)。

由此,SKILL.md 改动是基于结构性依据推进的,维护者(Jesse)在审阅 25/25 的数字后于 2026-07-06 签字批准——而不是基于一个已演示的错误率。评估文档对 GREEN 轮的声称被严格限定为两件事:回归安全性(合法的同计划恢复仍然能恢复)和恢复决策的成本对比(如实报告:机制变了,但原始工具调用数并没有下降)。

RED 引语库(逐字,承自 Task 1 证据文档)

轮次 v1(新会话框架,fixture v1——伪造哈希,17 vs 5 任务数差异):

  • s1-rep2:

    None of the aaa000N/bbb000N hashes the ledger cites exist as git objects (git cat-file -t aaa0001 → "Not a valid object name"). The ledger's claims are unverifiable/fabricated relative to actual repo history.

  • s2-rep1:

    Conclusion: this ledger is stale/fabricated and must not be trusted — none of its claimed commits exist in this repo's history.

  • s2-rep3(点名具体哈希):

    the commit hashes ccc0001/ddd0001/ccc0002/ddd0002 the ledger cites don't exist anywhere in history.

轮次 v2(新会话框架,fixture v2——真实可解析哈希,任务数 5/5 匹配):

  • s1-rep1:

    Cross-checked the commit hashes it cites (0d2b573, 4b84f94, 2dad909, d7b6770, 4affd09) against git log: they match docs/plans/2026-07-01-widget-backend.md (schema/validate/lock/registry/lint), a different, already-finished plan — not mine.

  • s2-rep5:

    All 9 commits in the repo's history are authored by eval <eval@example.com> at the identical timestamp, i.e. seeded fixture history, not a real prior session — there was no genuine implementer/reviewer pass behind these "review clean" annotations.

轮次 v3-probe(压缩-恢复框架,v2 风格 fixture,按重复记录 tool_uses):

  • rep1 (tool_uses=7):

    The workspace script (scripts/sdd-workspace) confirms the ledger path is a single fixed location ($root/.superpowers/sdd), not plan-scoped, so it will collide across any two plans run in the same repo.

  • rep5 (tool_uses=6):

    The ledger's "complete" claims do not apply to this plan — treating them as if they did would have caused skipping all 5 real tasks.

v1 和 v2 各自独立触发了评估自身的停止门(S1 通过 5/5 需要在任何技能编辑前进行人工重评估),并被上报而非擅自行动。RED 逐字回复保存在 eval-notes 历史记录的临时路径下(见 2026-07-06-sdd-plan-scoped-workspace-eval-notes-red.md 的 git log):tmp.HxHAMXx5og/red/(v1)、tmp.gBeQlWDSrO/red/(v2)、tmp.7WvvPaZcwZ/(probe)。

四、Fixture 迭代:为什么前两版都不合格

fixture 的演进本身是这次评估最值得注意的方法学部分——它展示了"让被测代理无法靠捷径通过"需要多严格:

  • Fixture v1(在任何技能编辑前被废弃):plan A 有 17 个任务而 plan B 只有 5 个(任务数就是一个破绽),且账本引用伪造的提交哈希。代理用 git 取证直接否掉了账本——S1 "通过"了但通过的理由是错的;S2(合法恢复控制)5/5 失败。
  • Fixture v2:改用真实引用的提交并匹配任务数。代理转而检查提交内容,将其与另一个计划文件匹配(S1),并裁定 v2 的桩实现是虚假的 "review clean" 记录(S2 再次 5/5 失败)。
  • Fixture v3(本轮所用):让每条账本声称都经得起内容审查——真实实现满足各任务规格、轮换作者、分散时间戳。

v3 有一个为实现透明而保留的实现注记:计划文本中写明的 fixture 生成器存在命令替换子 shell bug——ci 提交计数器在 $(commit_file ...) 内部自增,自增永远无法穿越子 shell 存活,于是所有提交坍缩到单一作者(Dana Okafor)和单一每计划时间戳上,恰好构成了使 v2 控制失效的"fixture 制造历史"破绽。计划自身的 Step 1 健全性门(每个引用哈希可解析 两个作者横跨两个日期)在任何场景重复运行之前捕获了它。修复是一个单 hunk 改动,把计数器持久化到文件(见附录 A,即实际使用的生成器);没有任何场景重复跑在损坏的构建上。

五、结果

被测文本 Fixture PASS 备注
S1 RED 发布版(v6.1.1 行) v1+v2+probe,3 种框架 15/15 拒绝采纳 恢复轮均值 9.0 次 tool_uses 的跨计划取证
S1 GREEN 本分支 v3 scoped 5/5 全部 5 个通过结构方式解决(工作区 + 身份行),无一通过提交内容取证;tool_uses 9/11/9/7/12
S2 RED(控制) 发布版 v3 flat 5/5 验证了 fixture:真实的同计划账本被接受,派发 Task 3;tool_uses 9/8/10/7/5
S2 GREEN 本分支 v3 scoped 5/5 回归:合法恢复仍然恢复(识别 Task 1–2,派发 Task 3);tool_uses 11/9/7/8/7

评分标准:S1 GREEN 通过当且仅当首次派发是 plan B 的 Task 1、没有任何 plan-B 任务被声称为 complete、且两个陈旧工件均未被采纳;S2(两臂)通过当且仅当 Task 1–2 被识别为完成且 Task 3 是首次派发。每个重复都是拿着附录 B 逐字提示词的全新 sonnet 子代理;每份回复全文通读并逐字保留(路径见"限制"一节)。

六、消歧成本:一张必须如实读的数字表

轮次 框架 文本 每次重复的 tool_uses 均值
RED probe 压缩-恢复 发布版 7 / 13 / 9 / 10 / 6 9.0
S1 GREEN 压缩-恢复 本分支 9 / 11 / 9 / 7 / 12 9.6

对这张表的诚实解读:原始工具调用数没有下降(9.6 vs 9.0)。两行之间有两点差异。其一,S1 GREEN 的 fixture 比探针 fixture 携带严格更多的陈旧材料——三个账本位置(空的本计划工作区、扁平遗留账本、plan A 已完成的 scoped 工作区)对比一个扁平账本——所以每个 GREEN 重复要枚举和分类更多工件。其二是实质变化:调用被花在了什么上面。探针轮的重复通过跨计划提交/计划文件取证建立来源归属(拉取引用提交的 diff 并把内容匹配到另一个计划文件),因为文本没有给它们其他方式判断"这是谁人的账本";GREEN 重复按结构决策——解析自己计划的工作区、检查身份首行——并把剩余调用花在佐证"自己的计划没有先前工作"(git log、文件列举)上,而这本来就是新启动控制器无论如何都会做的。同计划恢复成本在噪声范围内不变:S2 GREEN 均值 8.4 对 S2 RED 控制均值 7.8。

tool_uses 是粗糙代理(数调用次数,不数 token 或风险);结构性结论——没有任何 GREEN 重复需要内容取证来消歧,且当每个账本都命名自己的计划时,错误归属变得不可能——才是承重结果,而不是这个场景未能演示的调用数下降。

七、GREEN 行为注记:结构性决策如何发生

每个 GREEN 重复(10/10)都从解析计划级工作区开始——要么运行 scripts/sdd-workspace docs/plans/2026-07-06-widget-export.md,要么直接查看 .superpowers/sdd/2026-07-06-widget-export/——并把身份首行当作账本归属的权威依据。

S1 GREEN 逐重复的解决形态(期望形态:计划级工作区解析、无提交内容取证):

  • rep1 (9): 结构性决策加 git-log 关联(把散落账本的引用哈希对应到提交主题,从未拉取 diff):"旧扁平路径上一份无身份的散落账本属于另一个计划——不作为本计划的证据采用";plan-A scoped 账本的身份行"证明账本 #2 是那个计划的遗留副本,不是我的"。
  • rep2 (11): 纯结构性:扁平账本"没有 # SDD ledger — plan: … 身份行。按技能规则,扁平路径的账本是别的计划的散落进度——不是我的,原样不动。"
  • rep3 (9): 纯结构性;注意到扁平账本"与 widget-backend 账本字节级相同",两个外来工件都原样未动。
  • rep4 (7): 结构性加轻量哈希到 git log 交叉引用;自己的工作区经脚本解析后发现为空;两个陈旧工件"原样留在原地——不是我的"。
  • rep5 (12): 纯结构性;工作区"在脚本刚刚创建它之前并不存在",仅凭缺失头部就拒绝了扁平账本。

五个重复中没有一个拉取引用提交的 diff 去与另一个计划文件做内容匹配——那是 v2/probe 轮次的标志性取证动作。五个重复全部派发 plan B 的 Task 1;没有一个声称任何 plan-B 任务完成;两个陈旧工件都被留在原地(符合技能的 "leave it in place and start your own, fresh")。

S2 GREEN(回归): 5/5 从带身份行的账本识别出 Task 1–2 完成,把两个引用提交与 git log 做了交叉核对(提交级,与账本自身的恢复图角色一致),并派发 Task 3。没有重复重新派发已完成的工作;没有重复拒绝合法账本——击沉 v1/v2 S2 控制的失败模式没有在真实 fixture 上重现,无论是在控制臂还是 GREEN 臂。

精化迭代:无。 三道门全部一次跑通;本评估轮期间没有做任何 SKILL.md 措辞修改。

八、源码佐证:身份行与目录隔离是如何落地的

评估文档中的行为之所以"结构上不可能出错",根源在仓库中的三个脚本。它们位于 skills/subagent-driven-development/scripts/,是工作区位置的唯一事实来源:

  • sdd-workspace PLAN_FILE:解析并创建 <repo-root>/.superpowers/sdd/<plan-slug>/,维护位于 .superpowers/sdd/.gitignore 的自忽略 gitignore(父级,内容 *),打印计划目录的绝对路径;参数缺失或计划文件不存在时以 exit 2 报错;slug 去扩展名后必须非空。从源码结构看,mkdir -p "$dir"printf '*\n' > "$base/.gitignore" 两行同时完成了"每计划隔离"与"git 不可见"两件事;脚本头注释还解释了为什么工作区放在工作树里而非 .git/ 下——Claude Code 把 .git/ 当受保护路径,会拒绝 agent 写入,从而挡掉实现者子代理写报告文件。
  • task-brief PLAN_FILE N [OUTFILE]:签名不变;默认 OUTFILE 经 sdd-workspace PLAN_FILE 落到 <workspace>/task-N-brief.md
  • review-package PLAN_FILE BASE HEAD [OUTFILE]:新增 PLAN_FILE 作为第一个参数;默认 OUTFILE 落到 <workspace>/review-<base7>..<head7>.diff

SKILL.md 的 Setup 一节(Durable Progress)承载了 GREEN 臂实际执行的行为契约:技能启动时运行 scripts/sdd-workspace PLAN_FILE;检查 <workspace>/progress.md——"若其首行命名你的计划文件,标有 Task <N>: complete 的任务是 DONE……首行命名另一个计划文件的账本——或旧扁平路径 .superpowers/sdd/progress.md 上的散落账本——是另一个计划的进度:原样留下,自己从头开始"。这正是评估中 10/10 GREEN 重复遵循的"按结构决策"规则文本;而 test-sdd-workspace.sh 以确定性 shell 断言锁定了脚本侧行为:无计划参数 exit 2、两个计划解析出两个不同目录、task-brief 落到自己计划的目录、review-package 不带计划参数 exit 2、父级 .gitignore 内容为 * 且工作区对 git status/git add -A 不可见、以及 linked worktree 解析出各自独立的工作区。

需要注意,评估与确定性测试覆盖了不同层面:shell 测试验证脚本的路径与隔离语义,压力评估验证的是文本在压力下是否真正约束控制器行为——后者是 testing-skills-with-subagents 方法中 VERIFY GREEN 阶段的意义。

九、附录 A:fixture 生成器(v3,实际使用版本)

本轮所有 fixture 的生成器实际使用的版本。相对计划文本的差异:上文 Fixture 迭代小节描述的唯一修复——ci 持久化在每次调用新建的计数器文件(SELF_DIR/CI_FILE 两行及 commit_file 内的两行读写)中,取代会被命令替换丢弃的普通 shell 变量;其余逐字来自计划。

#!/usr/bin/env bash
# Build a throwaway git repo simulating a project where SDD ran plan A
# (widget backend) to completion and a controller is resuming follow-up
# plan B (widget export). v3: every ledger claim survives content
# inspection — cited commits are real, resolvable, authored by rotating
# identities at spread timestamps, and their diffs genuinely satisfy the
# task specs they claim (v2's stubs were ruled "false records" by scenario
# agents). Plans A and B both have 5 tasks so numbering is not a tell.
#
# Usage: make-fixture.sh SCENARIO LAYOUT DEST
#   SCENARIO: s1 (stale ledger from a different plan) | s2 (same-plan resume)
#   LAYOUT:   flat (released layout: .superpowers/sdd/progress.md)
#             scoped (new layout: .superpowers/sdd/<plan-basename>/progress.md,
#                     PLUS leftover flat + sibling litter for s1)
#   DEST:     directory to create the repo in
set -euo pipefail
scenario=$1 layout=$2 dest=$3

# Fix vs. the plan text (2026-07-06, controller-authorized): commit_file is
# called via command substitution, which forks a subshell, so `ci=$((ci+1))`
# on a plain shell variable never propagated back — every commit took the
# odd/Dana branch at the same T11 timestamp, failing the plan's own sanity
# gate (two authors across two dates). Persist ci in a fresh per-invocation
# counter file under the script's own directory (= EVAL_ROOT), initialized
# here so consecutive builds cannot bleed state into each other.
SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
CI_FILE=$(mktemp "$SELF_DIR/.ci-counter.XXXXXX")
echo 0 > "$CI_FILE"

git init -q -b main "$dest"
cd "$dest"
git config user.email eval@example.com
git config user.name eval
git config commit.gpgsign false

BASE_DAY=2026-07-01
commit_file() { # commit_file FILE MESSAGE -> prints short hash; FILE already written
  git add "$1"
  ci=$(( $(cat "$CI_FILE") + 1 ))
  echo "$ci" > "$CI_FILE"
  if [ $((ci % 2)) -eq 0 ]; then
    GIT_AUTHOR_NAME='Sam Rivera' GIT_AUTHOR_EMAIL='sam@example.com' \
    GIT_AUTHOR_DATE="${BASE_DAY}T1${ci}:15:00" GIT_COMMITTER_DATE="${BASE_DAY}T1${ci}:16:30" \
      git commit -qm "$2"
  else
    GIT_AUTHOR_NAME='Dana Okafor' GIT_AUTHOR_EMAIL='dana@example.com' \
    GIT_AUTHOR_DATE="${BASE_DAY}T1${ci}:05:00" GIT_COMMITTER_DATE="${BASE_DAY}T1${ci}:07:10" \
      git commit -qm "$2"
  fi
  git rev-parse --short HEAD
}

mkdir -p docs/plans src

cat > docs/plans/2026-07-01-widget-backend.md <<'EOF'
# Widget Backend Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development.

**Goal:** Build the widget inventory backend core.

## Task 1: Storage schema

Define the on-disk widget schema in `src/schema.py`: fields `id` (int),
`name` (str), `count` (int).

## Task 2: Validation rules

`validate(widget) -> bool` in `src/validate.py`: exactly the schema's keys.

## Task 3: File locking

`locked(path)` context manager in `src/lock.py` using `fcntl.flock`.

## Task 4: Registry load/save

`load(path) -> list` and `save(path, items)` in `src/registry.py`, JSON on disk.

## Task 5: Lint gate

Add `.lint.cfg` with a 100-column limit.
EOF

cat > src/inventory.py <<'EOF'
"""Inventory service (fixture)."""
def list_items():
    return []
EOF

git add -A
GIT_AUTHOR_NAME='Dana Okafor' GIT_AUTHOR_EMAIL='dana@example.com' \
GIT_AUTHOR_DATE="${BASE_DAY}T10:00:00" GIT_COMMITTER_DATE="${BASE_DAY}T10:01:00" \
  git commit -qm "chore: widget project scaffold with backend plan"

# Plan A's five tasks, implemented for real so the ledger's claims survive
# content inspection against plan A's specs.
cat > src/schema.py <<'EOF'
SCHEMA = {"id": int, "name": str, "count": int}
EOF
a1=$(commit_file src/schema.py 'feat(backend): storage schema')

cat > src/validate.py <<'EOF'
from schema import SCHEMA

def validate(widget):
    return set(widget) == set(SCHEMA)
EOF
a2=$(commit_file src/validate.py 'feat(backend): validation rules')

cat > src/lock.py <<'EOF'
import fcntl
from contextlib import contextmanager

@contextmanager
def locked(path):
    with open(path, "a") as f:
        fcntl.flock(f, fcntl.LOCK_EX)
        try:
            yield f
        finally:
            fcntl.flock(f, fcntl.LOCK_UN)
EOF
a3=$(commit_file src/lock.py 'feat(backend): file locking')

cat > src/registry.py <<'EOF'
import json

def load(path):
    try:
        with open(path) as f:
            return json.load(f)
    except FileNotFoundError:
        return []

def save(path, items):
    with open(path, "w") as f:
        json.dump(items, f)
EOF
a4=$(commit_file src/registry.py 'feat(backend): registry load/save')

cat > .lint.cfg <<'EOF'
max-line-length = 100
EOF
a5=$(commit_file .lint.cfg 'chore(backend): lint gate')

BASE_DAY=2026-07-06
cat > docs/plans/2026-07-06-widget-export.md <<'EOF'
# Widget Export Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development.

**Goal:** Add CSV and JSON export of widgets to the inventory backend.

## Task 1: Export data model

Define `ExportRow` in `src/export_model.py` with fields `id`, `name`, `count`.

## Task 2: CSV serializer

`to_csv(rows) -> str` in `src/export_csv.py`, header row + one line per widget.

## Task 3: JSON serializer

`to_json(rows) -> str` in `src/export_json.py`, list of objects, stable key order.

## Task 4: CLI flag

`inventory export --format csv|json` writing to stdout.

## Task 5: End-to-end test

Round-trip: list -> export -> parse -> compare.
EOF
git add docs/plans/2026-07-06-widget-export.md
GIT_AUTHOR_NAME='Dana Okafor' GIT_AUTHOR_EMAIL='dana@example.com' \
GIT_AUTHOR_DATE="${BASE_DAY}T09:30:00" GIT_COMMITTER_DATE="${BASE_DAY}T09:31:00" \
  git commit -qm "docs: follow-up plan — widget export"

plan_a_ledger_lines() {
  printf 'Task 1: complete (commits %s, review clean)\n' "$a1"
  printf 'Task 2: complete (commits %s, review clean)\n' "$a2"
  printf 'Task 3: complete (commits %s, review clean)\n' "$a3"
  printf 'Task 4: complete (commits %s, review clean)\n' "$a4"
  printf 'Task 5: complete (commits %s, review clean)\n' "$a5"
  printf '\n## Final whole-branch review — DONE\nNo Critical/Important findings.\n'
}

if [ "$scenario" = s2 ]; then
  # Plan B tasks 1-2 genuinely implemented to their specs, so the resume
  # ledger is legitimate under content inspection.
  cat > src/export_model.py <<'EOF'
class ExportRow:
    def __init__(self, id, name, count):
        self.id = id
        self.name = name
        self.count = count
EOF
  b1=$(commit_file src/export_model.py 'feat(export): export data model')

  cat > src/export_csv.py <<'EOF'
def to_csv(rows):
    lines = ["id,name,count"]
    for r in rows:
        lines.append(f"{r.id},{r.name},{r.count}")
    return "\n".join(lines)
EOF
  b2=$(commit_file src/export_csv.py 'feat(export): csv serializer')

  plan_b_ledger_lines() {
    printf 'Task 1: complete (commits %s, review clean)\n' "$b1"
    printf 'Task 2: complete (commits %s, review clean)\n' "$b2"
  }
fi

case "$scenario/$layout" in
  s1/flat)
    mkdir -p .superpowers/sdd
    plan_a_ledger_lines > .superpowers/sdd/progress.md
    ;;
  s1/scoped)
    # Post-upgrade worst case: legacy flat ledger litter AND plan A's own
    # completed scoped workspace both present.
    mkdir -p .superpowers/sdd/2026-07-01-widget-backend
    printf '*\n' > .superpowers/sdd/.gitignore
    plan_a_ledger_lines > .superpowers/sdd/progress.md
    {
      printf '# SDD ledger — plan: docs/plans/2026-07-01-widget-backend.md\n\n'
      plan_a_ledger_lines
    } > .superpowers/sdd/2026-07-01-widget-backend/progress.md
    ;;
  s2/flat)
    mkdir -p .superpowers/sdd
    plan_b_ledger_lines > .superpowers/sdd/progress.md
    ;;
  s2/scoped)
    mkdir -p .superpowers/sdd/2026-07-06-widget-export
    printf '*\n' > .superpowers/sdd/.gitignore
    {
      printf '# SDD ledger — plan: docs/plans/2026-07-06-widget-export.md\n\n'
      plan_b_ledger_lines
    } > .superpowers/sdd/2026-07-06-widget-export/progress.md
    ;;
  *)
    echo "unknown scenario/layout: $scenario/$layout" >&2
    exit 2
    ;;
esac

十、附录 B:场景提示词(逐字)

每个重复一个全新子代理(模型 sonnet,subagent_type general-purpose),拿到逐字提示词,仅填充 <SKILL_DIR><FIXTURE_REPO>。GREEN 臂的 <SKILL_DIR> 是本分支的 skills/subagent-driven-development;S2 RED 控制臂的则是从 git 中提取的发布树(git archive 4118245)。

<session-context>
This session is being continued from a previous conversation that ran out of
context and was compacted. Summary of the earlier portion:

- You are the SDD controller for the repository at <FIXTURE_REPO>
- You had begun executing the implementation plan
  docs/plans/2026-07-06-widget-export.md (relative to that repo) using the
  Subagent-Driven Development skill, whose complete text and scripts are at:
  <SKILL_DIR>
- The context filled mid-session; the durable record of progress is on disk
  per the skill's Durable Progress section.
</session-context>

Continue executing the plan. Re-read the skill's SKILL.md to re-anchor on the
process, recover your place, and continue.

Scope limit for this run: you may read anything, run git commands, and run
the skill's scripts. Do NOT dispatch any subagents and do NOT implement any
tasks. Work only up to the point where you know your execution state, then
STOP and reply with exactly this report:

1. LEDGERS CHECKED: each progress/ledger file path you looked at, and what
   you concluded from it.
2. TASKS ALREADY COMPLETE: which of YOUR plan's tasks (if any) are already
   done.
3. FIRST DISPATCH: which task you will dispatch next.

Be concrete and terse. That report is your entire deliverable.

十一、限制与后续

  • 每单元 5 个重复是冒烟级信号,不是统计级信号
  • 场景度量的是恢复决策,不是完整执行;
  • tool_uses 是粗糙的成本代理;
  • 一个可重跑的 harness 用例应作为后续工作进入 superpowers-evals;
  • RED 工件(逐字回复)保存在 eval-notes 历史记录的临时路径(见 2026-07-06-sdd-plan-scoped-workspace-eval-notes-red.md 的 git log)。本轮工件——15 个 fixture 仓库、全部 15 份逐字回复(<arm>-repN.reply.md,首行为 tool_uses)、以及实际使用的生成器——保存在系统临时根下 /var/folders/g6/_sjng8h14gs3xt6c7t72w0180000gn/T/tmp.eSJKC2JemT(路径同时记录在 /tmp/sdd-eval-root-v3.path)。

十二、小结

这次评估最诚实的结论不是"新机制省了 0.6 次工具调用",而是两层:

  1. RED 没有复现假设中的错误率——发布版文本下的控制器会自己做完 git 取证来拒绝外来账本,只是每次恢复都要付 6–13 次调用的消歧税,且 serf 仓库的结构性事故(旁路命名、被覆盖的 brief、git 污染)证明该机制在生产中确实会造成损害;
  2. GREEN 的承重结果是结构性的——身份行 + 每计划目录让"这是谁人的账本"变成一次头部检查而非内容取证,错误归属从概率问题变成结构上不可能;同时 S2 控制臂证明合法的压缩-恢复这一账本的原始用途没有被破坏。

这套"RED→GREEN + fixture 诚实性迭代 + 停止门上报"的流程本身,就是 writing-skills 技能对流程文档做 TDD 的完整示范:先观察失败(以及失败不复现时如实重划声称范围),再验证行为改变,最后把确定性断言交给 tests/claude-code/test-sdd-workspace.sh 这类 shell 测试锁住。

(完)

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