mirror of
https://github.com/obra/superpowers.git
synced 2026-06-18 16:49:04 +08:00
Compare commits
3 Commits
main
...
fix/1780-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54252ee401 | ||
|
|
ea0da051e3 | ||
|
|
1e912bc757 |
@@ -251,7 +251,7 @@ sequences — the single most expensive failure observed. Track progress in
|
|||||||
a ledger file, not only in todos.
|
a ledger file, not only in todos.
|
||||||
|
|
||||||
- At skill start, check for a ledger:
|
- At skill start, check for a ledger:
|
||||||
`cat "$(git rev-parse --git-path sdd)/progress.md"`. Tasks listed there
|
`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
|
as complete are DONE — do not re-dispatch them; resume at the first task
|
||||||
not marked complete.
|
not marked complete.
|
||||||
- When a task's review comes back clean, append one line to the ledger in
|
- When a task's review comes back clean, append one line to the ledger in
|
||||||
|
|||||||
@@ -5,9 +5,8 @@
|
|||||||
# tasks intact.
|
# tasks intact.
|
||||||
#
|
#
|
||||||
# Usage: review-package BASE HEAD [OUTFILE]
|
# Usage: review-package BASE HEAD [OUTFILE]
|
||||||
# Default OUTFILE: <git-dir>/sdd/review-<base7>..<head7>.diff — unique per
|
# Default OUTFILE: <repo-root>/.superpowers/sdd/review-<base7>..<head7>.diff
|
||||||
# repo instance and per range, so concurrent sessions cannot collide and a
|
# (named per range, so a re-review after fixes gets a distinct fresh file).
|
||||||
# re-review after fixes always gets a distinctly named fresh file.
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
||||||
@@ -24,9 +23,7 @@ git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&
|
|||||||
if [ $# -eq 3 ]; then
|
if [ $# -eq 3 ]; then
|
||||||
out=$3
|
out=$3
|
||||||
else
|
else
|
||||||
dir=$(git rev-parse --git-path sdd)
|
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
|
||||||
mkdir -p "$dir"
|
|
||||||
dir=$(cd "$dir" && pwd)
|
|
||||||
out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
|
out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
22
skills/subagent-driven-development/scripts/sdd-workspace
Executable file
22
skills/subagent-driven-development/scripts/sdd-workspace
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Resolve and ensure the working-tree directory SDD uses for its short-lived
|
||||||
|
# artifacts: task briefs, implementer reports, review packages, and the
|
||||||
|
# progress ledger. Print the directory's absolute path.
|
||||||
|
#
|
||||||
|
# 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 keeps the 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
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
root=$(git rev-parse --show-toplevel)
|
||||||
|
dir="$root/.superpowers/sdd"
|
||||||
|
mkdir -p "$dir"
|
||||||
|
printf '*\n' > "$dir/.gitignore"
|
||||||
|
cd "$dir" && pwd
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
# through the controller's context.
|
# through the controller's context.
|
||||||
#
|
#
|
||||||
# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
|
# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
|
||||||
# Default OUTFILE: <git-dir>/sdd/task-<N>-brief.md — unique per repo
|
# Default OUTFILE: <repo-root>/.superpowers/sdd/task-<N>-brief.md
|
||||||
# instance, so concurrent sessions cannot collide.
|
# (per worktree; concurrent runs in the same working tree share it).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
||||||
@@ -20,9 +20,7 @@ n=$2
|
|||||||
if [ $# -eq 3 ]; then
|
if [ $# -eq 3 ]; then
|
||||||
out=$3
|
out=$3
|
||||||
else
|
else
|
||||||
dir=$(git rev-parse --git-path sdd)
|
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
|
||||||
mkdir -p "$dir"
|
|
||||||
dir=$(cd "$dir" && pwd)
|
|
||||||
out="$dir/task-${n}-brief.md"
|
out="$dir/task-${n}-brief.md"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
142
tests/claude-code/test-sdd-workspace.sh
Executable file
142
tests/claude-code/test-sdd-workspace.sh
Executable file
@@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Tests for the SDD workspace: scripts/sdd-workspace resolves a self-ignoring
|
||||||
|
# working-tree directory for SDD artifacts, and the SDD scripts write into it.
|
||||||
|
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)"
|
||||||
|
|
||||||
|
local dir
|
||||||
|
dir="$(cd "$repo" && "$SDD_SCRIPTS/sdd-workspace")"
|
||||||
|
|
||||||
|
if [[ "$dir" == "$repo/.superpowers/sdd" ]]; then
|
||||||
|
pass "prints <repo-root>/.superpowers/sdd"
|
||||||
|
else
|
||||||
|
fail "prints <repo-root>/.superpowers/sdd"
|
||||||
|
echo " got: $dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$repo/.superpowers/sdd/.gitignore" && "$(cat "$repo/.superpowers/sdd/.gitignore")" == "*" ]]; then
|
||||||
|
pass "self-ignoring .gitignore created with '*'"
|
||||||
|
else
|
||||||
|
fail "self-ignoring .gitignore created with '*'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'x\n' > "$repo/.superpowers/sdd/artifact.md"
|
||||||
|
local status
|
||||||
|
status="$(cd "$repo" && git status --porcelain)"
|
||||||
|
if [[ -z "$status" ]]; 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 [[ -z "$staged" ]]; then
|
||||||
|
pass "git add -A does not stage the workspace"
|
||||||
|
else
|
||||||
|
fail "git add -A does not stage the workspace"
|
||||||
|
echo " staged: $staged"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "$repo/plan.md" <<'PLAN'
|
||||||
|
# Plan
|
||||||
|
|
||||||
|
## Task 1: First thing
|
||||||
|
|
||||||
|
Do the first thing.
|
||||||
|
PLAN
|
||||||
|
|
||||||
|
local brief_out brief_path
|
||||||
|
brief_out="$(cd "$repo" && "$SDD_SCRIPTS/task-brief" plan.md 1)"
|
||||||
|
brief_path="$(printf '%s\n' "$brief_out" | sed -n 's/^wrote \(.*\): [0-9][0-9]* lines$/\1/p')"
|
||||||
|
case "$brief_path" in
|
||||||
|
"$repo/.superpowers/sdd/"*) pass "task-brief writes its brief under the workspace" ;;
|
||||||
|
*)
|
||||||
|
fail "task-brief writes its brief under the workspace"
|
||||||
|
echo " got: $brief_path"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
local git_id=(-c user.email=t@example.com -c user.name=t -c commit.gpgsign=false)
|
||||||
|
( cd "$repo" \
|
||||||
|
&& git add plan.md \
|
||||||
|
&& 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" 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/"*) pass "review-package writes its diff under the workspace" ;;
|
||||||
|
*)
|
||||||
|
fail "review-package writes its diff under the workspace"
|
||||||
|
echo " got: $rp_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 )
|
||||||
|
local wt_root wt_dir
|
||||||
|
wt_root="$(cd "$wt" && git rev-parse --show-toplevel)"
|
||||||
|
wt_dir="$(cd "$wt" && "$SDD_SCRIPTS/sdd-workspace")"
|
||||||
|
if [[ "$wt_dir" == "$wt_root/.superpowers/sdd" && "$wt_dir" != "$dir" ]]; then
|
||||||
|
pass "linked worktree resolves its own distinct workspace"
|
||||||
|
else
|
||||||
|
fail "linked worktree resolves its own distinct workspace"
|
||||||
|
echo " main: $dir"
|
||||||
|
echo " wt: $wt_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'y\n' > "$wt/.superpowers/sdd/artifact.md"
|
||||||
|
local wt_status
|
||||||
|
wt_status="$(cd "$wt" && git status --porcelain)"
|
||||||
|
if [[ -z "$wt_status" ]]; 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 "$@"
|
||||||
Reference in New Issue
Block a user