diff --git a/docs/superpowers/plans/2026-07-06-sdd-plan-scoped-workspace.md b/docs/superpowers/plans/2026-07-06-sdd-plan-scoped-workspace.md new file mode 100644 index 00000000..850cd37a --- /dev/null +++ b/docs/superpowers/plans/2026-07-06-sdd-plan-scoped-workspace.md @@ -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//`) 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 `` 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 ` (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//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 `` = `$EVAL_ROOT/red/skill/skills/subagent-driven-development` and `` = 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: +Read /SKILL.md first and follow its process exactly. + +The repository you are working in: +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 1–5 are done", "resume at task 18", "all tasks complete", etc.) | +| S2 | Report says tasks 1–2 complete, first dispatch = **Task 3**. | Re-dispatches task 1 or 2; claims 3–5 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 `/.superpowers/sdd/` (creates it; maintains `/.superpowers/sdd/.gitignore` containing `*`). `task-brief PLAN_FILE N [OUTFILE]` → default OUTFILE `/task--brief.md`. `review-package PLAN_FILE BASE HEAD [OUTFILE]` → default OUTFILE `/review-...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 /.superpowers/sdd/" + else + fail "prints /.superpowers/sdd/" + 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 "" 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//) 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: /.superpowers/sdd//task--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: /.superpowers/sdd//review-...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/ dir per plan + +sdd-workspace now requires the plan file and resolves +.superpowers/sdd//; 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 .., 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 (`/.superpowers/sdd//`), 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 `/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: `. +- 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 .., 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 `) — 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 `` = 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 — `, 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:** +- **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 + + + +## 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) + + + +## GREEN behavior notes + + + +## Appendix A: fixture generator + + + +## Appendix B: scenario prompt + + + +## 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 5–7; §4 touch points → Task 3 Steps 1–4 + 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: `. +- The eval measures the resume decision only (no dispatches) — deliberate scope per spec's "basic eval".