Przeglądaj źródła

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.
Jesse Vincent 2 miesięcy temu
rodzic
commit
0da87665c8

+ 987 - 0
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/<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 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 `<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 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: <plan file path>`.
+- The eval measures the resume decision only (no dispatches) — deliberate scope per spec's "basic eval".