Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions skills/subagent-driven-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ that implementer. Single-file mechanical fixes also take the cheapest tier.

Implementer subagents report one of four statuses. Handle each appropriately:

**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.
**DONE:** Generate the review package (`scripts/review-package --ns NAMESPACE 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. NAMESPACE is PLAN_FILE's basename without its extension — see Durable Progress.

**DONE_WITH_CONCERNS:** The implementer completed the work but flagged doubts. Read the concerns before proceeding. If the concerns are about correctness or scope, address them before review. If they're observations (e.g., "this file is getting large"), note them and proceed to review.

Expand Down Expand Up @@ -179,8 +179,8 @@ final whole-branch review. When you fill a reviewer template:
test hygiene, review method) — the constraints block is for what THIS
project's spec demands.
- 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`,
`scripts/review-package --ns NAMESPACE 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
the commit list, stat summary, and full diff with context in one Read
Expand All @@ -201,7 +201,7 @@ final whole-branch review. When you fill a reviewer template:
Do not dismiss the finding because the plan mandates it, and do not
dispatch a fix that contradicts the plan without asking.
- The final whole-branch review gets a package too: run
`scripts/review-package MERGE_BASE HEAD` (MERGE_BASE = the commit the
`scripts/review-package --ns NAMESPACE MERGE_BASE HEAD` (MERGE_BASE = the commit the
branch started from, e.g. `git merge-base main HEAD`) and include the
printed path in the final review dispatch, so the final reviewer reads
one file instead of re-deriving the branch diff with git commands.
Expand All @@ -224,7 +224,8 @@ and is re-read on every later turn. Hand artifacts over as files:

- **Task brief:** before dispatching an implementer, run this skill's
`scripts/task-brief PLAN_FILE N` — it extracts the task's full text to a
uniquely named file and prints the path. Compose the dispatch so the
uniquely named file, under a workspace namespaced by PLAN_FILE's basename
so unrelated plans never collide, and prints the path. Compose the dispatch so the
brief stays the single source of requirements. Your dispatch should
contain: (1) one line on where this task fits in the project; (2) the
brief path, introduced as "read this first — it is your requirements,
Expand All @@ -250,8 +251,18 @@ controllers that lost their place have re-dispatched entire completed task
sequences — the single most expensive failure observed. Track progress in
a ledger file, not only in todos.

The scratch workspace (briefs, reports, review packages, and the ledger) is
namespaced per plan so two unrelated task sequences running concurrently in
the same working tree — each numbering tasks from 1 — never overwrite each
other's files. Compute NAMESPACE once, right after reading the plan:
PLAN_FILE's basename without its extension (e.g.
`docs/plans/2026-01-01-feature.md` → `2026-01-01-feature`). Reuse this same
NAMESPACE for every `scripts/review-package --ns NAMESPACE` call and for the
ledger path below — `scripts/task-brief` derives it automatically from
PLAN_FILE, so it needs no `--ns` flag.

- At skill start, check for a ledger:
`cat "$(git rev-parse --show-toplevel)/.superpowers/sdd/progress.md"`. Tasks listed there
`cat "$(scripts/sdd-workspace --ns NAMESPACE)/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
Expand Down Expand Up @@ -382,8 +393,8 @@ Done!
dispatch prompt ("treat it as Minor at most") — the plan's example code is
a starting point, not evidence that its weaknesses were chosen
- Dispatch a task reviewer without a diff file — generate it first
(`scripts/review-package BASE HEAD`) and name the printed path in the
prompt
(`scripts/review-package --ns NAMESPACE BASE HEAD`) and name the printed
path in the prompt
- Move to next task while the review has open Critical/Important issues
- Re-dispatch a task the progress ledger already marks complete — check
the ledger (and `git log`) after any compaction or resume
Expand Down
33 changes: 29 additions & 4 deletions skills/subagent-driven-development/scripts/review-package
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,34 @@
# call. Using the recorded per-task BASE (not HEAD~1) keeps multi-commit
# tasks intact.
#
# Usage: review-package BASE HEAD [OUTFILE]
# Default OUTFILE: <repo-root>/.superpowers/sdd/review-<base7>..<head7>.diff
# Usage: review-package [--ns NAMESPACE] BASE HEAD [OUTFILE]
# Default OUTFILE: <repo-root>/.superpowers/sdd[/NAMESPACE]/review-<base7>..<head7>.diff
# (named per range, so a re-review after fixes gets a distinct fresh file).
# Pass --ns with the same namespace used for this task sequence's briefs
# (scripts/task-brief derives it as PLAN_FILE's basename without extension)
# so unrelated concurrent task sequences in the same working tree land in
# different directories.
set -euo pipefail

ns=""
args=()
while [ $# -gt 0 ]; do
case "$1" in
--ns)
[ $# -ge 2 ] || { echo "usage: review-package [--ns NAMESPACE] BASE HEAD [OUTFILE]" >&2; exit 2; }
ns=$2
shift 2
;;
*)
args+=("$1")
shift
;;
esac
done
set -- "${args[@]}"

if [ $# -lt 2 ] || [ $# -gt 3 ]; then
echo "usage: review-package BASE HEAD [OUTFILE]" >&2
echo "usage: review-package [--ns NAMESPACE] BASE HEAD [OUTFILE]" >&2
exit 2
fi

Expand All @@ -23,7 +44,11 @@ git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&
if [ $# -eq 3 ]; then
out=$3
else
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
if [ -n "$ns" ]; then
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace" --ns "$ns")
else
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
fi
out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
fi

Expand Down
22 changes: 19 additions & 3 deletions skills/subagent-driven-development/scripts/sdd-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,27 @@
# Single source of truth for the workspace location, so task-brief and
# review-package cannot drift to different directories.
#
# Usage: sdd-workspace
# An optional --ns NAMESPACE scopes the workspace to one plan/epic's
# subdirectory, so two unrelated task sequences running concurrently in the
# same checkout (each numbering tasks from 1) don't overwrite each other's
# briefs, reports, and ledger.
#
# Usage: sdd-workspace [--ns NAMESPACE]
set -euo pipefail

ns=""
if [ "${1:-}" = "--ns" ]; then
[ $# -eq 2 ] || { echo "usage: sdd-workspace [--ns NAMESPACE]" >&2; exit 2; }
ns=$2
elif [ $# -ne 0 ]; then
echo "usage: sdd-workspace [--ns NAMESPACE]" >&2
exit 2
fi

root=$(git rev-parse --show-toplevel)
dir="$root/.superpowers/sdd"
base="$root/.superpowers/sdd"
dir="$base"
[ -n "$ns" ] && dir="$base/$ns"
mkdir -p "$dir"
printf '*\n' > "$dir/.gitignore"
printf '*\n' > "$base/.gitignore"
cd "$dir" && pwd
10 changes: 7 additions & 3 deletions skills/subagent-driven-development/scripts/task-brief
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
# through the controller's context.
#
# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
# Default OUTFILE: <repo-root>/.superpowers/sdd/task-<N>-brief.md
# (per worktree; concurrent runs in the same working tree share it).
# Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-slug>/task-<N>-brief.md
# (per worktree; the workspace is namespaced by PLAN_FILE's basename, so
# concurrent task sequences from different plans in the same working tree
# land in different directories instead of colliding on task-<N>-brief.md).
set -euo pipefail

if [ $# -lt 2 ] || [ $# -gt 3 ]; then
Expand All @@ -20,7 +22,9 @@ n=$2
if [ $# -eq 3 ]; then
out=$3
else
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
slug=$(basename "$plan")
slug=${slug%.*}
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace" --ns "$slug")
out="$dir/task-${n}-brief.md"
fi

Expand Down
58 changes: 58 additions & 0 deletions tests/claude-code/test-sdd-workspace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,64 @@ PLAN
;;
esac

# --- Namespacing: unrelated plans in the same checkout don't collide ---
cat > "$repo/plan-a.md" <<'PLAN'
# Plan A

## Task 1: First thing

Do plan A's first thing.
PLAN
cat > "$repo/plan-b.md" <<'PLAN'
# Plan B

## Task 1: First thing

Do plan B's first thing.
PLAN

local out_a path_a out_b path_b
out_a="$(cd "$repo" && "$SDD_SCRIPTS/task-brief" plan-a.md 1)"
path_a="$(printf '%s\n' "$out_a" | sed -n 's/^wrote \(.*\): [0-9][0-9]* lines$/\1/p')"
out_b="$(cd "$repo" && "$SDD_SCRIPTS/task-brief" plan-b.md 1)"
path_b="$(printf '%s\n' "$out_b" | sed -n 's/^wrote \(.*\): [0-9][0-9]* lines$/\1/p')"

if [[ "$path_a" != "$path_b" \
&& "$path_a" == "$repo/.superpowers/sdd/plan-a/task-1-brief.md" \
&& "$path_b" == "$repo/.superpowers/sdd/plan-b/task-1-brief.md" ]]; then
pass "task-brief namespaces unrelated plans' Task 1 briefs into different files"
else
fail "task-brief namespaces unrelated plans' Task 1 briefs into different files"
echo " plan-a: $path_a"
echo " plan-b: $path_b"
fi

if grep -q "plan A's first thing" "$path_a" && grep -q "plan B's first thing" "$path_b"; then
pass "each namespaced brief holds only its own plan's content"
else
fail "each namespaced brief holds only its own plan's content"
fi

local ns_dir
ns_dir="$(cd "$repo" && "$SDD_SCRIPTS/sdd-workspace" --ns plan-a)"
if [[ "$ns_dir" == "$repo/.superpowers/sdd/plan-a" ]]; then
pass "sdd-workspace --ns resolves the namespaced subdirectory"
else
fail "sdd-workspace --ns resolves the namespaced subdirectory"
echo " got: $ns_dir"
fi

local rp_ns_out rp_ns_path
rp_ns_out="$(cd "$repo" && "$SDD_SCRIPTS/review-package" --ns plan-a HEAD~1 HEAD)"
rp_ns_path="$(printf '%s\n' "$rp_ns_out" | sed -n 's/^wrote \(.*\): [0-9].*$/\1/p')"
case "$rp_ns_path" in
"$repo/.superpowers/sdd/plan-a/"*) pass "review-package --ns writes its diff under the namespaced subdirectory" ;;
*)
fail "review-package --ns writes its diff under the namespaced subdirectory"
echo " got: $rp_ns_path"
;;
esac

# --- Worktree isolation: a linked worktree resolves its own workspace ---
local wt="$TEST_ROOT/wt"
( cd "$repo" && git worktree add -q "$wt" -b wt-feature )
Expand Down