Setting the file. One moment.
Sdd Workspace · Subagent Driven Development · obra/superpowers · Skills Docs
ContentsBack to the top of the page (opens in a new tab)
scripts/ sdd-workspace
SDD-WORKSPACE · 110 lines · 5 KB
17 # workspaces keep resolving — which means the first collision on such a
18 # legacy workspace adopts instead of detecting; acceptable, marker-less
19 # workspaces age out as plans finish.
20 #
21 # The workspace lives in the working tree (not under .git/) because Claude Code
22 # treats .git/ as a protected path and denies agent writes there — which blocks
23 # an implementer subagent from writing its report file. A self-ignoring
24 # .gitignore at .superpowers/sdd/ keeps every plan's workspace out of
25 # `git status` and out of accidental commits without modifying any tracked file.
26 #
27 # Single source of truth for the workspace location, so task-brief and
28 # review-package cannot drift to different directories.
29 #
30 # The repository is the current directory's (see sdd-repo). -C DIR runs as
31 # if started in DIR, the way git -C does: a caller working on another
32 # checkout or worktree needs no `cd DIR ;` prefix, a compound that harnesses
33 # cannot allowlist because DIR differs on every run (#1799).
34 #
35 # Usage: sdd-workspace [-C DIR] PLAN_FILE
36 set -euo pipefail
37
38 here=$(cd "$(dirname "$0")" && pwd)
39 if [ "${1:-}" = "-C" ]; then
40 [ -d "${2:-}" ] || { echo "bad -C directory: ${2:-}" >&2; exit 2; }
41 cd "$2"
42 shift 2
43 fi
44
45 if [ $# -ne 1 ]; then
46 echo "usage: sdd-workspace [-C DIR] PLAN_FILE" >&2
47 exit 2
48 fi
49
50 plan=$1
51 [ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
52
53 slug=$(basename "$plan" .md)
54 [ -n "$slug" ] && [ "$slug" != "." ] && [ "$slug" != ".." ] \
55 || { echo "cannot derive a workspace name from: $plan" >&2; exit 2; }
56
57 # Physical path, spelled the way pwd -P spells the plan path below: on Git
58 # Bash, rev-parse prints C:/Users/... while pwd prints /c/Users/..., and the
59 # repo-relative marker check needs both in the same form. Two steps, so a
60 # failing sdd-repo stops the script instead of handing cd an empty path.
61 # Invoke via bash rather than direct exec: some extractors (Python zipfile)
62 # strip Unix exec bits when unpacking marketplace packages (#2040).
63 toplevel=$("${BASH:-bash}" "$here/sdd-repo")
64 root=$(CDPATH='' cd -- "$toplevel" && pwd -P)
65 base="$root/.superpowers/sdd"
66
67 # Normalize the plan path (physical directory, so relative/absolute/../
68 # spellings of one plan compare equal) and express it as the marker value:
69 # repo-relative when the plan lives under the repo root, absolute otherwise.
70 plan_dir=$(CDPATH='' cd -- "$(dirname "$plan")" && pwd -P)
71 plan_abs="$plan_dir/$(basename "$plan")"
72 case "$plan_abs" in
73 "$root"/*) plan_id=${plan_abs#"$root"/} ;;
74 *) plan_id=$plan_abs ;;
75 esac
76
77 # True when the workspace at $1 is (or becomes) this plan's: an existing
78 # marker must name this plan; a missing marker means a new workspace or a
79 # pre-marker legacy one, and either way the plan claims it by writing one.
80 owns() {
81 if [ -e "$1/plan-path" ]; then
82 [ "$(cat "$1/plan-path")" = "$plan_id" ]
83 else
84 mkdir -p "$1"
85 printf '%s\n' "$plan_id" > "$1/plan-path"
86 fi
87 }
88
89 dir="$base/$slug"
90 if ! owns "$dir"; then
91 parent=$(basename "$plan_dir")
92 dir="$base/$slug-$parent"
93 if ! owns "$dir"; then
94 n=2
95 while ! owns "$base/$slug-$parent-$n"; do n=$((n + 1)); done
96 dir="$base/$slug-$parent-$n"
97 fi
98 fi
99
100 # Create-only: a repo may commit its own .gitignore here (e.g. to version
101 # progress.md ledgers); overwriting it would revert that on every run.
102 [ -e "$base/.gitignore" ] || printf '*\n' > "$base/.gitignore"
103
104 # On Windows (Git Bash, MSYS2, Cygwin) print C:/Users/... rather than
105 # /c/Users/... or /tmp/...: the caller may be PowerShell or another native
106 # tool, which can't resolve MSYS paths, and bash accepts this form too.
107 # Only the printed path changes; the plan-path markers above keep pwd -P's
108 # spelling, so existing workspaces still match their plans.
109 CDPATH='' cd -- "$dir"
110 if command -v cygpath >/dev/null 2>&1; then cygpath -m "$PWD"; else pwd; fi