Superpowers SDD 计划级工作区:用结构化身份根治 Subagent-Driven Development 的跨计划账本冲突
本文基于 superpowers 仓库的设计规格 2026-07-06-sdd-plan-scoped-workspace.md,完整解读 SDD(Subagent-Driven Development)持久进度工作区从"扁平单目录"演进为"每计划一个独立目录"(.superpowers/sdd/<plan-slug>/)的设计动机、三层修复方案、配套脚本接口与确定性测试/RED→GREEN 评估方法。读完后你能理解:为什么账本(ledger)缺少计划身份会在多计划工作树中导致任务被静默跳过、如何通过 sdd-workspace / task-brief / review-package 三个脚本实现结构性隔离,以及这套改动是如何用可复现的压力测试验证"修复没有破坏合法恢复"的。
背景:持久进度工作区的设计初衷与暴露的缺陷
SDD 技能(skills/subagent-driven-development/SKILL.md)的核心循环是:为计划中的每个任务派发一个全新实现者子代理,任务完成后做一次任务评审,全部任务完成后再做整分支评审。这个流程有一个昂贵的失败模式:控制器(controller)在会话上下文压缩(compaction)后丢失"做到哪了"的记忆,从而把已完成的任务重新派发一遍。为此,技能引入了持久进度工作区 .superpowers/sdd/(v6.0.0/v6.0.3 引入),其中 progress.md 账本记录每个任务的完成状态,SKILL.md 要求控制器在技能启动时检查账本、跳过已标记完成的任务、从第一个未完成的任务恢复。
但规格文档指出了两个结构性缺陷:工作区没有计划身份,也没有生命周期终点。
- 所有产物都只用裸任务编号命名(
progress.md、task-N-brief.md、task-N-report.md),账本里没有记录"这个账本属于哪个计划文件"; - 没有任何指令负责删除工作区,过期状态会永久残留并不断累积。
后果是:同一个工作树(worktree)中执行后续计划的新会话,会把上一个计划的账本当成自己的进度,按技能的字面指令跳过整批任务。
实际观察到的失败(serf 仓库,2026-06-22 → 2026-07-05)
规格记录了三类真实发生的事故,这是本次设计的事实基础:
- 跨计划冲突,只能临时绕开:
cc-plugin-marketplaces工作树在一次会话周期内累积了 68 个文件、横跨三个计划。第二个计划(P2)的控制器被迫自创progress-p2.md和p2-task-N-report.md来躲避 P1 的账本;而 P2 的 brief 文件却悄悄覆盖了 P1 在默认路径上的同名文件;现场还留下一个被遗弃的progress-p3.md存根。 - Git 污染,发生三次:SDD 的临时文件被提交进版本库,需要两次清理提交(
8305e340d、c966261a5);serf 主分支至今仍跟踪着三个产物,其中包括一份在另一台机器上写成的报告——它现在会在每个新工作树里"显形"。一个后续计划的 task-1 报告还覆盖了无关的已跟踪文件,留下永久性的git status噪音。 - 自愈式
.gitignore只在脚本运行时才写入:被观察到有控制器是手工追加账本的,从未创建.gitignore;而一旦文件被 git 跟踪,gitignore 就完全失效。
根因判断
规格的根因表述非常凝练:身份在数据中无处安放,正确性依赖一个没有触发器的清理动作。任何只靠"计划结束时清理"的方案,恰好会在账本本来要存活下来的崩溃/压缩场景里失效。因此修复必须是结构性的——身份要写进路径和文件本身,而不是依赖纪律。
设计一:每计划一个工作区目录(结构性身份)
核心改动:工作区从 .superpowers/sdd/ 变为 .superpowers/sdd/<plan-slug>/,其中 <plan-slug> 是计划文件去掉 .md 扩展名后的文件名(basename)。superpowers 的计划文件本来就遵循带日期的 kebab-case 命名(如 2026-07-04-plugin-marketplaces-p1-backend-core),所以 slug 天然稳定且可区分。不同计划的产物从此不可能再互相覆盖;一个过期的兄弟目录是"惰性的"——因为没有任何指令会指向它。
sdd-workspace:工作区位置的唯一事实来源
解析工作区的脚本是 scripts/sdd-workspace。它的职责是"解析并创建某计划的产物目录,把绝对路径打印到 stdout"。核心实现逻辑(节选):
# Usage: sdd-workspace PLAN_FILE
set -euo pipefail
if [ $# -ne 1 ]; then
echo "usage: sdd-workspace PLAN_FILE" >&2
exit 2
fi
plan=$1
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
slug=$(basename "$plan" .md)
[ -n "$slug" ] && [ "$slug" != "." ] && [ "$slug" != ".." ] \
|| { echo "cannot derive a workspace name from: $plan" >&2; exit 2; }
root=$(git rev-parse --show-toplevel)
base="$root/.superpowers/sdd"
dir="$base/$slug"
mkdir -p "$dir"
printf '*\n' > "$base/.gitignore"
cd "$dir" && pwd
从源码可以确认几个设计决策:
- 参数契约:缺少参数、计划文件不存在、或 slug 剥离
.md后为空,一律以 exit 2 报错;成功时打印计划目录的绝对路径。 - 自愈式
.gitignore放在父级:脚本每次运行时向.superpowers/sdd/.gitignore(注意是父目录,不是各计划目录)写入一行*,使所有计划的工作区整体对git status与git add -A不可见,且不需要修改任何已跟踪文件。这同时回答了规格中"为什么.gitignore要放在父级"的问题:只要任何脚本跑过一次,全部兄弟目录都被忽略。 - 为什么放在工作树而不是
.git/下:脚本头注释明确说明——Claude Code 把.git/视为受保护路径并拒绝代理写入,会导致实现者子代理无法写报告文件;放在工作树里配合自忽略的.gitignore才能两全。 - 单一事实来源:
task-brief和review-package都通过调用sdd-workspace来解析目录(见下),杜绝三个脚本各自硬编码路径产生漂移。
三个脚本的新接口
规格为 skills/subagent-driven-development/scripts/ 下三个脚本定义了新接口,当前仓库中三者均已落地:
| 脚本 | 签名 | 默认输出位置 |
|---|---|---|
| sdd-workspace | sdd-workspace PLAN_FILE |
打印 <repo-root>/.superpowers/sdd/<plan-slug>/ 绝对路径 |
| task-brief | task-brief PLAN_FILE N [OUTFILE] |
<workspace>/task-N-brief.md |
| review-package | review-package PLAN_FILE BASE HEAD [OUTFILE] |
<workspace>/review-<base7>..<head7>.diff |
task-brief 的签名不变,只是默认 OUTFILE 经由 sdd-workspace 落到本计划的目录下;它用 awk 按 Task N 标题从计划文件中抽取该任务全文,保证实现者"一次读取"就能拿到完整需求,任务文本不必穿过控制器上下文。review-package 则新增了 PLAN_FILE 作为第一参数(这是向后不兼容的签名变化),生成包含提交列表、--stat 摘要和 -U10 完整 diff 的评审包,并按提交范围命名,使修复轮次的再评审天然得到不同的文件。
规格明确声明:不为旧的扁平布局保留兼容路径。理由是脚本与 SKILL.md 在同一个插件版本中一起发布,且没有别的东西调用这些脚本——这一点在规格中被标注为"已明确确认:无向后兼容处理"。
设计二:账本首行显式写明所属计划(手工账本的兜底)
目录隔离防住了"走脚本"的控制器,但现实中存在不跑脚本、手工写账本的控制器(在 serf 的 ask_user 会话中被实际观察到)。因此账本文件 <workspace>/progress.md 在创建时的第一行必须是:
# SDD ledger — plan: docs/superpowers/plans/<plan-file>.md
相应地,SKILL.md 的启动检查被改写为"计划作用域 + 条件守卫"。当前 SKILL.md 的 Setup 一节(约 L122–L140)的实际表述是正向的配方式指令而非禁令:
- 技能启动时运行
scripts/sdd-workspace PLAN_FILE,打印出本计划的 git 忽略目录,它是本计划全部产物(账本、brief、报告、评审包)的家;"另一个计划的目录永远轮不到你读或写"。 - 检查
<workspace>/progress.md:首行写明你的计划文件的账本中,带Task <N>: complete行的任务是 DONE,不要重新派发;最后一个任务是修复轮次的则说明卡在修复循环中间,从下一轮恢复。首行写明的是另一个计划文件、或残留在旧扁平路径.superpowers/sdd/progress.md的账本——那是别的计划的进度:原地保留,自己新开一份。
这个守卫恰好覆盖两类情况:绕过脚本手工记账的控制器,以及升级前遗留在旧扁平路径上的脏数据。规格还保留了一条方法论约束:守卫的具体措辞服从评估结果(见下文 Evaluation),只为 RED 基线中真实观测到的失败追加计数器,不做臆防。
设计三:工作区生命周期终点(是卫生问题,不是正确性问题)
规格刻意区分了"正确性"与"卫生":目录隔离已经保证了正确性,删除只是收尾。
时机是:最终整分支评审干净、且其修复波次(如有)已合并——在移交给 finishing-a-development-branch 技能之前——控制器删除自己计划的工作区目录(rm -rf "$WORKSPACE")。理由:工作的记录此刻已经在 git 历史里,账本的存在意义(计划中途的压缩恢复)已经用尽。
SKILL.md 的 Finish 一节(约 L416–L421)落地了这一条:"当最终整分支评审干净且修复已合并,删除本计划的工作区——记录现在在 git 里。兄弟目录属于其他计划,不要动。"这条"不碰兄弟目录"规则同样保护了被观察到的一类人为留存物:跨计划交接文件(如 WAVE1-HANDOFF.md)直接放在 .superpowers/sdd/ 根下,不属于任何计划的清理范围。
设计四:SKILL.md 的触点清单
规格列出了 SKILL.md 中需要改动的位置,当前仓库中均已体现:
- Durable Progress / Setup:工作区解析走
sdd-workspace PLAN_FILE;账本检查限定在本计划工作区内;账本创建格式含计划身份首行;失配守卫;完成时删除;git clean -fdx危险提示更新为新路径(工作区是 git 忽略的临时区,被git clean -fdx清掉后从git log恢复)。 - Handling Implementer Status / Constructing Reviewer Prompts / File Handoffs / Red Flags / Example Workflow:脚本调用全部更新为新签名
review-package PLAN_FILE BASE HEAD。SKILL.md 的 Example Workflow 一节现在演示了完整链路:sdd-workspace解析 →task-brief生成 →review-package PLAN_FILE BASE HEAD出包 → 账本记账 → 最终删除工作区。 - 规格验证过 implementer-prompt.md 与 task-reviewer-prompt.md 不含任何工作区路径,无需改动。
- Red Flags 表格只在 RED 基线显示出"结构修复 + 守卫文本"都关不掉的失败时才追加。
明确不做的事(Out of scope)
规格用一整节划定了边界,避免范围蔓延:
- 不改动
finishing-a-development-branch或任何其他技能; - 除现有的父级
.gitignore外,不引入针对.superpowers/提交的其他 git 级防护; - 不回头清理 serf 仓库的历史污染(单独立项跟进);
- 不做旧布局迁移或回退读取。
确定性测试:test-sdd-workspace.sh
规格要求扩展 tests/claude-code/test-sdd-workspace.sh,当前该文件已实现全部断言,并在临时目录中的干净 git 仓库上运行,覆盖:
- 参数校验:
sdd-workspace无参数、或缺失计划文件,均以 exit 2 报错; - 每计划解析:
plan-a.md与plan-b.md解析到<repo>/.superpowers/sdd/plan-a与plan-b两个互不相同的已存在目录; - 自忽略验证:
.superpowers/sdd/.gitignore内容为*;写入artifact.md后git status --porcelain不出现.superpowers;git add -A之后暂存区里也没有它; - 产物落位:
task-brief plan-a.md 1的输出路径必须是<repo>/.superpowers/sdd/plan-a/task-1-brief.md; review-package新签名:review-package plan-a.md HEAD~1 HEAD写出<repo>/.superpowers/sdd/plan-a/review-*.diff;不带 PLAN_FILE 的旧调用方式以 exit 2 报错;显式 OUTFILE 参数被尊重;- 链接工作树隔离:
git worktree add出的第二个工作树解析出自己根下的.superpowers/sdd/plan-a(与主工作树不同路径),且同样对git status不可见——这条断言重新锚定到新布局上,确认"每工作树、每计划"的双维度隔离。
规格还要求对既有的 test-subagent-driven-development.sh / -integration.sh 做旧路径期望审计(初次 grep 未发现,审计本身是任务门禁)。
RED → GREEN 评估:为什么"结构修复"在没有复现假设失败的情况下仍然值得发布
这部分是本规格最有方法论价值的内容,结果完整记录在 2026-07-06-sdd-plan-scoped-workspace-eval-results.md。
方法
压力场景以全新 sonnet 子代理会话跑在临时目录中的夹具仓库上(绝不在本工作树内),采用"压缩后恢复"的会话框架,每次重复人工评分;被测输出是控制器的恢复决策(不真正派发实现者)。
RED 基线意外:假设的失败没有复现
最初假设的失败——控制器盲目把陈旧的外部账本当作自己的进度——没有复现:跨三种会话框架(新会话、可能已恢复、带技能"信任账本"指令的忠实压缩后恢复)共 25/25 次重复,控制器都通过法证手段核对了账本引用的提交与 git 历史及计划文件,拒绝外部账本并从 Task 1 开始——代价是每次恢复花 6–13 次工具调用做跨计划法证。为诚实证明这一点,烧掉了两代夹具:v1(伪造哈希被一眼识破)、v2(桩实现被判定为虚假的"评审通过"记录),S2 对照组两次都失败。
因此规格重新界定(re-scope)了主张:改动凭结构性记录(冲突、临时旁路命名、被覆盖的 brief、git 污染——都来自 serf 仓库)加上实测的鉴别成本发布,由维护者签核代替 writing-skills 方法论中"失败基线"的要求。重新界定后的门禁:
- S1 GREEN(要求 5/5):夹具同时存在旧扁平账本残留和计划 A 的已完成计划级工作区(升级后最坏情况);恢复中的控制器在计划 B 上直接解析自己的计划级工作区并从 Task 1 开始;每次重复记录
tool_uses,与 RED 基线(7/13/9/10/6)对比成本差。 - S2 RED 对照(要求 ≥4/5)与 S2 GREEN(要求 5/5):使用"诚实"的 v3 夹具(引用的提交真实实现了各任务的规格、轮换作者、时间戳分散),验证合法的同计划恢复——识别任务 1–2 已完成、派发 Task 3。这一条保护的是账本的原始用途:修复不能破坏它,对照组同时验证夹具本身的可信度。
实际结果
评估文档的结果表:
| 组 | 被测文本 | 夹具 | 结果 |
|---|---|---|---|
| S1 RED | 发布版 | v1+v2+probe,三种框架 | 15/15 拒绝误采;平均 9.0 次工具调用做跨计划法证 |
| S1 GREEN | 本分支 | v3 scoped | 5/5,全部经结构判定(工作区 + 身份首行),无一靠提交内容法证 |
| S2 RED(对照) | 发布版 | v3 flat | 5/5,验证夹具有效 |
| S2 GREEN | 本分支 | v3 scoped | 5/5,合法恢复未被破坏 |
评估文档对成本对比的处理值得注意:S1 GREEN 的原始工具调用数(均值 9.6)并没有低于 RED probe(均值 9.0),文档对此做了诚实披露——GREEN 夹具含有更多陈旧物(三处账本位置),且判定方式的本质区别在于:probe 轮的控制器必须靠跨计划提交/计划文件法证来断定"账本是谁的",GREEN 轮的控制器按结构判定(解析自己的工作区、核对身份首行),误归因在新布局下从机制上不可能发生。这才是承重结论,而非调用数下降。
评估还发现并披露了夹具生成器自身的 bug:ci 计数器在命令替换子shell中被自增导致不生效,使所有提交塌缩到同一作者、同一时间戳——恰好是使 v2 对照组失效的"夹具制造的历史"破绽;被计划文本中 Step 1 的自检门禁(引用哈希可解析 + 两个作者跨两天)在场景运行前抓住,用"把计数器持久化到文件"的一行级修复解决。
风险与接受理由
规格对三类已知风险给出了明确处置:
- 不同目录下同名计划文件的 slug 冲突:接受。计划文件名按惯例带日期前缀,实践中同 basename 即同一计划(此时"恢复"正是期望行为)。
- 控制器完全绕过脚本、全手工记账:缓解手段是账本身份首行守卫;S1 评估测量文本指令是否真正约束行为。
- 计划完成后工作区残留、又从头重跑同一计划:账本合法地属于同一计划,"恢复而非重启"就是设计行为;分叉场景由既有的
git log交叉核对(技能原文已有)覆盖。
参考文件
| 路径 | 内容 |
|---|---|
| docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace.md | 本文主体:设计规格(问题、根因、四层设计、边界、测试与风险) |
| docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace-eval-results.md | RED→GREEN 评估完整记录:夹具迭代、逐字回复引用、成本表 |
| docs/superpowers/plans/2026-07-06-sdd-plan-scoped-workspace.md | 该设计的实施计划 |
| skills/subagent-driven-development/scripts/sdd-workspace | 工作区解析脚本(唯一事实来源) |
| skills/subagent-driven-development/scripts/task-brief | 任务 brief 抽取脚本 |
| skills/subagent-driven-development/scripts/review-package | 评审包生成脚本(新签名) |
| skills/subagent-driven-development/SKILL.md | SDD 技能定义,含新守卫文本与 Example Workflow |
| tests/claude-code/test-sdd-workspace.sh | 工作区脚本的确定性 shell 测试套件 |
这套设计给出的通用启示是:当持久化状态会被多个"逻辑实体"(此处是计划)共享时,身份必须编码在路径结构里并由文件首行自证,清理策略只能作为卫生手段而非正确性前提;同时,一个无法复现的"假设失败"并不妨碍基于真实结构性事故和可测量成本发布改动——前提是像本规格这样,用对照组守住原机制仍有效的回归线。
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 StartedRust0624
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