@loneexile/omp-superpowers 7.0.1-omp.4 → 7.0.2-omp.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -153,7 +153,8 @@ The workspace and ledger are shared with superpowers:subagent-driven-development
153
153
  and the new one resumes from the same ledger.
154
154
 
155
155
  - Each plan owns a workspace: at skill start, run
156
- `../subagent-driven-development/scripts/sdd-workspace PLAN_FILE` — it
156
+ `../subagent-driven-development/scripts/sdd-workspace PLAN_FILE` by its
157
+ absolute path, from the working tree you're executing in — it
157
158
  prints the plan's git-ignored directory
158
159
  (`<repo-root>/.superpowers/sdd/<plan-basename>/`), home to every
159
160
  artifact for THIS plan: ledger, briefs, review packages. Another plan's
@@ -175,8 +176,9 @@ and the new one resumes from the same ledger.
175
176
  if that happens, recover from `git log`.
176
177
  - **On omp, resolve the scripts directories first.** The relative paths in
177
178
  this skill (`scripts/task-start`, `../subagent-driven-development/scripts/…`)
178
- do not resolve from the repo root, and `omp read skill://…` lists the files
179
- but gives no filesystem path. Once, before the first script, run
179
+ do not resolve from the repo root, so each script runs by its absolute path,
180
+ and `omp read skill://…` lists the files but gives no filesystem path. Once,
181
+ before the first script, run
180
182
  `realpath skill://executing-plans/scripts` and
181
183
  `realpath skill://subagent-driven-development/scripts` in the `bash` tool
182
184
  and add the printed paths to the ledger, after the identity line, as
@@ -187,11 +189,11 @@ and the new one resumes from the same ledger.
187
189
  `rtk ls`. Use the printed paths everywhere else. Ledger lines are found by
188
190
  their prefix, never by position. This ledger is shared with
189
191
  subagent-driven-development, which writes only `SDD scripts:`: add
190
- whichever of the two lines it lacks. Then run
191
- each script as `bash <dir>/<name>` — `scripts/task-start` and
192
- `scripts/task-done` from the executing-plans directory, `sdd-workspace` and
193
- `review-package` from the SDD one — with the working directory at the repo
194
- root; the scripts call `git rev-parse`. The invocation lines in this skill
192
+ whichever of the two lines it lacks. Then run each script by its
193
+ absolute path, `bash <dir>/<name>`, from the working tree you're executing
194
+ in (the scripts act on its repository, as git does): `task-start` and
195
+ `task-done` from the executing-plans directory, `sdd-workspace` and
196
+ `review-package` from the SDD one. The invocation lines in this skill
195
197
  stay as upstream wrote them.
196
198
 
197
199
  Read the plan once, note its context and Global Constraints, and create a
@@ -223,7 +225,8 @@ in the workspace and read its tail; read a brief, not the whole plan.
223
225
 
224
226
  ### 1. Take the task
225
227
 
226
- - Run this skill's `scripts/task-start PLAN_FILE N`. It prints the brief
228
+ - Run this skill's `scripts/task-start PLAN_FILE N` by its absolute path,
229
+ from the working tree you're executing in. It prints the brief
227
230
  path and BASE (the commit the task's range is cut from) in one call.
228
231
  Read the brief for every task, including ones you remember from setup:
229
232
  what you remember is a summary, the brief has the exact values,
@@ -276,7 +279,7 @@ the claim. If any item is missing, the task is not complete: finish it.
276
279
  ### 4. Complete the task
277
280
 
278
281
  Run this skill's `scripts/task-done PLAN_FILE N BASE -- <test command>`
279
- with the test command the brief names for the whole task. It runs the
282
+ by its absolute path, from the working tree you're executing in, with the test command the brief names for the whole task. It runs the
280
283
  tests, keeps the full output in the workspace, prints the tail, and — only
281
284
  if they pass — appends the completion line to the ledger:
282
285
 
@@ -288,7 +291,7 @@ mark the todo complete and take the next task.
288
291
  ## Final Review
289
292
 
290
293
  Run `../subagent-driven-development/scripts/review-package PLAN_FILE MERGE_BASE HEAD`
291
- (MERGE_BASE = the commit the branch started from, e.g.
294
+ by its absolute path, from the working tree you're executing in (MERGE_BASE = the commit the branch started from, e.g.
292
295
  `git merge-base main HEAD`) and review from the file it prints.
293
296
 
294
297
  **With a subagent tool:** dispatch the reviewer on the most capable
@@ -20,7 +20,10 @@ base=$3
20
20
  shift 4
21
21
  sdd="$(cd "$(dirname "$0")/../../subagent-driven-development/scripts" && pwd)"
22
22
 
23
- git rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
23
+ # The repository the workspace and ledger belong to (see sdd-repo); BASE and
24
+ # HEAD are read there, not in the bare cwd.
25
+ repo=$("${BASH:-bash}" "$sdd/sdd-repo" --with-commit)
26
+ git -C "$repo" rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
24
27
 
25
28
  dir=$("$sdd/sdd-workspace" "$plan")
26
29
  log="$dir/task-${n}-tests.log"
@@ -47,6 +50,6 @@ fi
47
50
 
48
51
  last=$(grep -v '^[[:space:]]*$' "$log" | tail -n 1) || last="(no output)"
49
52
  [ -f "$ledger" ] || printf '# SDD ledger — plan: %s\n' "$plan" > "$ledger"
50
- line="Task $n: complete (commits $(git rev-parse --short=7 "$base")..$(git rev-parse --short=7 HEAD), tests: $cmd → $last)"
53
+ line="Task $n: complete (commits $(git -C "$repo" rev-parse --short=7 "$base")..$(git -C "$repo" rev-parse --short=7 HEAD), tests: $cmd → $last)"
51
54
  printf '%s\n' "$line" >> "$ledger"
52
55
  echo "ledger: $line"
@@ -20,9 +20,13 @@ plan=$1
20
20
  n=$2
21
21
  sdd="$(cd "$(dirname "$0")/../../subagent-driven-development/scripts" && pwd)"
22
22
 
23
+ # BASE comes from the same repository the brief is written into (sdd-repo
24
+ # also refuses one with no commit yet), not from the bare cwd.
25
+ repo=$("${BASH:-bash}" "$sdd/sdd-repo" --with-commit)
23
26
  out=$("$sdd/task-brief" "$plan" "$n")
24
27
  brief=$(printf '%s\n' "$out" | sed -n 's/^wrote \(.*\): [0-9][0-9]* lines$/\1/p')
25
28
  [ -n "$brief" ] || { echo "task-brief did not report a path: $out" >&2; exit 1; }
29
+ base=$(git -C "$repo" rev-parse --verify HEAD)
26
30
 
27
31
  echo "brief: $brief"
28
- echo "base: $(git rev-parse HEAD)"
32
+ echo "base: $base"
@@ -43,6 +43,15 @@ This determines which menu to show and how cleanup works:
43
43
  | `GIT_DIR != GIT_COMMON`, named branch | Standard 3 options | Provenance-based (see Step 6) |
44
44
  | `GIT_DIR != GIT_COMMON`, detached HEAD | Reduced 2 options (no merge) | Externally managed — leave in place |
45
45
 
46
+ **On omp, check for an ovnfs checkout too:** `git config --get ovnfs.checkout`
47
+ prints the checkout's name inside one and nothing elsewhere. An ovnfs
48
+ checkout is a copy-on-write copy of your human partner's folder with its
49
+ own private git directory. The table calls it a normal repo
50
+ (`GIT_DIR == GIT_COMMON`), but nothing committed in it is in their
51
+ repository until it is applied, and the `MAIN_ROOT` lines below fail there
52
+ (`fatal: not a git repository`). Present the standard 3 options and carry
53
+ out the choice as *In an ovnfs checkout* (end of Step 5) says.
54
+
46
55
  ## Step 3: Determine Base Branch
47
56
 
48
57
  The base branch is whatever this work forked from — usually named in the
@@ -160,6 +169,47 @@ git switch <base-branch> # normal repo only
160
169
  git branch -D <feature-branch>
161
170
  ```
162
171
 
172
+ ### In an ovnfs checkout
173
+
174
+ `<checkout>` is the name Step 2 printed. Your human partner's repository is
175
+ the folder `ovnfs ls` lists as that checkout's SOURCE. Run these from the
176
+ checkout, still on `<feature-branch>`: ovnfs applies the checkout's HEAD.
177
+
178
+ **Option 1:**
179
+
180
+ ```bash
181
+ ovnfs apply <checkout> --branch <feature-branch>
182
+ ```
183
+
184
+ That makes the checkout's commits `<feature-branch>` in the folder's
185
+ repository and changes no file there. Then merge in the folder, with the
186
+ `bash` tool's `cwd` set to it: if `git branch --show-current` prints
187
+ `<base-branch>` and `git status --porcelain` prints nothing, run `git pull`,
188
+ `git merge <feature-branch>` and the tests there, and once they pass,
189
+ `git branch -d <feature-branch>`. Otherwise stop and tell your human partner
190
+ that `<feature-branch>` is now in their repository and why you did not
191
+ merge it. `ovnfs apply <checkout> --branch <base-branch>` while the folder
192
+ has `<base-branch>` checked out moves that branch and its index but leaves
193
+ the folder's files as they were, so the merge shows there as uncommitted
194
+ changes that undo it. Plain `ovnfs apply <checkout>`, without `--branch`,
195
+ brings the changes back as uncommitted edits, without the commits.
196
+
197
+ **Option 2:** as above; the push goes from the checkout.
198
+
199
+ **Option 3:** report "Keeping branch <name> in ovnfs checkout <checkout>
200
+ (<path>). `ovnfs apply <checkout> --branch <name>` puts it in your
201
+ repository."
202
+
203
+ **Discard:** in the checkout, `git switch <base-branch>` and
204
+ `git branch -D <feature-branch>`. The branch never reached the folder.
205
+
206
+ **The checkout itself:** after Option 1 or a discard, remove it with
207
+ `ovnfs rm <checkout>`, run from the folder, only if this session opened it
208
+ with `ovnfs new`, the session's working directory is not inside it, and
209
+ `git status --porcelain` in it prints nothing. Otherwise leave it and tell
210
+ your human partner that `ovnfs rm <checkout>`, run from their folder,
211
+ removes it.
212
+
163
213
  ## Step 6: Cleanup Workspace
164
214
 
165
215
  **Runs for Option 1 and confirmed discards.** Options 2 and 3 always
@@ -191,7 +191,8 @@ sequences — the single most expensive failure observed. Track progress in
191
191
  a ledger file, not only in todos.
192
192
 
193
193
  - Each plan owns a workspace: at skill start, run this skill's
194
- `bash scripts/sdd-workspace PLAN_FILE` — it prints the plan's git-ignored
194
+ `bash scripts/sdd-workspace PLAN_FILE` by its absolute path, from the
195
+ working tree you're executing in — it prints the plan's git-ignored
195
196
  directory (under `<repo-root>/.superpowers/sdd/`), home to
196
197
  every artifact for THIS plan: ledger, briefs, reports, review packages.
197
198
  Another plan's directory is never yours to read or write.
@@ -497,7 +498,8 @@ the review package and fix-round diffs need it. Every task in a wave shares
497
498
  the wave's BASE.
498
499
 
499
500
  - **Task brief:** before dispatching an implementer, run this skill's
500
- `bash scripts/task-brief PLAN_FILE N` — it extracts the task's full text to a
501
+ `bash scripts/task-brief PLAN_FILE N` by its absolute path, from the
502
+ working tree you're executing in — it extracts the task's full text to a
501
503
  uniquely named file and prints the path. Compose the dispatch so the
502
504
  brief stays the single source of
503
505
  requirements. Your dispatch should contain: (1) one line on where this
@@ -573,6 +575,20 @@ the wave's BASE.
573
575
  omp worktree add -b "sdd/<plan>/task-<N>" ~/.omp/wt/"$repo-<plan>-task-<N>"
574
576
  ```
575
577
 
578
+ **With ovnfs on PATH, wave tasks still use `omp worktree add`,** though
579
+ omp's system prompt then asks for ovnfs checkouts as second copies. A
580
+ wave task's branch must be in the plan checkout's repository, where the
581
+ review packages and the merge read it. `omp worktree add` puts it there,
582
+ inside an ovnfs checkout too. An ovnfs checkout keeps its commits in its
583
+ own private git directory, and its `ovnfs apply --branch` writes them to
584
+ the folder ovnfs copied, which is not the plan checkout when the plan
585
+ checkout is an ovnfs checkout itself. If a wave task's copy is an ovnfs
586
+ checkout anyway, bring its branch into the plan checkout with
587
+ `git fetch <its path> sdd/<plan>/task-<N>:sdd/<plan>/task-<N>` before
588
+ each review package and before the merge, never with `ovnfs apply`, and
589
+ remove it with `ovnfs rm <name>` where this skill says
590
+ `git worktree remove`.
591
+
576
592
  `<plan>` is the plan file's name without `.md`;
577
593
  superpowers:using-git-worktrees says why `omp worktree add` and
578
594
  `~/.omp/wt/`. Then, in each new worktree and before dispatching, run the
@@ -627,7 +643,7 @@ your own checkout gained no changes. A task that wrote into your checkout
627
643
  goes back to its implementer with the paths. Then check the task's line in
628
644
  the review list.
629
645
 
630
- - **`review per-task`:** generate the review package (`bash 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; for a wave task, its branch `sdd/<plan>/task-<N>` stands in for HEAD), then dispatch `sdd-reviewer` with the printed path.
646
+ - **`review per-task`:** generate the review package (`bash scripts/review-package PLAN_FILE BASE HEAD` by its absolute path, from the working tree you're executing in — 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; for a wave task, its branch `sdd/<plan>/task-<N>` stands in for HEAD), then dispatch `sdd-reviewer` with the printed path.
631
647
  - **`review at final`:** run the covering test command from the brief yourself, once, in the task's checkout, with its output redirected to a file in the plan's workspace; read the tail. A brief that names no test (docs, configuration) gets the cheapest check that can fail instead (a build, a lint, a parse), or goes on the review list. That run is the only check this task gets before the final review, so never take the implementer's report in its place. A pass completes the task (step 5). A failure goes back to the implementer with the output file's path: rounds 1-2 resume it, round 3 is a fresh `sdd-escalation-implementer`, as in the fix loop, and each round ends when your rerun passes, with no re-review. A failure after round 3 is a plan the tests disagree with: stop and ask.
632
648
 
633
649
  **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, and put a `review at final` task on the review list (`Task <N>: review per-task — <concern>`). A wave task that changed a file outside its brief's Files list broke its wave's independence: merge it after its wave's other tasks, and run their covering tests too on the merged tree. If the concerns are observations (e.g., "this file is getting large"), note them and proceed.
@@ -666,7 +682,8 @@ self-review never replaces a review: for a listed task the task review is
666
682
  it, for any other the final review is.
667
683
 
668
684
  - Hand the reviewer its diff as a file: run this skill's
669
- `bash scripts/review-package PLAN_FILE BASE HEAD` and pass the reviewer the file path
685
+ `bash scripts/review-package PLAN_FILE BASE HEAD` by its absolute path, from
686
+ the working tree you're executing in, and pass the reviewer the file path
670
687
  it prints (or, without bash: `git log --oneline`, `git diff --stat`,
671
688
  and `git diff -U10` for the range, redirected to one uniquely named
672
689
  file). The output never enters your own context, and the reviewer sees
@@ -802,7 +819,8 @@ covering test files in the fix message — a one-line fix does not need the
802
819
  whole suite.
803
820
 
804
821
  **The re-review is scoped.** Run `bash scripts/review-package PLAN_FILE FIX_BASE HEAD`
805
- where FIX_BASE is the head the previous review saw, as a commit SHA (for a
822
+ by its absolute path, from the working tree you're executing in, where
823
+ FIX_BASE is the head the previous review saw, as a commit SHA (for a
806
824
  wave task, its branch tip then: `git rev-parse sdd/<plan>/task-<N>`; never
807
825
  the branch name, which moves with the fix), and dispatch
808
826
  `sdd-rereviewer` with [re-review-prompt.md](re-review-prompt.md): the
@@ -938,7 +956,8 @@ the cap.
938
956
  ## Final Review
939
957
 
940
958
  The final whole-branch review gets a package too: run
941
- `bash scripts/review-package PLAN_FILE MERGE_BASE HEAD` (MERGE_BASE = the commit the
959
+ `bash scripts/review-package PLAN_FILE MERGE_BASE HEAD` by its absolute path,
960
+ from the working tree you're executing in (MERGE_BASE = the commit the
942
961
  branch started from, e.g. `git merge-base main HEAD`) and include the
943
962
  printed path in the final review dispatch, so the final reviewer reads
944
963
  one file instead of re-deriving the branch diff with git commands. Dispatch
@@ -970,7 +989,8 @@ with the complete findings list — not one fixer per finding.
970
989
  Per-finding fixers each rebuild context and re-run suites; a real
971
990
  session's final-review fix wave cost more than all its tasks combined.
972
991
  Then run exactly one scoped re-review of the fix wave
973
- (`bash scripts/review-package PLAN_FILE FIX_BASE HEAD` over the fix range,
992
+ (`bash scripts/review-package PLAN_FILE FIX_BASE HEAD` by its absolute path,
993
+ from the working tree you're executing in, over the fix range,
974
994
  [re-review-prompt.md](re-review-prompt.md)).
975
995
  Adjudicate any residual findings as in the task loop's breaker: park with
976
996
  rulings, or rule on the load-bearing ones and ledger what you decided. Only
@@ -118,6 +118,7 @@ that the fix itself broke nothing.
118
118
  - `[HEAD_SHA]` — current commit; for a wave task that has not merged yet,
119
119
  its branch tip (`git rev-parse sdd/<plan>/task-<N>`)
120
120
  - `[DIFF_FILE]` — the path `bash scripts/review-package PLAN_FILE FIX_BASE HEAD` printed
121
+ (run by its absolute path, from the working tree you're executing in)
121
122
  - `[CHECKOUT_LINE]` — same as the task reviewer's: the worktree line for a wave
122
123
  task that has not merged yet, omitted otherwise
123
124
 
@@ -4,13 +4,22 @@
4
4
  # call. Using the recorded per-task BASE (not HEAD~1) keeps multi-commit
5
5
  # tasks intact.
6
6
  #
7
- # Usage: review-package PLAN_FILE BASE HEAD [OUTFILE]
7
+ # Usage: review-package [-C DIR] PLAN_FILE BASE HEAD [OUTFILE]
8
8
  # Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/review-<base7>..<head7>.diff
9
9
  # (named per range, so a re-review after fixes gets a distinct fresh file).
10
+ # The repository is the one sdd-repo names; -C DIR runs as if started in DIR,
11
+ # as in sdd-workspace.
10
12
  set -euo pipefail
11
13
 
14
+ here=$(cd "$(dirname "$0")" && pwd)
15
+ if [ "${1:-}" = "-C" ]; then
16
+ [ -d "${2:-}" ] || { echo "bad -C directory: ${2:-}" >&2; exit 2; }
17
+ cd "$2"
18
+ shift 2
19
+ fi
20
+
12
21
  if [ $# -lt 3 ] || [ $# -gt 4 ]; then
13
- echo "usage: review-package PLAN_FILE BASE HEAD [OUTFILE]" >&2
22
+ echo "usage: review-package [-C DIR] PLAN_FILE BASE HEAD [OUTFILE]" >&2
14
23
  exit 2
15
24
  fi
16
25
 
@@ -19,35 +28,37 @@ base=$2
19
28
  head=$3
20
29
  [ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
21
30
 
22
- git rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
23
- git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; }
31
+ # Invoke via bash rather than direct exec: some extractors (Python zipfile)
32
+ # strip Unix exec bits when unpacking marketplace packages (#2040).
33
+ repo=$("${BASH:-bash}" "$here/sdd-repo" --with-commit)
34
+
35
+ git -C "$repo" rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
36
+ git -C "$repo" rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; }
24
37
 
25
38
  # Range guards (exit 3): a wrong-branch HEAD yields a range that is empty or
26
39
  # not rooted at BASE; either would silently produce a bogus review package.
27
- git merge-base --is-ancestor "$base" "$head" || { echo "HEAD is not a descendant of BASE: ${base}..${head}" >&2; exit 3; }
28
- [ "$(git rev-list --count "${base}..${head}")" -gt 0 ] || { echo "empty commit range: ${base}..${head}" >&2; exit 3; }
40
+ git -C "$repo" merge-base --is-ancestor "$base" "$head" || { echo "HEAD is not a descendant of BASE: ${base}..${head}" >&2; exit 3; }
41
+ [ "$(git -C "$repo" rev-list --count "${base}..${head}")" -gt 0 ] || { echo "empty commit range: ${base}..${head}" >&2; exit 3; }
29
42
 
30
43
  if [ $# -eq 4 ]; then
31
44
  out=$4
32
45
  else
33
- # Invoke via bash rather than direct exec: some extractors (Python zipfile)
34
- # strip Unix exec bits when unpacking marketplace packages (#2040).
35
- dir=$("${BASH:-bash}" "$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
36
- out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
46
+ dir=$("${BASH:-bash}" "$here/sdd-workspace" "$plan")
47
+ out="$dir/review-$(git -C "$repo" rev-parse --short "$base")..$(git -C "$repo" rev-parse --short "$head").diff"
37
48
  fi
38
49
 
39
50
  {
40
51
  echo "# Review package: ${base}..${head}"
41
52
  echo
42
53
  echo "## Commits"
43
- git log --oneline "${base}..${head}"
54
+ git -C "$repo" log --oneline "${base}..${head}"
44
55
  echo
45
56
  echo "## Files changed"
46
- git diff --stat "${base}..${head}"
57
+ git -C "$repo" diff --stat "${base}..${head}"
47
58
  echo
48
59
  echo "## Diff"
49
- git diff -U10 "${base}..${head}"
60
+ git -C "$repo" diff -U10 "${base}..${head}"
50
61
  } > "$out"
51
62
 
52
- commits=$(git rev-list --count "${base}..${head}")
63
+ commits=$(git -C "$repo" rev-list --count "${base}..${head}")
53
64
  echo "wrote ${out}: ${commits} commit(s), $(wc -c < "$out" | tr -d ' ') bytes"
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ # Print the top-level directory of the git working tree the SDD scripts use:
3
+ # the current directory's, exactly as git itself picks one. A caller that
4
+ # means a different working tree says so with the scripts' -C DIR, which
5
+ # they apply (cd) before calling this.
6
+ #
7
+ # The plan file's location never picks the repository. An executor working
8
+ # in a linked worktree commonly holds an absolute path to the plan in the
9
+ # main checkout (writing-plans leaves the plan uncommitted there), and its
10
+ # workspace and commits belong to the worktree it is working in. And a
11
+ # caller standing outside any repository, such as the skill's own directory
12
+ # in an installed plugin, is in the wrong place: guessing a repository from
13
+ # the plan would silently act on whichever checkout the path points into.
14
+ #
15
+ # Outside any working tree, stop. --with-commit also stops on a repository
16
+ # with no commit yet, for callers that need HEAD as a BASE or a review
17
+ # range. The message covers both ways to get here: running from the wrong
18
+ # directory (where 'git init' would be the wrong fix) and a new project
19
+ # that has no repository or commit yet.
20
+ #
21
+ # Single source of truth for which repository the SDD and executing-plans
22
+ # scripts work in, so no two of them can disagree.
23
+ #
24
+ # Usage: sdd-repo [--with-commit]
25
+ set -euo pipefail
26
+
27
+ with_commit=false
28
+ if [ "${1:-}" = "--with-commit" ]; then
29
+ with_commit=true
30
+ shift
31
+ fi
32
+
33
+ if [ $# -ne 0 ]; then
34
+ echo "usage: sdd-repo [--with-commit]" >&2
35
+ exit 2
36
+ fi
37
+
38
+ if root=$(git rev-parse --show-toplevel 2>/dev/null); then
39
+ if [ "$with_commit" = false ] || git -C "$root" rev-parse --verify --quiet HEAD >/dev/null; then
40
+ printf '%s\n' "$root"
41
+ exit 0
42
+ fi
43
+ said=$(git -C "$root" rev-parse --verify HEAD 2>&1 >/dev/null) || true
44
+ else
45
+ said=$(git rev-parse --show-toplevel 2>&1 >/dev/null) || true
46
+ fi
47
+
48
+ # git's own first line always follows the shared message: where git refuses
49
+ # a working tree the caller is inside (dubious ownership, git missing from
50
+ # PATH, the cwd inside .git), the shared message alone would be wrong, and
51
+ # git's line names the real fix.
52
+ echo "SDD scripts must run from inside the git working tree you're executing in, with at least one commit (current directory: $PWD). If this is a new project, run 'git init' and make a first commit, then rerun." >&2
53
+ echo "git said: ${said%%$'\n'*}" >&2
54
+ exit 2
@@ -27,11 +27,23 @@
27
27
  # Single source of truth for the workspace location, so task-brief and
28
28
  # review-package cannot drift to different directories.
29
29
  #
30
- # Usage: sdd-workspace PLAN_FILE
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
31
36
  set -euo pipefail
32
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
+
33
45
  if [ $# -ne 1 ]; then
34
- echo "usage: sdd-workspace PLAN_FILE" >&2
46
+ echo "usage: sdd-workspace [-C DIR] PLAN_FILE" >&2
35
47
  exit 2
36
48
  fi
37
49
 
@@ -44,8 +56,12 @@ slug=$(basename "$plan" .md)
44
56
 
45
57
  # Physical path, spelled the way pwd -P spells the plan path below: on Git
46
58
  # Bash, rev-parse prints C:/Users/... while pwd prints /c/Users/..., and the
47
- # repo-relative marker check needs both in the same form.
48
- root=$(CDPATH='' cd -- "$(git rev-parse --show-toplevel)" && pwd -P)
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)
49
65
  base="$root/.superpowers/sdd"
50
66
 
51
67
  # Normalize the plan path (physical directory, so relative/absolute/../
@@ -84,4 +100,11 @@ fi
84
100
  # Create-only: a repo may commit its own .gitignore here (e.g. to version
85
101
  # progress.md ledgers); overwriting it would revert that on every run.
86
102
  [ -e "$base/.gitignore" ] || printf '*\n' > "$base/.gitignore"
87
- CDPATH='' cd -- "$dir" && pwd
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
@@ -3,14 +3,21 @@
3
3
  # implementer reads in one call, so the task text never has to be pasted
4
4
  # through the controller's context.
5
5
  #
6
- # Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
6
+ # Usage: task-brief [-C DIR] PLAN_FILE TASK_NUMBER [OUTFILE]
7
7
  # Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/task-<N>-brief.md
8
8
  # (per plan and per worktree; concurrent runs of the SAME plan in the same
9
- # working tree share it).
9
+ # working tree share it). -C DIR runs as if started in DIR, as in sdd-workspace.
10
10
  set -euo pipefail
11
11
 
12
+ here=$(cd "$(dirname "$0")" && pwd)
13
+ if [ "${1:-}" = "-C" ]; then
14
+ [ -d "${2:-}" ] || { echo "bad -C directory: ${2:-}" >&2; exit 2; }
15
+ cd "$2"
16
+ shift 2
17
+ fi
18
+
12
19
  if [ $# -lt 2 ] || [ $# -gt 3 ]; then
13
- echo "usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]" >&2
20
+ echo "usage: task-brief [-C DIR] PLAN_FILE TASK_NUMBER [OUTFILE]" >&2
14
21
  exit 2
15
22
  fi
16
23
 
@@ -23,17 +30,75 @@ if [ $# -eq 3 ]; then
23
30
  else
24
31
  # Invoke via bash rather than direct exec: some extractors (Python zipfile)
25
32
  # strip Unix exec bits when unpacking marketplace packages (#2040).
26
- dir=$("${BASH:-bash}" "$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
33
+ dir=$("${BASH:-bash}" "$here/sdd-workspace" "$plan")
27
34
  out="$dir/task-${n}-brief.md"
28
35
  fi
29
36
 
30
- awk -v n="$n" '
31
- /^```/ { infence = !infence }
32
- !infence && /^#+[ \t]+Task[ \t]+[0-9]+/ {
33
- intask = ($0 ~ ("^#+[ \t]+Task[ \t]+" n "([^0-9]|$)"))
37
+ # A task heading is `Task` followed by an ID: letters and digits, optionally
38
+ # dot-separated, containing at least one digit (3, 6b, A1, 2.1). The digit
39
+ # keeps headings like "## Task Overview" or "#### Task notes" from becoming
40
+ # task boundaries. The ID is compared to TASK_NUMBER as a whole string, so
41
+ # Task 6 never matches Task 6b, Task 1 never matches Task 1.1, and
42
+ # TASK_NUMBER never reaches awk as a regex (#2459). A deeper Task heading
43
+ # whose ID extends the requested one with a letter or dot is a sub-task and
44
+ # stays in its brief (### Task 7 keeps #### Task 7a), as it always has.
45
+ # Depth alone is not enough: plans drift from "## Task 1" to "### Task 2".
46
+ #
47
+ # The word stays the English literal "Task", whatever language the rest of
48
+ # the plan is in. A plan with no Task headings at all usually translated it
49
+ # ("## Tarea 1"), so list the headings shaped like tasks before the
50
+ # not-found error instead of implying the plan has no task 1 (#2496).
51
+ #
52
+ # A fence that never closes hides every Task heading after it (#2436). When
53
+ # it hides one while the requested task is being captured, that task's end
54
+ # can't be found and the brief would run on through every later task: refuse
55
+ # (exit 4) rather than hand an implementer several tasks as its requirements.
56
+ # When it hides the requested heading itself, say so before the not-found
57
+ # error. A stray fence that hides no Task heading (real plans end with a lone
58
+ # ``` line) leaves the brief complete, so brief it with a warning.
59
+ # The plan path goes through ENVIRON, not -v, which would mangle the
60
+ # backslashes of a Windows path.
61
+ rc=0
62
+ TASK_BRIEF_PLAN="$plan" awk -v n="$n" '
63
+ /^```/ { infence = !infence; if (infence) { fence_line = NR; hidden = 0 } }
64
+ infence && /^#+[ \t]+Task[ \t]/ { hidden = 1 }
65
+ !infence && /^#+[ \t]+Task[ \t]/ {
66
+ id = $0
67
+ sub(/^#+[ \t]+Task[ \t]+/, "", id)
68
+ if (match(id, /^[A-Za-z0-9]+(\.[A-Za-z0-9]+)*/) && substr(id, 1, RLENGTH) ~ /[0-9]/) {
69
+ tasks++
70
+ id = substr(id, 1, RLENGTH)
71
+ match($0, /^#+/)
72
+ subtask = intask && RLENGTH > level && substr(id, 1, length(n)) == n \
73
+ && substr(id, length(n) + 1, 1) ~ /[A-Za-z.]/
74
+ if (!subtask) {
75
+ intask = (id == n)
76
+ level = RLENGTH
77
+ }
78
+ }
79
+ }
80
+ !infence && /^#+[ \t]+[^ \t]+[ \t]+[A-Za-z0-9.]*[0-9]/ && shaped < 5 { shaped_heading[++shaped] = $0 }
81
+ intask { print; found = 1 }
82
+ END {
83
+ where = "the code fence opened at line " fence_line " of " ENVIRON["TASK_BRIEF_PLAN"] " never closes"
84
+ if (infence && hidden && (intask || !found)) {
85
+ print "task-brief: " where ", so no Task heading after it can be seen; close that fence in the plan and rerun" | "cat >&2"
86
+ if (intask) exit 4
87
+ } else if (infence && intask) {
88
+ print "task-brief: warning: " where "; it hides no Task heading, so this brief is complete" | "cat >&2"
89
+ }
90
+ # An open fence that hid Task headings already explains why none were
91
+ # seen; the English-word hint would contradict it.
92
+ if (!tasks && shaped && !(infence && hidden)) {
93
+ print "task-brief: no task headings found. Task headings must use the English word \"Task\" (\"### Task 1: ...\"), even when the rest of the plan is in another language. Headings shaped like tasks:" | "cat >&2"
94
+ for (i = 1; i <= shaped; i++) print " " shaped_heading[i] | "cat >&2"
95
+ }
34
96
  }
35
- intask { print }
36
- ' "$plan" > "$out"
97
+ ' "$plan" > "$out" || rc=$?
98
+ if [ "$rc" -ne 0 ]; then
99
+ if [ -f "$out" ]; then rm -f "$out"; fi
100
+ exit "$rc"
101
+ fi
37
102
 
38
103
  if [ ! -s "$out" ]; then
39
104
  echo "task ${n} not found in ${plan} (no heading matching 'Task ${n}')" >&2
@@ -194,7 +194,8 @@ more, nothing less) and is well-built (clean, tested, maintainable)
194
194
 
195
195
  **Placeholders:**
196
196
  - `agent: sdd-reviewer` — REQUIRED (see SKILL.md Agent Selection)
197
- - `[BRIEF_FILE]` — REQUIRED: the task brief file (`bash scripts/task-brief PLAN N`
197
+ - `[BRIEF_FILE]` — REQUIRED: the task brief file (`bash scripts/task-brief PLAN N`,
198
+ run by its absolute path, from the working tree you're executing in,
198
199
  prints the path; same file the implementer worked from)
199
200
  - `[GLOBAL_CONSTRAINTS]` — the binding requirements copied verbatim from
200
201
  the plan's Global Constraints section or the spec: exact values, formats,
@@ -207,7 +208,8 @@ more, nothing less) and is well-built (clean, tested, maintainable)
207
208
  its branch tip (`git rev-parse sdd/<plan>/task-<N>`). Record this SHA:
208
209
  it is the next re-review's FIX_BASE
209
210
  - `[DIFF_FILE]` — REQUIRED: the path the controller wrote the review
210
- package to (`bash scripts/review-package PLAN_FILE BASE HEAD` prints the unique
211
+ package to (`bash scripts/review-package PLAN_FILE BASE HEAD`, run by its
212
+ absolute path, from the working tree you're executing in, prints the unique
211
213
  path it wrote; the package never enters the controller's context)
212
214
  - `[CHECKOUT_LINE]` — for a wave task that has not merged yet: `**Checkout:**
213
215
  <worktree absolute path> — the task's code lives here, not in your