docs(plans): SDD plan-scoped workspace implementation plan

Five tasks: RED baseline eval (writing-skills Iron Law — before any
skill edit), plan-scoped scripts via TDD, SKILL.md durable-progress
rewrite with mismatch guard and end-of-plan cleanup, GREEN eval with
refinement loop, consistency sweep. Eval = 5 fresh sonnet subagents per
scenario per arm, hand-scored.
This commit is contained in:
Jesse Vincent
2026-07-06 11:14:53 -07:00
parent 9e557bd1c8
commit 6cbd2bd26c

View File

@@ -0,0 +1,987 @@
# SDD Plan-Scoped Workspace Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make SDD's durable-progress workspace plan-scoped (`.superpowers/sdd/<plan-basename>/`) with a self-identifying ledger and end-of-plan cleanup, so a follow-up plan can never mistake a previous plan's ledger for its own progress.
**Architecture:** Three shell scripts in `skills/subagent-driven-development/scripts/` gain plan awareness (`sdd-workspace PLAN_FILE` becomes the single source of truth for the per-plan directory); SKILL.md's Durable Progress section is rewritten around the plan-scoped workspace with a mismatch guard keyed to the ledger's first line; a RED→GREEN pressure-test eval (writing-skills methodology) proves the old text fails and the new text binds. Spec: `docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace.md`.
**Tech Stack:** bash, shellcheck (via `scripts/lint-shell.sh`), repo shell-test conventions (`tests/claude-code/test-sdd-workspace.sh`), subagent pressure-test evals.
## Global Constraints
- Execute tasks in order 1 → 5. Task 1 (RED baseline) MUST complete before Task 3 touches SKILL.md — no skill edit without a captured failing baseline (writing-skills Iron Law).
- No backward-compatibility code paths: no legacy-layout reads, no dual-signature support in scripts. Scripts and SKILL.md ship together.
- Eval fixtures and scenario workdirs live under `mktemp -d` and are deleted afterward; they are NEVER committed and NEVER created inside this repository checkout.
- Eval scenario subagents: model `sonnet`, subagent_type `general-purpose`, one fresh subagent per rep, prompt used VERBATIM as given (fill only the `<PLACEHOLDER>` paths). Do not add hints about ledgers, staleness, or the fix.
- Every shell file you create or modify must pass `bash scripts/lint-shell.sh <file>` (shellcheck 0.11.0 is installed).
- Match SKILL.md's existing prose conventions: two-space bullet continuation indent, em-dashes (`—`), sentence-per-line wrapping style.
- Commit at the end of every task with the message given in the task.
---
### Task 1: RED baseline eval — capture the failure with the released skill text
**Files:**
- Create (temp only, not committed): `$EVAL_ROOT/make-fixture.sh`, `$EVAL_ROOT/red/` working files
- Create: `docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace-eval-notes-red.md` (interim RED evidence; folded into the final results doc in Task 4)
**Interfaces:**
- Consumes: `skills/subagent-driven-development/` at current HEAD (pre-edit text).
- Produces: RED scoring table + verbatim failure quotes that Task 3 uses to tune wording and Task 4 folds into the final results doc. Also the fixture generator script content, reused verbatim in Task 4.
- [ ] **Step 1: Create the eval root and the fixture generator**
```bash
EVAL_ROOT=$(mktemp -d)
echo "$EVAL_ROOT" > /tmp/sdd-eval-root.path # so later steps/tasks can find it
mkdir -p "$EVAL_ROOT/red"
cat > "$EVAL_ROOT/make-fixture.sh" <<'FIXTURE'
#!/usr/bin/env bash
# Build a throwaway git repo simulating a project where SDD ran plan A to
# completion and a controller is now starting follow-up plan B.
#
# 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
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
mkdir -p docs/plans src
cat > src/inventory.py <<'EOF'
"""Inventory service (fixture)."""
def list_items():
return []
EOF
cat > docs/plans/2026-07-01-inventory-backend.md <<'EOF'
# Inventory Backend Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development.
**Goal:** Build the inventory backend core.
## Task 1: Storage schema
## Task 2: Atomic writes
## Task 3: File locking
## Task 4: Registry load/save
## Task 5: Git fetcher
## Task 6: Source model
## Task 7: Marketplace refresh
## Task 8: Catalog parsing
## Task 9: Version comparison
## Task 10: Validation
## Task 11: Source fetch
## Task 12: Install
## Task 13: Upgrade
## Task 14: Remove and enable
## Task 15: List and update-all
## Task 16: Lint gate
## Task 17: Integration pass
EOF
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` with fields `id`, `name`, `count`.
## Task 2: CSV serializer
`to_csv(rows) -> str`, header row + one line per widget.
## Task 3: JSON serializer
`to_json(rows) -> str`, 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 -A
git commit -qm "fixture: inventory project with completed plan A and new plan B"
plan_a_ledger_lines() {
local i
for i in $(seq 1 17); do
printf 'Task %d: complete (commits aaa%04d..bbb%04d, review clean)\n' "$i" "$i" "$i"
done
printf '\n## Final whole-branch review — DONE\nNo Critical/Important findings.\n'
}
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-inventory-backend
printf '*\n' > .superpowers/sdd/.gitignore
plan_a_ledger_lines > .superpowers/sdd/progress.md
{
printf '# SDD ledger — plan: docs/plans/2026-07-01-inventory-backend.md\n\n'
plan_a_ledger_lines
} > .superpowers/sdd/2026-07-01-inventory-backend/progress.md
;;
s2/flat)
mkdir -p .superpowers/sdd
{
printf 'Task 1: complete (commits ccc0001..ddd0001, review clean)\n'
printf 'Task 2: complete (commits ccc0002..ddd0002, review clean)\n'
} > .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'
printf 'Task 1: complete (commits ccc0001..ddd0001, review clean)\n'
printf 'Task 2: complete (commits ccc0002..ddd0002, review clean)\n'
} > .superpowers/sdd/2026-07-06-widget-export/progress.md
;;
*)
echo "unknown scenario/layout: $scenario/$layout" >&2
exit 2
;;
esac
FIXTURE
chmod +x "$EVAL_ROOT/make-fixture.sh"
```
- [ ] **Step 2: Extract the pre-edit skill directory (the text under test)**
```bash
EVAL_ROOT=$(cat /tmp/sdd-eval-root.path)
mkdir -p "$EVAL_ROOT/red/skill"
git archive HEAD -- skills/subagent-driven-development | tar -x -C "$EVAL_ROOT/red/skill"
ls "$EVAL_ROOT/red/skill/skills/subagent-driven-development/SKILL.md"
```
Expected: the SKILL.md path prints (file exists). This snapshot is the RELEASED behavior baseline; Task 3 has not run yet so HEAD's copy is the released text.
- [ ] **Step 3: Build 10 RED fixtures (5 reps × scenarios S1, S2, `flat` layout)**
```bash
EVAL_ROOT=$(cat /tmp/sdd-eval-root.path)
for i in 1 2 3 4 5; do
"$EVAL_ROOT/make-fixture.sh" s1 flat "$EVAL_ROOT/red/s1-rep$i"
"$EVAL_ROOT/make-fixture.sh" s2 flat "$EVAL_ROOT/red/s2-rep$i"
done
ls "$EVAL_ROOT/red"
```
Expected: directories `s1-rep1..5`, `s2-rep1..5`, plus `skill/`.
- [ ] **Step 4: Run the 10 scenario reps as fresh subagents**
Dispatch one fresh subagent per rep (model `sonnet`, subagent_type `general-purpose`). Use this prompt VERBATIM, substituting only `<SKILL_DIR>` = `$EVAL_ROOT/red/skill/skills/subagent-driven-development` and `<FIXTURE_REPO>` = the rep's fixture path. The S1 and S2 prompts are identical — only the fixture differs.
```
You are a controller about to execute an implementation plan using the
Subagent-Driven Development skill. The complete skill (SKILL.md plus its
scripts/ directory) is at: <SKILL_DIR>
Read <SKILL_DIR>/SKILL.md first and follow its process exactly.
The repository you are working in: <FIXTURE_REPO>
Your plan: docs/plans/2026-07-06-widget-export.md (relative to that repo)
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 first.
Be concrete and terse. That report is your entire deliverable.
```
Save each subagent's reply verbatim to `$EVAL_ROOT/red/s1-repN.reply.md` / `$EVAL_ROOT/red/s2-repN.reply.md`.
- [ ] **Step 5: Score every rep by hand against these criteria**
Read each reply in full (do not keyword-grep) and assign PASS/FAIL:
| Scenario | PASS iff | FAIL if |
|---|---|---|
| S1 | Report says first dispatch = plan B **Task 1** AND does not claim any plan-B task is complete. Explicitly identifying the found ledger as another plan's also passes. | Claims any plan-B task complete; resumes at any task other than 1; treats the plan-A ledger as governing its plan ("tasks 15 are done", "resume at task 18", "all tasks complete", etc.) |
| S2 | Report says tasks 12 complete, first dispatch = **Task 3**. | Re-dispatches task 1 or 2; claims 35 complete; ignores the ledger. |
Record a table plus at least one verbatim quote per FAIL (the sentence where the agent adopts the stale ledger — these quotes drive Task 3/4 wording).
- [ ] **Step 6: Apply the gates**
- S1 RED: expected result is FAIL on 1 or more of 5 reps (any failure proves the bug). **If S1 RED passes 5/5, STOP — return BLOCKED** with the replies attached; the human partner must reassess before any skill text changes (no failing test = no edit).
- S2 RED: expected PASS 5/5 (released text handles same-plan resume). If any S2 rep fails, note it — it is baseline data, not a blocker.
- [ ] **Step 7: Write the interim RED evidence file and commit**
Write `docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace-eval-notes-red.md` containing: the scoring table, per-rep one-line outcomes, every FAIL quote verbatim, and the exact `$EVAL_ROOT` paths used (for traceability within this branch's history; the file is interim and gets superseded in Task 4).
```bash
git add docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace-eval-notes-red.md
git commit -m "eval(sdd): RED baseline — released text vs stale-ledger and resume scenarios"
```
---
### Task 2: Plan-scoped workspace scripts (TDD)
**Files:**
- Modify: `skills/subagent-driven-development/scripts/sdd-workspace`
- Modify: `skills/subagent-driven-development/scripts/task-brief`
- Modify: `skills/subagent-driven-development/scripts/review-package`
- Test: `tests/claude-code/test-sdd-workspace.sh` (full rewrite below)
**Interfaces:**
- Consumes: nothing from other tasks.
- Produces: `sdd-workspace PLAN_FILE` → prints `<repo-root>/.superpowers/sdd/<plan-basename-without-.md>` (creates it; maintains `<repo-root>/.superpowers/sdd/.gitignore` containing `*`). `task-brief PLAN_FILE N [OUTFILE]` → default OUTFILE `<workspace>/task-<N>-brief.md`. `review-package PLAN_FILE BASE HEAD [OUTFILE]` → default OUTFILE `<workspace>/review-<base7>..<head7>.diff`. Task 3's SKILL.md text names exactly these signatures.
- [ ] **Step 1: Replace the test file with the plan-scoped expectations**
Overwrite `tests/claude-code/test-sdd-workspace.sh` with exactly:
```bash
#!/usr/bin/env bash
# Tests for the SDD workspace: scripts/sdd-workspace resolves a self-ignoring,
# PER-PLAN working-tree directory for SDD artifacts, and the SDD scripts write
# into their plan's directory.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SDD_SCRIPTS="$REPO_ROOT/skills/subagent-driven-development/scripts"
FAILURES=0
TEST_ROOT=""
pass() { echo " [PASS] $1"; }
fail() {
echo " [FAIL] $1"
FAILURES=$((FAILURES + 1))
}
cleanup() {
if [[ -n "$TEST_ROOT" && -d "$TEST_ROOT" ]]; then
rm -rf "$TEST_ROOT"
fi
}
main() {
echo "=== Test: sdd-workspace ==="
TEST_ROOT="$(mktemp -d)"
trap cleanup EXIT
# Resolve repo to its physical path so string comparisons match the
# helper's output (git rev-parse --show-toplevel resolves symlinks; on
# macOS mktemp lives under /var -> /private/var).
git init -q -b main "$TEST_ROOT/repo"
local repo
repo="$(cd "$TEST_ROOT/repo" && git rev-parse --show-toplevel)"
cat > "$repo/plan-a.md" <<'PLAN'
# Plan A
## Task 1: First thing
Do the first thing.
PLAN
cat > "$repo/plan-b.md" <<'PLAN'
# Plan B
## Task 1: Other thing
Do the other thing.
PLAN
# --- argument validation ---
local rc=0
(cd "$repo" && "$SDD_SCRIPTS/sdd-workspace" >/dev/null 2>&1) || rc=$?
if [[ "$rc" -eq 2 ]]; then
pass "sdd-workspace without a plan errors with exit 2"
else
fail "sdd-workspace without a plan errors with exit 2"
echo " exit: $rc"
fi
rc=0
(cd "$repo" && "$SDD_SCRIPTS/sdd-workspace" no-such-plan.md >/dev/null 2>&1) || rc=$?
if [[ "$rc" -eq 2 ]]; then
pass "sdd-workspace with a missing plan file errors with exit 2"
else
fail "sdd-workspace with a missing plan file errors with exit 2"
echo " exit: $rc"
fi
# --- per-plan resolution ---
local dir_a dir_b
dir_a="$(cd "$repo" && "$SDD_SCRIPTS/sdd-workspace" plan-a.md)"
dir_b="$(cd "$repo" && "$SDD_SCRIPTS/sdd-workspace" plan-b.md)"
if [[ "$dir_a" == "$repo/.superpowers/sdd/plan-a" ]]; then
pass "prints <repo-root>/.superpowers/sdd/<plan-basename>"
else
fail "prints <repo-root>/.superpowers/sdd/<plan-basename>"
echo " got: $dir_a"
fi
if [[ "$dir_a" != "$dir_b" && -d "$dir_a" && -d "$dir_b" ]]; then
pass "two plans resolve to two distinct directories"
else
fail "two plans resolve to two distinct directories"
echo " a: $dir_a"
echo " b: $dir_b"
fi
if [[ -f "$repo/.superpowers/sdd/.gitignore" && "$(cat "$repo/.superpowers/sdd/.gitignore")" == "*" ]]; then
pass "self-ignoring .gitignore created at .superpowers/sdd/ with '*'"
else
fail "self-ignoring .gitignore created at .superpowers/sdd/ with '*'"
fi
printf 'x\n' > "$dir_a/artifact.md"
local status
status="$(cd "$repo" && git status --porcelain)"
# plan-a.md/plan-b.md are intentionally untracked fixture files; only the
# workspace must be invisible.
if [[ "$status" != *".superpowers"* ]]; then
pass "workspace invisible to git status"
else
fail "workspace invisible to git status"
echo " status: $status"
fi
( cd "$repo" && git add -A )
local staged
staged="$(cd "$repo" && git diff --cached --name-only)"
if [[ "$staged" != *".superpowers"* ]]; then
pass "git add -A does not stage the workspace"
else
fail "git add -A does not stage the workspace"
echo " staged: $staged"
fi
# --- task-brief lands in its plan's directory ---
local brief_out brief_path
brief_out="$(cd "$repo" && "$SDD_SCRIPTS/task-brief" plan-a.md 1)"
brief_path="$(printf '%s\n' "$brief_out" | sed -n 's/^wrote \(.*\): [0-9][0-9]* lines$/\1/p')"
if [[ "$brief_path" == "$repo/.superpowers/sdd/plan-a/task-1-brief.md" ]]; then
pass "task-brief writes its brief under the plan's workspace"
else
fail "task-brief writes its brief under the plan's workspace"
echo " got: $brief_path"
fi
# --- review-package takes the plan first and lands in its directory ---
local git_id=(-c user.email=t@example.com -c user.name=t -c commit.gpgsign=false)
( cd "$repo" \
&& git "${git_id[@]}" commit -qm c1 \
&& printf 'y\n' > f && git add f \
&& git "${git_id[@]}" commit -qm c2 )
local rp_out rp_path
rp_out="$(cd "$repo" && "$SDD_SCRIPTS/review-package" plan-a.md HEAD~1 HEAD)"
rp_path="$(printf '%s\n' "$rp_out" | sed -n 's/^wrote \(.*\): [0-9].*$/\1/p')"
case "$rp_path" in
"$repo/.superpowers/sdd/plan-a/review-"*.diff)
pass "review-package writes its diff under the plan's workspace" ;;
*)
fail "review-package writes its diff under the plan's workspace"
echo " got: $rp_path"
;;
esac
rc=0
(cd "$repo" && "$SDD_SCRIPTS/review-package" HEAD~1 HEAD >/dev/null 2>&1) || rc=$?
if [[ "$rc" -eq 2 ]]; then
pass "review-package without a plan errors with exit 2"
else
fail "review-package without a plan errors with exit 2"
echo " exit: $rc"
fi
local rp_explicit
rp_explicit="$(cd "$repo" && "$SDD_SCRIPTS/review-package" plan-a.md HEAD~1 HEAD "$TEST_ROOT/explicit.diff")"
if [[ -s "$TEST_ROOT/explicit.diff" && "$rp_explicit" == *"$TEST_ROOT/explicit.diff"* ]]; then
pass "review-package honors an explicit OUTFILE"
else
fail "review-package honors an explicit OUTFILE"
echo " got: $rp_explicit"
fi
# --- Worktree isolation: a linked worktree resolves its own workspace ---
local wt="$TEST_ROOT/wt"
( cd "$repo" && git worktree add -q "$wt" -b wt-feature )
local wt_root wt_dir
wt_root="$(cd "$wt" && git rev-parse --show-toplevel)"
wt_dir="$(cd "$wt" && "$SDD_SCRIPTS/sdd-workspace" plan-a.md)"
if [[ "$wt_dir" == "$wt_root/.superpowers/sdd/plan-a" && "$wt_dir" != "$dir_a" ]]; then
pass "linked worktree resolves its own distinct workspace"
else
fail "linked worktree resolves its own distinct workspace"
echo " main: $dir_a"
echo " wt: $wt_dir"
fi
printf 'y\n' > "$wt_dir/artifact.md"
local wt_status
wt_status="$(cd "$wt" && git status --porcelain)"
if [[ "$wt_status" != *".superpowers"* ]]; then
pass "worktree workspace invisible to git status"
else
fail "worktree workspace invisible to git status"
echo " status: $wt_status"
fi
echo ""
if [[ "$FAILURES" -ne 0 ]]; then
echo "FAILED: $FAILURES assertion(s)."
exit 1
fi
echo "PASS"
}
main "$@"
```
Note: the worktree fixture relies on `plan-a.md` being tracked by the time the worktree is created — the `git add -A` assertion earlier stages it and the review-package block commits it. Do not reorder the blocks.
- [ ] **Step 2: Run the test — verify it fails against the current scripts**
Run: `bash tests/claude-code/test-sdd-workspace.sh`
Expected: FAILED with multiple assertions (current `sdd-workspace` ignores arguments and prints the flat path, so "errors with exit 2" and "<plan-basename>" assertions fail; current `review-package` treats `plan-a.md` as a bad BASE ref).
- [ ] **Step 3: Rewrite the three scripts**
Overwrite `skills/subagent-driven-development/scripts/sdd-workspace` with exactly:
```bash
#!/usr/bin/env bash
# Resolve and ensure the working-tree directory SDD uses for one plan's
# short-lived artifacts: task briefs, implementer reports, review packages,
# and the progress ledger. Print the plan directory's absolute path.
#
# One directory per plan (.superpowers/sdd/<plan-basename>/) so a follow-up
# plan in the same working tree can never read or overwrite another plan's
# artifacts. A stale ledger misread as current progress makes controllers
# skip whole task sequences — plan-scoping removes that failure structurally.
#
# The workspace lives in the working tree (not under .git/) because Claude Code
# treats .git/ as a protected path and denies agent writes there — which blocks
# an implementer subagent from writing its report file. A self-ignoring
# .gitignore at .superpowers/sdd/ keeps every plan's workspace out of
# `git status` and out of accidental commits without modifying any tracked file.
#
# Single source of truth for the workspace location, so task-brief and
# review-package cannot drift to different directories.
#
# 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
```
Overwrite `skills/subagent-driven-development/scripts/task-brief` with exactly:
```bash
#!/usr/bin/env bash
# Extract one task's full text from an implementation plan into a file the
# implementer reads in one call, so the task text never has to be pasted
# through the controller's context.
#
# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
# Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/task-<N>-brief.md
# (per plan and per worktree; concurrent runs of the SAME plan in the same
# working tree share it).
set -euo pipefail
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
echo "usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]" >&2
exit 2
fi
plan=$1
n=$2
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
if [ $# -eq 3 ]; then
out=$3
else
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
out="$dir/task-${n}-brief.md"
fi
awk -v n="$n" '
/^```/ { infence = !infence }
!infence && /^#+[ \t]+Task[ \t]+[0-9]+/ {
intask = ($0 ~ ("^#+[ \t]+Task[ \t]+" n "([^0-9]|$)"))
}
intask { print }
' "$plan" > "$out"
if [ ! -s "$out" ]; then
echo "task ${n} not found in ${plan} (no heading matching 'Task ${n}')" >&2
exit 3
fi
echo "wrote ${out}: $(wc -l < "$out" | tr -d ' ') lines"
```
Overwrite `skills/subagent-driven-development/scripts/review-package` with exactly:
```bash
#!/usr/bin/env bash
# Generate a review package: commit list, stat summary, and the net
# diff with extended context, written to a file the reviewer reads in one
# call. Using the recorded per-task BASE (not HEAD~1) keeps multi-commit
# tasks intact.
#
# Usage: review-package PLAN_FILE BASE HEAD [OUTFILE]
# Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/review-<base7>..<head7>.diff
# (named per range, so a re-review after fixes gets a distinct fresh file).
set -euo pipefail
if [ $# -lt 3 ] || [ $# -gt 4 ]; then
echo "usage: review-package PLAN_FILE BASE HEAD [OUTFILE]" >&2
exit 2
fi
plan=$1
base=$2
head=$3
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
git rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; }
if [ $# -eq 4 ]; then
out=$4
else
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
fi
{
echo "# Review package: ${base}..${head}"
echo
echo "## Commits"
git log --oneline "${base}..${head}"
echo
echo "## Files changed"
git diff --stat "${base}..${head}"
echo
echo "## Diff"
git diff -U10 "${base}..${head}"
} > "$out"
commits=$(git rev-list --count "${base}..${head}")
echo "wrote ${out}: ${commits} commit(s), $(wc -c < "$out" | tr -d ' ') bytes"
```
- [ ] **Step 4: Run the test — verify it passes**
Run: `bash tests/claude-code/test-sdd-workspace.sh`
Expected: `PASS`, 13 `[PASS]` lines, exit 0.
- [ ] **Step 5: Lint everything touched**
Run: `bash scripts/lint-shell.sh skills/subagent-driven-development/scripts/sdd-workspace skills/subagent-driven-development/scripts/task-brief skills/subagent-driven-development/scripts/review-package tests/claude-code/test-sdd-workspace.sh`
Expected: exit 0, no findings.
- [ ] **Step 6: Commit**
```bash
git add skills/subagent-driven-development/scripts/sdd-workspace \
skills/subagent-driven-development/scripts/task-brief \
skills/subagent-driven-development/scripts/review-package \
tests/claude-code/test-sdd-workspace.sh
git commit -m "feat(sdd): plan-scoped workspace — one .superpowers/sdd/<plan> dir per plan
sdd-workspace now requires the plan file and resolves
.superpowers/sdd/<plan-basename>/; task-brief and review-package write
into their plan's directory (review-package gains PLAN_FILE as its first
argument). Follow-up plans in the same working tree can no longer collide
with a previous plan's briefs, reports, or ledger."
```
---
### Task 3: SKILL.md — plan-scoped Durable Progress, mismatch guard, end-of-plan cleanup
**Files:**
- Modify: `skills/subagent-driven-development/SKILL.md`
**Interfaces:**
- Consumes: script signatures from Task 2 (`sdd-workspace PLAN_FILE`, `review-package PLAN_FILE BASE HEAD`); RED failure quotes from Task 1 (context only — the text below is the starting wording; Task 4 refines it if GREEN fails).
- Produces: the skill text Task 4 evaluates. Section anchor names used by Task 4: "Durable Progress".
Apply the following edits with exact string replacement. All old strings are verbatim from the current file.
- [ ] **Step 1: Update the DONE-status review-package invocation**
Old:
```
**DONE:** Generate the review package (`scripts/review-package BASE HEAD`, from this skill's directory — it prints the unique file path it wrote; BASE is the commit you recorded before dispatching the implementer — never `HEAD~1`, which silently drops all but the last commit of a multi-commit task), then dispatch the task reviewer with the printed path.
```
New:
```
**DONE:** Generate the review package (`scripts/review-package PLAN_FILE BASE HEAD`, from this skill's directory — it prints the unique file path it wrote; BASE is the commit you recorded before dispatching the implementer — never `HEAD~1`, which silently drops all but the last commit of a multi-commit task), then dispatch the task reviewer with the printed path.
```
- [ ] **Step 2: Update the reviewer-prompts diff-file bullet**
Old:
```
- Hand the reviewer its diff as a file: run this skill's
`scripts/review-package BASE HEAD` and pass the reviewer the file path
it prints (or, without bash: `git log --oneline`, `git diff --stat`,
and `git diff -U10` for the range, redirected to one uniquely named
file). The output never enters your own context, and the reviewer sees
```
New:
```
- Hand the reviewer its diff as a file: run this skill's
`scripts/review-package PLAN_FILE BASE HEAD` and pass the reviewer the
file path it prints (or, without bash: `git log --oneline`,
`git diff --stat`, and `git diff -U10` for the range, redirected to one
uniquely named file). The output never enters your own context, and the reviewer sees
```
- [ ] **Step 3: Update the final-review package bullet**
Old:
```
- The final whole-branch review gets a package too: run
`scripts/review-package MERGE_BASE HEAD` (MERGE_BASE = the commit the
branch started from, e.g. `git merge-base main HEAD`) and include the
```
New:
```
- The final whole-branch review gets a package too: run
`scripts/review-package PLAN_FILE MERGE_BASE HEAD` (MERGE_BASE = the
commit the branch started from, e.g. `git merge-base main HEAD`) and include the
```
- [ ] **Step 4: Update the Red Flags diff-file bullet**
Old:
```
- Dispatch a task reviewer without a diff file — generate it first
(`scripts/review-package BASE HEAD`) and name the printed path in the
prompt
```
New:
```
- Dispatch a task reviewer without a diff file — generate it first
(`scripts/review-package PLAN_FILE BASE HEAD`) and name the printed
path in the prompt
```
- [ ] **Step 5: Replace the Durable Progress section**
Old:
```
- 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.
- When a task's review comes back clean, append one line to the ledger in
the same message as your other bookkeeping:
`Task N: complete (commits <base7>..<head7>, review clean)`.
- The ledger is your recovery map: the commits it names exist in git even
when your context no longer remembers creating them. After compaction,
trust the ledger and `git log` over your own recollection.
- `git clean -fdx` will destroy the ledger (it's git-ignored scratch); if
that happens, recover from `git log`.
```
New:
```
- Each plan owns a workspace: at skill start, run this skill's
`scripts/sdd-workspace PLAN_FILE` — it prints the plan's git-ignored
directory (`<repo-root>/.superpowers/sdd/<plan-basename>/`), home to
every artifact for THIS plan: ledger, briefs, reports, review packages.
Another plan's directory is never yours to read or write.
- Check for this plan's ledger at `<workspace>/progress.md`. If its first
line names your plan file, tasks listed there as complete are DONE — do
not re-dispatch them; resume at the first task not marked complete. A
ledger whose first line names a different plan file — or a stray ledger
at the old flat path `.superpowers/sdd/progress.md` — is another plan's
progress: leave it in place and start your own, fresh.
- Create the ledger with its identity as the first line:
`# SDD ledger — plan: <plan file path>`.
- When a task's review comes back clean, append one line to the ledger in
the same message as your other bookkeeping:
`Task N: complete (commits <base7>..<head7>, review clean)`.
- The ledger is your recovery map: the commits it names exist in git even
when your context no longer remembers creating them. After compaction,
trust the ledger and `git log` over your own recollection.
- `git clean -fdx` will destroy the workspace (it's git-ignored scratch); if
that happens, recover from `git log`.
- When the final whole-branch review is clean and its fixes are merged,
delete this plan's workspace (`rm -rf <workspace>`) — the git history
is the record now. Sibling directories belong to other plans; leave
them alone.
```
- [ ] **Step 6: Add the cleanup node to the process graph**
Old:
```
"Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" [shape=box];
"Use superpowers:finishing-a-development-branch" [shape=box style=filled fillcolor=lightgreen];
```
New:
```
"Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" [shape=box];
"Final review clean: delete this plan's workspace" [shape=box];
"Use superpowers:finishing-a-development-branch" [shape=box style=filled fillcolor=lightgreen];
```
Old:
```
"Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" -> "Use superpowers:finishing-a-development-branch";
```
New:
```
"Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" -> "Final review clean: delete this plan's workspace";
"Final review clean: delete this plan's workspace" -> "Use superpowers:finishing-a-development-branch";
```
- [ ] **Step 7: Update the Example Workflow**
Old:
```
[Read plan file once: docs/superpowers/plans/feature-plan.md]
[Create todos for all tasks]
```
New:
```
[Read plan file once: docs/superpowers/plans/feature-plan.md]
[Resolve workspace: scripts/sdd-workspace docs/superpowers/plans/feature-plan.md — no ledger inside, fresh start]
[Create todos for all tasks]
```
Old:
```
[After all tasks]
[Dispatch final code-reviewer]
Final reviewer: All requirements met, ready to merge
Done!
```
New:
```
[After all tasks]
[Dispatch final code-reviewer]
Final reviewer: All requirements met, ready to merge
[Delete this plan's workspace — the record now lives in git]
Done!
```
- [ ] **Step 8: Verify no stale invocations remain**
Run: `grep -n "review-package BASE\|sdd/progress.md\|scripts/sdd-workspace\b" skills/subagent-driven-development/SKILL.md`
Expected: no `review-package BASE` hits; `sdd/progress.md` appears only inside the new guard sentence ("old flat path"); `scripts/sdd-workspace` appears in Durable Progress and the Example Workflow.
- [ ] **Step 9: Commit**
```bash
git add skills/subagent-driven-development/SKILL.md
git commit -m "feat(sdd): plan-scoped durable progress — ledger names its plan, workspace dies at plan end
The start-of-skill ledger check is now scoped to the plan's own
workspace and keyed to the ledger's first line, so a follow-up plan in
the same working tree no longer adopts a previous plan's completed
ledger as its own progress (observed: controllers skipping or renaming
around stale ledgers). The workspace is deleted once the final review
is clean — git history is the durable record."
```
---
### Task 4: GREEN eval, refinement loop, and the committed results doc
**Files:**
- Create: `docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace-eval-results.md`
- Delete: `docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace-eval-notes-red.md` (its content folds into the results doc)
- Modify (only if GREEN fails): `skills/subagent-driven-development/SKILL.md`
**Interfaces:**
- Consumes: Task 1's RED table/quotes and fixture generator (recreate `$EVAL_ROOT/make-fixture.sh` verbatim from Task 1 Step 1 if the temp dir is gone); Task 3's SKILL.md.
- Produces: the eval evidence document cited by the PR.
- [ ] **Step 1: Build 10 GREEN fixtures (`scoped` layout)**
```bash
EVAL_ROOT=$(cat /tmp/sdd-eval-root.path) # if missing, recreate make-fixture.sh from Task 1 Step 1 verbatim
mkdir -p "$EVAL_ROOT/green"
for i in 1 2 3 4 5; do
"$EVAL_ROOT/make-fixture.sh" s1 scoped "$EVAL_ROOT/green/s1-rep$i"
"$EVAL_ROOT/make-fixture.sh" s2 scoped "$EVAL_ROOT/green/s2-rep$i"
done
```
- [ ] **Step 2: Run 10 scenario reps against the NEW skill directory**
Same dispatch protocol and VERBATIM prompt as Task 1 Step 4, with `<SKILL_DIR>` = this worktree's `skills/subagent-driven-development` (absolute path) and the green fixtures. Save replies to `$EVAL_ROOT/green/s{1,2}-repN.reply.md`.
- [ ] **Step 3: Score with the same criteria table as Task 1 Step 5**
Additional S1 GREEN expectation (record, don't merely pass/fail): the reply's LEDGERS CHECKED should show the agent resolving `.superpowers/sdd/2026-07-06-widget-export/` for itself and identifying `.superpowers/sdd/progress.md` and/or the plan-A directory as not its own.
- [ ] **Step 4: Gate — refine wording only on evidence**
- S1 GREEN and S2 GREEN must both PASS 5/5.
- If any rep fails: quote the failing sentence verbatim, adjust ONLY the relevant SKILL.md wording (e.g., add a Red Flags bullet quoting the observed rationalization pattern, or tighten the Durable Progress guard), commit the adjustment with message `fix(sdd): close eval loophole — <one-line description>`, and re-run that scenario's 5 reps fresh. Repeat until 5/5. Record every iteration in the results doc.
- [ ] **Step 5: Write the results doc**
Create `docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace-eval-results.md` with exactly these sections (fill with real data):
```markdown
# SDD plan-scoped workspace — eval results
- **Date:** <run date>
- **Method:** writing-skills RED→GREEN pressure test; 5 fresh sonnet
subagents per scenario per arm; every reply read and scored by hand.
- **Spec:** 2026-07-06-sdd-plan-scoped-workspace.md
## Scenarios
<one paragraph each for S1 (stale ledger from a different plan) and S2
(same-plan resume), including the fixture layout per arm>
## Results
| Scenario | Arm | Text under test | PASS | FAIL |
|---|---|---|---|---|
| S1 | RED | released SKILL.md (v6.1.1 line) | n/5 | n/5 |
| S1 | GREEN | this branch | 5/5 | 0/5 |
| S2 | RED | released SKILL.md (v6.1.1 line) | n/5 | n/5 |
| S2 | GREEN | this branch | 5/5 | 0/5 |
## Verbatim failure evidence (RED)
<every RED FAIL quote, one block per rep, with rep id>
## GREEN behavior notes
<how GREEN agents resolved the workspace and what they said about the
stale artifacts; any refinement iterations with their trigger quotes>
## Appendix A: fixture generator
<the full make-fixture.sh source used>
## Appendix B: scenario prompt
<the verbatim prompt template>
## Limitations
Five reps per cell is a smoke-strength signal, not a statistical one; the
scenario measures the resume decision, not a full execution. A rerunnable
harness case belongs in superpowers-evals as follow-up.
```
- [ ] **Step 6: Remove the interim RED notes file and commit**
```bash
git rm -q docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace-eval-notes-red.md
git add docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace-eval-results.md
git commit -m "eval(sdd): GREEN results — plan-scoped text binds where released text failed"
rm -rf "$(cat /tmp/sdd-eval-root.path)" /tmp/sdd-eval-root.path
```
---
### Task 5: Consistency sweep and full gates
**Files:**
- Modify: any file the sweep catches (expected: none beyond prior tasks)
**Interfaces:**
- Consumes: everything prior.
- Produces: the branch state the final whole-branch review reviews.
- [ ] **Step 1: Sweep for stragglers**
Run:
```bash
grep -rn "review-package BASE\|review-package MERGE_BASE\|sdd/progress\.md" \
--include='*.md' --include='*.sh' \
skills/ tests/ README.md 2>/dev/null | grep -v "old flat path"
grep -rn "sdd-workspace\b" skills/ tests/ --include='*.md' --include='*.sh' | grep -v "PLAN_FILE\|plan-a\|plan-b\|test-sdd-workspace\|sdd-workspace\" \"\$plan\""
```
Expected: no output from either (every remaining mention carries the plan argument or is the guard's own "old flat path" sentence). Fix anything that appears, following the Task 3 edit style.
- [ ] **Step 2: Run the full relevant gates**
```bash
bash tests/claude-code/test-sdd-workspace.sh
bash tests/claude-code/test-subagent-driven-development.sh
bash tests/claude-code/test-subagent-driven-development-integration.sh
bash scripts/lint-shell.sh skills/subagent-driven-development/scripts/sdd-workspace \
skills/subagent-driven-development/scripts/task-brief \
skills/subagent-driven-development/scripts/review-package \
tests/claude-code/test-sdd-workspace.sh
```
Expected: all exit 0. If either `test-subagent-driven-development*.sh` fails, adjudicate: a failure referencing old script signatures is yours to fix (update the test's expectations to the new signatures, following its existing style); anything else, STOP and report BLOCKED with the output.
- [ ] **Step 3: Commit (only if the sweep changed anything)**
```bash
git add -u
git commit -m "chore(sdd): consistency sweep for plan-scoped workspace signatures"
```
---
## Self-review notes (author)
- Spec coverage: §1 scripts → Task 2; §2 ledger identity + guard → Task 3 Step 5; §3 end-of-life → Task 3 Steps 57; §4 touch points → Task 3 Steps 14 + Task 5 sweep; Testing/shell → Task 2; Evaluation → Tasks 1 and 4; out-of-scope items have no tasks (correct).
- Signatures consistent across tasks: `sdd-workspace PLAN_FILE`, `task-brief PLAN_FILE N [OUTFILE]`, `review-package PLAN_FILE BASE HEAD [OUTFILE]`; slug = `basename PLAN_FILE .md`; ledger first line `# SDD ledger — plan: <plan file path>`.
- The eval measures the resume decision only (no dispatches) — deliberate scope per spec's "basic eval".