@gobing-ai/spur 0.3.41 → 0.3.43

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.
Files changed (185) hide show
  1. package/.claude-plugin/marketplace.json +15 -0
  2. package/README.md +15 -1
  3. package/config/templates/AGENTS.md +5 -3
  4. package/config/workflows/task-pipeline.yaml +113 -12
  5. package/package.json +15 -12
  6. package/plugins/README.md +655 -0
  7. package/plugins/sp/README.md +621 -0
  8. package/plugins/sp/agents/expert-spur.md +109 -0
  9. package/plugins/sp/agents/super-coder.md +126 -0
  10. package/plugins/sp/agents/super-planner.md +283 -0
  11. package/plugins/sp/agents/super-reviewer.md +154 -0
  12. package/plugins/sp/commands/dev-arch.md +30 -0
  13. package/plugins/sp/commands/dev-brainstorm.md +40 -0
  14. package/plugins/sp/commands/dev-changelog.md +28 -0
  15. package/plugins/sp/commands/dev-daily.md +29 -0
  16. package/plugins/sp/commands/dev-debug.md +29 -0
  17. package/plugins/sp/commands/dev-dogfood.md +33 -0
  18. package/plugins/sp/commands/dev-featurechange.md +130 -0
  19. package/plugins/sp/commands/dev-find-conflict.md +51 -0
  20. package/plugins/sp/commands/dev-find-issue.md +76 -0
  21. package/plugins/sp/commands/dev-find-next.md +60 -0
  22. package/plugins/sp/commands/dev-fixall.md +33 -0
  23. package/plugins/sp/commands/dev-gitmsg.md +28 -0
  24. package/plugins/sp/commands/dev-gtd.md +97 -0
  25. package/plugins/sp/commands/dev-handover.md +26 -0
  26. package/plugins/sp/commands/dev-idea.md +50 -0
  27. package/plugins/sp/commands/dev-next.md +31 -0
  28. package/plugins/sp/commands/dev-parallel.md +30 -0
  29. package/plugins/sp/commands/dev-plan.md +46 -0
  30. package/plugins/sp/commands/dev-refine.md +48 -0
  31. package/plugins/sp/commands/dev-refineall.md +65 -0
  32. package/plugins/sp/commands/dev-refresh.md +32 -0
  33. package/plugins/sp/commands/dev-reverse.md +32 -0
  34. package/plugins/sp/commands/dev-review.md +31 -0
  35. package/plugins/sp/commands/dev-run.md +63 -0
  36. package/plugins/sp/commands/dev-runall.md +83 -0
  37. package/plugins/sp/commands/dev-simplify.md +39 -0
  38. package/plugins/sp/commands/dev-unit.md +29 -0
  39. package/plugins/sp/commands/dev-verify.md +48 -0
  40. package/plugins/sp/commands/dev-verifyall.md +72 -0
  41. package/plugins/sp/commands/dev-wrap.md +48 -0
  42. package/plugins/sp/commands/dev-wrapall.md +51 -0
  43. package/plugins/sp/commands/rule-add.md +18 -0
  44. package/plugins/sp/commands/rule-refine.md +18 -0
  45. package/plugins/sp/commands/rule-scan.md +18 -0
  46. package/plugins/sp/commands/spur-init.md +22 -0
  47. package/plugins/sp/commands/workflow-add.md +18 -0
  48. package/plugins/sp/commands/workflow-refine.md +18 -0
  49. package/plugins/sp/hooks/agent-hint.ts +60 -0
  50. package/plugins/sp/hooks/careful-guard.ts +147 -0
  51. package/plugins/sp/hooks/context-post-tool.ts +325 -0
  52. package/plugins/sp/hooks/context-session-start.ts +182 -0
  53. package/plugins/sp/hooks/context-session-stop.ts +95 -0
  54. package/plugins/sp/hooks/hooks.json +51 -0
  55. package/plugins/sp/hooks/pi/guard-extension.ts +341 -0
  56. package/plugins/sp/hooks/task-write-guard.ts +84 -0
  57. package/plugins/sp/plugin.json +8 -0
  58. package/plugins/sp/scripts/batch-preflight.ts +204 -0
  59. package/plugins/sp/scripts/daily-summary/daily-summary.ts +606 -0
  60. package/plugins/sp/scripts/daily-summary/logger.ts +28 -0
  61. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +363 -0
  62. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +168 -0
  63. package/plugins/sp/scripts/feature-sync-bounded.ts +451 -0
  64. package/plugins/sp/scripts/stage-registry-adapter.ts +1341 -0
  65. package/plugins/sp/scripts/task-size-precheck.ts +202 -0
  66. package/plugins/sp/scripts/validate-commands.ts +651 -0
  67. package/plugins/sp/scripts/validate-flag-contracts.ts +875 -0
  68. package/plugins/sp/skills/brainstorm/SKILL.md +361 -0
  69. package/plugins/sp/skills/brainstorm/agents/openai.yaml +14 -0
  70. package/plugins/sp/skills/brainstorm/examples/ideation-example.md +209 -0
  71. package/plugins/sp/skills/brainstorm/metadata.openclaw +7 -0
  72. package/plugins/sp/skills/brainstorm/references/grilling-interview.md +68 -0
  73. package/plugins/sp/skills/brainstorm/references/workflows.md +255 -0
  74. package/plugins/sp/skills/branch-workflow/SKILL.md +109 -0
  75. package/plugins/sp/skills/branch-workflow/references/branch-lifecycle.md +69 -0
  76. package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +100 -0
  77. package/plugins/sp/skills/code-implementation/SKILL.md +203 -0
  78. package/plugins/sp/skills/code-implementation/references/debugging.md +64 -0
  79. package/plugins/sp/skills/code-implementation/references/implementation-patterns.md +113 -0
  80. package/plugins/sp/skills/code-improvement/SKILL.md +231 -0
  81. package/plugins/sp/skills/code-improvement/references/deepening-signals.md +205 -0
  82. package/plugins/sp/skills/code-review/SKILL.md +112 -0
  83. package/plugins/sp/skills/code-review/references/receiving-code-review.md +70 -0
  84. package/plugins/sp/skills/code-review/references/review-lenses.md +117 -0
  85. package/plugins/sp/skills/code-review/references/self-review-checklist.md +53 -0
  86. package/plugins/sp/skills/code-review/references/verification-before-completion.md +68 -0
  87. package/plugins/sp/skills/code-simplification/SKILL.md +225 -0
  88. package/plugins/sp/skills/code-testing/SKILL.md +108 -0
  89. package/plugins/sp/skills/code-testing/references/stacks/bun-ts.md +165 -0
  90. package/plugins/sp/skills/code-testing/references/stacks/go.md +88 -0
  91. package/plugins/sp/skills/code-testing/references/stacks/python.md +117 -0
  92. package/plugins/sp/skills/code-testing/references/test-loop-breaker.md +37 -0
  93. package/plugins/sp/skills/code-testing/references/test-output-discipline.md +52 -0
  94. package/plugins/sp/skills/code-testing/references/unit-testing.md +212 -0
  95. package/plugins/sp/skills/code-verification/SKILL.md +535 -0
  96. package/plugins/sp/skills/code-verification/references/code-improvement.md +39 -0
  97. package/plugins/sp/skills/code-verification/references/secu-review.md +94 -0
  98. package/plugins/sp/skills/code-verification/references/verdict-schema.md +141 -0
  99. package/plugins/sp/skills/conflict-finding/SKILL.md +254 -0
  100. package/plugins/sp/skills/conflict-finding/references/authority-resolution.md +258 -0
  101. package/plugins/sp/skills/conflict-finding/references/comparison-protocol.md +277 -0
  102. package/plugins/sp/skills/conflict-finding/references/finding-contract.md +346 -0
  103. package/plugins/sp/skills/conflict-finding/references/remediation-routing.md +276 -0
  104. package/plugins/sp/skills/daily-summary/SKILL.md +171 -0
  105. package/plugins/sp/skills/daily-summary/agents/openai.yaml +13 -0
  106. package/plugins/sp/skills/doc-evolve/SKILL.md +175 -0
  107. package/plugins/sp/skills/doc-evolve/references/operations.md +93 -0
  108. package/plugins/sp/skills/dogfood-testing/SKILL.md +608 -0
  109. package/plugins/sp/skills/dogfood-testing/references/monitor-ledger.md +211 -0
  110. package/plugins/sp/skills/dogfood-testing/references/report-template.md +351 -0
  111. package/plugins/sp/skills/doubt-driven-development/SKILL.md +136 -0
  112. package/plugins/sp/skills/functional-review/SKILL.md +364 -0
  113. package/plugins/sp/skills/functional-review/references/verdict-schema.md +133 -0
  114. package/plugins/sp/skills/indexed-context/SKILL.md +164 -0
  115. package/plugins/sp/skills/issue-finding/SKILL.md +442 -0
  116. package/plugins/sp/skills/issue-finding/agents/openai.yaml +15 -0
  117. package/plugins/sp/skills/issue-finding/examples/expected-findings.json +27 -0
  118. package/plugins/sp/skills/issue-finding/examples/session-test-loop.jsonl +23 -0
  119. package/plugins/sp/skills/issue-finding/references/session-formats.md +151 -0
  120. package/plugins/sp/skills/next-feature/SKILL.md +125 -0
  121. package/plugins/sp/skills/next-feature/references/handoff-routing.md +92 -0
  122. package/plugins/sp/skills/next-feature/references/proposal-contract.md +49 -0
  123. package/plugins/sp/skills/next-feature/references/ranking-rubric.md +52 -0
  124. package/plugins/sp/skills/next-feature/references/signal-derivation.md +67 -0
  125. package/plugins/sp/skills/next-router/SKILL.md +181 -0
  126. package/plugins/sp/skills/next-router/references/messages.md +90 -0
  127. package/plugins/sp/skills/next-router/references/routing-table.md +216 -0
  128. package/plugins/sp/skills/parallel-execution/SKILL.md +148 -0
  129. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +109 -0
  130. package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +101 -0
  131. package/plugins/sp/skills/parallel-execution/references/result-synthesis.md +94 -0
  132. package/plugins/sp/skills/reverse-engineering/SKILL.md +375 -0
  133. package/plugins/sp/skills/reverse-engineering/agents/openai.yaml +18 -0
  134. package/plugins/sp/skills/source-driven-development/SKILL.md +124 -0
  135. package/plugins/sp/skills/spec-decomposition/SKILL.md +117 -0
  136. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +539 -0
  137. package/plugins/sp/skills/spur-cli/SKILL.md +133 -0
  138. package/plugins/sp/skills/spur-cli/references/agent.md +179 -0
  139. package/plugins/sp/skills/spur-cli/references/features/acceptance-criteria.md +82 -0
  140. package/plugins/sp/skills/spur-cli/references/features/hierarchy-mece.md +177 -0
  141. package/plugins/sp/skills/spur-cli/references/features/roadmap-priority.md +84 -0
  142. package/plugins/sp/skills/spur-cli/references/features/verbs.md +122 -0
  143. package/plugins/sp/skills/spur-cli/references/features.md +234 -0
  144. package/plugins/sp/skills/spur-cli/references/init.md +119 -0
  145. package/plugins/sp/skills/spur-cli/references/message.md +98 -0
  146. package/plugins/sp/skills/spur-cli/references/rules/authoring-rules.md +212 -0
  147. package/plugins/sp/skills/spur-cli/references/rules/fine-tuning.md +148 -0
  148. package/plugins/sp/skills/spur-cli/references/rules/operations.md +228 -0
  149. package/plugins/sp/skills/spur-cli/references/rules/validation-and-extension.md +129 -0
  150. package/plugins/sp/skills/spur-cli/references/rules.md +209 -0
  151. package/plugins/sp/skills/spur-cli/references/serve.md +58 -0
  152. package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +98 -0
  153. package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +95 -0
  154. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +361 -0
  155. package/plugins/sp/skills/spur-cli/references/tasks.md +280 -0
  156. package/plugins/sp/skills/spur-cli/references/team.md +133 -0
  157. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +226 -0
  158. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +211 -0
  159. package/plugins/sp/skills/spur-cli/references/workflows/validation-and-extension.md +129 -0
  160. package/plugins/sp/skills/spur-cli/references/workflows.md +355 -0
  161. package/plugins/sp/skills/spur-dev/SKILL.md +224 -0
  162. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +196 -0
  163. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +680 -0
  164. package/plugins/sp/skills/spur-dev/references/decision-brief.md +80 -0
  165. package/plugins/sp/skills/spur-dev/references/dev-operations.md +546 -0
  166. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +164 -0
  167. package/plugins/sp/skills/spur-dev/references/execution-batch.md +790 -0
  168. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +352 -0
  169. package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +191 -0
  170. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +417 -0
  171. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +171 -0
  172. package/plugins/sp/skills/spur-dev/references/glossary.md +95 -0
  173. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +80 -0
  174. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +120 -0
  175. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +302 -0
  176. package/plugins/sp/skills/spur-dev/references/product-planning.md +206 -0
  177. package/plugins/sp/skills/spur-dev/references/section-batching.md +24 -0
  178. package/plugins/sp/skills/sys-architecture/SKILL.md +155 -0
  179. package/plugins/sp/skills/sys-architecture/references/decision-method.md +137 -0
  180. package/plugins/sp/skills/sys-architecture/references/upkeep-survey.md +75 -0
  181. package/plugins/sp/skills/sys-debugging/SKILL.md +228 -0
  182. package/plugins/sp/skills/sys-debugging/references/debugging-protocol.md +62 -0
  183. package/plugins/sp/skills/test-driven-development/SKILL.md +248 -0
  184. package/plugins/sp/skills/wayfinder/SKILL.md +292 -0
  185. package/spur.js +6035 -4963
@@ -0,0 +1,790 @@
1
+ ---
2
+ name: execution-batch
3
+ description: "The batch driver loop — resolve+freeze a task set, topologically order by dependencies, run each task's pipeline in order by default or fan out a proven-independent subset on request, inspect terminal verdicts, decide continue/halt, and emit a structured batch report. Owns R1–R5 for batch execution; per-task execution reuses task-pipeline.yaml verbatim (ADR-022: orchestration is a loop in the skill, not a new FSM)."
4
+ see_also:
5
+ - spur-dev
6
+ - execution-workflow
7
+ - dev-operations
8
+ ---
9
+
10
+ # Execution Batch
11
+
12
+ `/sp:dev-runall` runs a **set** of task files through their pipelines in one operation, in
13
+ dependency-correct order. This file owns the batch algorithm: selector resolution, set freeze,
14
+ topological ordering, the per-task run loop, optional parallel fan-out, the failure policy, and the
15
+ report shape.
16
+
17
+ Single-task execution is documented in **[execution-workflow.md](execution-workflow.md)** — this file
18
+ extends that procedure to the batch case. Read that file first for the single-task pipeline contract;
19
+ everything here assumes a task runs through `.spur/workflows/task-pipeline.yaml` unchanged.
20
+
21
+ **Zero engine code, zero schema changes (ADR-022).** The batch is orchestration over existing seams —
22
+ the status vocabulary (`packages/domain/src/planning/schema.ts`), the `dependencies[]` frontmatter
23
+ field, and the per-task task-pipeline driver. Per ADR-022 ("orchestration is configuration / loops in
24
+ the skill"), the batch driver is a loop in the host session for interactive sequential omit/inline,
25
+ or in `sp:super-planner` for explicit/parallel subprocess execution — never a new meta-workflow FSM.
26
+ HITL surfacing, per-task verdict inspection, and continue/halt decisions need judgment between runs
27
+ that a flat FSM cannot express.
28
+
29
+ ```
30
+ /sp:dev-runall ─┬─ interactive sequential omit/inline → host batch loop → inline YAML driver (task N)
31
+ └─ explicit/parallel/headless → sp:super-planner → spur workflow run (task N)
32
+ └─ agent.run spawns vars.agent
33
+ ```
34
+
35
+ The active batch orchestrator owns the spaces **between** task runs: resolve+freeze the set,
36
+ topo-sort, run each task's pipeline, inspect terminal state, decide continue/halt, and emit the
37
+ report. It does not redefine individual steps. Interactive sequential omit/inline delegates each
38
+ ready WBS to [inline-pipeline-driver.md](inline-pipeline-driver.md); explicit/parallel/headless paths
39
+ delegate to the workflow engine and `vars.agent` resolution.
40
+
41
+ ## Step 1 — Selector resolution (R1)
42
+
43
+ The batch accepts either `--tasks <value>` or the convenience `--feature <id>`.
44
+
45
+ **Normalization helper (pseudo-code / comment for implementers):**
46
+
47
+ ```ts
48
+ // In command layer or batch resolver (before passing to super-planner)
49
+ function normalizeArgs(raw: Args): Args {
50
+ const args = { ...raw };
51
+ if (args.feature && !args.tasks) {
52
+ args.tasks = `feature:${args.feature}`;
53
+ // optional: delete args.feature; or keep for reporting
54
+ }
55
+ if (args.feature && args.tasks) {
56
+ // explicit --tasks wins (per Option A)
57
+ console.warn(`--feature ignored because --tasks was provided`);
58
+ }
59
+ return args;
60
+ }
61
+ ```
62
+
63
+ **Normalization rules (performed by the command layer before the skill sees $ARGUMENTS, or by the batch resolver):**
64
+ - If `--feature FOO` is present and `--tasks` is absent, treat the effective selector as `feature:FOO`.
65
+ - If both are present, `--tasks` wins (with a one-line note in the batch report).
66
+
67
+ **Feature-derived strict preflight (R2, task 0510).** After normalization, if the **effective
68
+ selector** is `feature:<id>` (whether via `--tasks feature:<id>` or the `--feature <id>` sugar),
69
+ run a source-local strict feature check **once** before any task-list resolution, freeze,
70
+ dependency resolution, or worktree task execution:
71
+
72
+ ```bash
73
+ # monorepo source; installed projects use their resolved `spur` binary
74
+ bun run apps/cli/src/index.ts feature check <id> --strict --json
75
+ ```
76
+
77
+ - **Abort shape.** A non-zero check aborts the batch immediately: verdict `aborted`, zero attempted
78
+ tasks, and the structured feature findings (the `--json` finding list) reported verbatim. This is
79
+ the same abort vocabulary as cycle / unknown selector (Step 4/Step 5).
80
+ - **Exactly once.** The check runs once per batch, before `task list`; it is not re-run per task.
81
+ - **Non-feature exclusion.** Explicit WBS lists, status pseudo-lists, and `ready` selectors add no
82
+ feature check — only an effective `feature:<id>` selector is feature-derived. When explicit
83
+ `--tasks` overrides `--feature`, the effective selector is not feature-derived, so no check runs.
84
+ - **Why.** `FeatureCheckService` emits `L3.scope-delineation` (Scope lacking an In/Out split) as a
85
+ **warning**; feature-scoped batches never ran a strict check before freezing, so the late feature
86
+ transition became the first blocking check. A selector-local strict preflight catches a known
87
+ strict finding before any task pipeline action without changing corpus-wide severity.
88
+ - **Scope.** This preflight is advisory to severity policy: it does not alter `FeatureCheckService`,
89
+ `L3.scope-delineation` severity, `feature sync`, or batch-create.
90
+
91
+ `--tasks <value>` (or the effective value after normalization) resolves to a frozen set of task WBS numbers. Resolution happens **once, at
92
+ kickoff** — the driver never re-queries `spur task list` to recompute membership mid-batch (R2.1).
93
+
94
+ | Selector form | Regex / match | Resolution |
95
+ |---|---|---|
96
+ | Explicit WBS list | `^[0-9, ]+$` | Split on comma; validate each token is a 4-digit WBS; collect the explicit set. (R1.1) |
97
+ | `feature:<id>` (via `--tasks` or `--feature <id>`) | literal `feature:` prefix or `--feature` flag | `spur task list --feature <id> --json`; collect `wbs` from each row. The `--feature` flag is sugar that becomes `--tasks feature:<id>` at the command layer. (R1.3) |
98
+ | `ready` | literal `ready` | Resolve the union of `spur task list --status todo --json` + `spur task list --status backlog --json`, drop tasks with open children (R1.5, umbrella-parent exclusion below), then keep only tasks whose every `dependencies[]` entry resolves to `status == done` (via `spur task show <dep> --json | jq '{wbs, status, dependencies, feature_id}'` — R5 metadata-only). Report each excluded task with its unmet dependency. (R1.4) |
99
+ | Status pseudo-list | `todo` \| `backlog` \| `wip` \| `blocked` \| `testing` | `spur task list --status <value> --json`; collect `wbs` from each row. (R1.2) |
100
+ | *(else)* | no match | Error: "unknown selector `<value>`" — list the valid forms and halt before running anything. |
101
+
102
+ **Dedup:** an explicit list with a repeated WBS (`--tasks 0040,0040`) collapses to a single entry;
103
+ the frozen set is a set, not a multiset.
104
+
105
+ **Umbrella-parent exclusion:** a `ready` candidate whose `spur task list` shows at least one child
106
+ task (any non-`done`/non-`cancelled` task with `parent_wbs == <wbs>`) is dropped from the `ready`
107
+ set. By decomposition contract a parent "implements nothing itself" — running it would
108
+ re-implement a task that is the abstraction over its children. `spur task batch-create` now
109
+ auto-transitions decomposed parents to `wip` and refreshes their `## Plan` roster (task 0178
110
+ F1/F2), so a `todo` umbrella with open children is a near-impossible-by-construction state;
111
+ this rule is belt-and-braces for the rare case where the parent is re-opened or a child was
112
+ created outside `batch-create`. Each excluded parent is reported in the batch report with
113
+ `reason: "umbrella parent — <n> open children (<child-wbs-list>)"`.
114
+
115
+ **`ready` edge note:** a `ready` candidate whose dependency is **out-of-set** is resolved here by
116
+ status lookup (satisfied → included). In-set dependencies (a task in the set depending on another
117
+ task in the set) are NOT pre-validated by the `ready` selector — they are handled by the ordering
118
+ algorithm in Step 2, which guarantees the dep runs first. The `ready` selector only filters on
119
+ **out-of-set / already-done** deps.
120
+
121
+ ## Step 2 — Freeze + dependency ordering (R2)
122
+
123
+ ### 2.1 Freeze (R2.1)
124
+
125
+ The resolved set is **frozen** into an ordered plan before the first `spur workflow run`. The driver
126
+ iterates this frozen plan; it never shrinks or re-queries membership mid-batch. Even if a task
127
+ transitions to `wip` or `testing` as it runs, every originally-selected task is still attempted in
128
+ plan order.
129
+
130
+ ### 2.2 Build the dependency graph
131
+
132
+ Build a directed graph over the **frozen set** using each task's `dependencies[]` frontmatter. An
133
+ edge `A → B` means "A depends on B" (B must complete before A runs). Only edges whose target is
134
+ **in the set** contribute to the topological sort; out-of-set deps are resolved by status lookup
135
+ (Step 2.3).
136
+
137
+ ### 2.3 Out-of-set dependency resolution
138
+
139
+ For each dependency edge to a task **outside** the frozen set, resolve its current status via
140
+ `spur task show <dep-wbs> --json | jq '{wbs, status, dependencies, feature_id}'` (R5 metadata-only):
141
+
142
+ - status `done` → edge satisfied, drop it from the graph (R2.5). The dependent is unblocked.
143
+ - status ≠ `done` → mark the dependent **blocked**. Transitively mark its in-set descendants blocked
144
+ too (fixpoint propagation: any task depending on a blocked task is itself blocked). Exclude all
145
+ blocked tasks from execution and record the unmet dependency + the blocked subtree for the report
146
+ (R2.4). Independent (non-blocked) tasks in the set still run.
147
+
148
+ ### 2.4 Topological sort
149
+
150
+ Topological-sort the remaining in-set, non-blocked tasks using Kahn's algorithm:
151
+
152
+ 1. Seed the queue with zero-indegree nodes, **sorted WBS-ascending** (deterministic tie-break).
153
+ 2. Repeatedly dequeue the lowest-WBS zero-indegree node, emit it, decrement its successors'
154
+ indegree, and enqueue any newly-zero nodes — preserving WBS-ascending order on each enqueue.
155
+ 3. If Kahn exhausts the queue with nodes still unsorted, a **cycle** exists.
156
+
157
+ **Cycle handling (R2.3):** a cycle aborts the **entire batch** before any task runs. Reconstruct a
158
+ representative cycle path via DFS over the remaining unsorted nodes and report it (e.g.
159
+ `0040 → 0042 → 0040`). Do not run any task in a cyclic batch — running a prefix would partially
160
+ execute work whose ordering is undefined.
161
+
162
+ ### 2.5 Result
163
+
164
+ The ordered execution plan: a WBS-ascending-topological list of tasks to run, plus a `blocked` list
165
+ (with unmet-dep reasons) and (on cycle) an `aborted` flag with the cycle path.
166
+
167
+ ### 2.6 Preflight — TABLE A STOP rows (task 0279 / next-router consumer)
168
+
169
+ **Before** each `spur workflow run` for a WBS still on the plan, re-check readiness with the pure
170
+ helper (preferred) or `sp:next-router` dry-run:
171
+
172
+ ```bash
173
+ bun plugins/sp/scripts/batch-preflight.ts \
174
+ --wbs <wbs> --status <status> \
175
+ --deps <comma-deps> --dep-status <wbs:status,...> --json
176
+ ```
177
+
178
+ | Result | Batch action |
179
+ |--------|----------------|
180
+ | `action: run` | Launch `task-pipeline.yaml` for this WBS (happy path **unchanged**) |
181
+ | `action: skip` code **A2** | Do not launch; report `preflight-skip` + unmet deps (mirrors TABLE A2) |
182
+ | `action: skip` code **A7** | Do not launch; report blocked (handover is operator-side) |
183
+ | `action: skip` code **A8**/**A9** | Do not launch; already done / cancelled |
184
+
185
+ **Invariants:** Preflight never replaces the pipeline with a loop of `/sp:dev-next`. TABLES A/B/C
186
+ remain SSOT in `next-router/references/routing-table.md`. Step 2.3 already pre-blocks many unmet
187
+ out-of-set deps; 2.6 is belt-and-braces for status STOP rows and a uniform report shape
188
+ (`dev-next:`-style reasons). Parallel mode: preflight each WBS before fan-out.
189
+
190
+ ## Step 3 — The driver loop (R3, R4)
191
+
192
+ ```
193
+ plan = resolve(--tasks) → freeze → order(deps) # may abort (cycle) or pre-block (unmet dep)
194
+ report = []
195
+ for wbs in plan: # default sequential mode
196
+ if any dependency of wbs failed earlier in THIS batch:
197
+ report += skipped(wbs, reason); continue # only relevant under --keep-going
198
+ preflight = batch-preflight(wbs) # Step 2.6 — TABLE A STOP
199
+ if preflight.action == skip:
200
+ report += preflight-skip(wbs, preflight); continue
201
+ run: if interactive sequential omit/inline:
202
+ inline-pipeline-driver(task-pipeline.yaml, wbs)
203
+ else:
204
+ spur workflow run task-pipeline.yaml --vars <vars> --async --json
205
+ follow trace until terminal
206
+ inspect terminal state + .spur/run/<wbs>-verdict.json
207
+ report += outcome(wbs)
208
+ if terminal == failed OR stuck status:
209
+ recovery = recoveryHint(status, wbs) # Step 3.3b — at most once
210
+ report += recovery-hint(wbs, recovery)
211
+ # optional: if batch --auto and cardinality==1, dispatch recovery.command once
212
+ if terminal == failed:
213
+ if --keep-going: mark wbs + in-batch dependents as failed/skipped; continue
214
+ else: HALT; remaining → not-attempted; break # stop-the-batch default (R3.1)
215
+ emit batch report (per-task outcome + preflight skips + recovery hints + batch verdict)
216
+ ```
217
+
218
+ Parallel mode keeps the same lifecycle but swaps the inner loop for the independent-task batch
219
+ pattern in [sp:parallel-execution](../../parallel-execution/SKILL.md): identify a zero-edge,
220
+ non-overlapping subset; **preflight each** selected task; run each ready task's `task-pipeline.yaml`
221
+ invocation in its own subagent/worktree-safe context; synthesize outcomes; recovery stays
222
+ **sequential** (one WBS). If any decision-framework check fails, serialize and record the reason.
223
+
224
+ ### 3.1 Per-task execution reuses the pipeline verbatim (R4)
225
+
226
+ Each task runs through the **standard single-task pipeline** — `.spur/workflows/task-pipeline.yaml`
227
+ — with no new FSM and no step edits. Interactive sequential omit/inline invokes the host
228
+ [inline pipeline driver](inline-pipeline-driver.md), which interprets that file; explicit/parallel
229
+ execution invokes the workflow engine. The batch loop inspects the result and never redefines a
230
+ step.
231
+
232
+ **Explicit/parallel path: launch async and poll the trace** (per execution-workflow.md §"Step 2"): a pipeline with
233
+ `agent.run` stages runs for many minutes. Always use `--async` + `spur workflow trace` polling:
234
+
235
+ ```bash
236
+ RUN=$(spur workflow run .spur/workflows/task-pipeline.yaml \
237
+ --vars '{"wbs":"<wbs>","profile":"auto","agent":"claude"}' --async --json | jq -r '.runId')
238
+ spur workflow trace "$RUN" --json | jq '{runId, status, terminalState}' # poll until status is terminal (done/failed)
239
+ ```
240
+
241
+ ### 3.2 Flag → `--vars` passthrough (R4.2, R4.3)
242
+
243
+ Only two flags cross the orchestrator→pipeline boundary; both are merged into the per-task
244
+ `--vars` JSON:
245
+
246
+ | Flag | Effect on per-task `--vars` |
247
+ |---|---|
248
+ | `--auto` | sets `"profile":"auto"` (skips the HITL approve gate). Omitting it forwards nothing, so the pipeline uses its default profile (standard — HITL pause surfaces to the operator). (R4.2) |
249
+ | `--agent <value>` | omit/`inline` in interactive sequential mode selects the host driver and is not forwarded. `auto` or a name sets **both** `"agent":"<value>"` and `"implementAgent":"<value>"` so every workflow `agent.run` step — including implement — spawns that executor. Headless omit/inline falls through the executor precedence chain. To pin ONLY implement, pass `--vars '{"implementAgent":"..."}'` separately; that explicit var selects the subprocess path. (R4.3, tasks 0483/0503) |
250
+
251
+ The host session remains the orchestrator for interactive sequential omit/inline. `sp:super-planner`
252
+ owns explicit-executor and parallel paths; there the flag pins the per-task step executor, not the
253
+ orchestrator.
254
+
255
+ ### 3.3 Terminal-state inspection
256
+
257
+ Each pipeline run ends in one of two terminal states:
258
+
259
+ - **`done`** → the task's `## Testing` / `## Review` sections were filled by the pipeline's `record`
260
+ step; the verdict artifact at `.spur/run/<wbs>-verdict.json` confirms `verdict == PASS`. Record
261
+ `done` in the report.
262
+ - **`failed`** → the pipeline hit a gate failure (precheck, verify verdict ≠ PASS, or an
263
+ `onEnter` exception). Record `failed` with the blocking reason from the trace. This triggers the
264
+ failure policy.
265
+
266
+ ### 3.3b One-shot recovery (task 0279 — next-router consumer)
267
+
268
+ After a non-PASS terminal state (or when the task status is stuck at `wip`/`testing` without a clean
269
+ verdict), consult **one** recovery hop:
270
+
271
+ ```bash
272
+ bun plugins/sp/scripts/batch-preflight.ts --wbs <wbs> --status <status> --recovery
273
+ # → e.g. /sp:dev-verify 0042 --auto --next
274
+ ```
275
+
276
+ | Rule | Detail |
277
+ |------|--------|
278
+ | Budget | **≤ 1** recovery consult per WBS per batch — never loop until done |
279
+ | Default | Print the exact child command in the batch report |
280
+ | `--auto` batch | May dispatch the child **once** when cardinality is 1 and the hop is a single lifecycle command |
281
+ | Multi-candidate | HITL stop — do not silent-pick (batch `--auto` does not break ties) |
282
+ | Forbidden | Replacing the whole batch with repeated `/sp:dev-next` (deep-merge) |
283
+
284
+ Helper: `recoveryHint(status, wbs)` in `plugins/sp/scripts/batch-preflight.ts`. Tables remain SSOT
285
+ in next-router; this only maps status → primary TABLE A hop for recovery.
286
+
287
+ ### 3.3c Bounded feature-sync retry suppression (task 0411)
288
+
289
+
290
+ During a batch, the per-task `record` step and the wrap-up `feature-transition` step each invoke
291
+ feature status sync. When a feature is L4-gate-blocked (e.g. not all linked tasks are `done`), the
292
+ identical blocked proposal repeats on every call with no intervening input change — in the H9
293
+ dogfood, 4 redundant sync calls produced the same blocked result. The orchestration seam fixes
294
+ this, not the engine.
295
+
296
+ Both `task-pipeline.yaml` (`record` step) and `wrapup-pipeline.yaml` (`feature-transition` step)
297
+ invoke the bounded wrapper instead of raw `feature sync`:
298
+
299
+ ```bash
300
+ bun plugins/sp/scripts/feature-sync-bounded.ts <feature-id> --spur-bin "<spurBin>" --json
301
+ ```
302
+
303
+ The wrapper:
304
+
305
+ 1. Reads an input fingerprint (feature file content hash, linked task statuses, verdict artifact
306
+ mtimes) **before** invoking `feature sync`.
307
+ 2. Classifies the structured result — `gateBlocked` checked first (a partial hop can have
308
+ `applied: true` while still gate-blocked), then `applied`, then `no-op`.
309
+ 3. On a **blocked** result, persists `.spur/run/feature-sync-blocked-<id>.json` and, on the next
310
+ call with an **identical fingerprint**, suppresses the redundant sync and replays the prior
311
+ blocked result.
312
+ 4. On **applied** or **no-op** results, passes through unchanged (no suppression).
313
+ 5. When the fingerprint **changes** (a task completed, a verdict file updated), suppression is
314
+ invalidated and a fresh sync runs.
315
+
316
+ **Batch driver contract:** the orchestrator does **nothing extra** — the wrapper lives inside the
317
+ pipeline's `record` step and the wrap-up's `feature-transition` step. The driver still launches
318
+ `task-pipeline.yaml` verbatim (R4.1). Suppression is transparent: the wrapper emits the same
319
+ `FeatureSyncResult` JSON shape as `feature sync --json`, so downstream report logic is unchanged.
320
+ The only observable difference is fewer redundant `feature sync` invocations and a one-line
321
+ `feature-sync-bounded:` annotation on stderr when a duplicate is suppressed.
322
+
323
+ ### 3.4 Metadata-only host controller (R5, task 0510)
324
+
325
+ The batch **orchestrator** reads status, ordering, and terminal state — never task bodies or trace
326
+ output. Task `content`, section bodies (Solution/Testing/Review), and full workflow `output` are
327
+ stage/subagent data and must not enter the host context on the green path; the controller that
328
+ dispatches native subagents must not defeat that isolation by ingesting the very bodies the
329
+ subagents are meant to hold (task 0508's dispatch contract is preserved unchanged).
330
+
331
+ **Green-path projections — every controller-side read is projected to metadata:**
332
+
333
+ - `task show --json` reads pipe to `{wbs, status, dependencies, feature_id}` only:
334
+
335
+ ```bash
336
+ spur task show <wbs> --json | jq '{wbs, status, dependencies, feature_id}'
337
+ ```
338
+
339
+ Use this shape for out-of-set dependency resolution (Step 2.3), the `ready` selector's
340
+ dep-status lookups (Step 1), and any other controller-side `task show`. A status-only lookup may
341
+ narrow further (`| jq '.status'`), but never widen.
342
+
343
+ - Green-path trace observation projects to `{runId, status, terminalState}` only:
344
+
345
+ ```bash
346
+ spur workflow trace "$RUN" --json | jq '{runId, status, terminalState}'
347
+ ```
348
+
349
+ The controller decides continue/halt from `status`/`terminalState` (ADR-044: judge a run by
350
+ `status === 'done'`, never by string-matching a `finalState` name) plus the bounded verdict
351
+ artifact `.spur/run/<wbs>-verdict.json`. It never streams or re-reads a full trace merely to
352
+ summarize status.
353
+
354
+ **Failure-path reads are bounded.** On a failed/blocked task, request only the terminal error and
355
+ the minimal anchor set the batch report needs (e.g. the blocking finding line, the unmet-dep WBS,
356
+ the verdict line) — never the entire trace. If a fuller trace is needed for diagnosis, that read
357
+ belongs to a subagent or the operator, not to the batch controller's report loop.
358
+
359
+ ## Step 4 — Failure policy (R3)
360
+
361
+ ### 4.1 Stop-the-batch (default) (R3.1)
362
+
363
+ By default, the **first** pipeline failure halts the batch. Remaining tasks in the plan are reported
364
+ as `not-attempted`. The report lists succeeded, failed, and not-attempted tasks.
365
+
366
+ ### 4.2 `--keep-going` (R3.2)
367
+
368
+ With `--keep-going`, a failed task does **not** halt the batch. Instead:
369
+
370
+ - The failed task's **in-batch dependents** (tasks in the plan that transitively depend on it) are
371
+ marked `skipped` with the failed dependency as the reason — they cannot run because their dep did
372
+ not reach `done`.
373
+ - **Independent** tasks (no dependency path to the failed task) still run.
374
+
375
+ This requires the driver to track, per failed task, which later plan entries depend on it —
376
+ derivable from the same dependency graph built in Step 2.
377
+
378
+ ## Step 5 — Batch report (R5.2)
379
+
380
+ When the batch finishes — clean (all `done`), halted (default failure policy), or aborted (cycle /
381
+ unknown selector) — emit a structured report. The report is the orchestrator's sole output; it does
382
+ not mutate the corpus (the pipeline's `record` step already wrote per-task results).
383
+
384
+ ```
385
+ ## Batch Report — <selector>
386
+
387
+ **Selector:** <value>
388
+ **Plan:** <n> tasks (ordered: <wbs-list>) · <m> blocked · <p> not-attempted
389
+ **Mode:** stop-the-batch | --keep-going | --auto
390
+ **Verdict:** clean | halted | aborted
391
+
392
+ | WBS | Status | Reason |
393
+ |-----|--------|--------|
394
+ | 0040 | done | — |
395
+ | 0042 | failed | verify verdict PARTIAL (see .spur/run/0042-verdict.json) |
396
+ | 0050 | not-attempted | batch halted after 0042 (stop-the-batch) |
397
+ | 0051 | skipped | dependency 0040 failed (--keep-going) |
398
+ | 0060 | blocked | unmet out-of-set dep: 0099 is wip |
399
+
400
+ **Next:** <one-line action — pick up halted run / resolve 0099 / all green, feature H1 complete>
401
+ ```
402
+
403
+ The per-task outcome vocabulary: `done` | `failed` | `blocked` | `skipped` | `not-attempted`.
404
+ The batch verdict: `clean` (all attempted tasks `done`) | `halted` (a failure stopped the batch) |
405
+ `aborted` (cycle or selector error before any run).
406
+
407
+ ## Worktree isolation (`--worktree [<name>]`)
408
+
409
+ When a batch command (`dev-runall`, `dev-refineall`, `dev-verifyall`) is invoked with
410
+ [`--worktree [<name>]`](flag-glossary.md#flag-worktree), the entire driver loop runs inside an
411
+ isolated git worktree instead of the operator's working directory. This section owns the worktree
412
+ lifecycle for the sequential batch loop. Per-task worktrees and `--mode parallel` isolation stay out
413
+ of scope (task 0142 Slice A); `--worktree --mode parallel` is rejected.
414
+
415
+ One flag, two modes (see the glossary entry for the ownership rule). Bare `--worktree` is **create
416
+ mode** (cut a fresh branch + sibling tree). `--worktree <name>` is **reuse mode** (attach to a tree
417
+ that already exists); name resolution (§ WT-2 below) runs before WT-1. The deltas each mode applies
418
+ are stated inline in WT-1…WT-4. `--continue` re-entry (WT-6) resolves by explicit name first, then
419
+ falls back to the command+selector marker scan.
420
+
421
+ The lifecycle wraps Steps 1–5 unchanged: a precheck creates or adopts the worktree before selector
422
+ resolution runs, the loop executes with the worktree as process cwd, and a terminal action merges or
423
+ retains after the batch report is emitted. Steps 1–5 themselves are not modified — only their cwd
424
+ differs.
425
+
426
+ **Portability (R10).** Use portable `git worktree` commands only. Do **not** depend on the Claude
427
+ Code `EnterWorktree`/`ExitWorktree` tools — the `sp` plugin ships to Codex, Gemini CLI, pi, omp, and
428
+ OpenCode. The underlying git mechanics (create / list / remove / prune, sibling-directory naming,
429
+ disk-space awareness) are reused from [worktree-patterns.md](../../branch-workflow/references/worktree-patterns.md);
430
+ this section does not re-author them.
431
+
432
+ ### WT-1 — Dirty-tree precheck (R3)
433
+
434
+ `git worktree add` branches from a ref, so uncommitted changes in the main tree do **not** carry
435
+ into the worktree — a batch would silently run against different tree state than the operator sees.
436
+ Before creating the worktree, check the main tree:
437
+
438
+ ```bash
439
+ git status --porcelain
440
+ ```
441
+
442
+ - **Clean tree** → proceed to WT-2.
443
+ - **Dirty tree** → **abort** before any worktree is created. Name the offending files (from
444
+ `git status --porcelain`) and instruct the operator to commit or stash. No task work has run.
445
+ - **`--force`** → proceed past a dirty tree with a divergence warning that names the uncommitted
446
+ files. The worktree is created and the batch proceeds against the committed base ref, not the
447
+ operator's working-directory state.
448
+
449
+ **Reuse-mode delta.** The main-tree precheck is unchanged — the divergence hazard is identical (the
450
+ batch still runs somewhere the operator is not standing). The *target* worktree being dirty is
451
+ **expected** (it holds retained partial work from a prior halt) and must **not** abort: report the
452
+ file list once and proceed. The batch runs against the target worktree's working-directory state, not
453
+ a clean checkout — which is exactly the point of reuse mode.
454
+
455
+ ### WT-2 — Worktree creation or adoption
456
+
457
+ **Name resolution runs first** for both modes — bare `--worktree` skips it (no name to resolve);
458
+ `--worktree <name>` must resolve before WT-1's precheck touches anything. Resolution is specified
459
+ in [§ Name resolution](#name-resolution---worktree-name) below; this section covers creation
460
+ (create mode) and adoption (reuse mode).
461
+
462
+ #### Create mode (bare `--worktree`)
463
+
464
+ Create one worktree on a new branch cut from the current HEAD's ref (the **base ref** — often a
465
+ `feat/…` branch, not literally `main`). Location follows the sibling-directory convention in
466
+ [worktree-patterns.md](../../branch-workflow/references/worktree-patterns.md):
467
+
468
+ ```bash
469
+ BASE_REF=$(git rev-parse --abbrev-ref HEAD)
470
+ BASE_SHA=$(git rev-parse HEAD)
471
+ BRANCH="sp/<command>-<selector-slug>-<short-id>" # e.g. sp/runall-h1-a3f2
472
+ git worktree add "../<repo>-<command>-<selector-slug>-<short-id>" -b "$BRANCH" "$BASE_REF"
473
+ ```
474
+
475
+ Branch and directory names are derived (command + selector slug + short id); the create path never
476
+ takes an operator-supplied name (R8.3 — no create-with-name; `--worktree <name>` where `<name>` does
477
+ not resolve is an error, not a create).
478
+
479
+ A fresh worktree has no `node_modules` (gitignored), so the first `bun test` or
480
+ typecheck fails on the first workspace import. Install before any task work:
481
+
482
+ cd "../<worktree-dir>" && bun install --frozen-lockfile
483
+
484
+ `--frozen-lockfile` pins the worktree to `bun.lock` rather than re-resolving,
485
+ so the worktree's dependency tree matches the base ref's.
486
+
487
+ #### Reuse mode (`--worktree <name>`)
488
+
489
+ Creation is **skipped entirely**. `$BRANCH` is the resolved worktree's already-checked-out branch; a
490
+ detached HEAD aborts (no branch can serve as `$BRANCH` for WT-4's FF-merge). `BASE_REF` is the
491
+ invoking tree's current HEAD ref (not the worktree's branch) and `BASE_SHA` is
492
+ `git merge-base <BASE_REF> <BRANCH>` — so WT-4's FF-merge lands the worktree's accumulated commits
493
+ onto the invoking tree's base ref, exactly as create mode does.
494
+
495
+ `bun install --frozen-lockfile` runs **only when `node_modules` is absent** in the resolved
496
+ worktree. A warm reused tree does not re-pay the install; a cold one (hand-made, or a retained tree
497
+ whose deps were removed) installs exactly once before the first task. This is the R3 conditional
498
+ install rule (source: task 0481) — create mode always installs because a fresh tree is always cold.
499
+
500
+ After creation or adoption, immediately write/adopt the state marker (WT-3), then run the
501
+ existing batch loop (Steps 1–5) with the worktree as process cwd. `spur workflow run` resolves cwd
502
+ from the process (`apps/cli/src/commands/workflow.ts:124`), so no CLI change is needed — `cd` into
503
+ the worktree directory before launching the loop.
504
+
505
+ #### Name resolution (`--worktree <name>`)
506
+
507
+ <a id="name-resolution---worktree-name"></a>
508
+
509
+ Authority: **git**, not the marker store — a marker may be stale, git is not. A foreign worktree
510
+ with no marker is a valid target (R3 synthesizes one). Resolve `<name>` against
511
+ `git worktree list --porcelain`, matching in this order and stopping at the first tier that yields
512
+ ≥1 hit:
513
+
514
+ 1. **exact `worktree <path>`** (after path normalization against the invoking tree)
515
+ 2. **`basename(<path>)`**
516
+ 3. **checked-out branch** — accept both `<name>` and the full `refs/heads/<name>` form
517
+
518
+ Then require exactly one survivor across the tiers:
519
+
520
+ - **0 hits** → abort before any task work. Print each candidate worktree as
521
+ `<basename> <branch> <path>`, and the line: *"`--worktree <name>` selects an existing worktree;
522
+ it never creates one. Use bare `--worktree` to create."*
523
+ - **≥2 hits** → abort naming the candidates and require the path form (tier 1).
524
+ - **1 hit, but** the worktree is `locked`, `prunable`, or belongs to a different repo → abort naming
525
+ the condition.
526
+
527
+ ### `spur` on PATH is not this checkout
528
+
529
+ `spur` (`~/.bun/bin/spur`) resolves to a *published* bundle in `~/node_modules/`,
530
+ not to the repo you are standing in and not to the worktree. `resolveSpurBin()`
531
+ propagates whichever binary you entered through into `vars.spurBin`, which the
532
+ `task-lifecycle.yaml` guards run as `$spurBin task check` — so one wrong entry
533
+ point silently gate-checks against the published bundle.
534
+
535
+ Inside a worktree, and in the monorepo whenever CLI behavior is under test, invoke
536
+ the tree's own source:
537
+
538
+ cd "<worktree>" && bun apps/cli/src/index.ts task check <wbs> --json
539
+
540
+ Confirm isolation by making a distinctive change in the worktree and checking that
541
+ the command reflects it.
542
+
543
+ **General rule — every path-resolving tool, not just `spur`.** The same failure class is not
544
+ limited to the CLI on PATH. A host-agent file-edit or hash/`hashline` tool may resolve main-repo
545
+ paths while the shell `cwd` is the worktree, silently acting on the wrong tree. Before relying on any
546
+ path-resolving tool inside a `--worktree` batch, verify it is acting on the worktree — for example
547
+ by making a distinctive change and confirming the path the tool reports matches the worktree. When a
548
+ tool cannot be pointed at the worktree, fall back to `perl -i` in-place edits (or the agent's `write`
549
+ verb) whose path argument you control.
550
+
551
+ ### WT-3 — Crash-safe state marker (R6)
552
+
553
+ Worktree identity lives on disk under `.spur/run/`, not only in the orchestrator's memory, so a
554
+ session that dies mid-batch is recoverable. Write the marker at creation and update it at the
555
+ terminal transition (merged / retained). Schema:
556
+
557
+ ```json
558
+ {
559
+ "id": "<marker-id>",
560
+ "path": "../<repo>-<command>-<selector-slug>-<short-id>",
561
+ "branch": "sp/<command>-<selector-slug>-<short-id>",
562
+ "baseRef": "feat/example",
563
+ "baseSha": "<sha-at-creation>",
564
+ "command": "dev-runall",
565
+ "selector": "feature:H1",
566
+ "createdAt": "<iso-8601>",
567
+ "status": "active"
568
+ }
569
+ ```
570
+
571
+ `status` transitions: `active` → `merged` (WT-4 success) | `retained` (WT-5 failure/halt/non-FF),
572
+ and `retained` → `active` on a reuse re-entry (WT-6). The marker file is named
573
+ `.spur/run/worktree-<marker-id>.json`. It is the authority for WT-6 resume and for operator recovery
574
+ after a crash: a killed session leaves the marker at `status: active`, which the operator reads to
575
+ find the worktree path, branch, and base ref.
576
+
577
+ **Reuse-mode marker adoption.** Two new optional fields record when a run did not create the tree:
578
+
579
+ ```json
580
+ {
581
+ "adopted": true,
582
+ "adoptedAt": "<iso-8601>"
583
+ }
584
+ ```
585
+
586
+ `adopted` is what WT-4 reads to decide retain-vs-remove, so it is set whenever the current run did
587
+ not create the tree — including when reuse mode adopts a marker that create mode originally wrote.
588
+ It records "this run did not create this tree", not "this tree was never created by the flag".
589
+
590
+ Reuse mode resolves the marker by the resolved worktree's `path` (not by `command`+`selector`):
591
+
592
+ - **Existing marker for that path** → adopt it in place: update `command`/`selector` to the current
593
+ invocation, set `status` to `active`, set `adopted: true` + `adoptedAt`, **preserve `baseRef` and
594
+ `baseSha`**. This makes cross-command resume — `dev-runall` halted, now `dev-verifyall` over the
595
+ same tree — a supported path (R5.2).
596
+ - **No marker** (hand-made or foreign worktree) → synthesize one with `baseRef` = the invoking
597
+ tree's current HEAD ref, `baseSha` = `git merge-base <baseRef> <BRANCH>`, `adopted: true`.
598
+ - **Marker already at `status: active`** → **abort** — another session may own that tree
599
+ (AGENTS.md one-writer-per-tree; task 0487 R5). Overridable with `--force` (the operator can tell
600
+ a crashed-session marker from a live-session one; the harness cannot).
601
+
602
+ ### WT-4 — Success path (R4)
603
+
604
+ When the batch completes with **no failed task**, fast-forward-merge the worktree branch onto the
605
+ base ref. The terminal action after the merge differs by mode (ownership rule: *the flag removes
606
+ only what it created*):
607
+
608
+ #### Create mode — merge, remove, delete
609
+
610
+ ```bash
611
+ # Run these from the main tree (not inside the worktree) - you merge the worktree branch
612
+ # back onto the base ref there:
613
+ git checkout "$BASE_REF"
614
+ git merge --ff-only "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
615
+ # if FF succeeded:
616
+ git worktree remove "../<worktree-dir>"
617
+ git branch -d "$BRANCH"
618
+ # update marker: status = "merged"
619
+ ```
620
+
621
+ #### Reuse mode — merge, retain
622
+
623
+ The FF-merge runs identically (same `git checkout "$BASE_REF" && git merge --ff-only "$BRANCH"`),
624
+ but then the worktree and branch are **retained**, not removed:
625
+
626
+ ```bash
627
+ git checkout "$BASE_REF"
628
+ git merge --ff-only "$BRANCH"
629
+ # update marker: status = "merged" (worktree and branch are intentionally NOT removed)
630
+ ```
631
+
632
+ The operator supplied the tree, so the operator owns its lifetime. After a green reuse batch
633
+ `baseRef == $BRANCH`, so the same worktree keeps fast-forwarding on the next invocation instead of
634
+ having to be rebuilt — the continue-the-work loop is stable.
635
+
636
+ **Fast-forward only.** If the base ref has moved since the worktree was created and FF is
637
+ impossible, do **not** rebase, merge-commit, or resolve conflicts — fall through to the retention
638
+ path (WT-5) and report the divergence. The corpus files (`docs/tasks*/`, kanban/index) are
639
+ auto-generated and conflict-prone; automated conflict resolution over generated files is exactly the
640
+ wrong thing to attempt unattended. FF-only means the merge either is trivially correct or does not
641
+ happen.
642
+
643
+ **Auto-decision carve-out.** The create-mode success path runs unattended on a fully-passing
644
+ `--worktree --auto` batch — it does **not** pause even though it performs a merge and a branch
645
+ deletion. That is the explicit single exception to Auto-Decision Principle #6 (`cross-cutting.md`),
646
+ which otherwise pauses any `--merge` / branch-deletion action regardless of `--auto`. The exception
647
+ is safe because `git merge --ff-only` and `git branch -d` both fail closed (they refuse rather than
648
+ risk losing work); WT-5 retains the worktree and branch whenever FF is impossible or any task fails.
649
+ Reuse mode is **narrower** than the carve-out (it merges but does not delete the branch), so the
650
+ carve-out text needs no widening.
651
+
652
+ ### WT-5 — Failure path: retain and report (R5)
653
+
654
+ On any per-task failure, batch halt, HITL pause that ends the run, or non-FF merge from WT-4, the
655
+ worktree directory and branch are left **intact**. No destructive automation on this path under any
656
+ flag combination (`--auto`, `--force`, `--keep-going` — all leave the worktree in place). Update the
657
+ marker: `status = "retained"`. Emit a retention report in the existing halt-report shape:
658
+
659
+ ```
660
+ ## Worktree retained — <command> <selector>
661
+
662
+ **Halt cause:** <one-line cause — batch halted at task <wbs> / non-FF base ref / HITL pause>
663
+ **Worktree path:** ../<worktree-dir>
664
+ **Branch:** sp/<command>-<selector-slug>-<short-id>
665
+ **Base ref:** <base-ref> (<base-sha>)
666
+
667
+ The worktree and its branch are intact. Nothing was merged onto the base ref.
668
+ Resume, merge, or discard:
669
+
670
+ resume: cd <worktree-path> && <command> --continue --worktree <worktree-path>
671
+ merge: git checkout <base-ref> && git merge <branch> # resolve conflicts manually
672
+ discard: git worktree remove <worktree-path> && git branch -D <branch>
673
+ ```
674
+
675
+ The report reuses the [`--next` chain contract](flag-glossary.md#-next-chain-contract) halt-report
676
+ shape (halt cause + where + why), not new vocabulary. Retention is the right default: these batches
677
+ are long and already resumable via `--continue`; auto-deleting is data loss, auto-merging is a
678
+ partial result presented as a whole. The answer to "what happens if it fails" is "nothing happens,
679
+ and we tell you where the work is."
680
+
681
+ ### WT-6 — `--continue` re-entry (R7)
682
+
683
+ A `--continue` resume of a batch started with `--worktree` must re-enter the existing worktree via
684
+ its WT-3 marker rather than creating a second one. Marker lookup tries two paths in order:
685
+
686
+ 1. **Name-resolution path (when `--worktree <name>` is present)** — run the [§ Name resolution](#name-resolution---worktree-name)
687
+ algorithm against `<name>`. The resolved worktree's path identifies the marker file to adopt.
688
+ This path covers the common resume shapes: the operator remembers the name used last time, or
689
+ passes the path (tier-1 match).
690
+ 2. **Command+selector fallback (bare `--worktree` or absent flag)** — scan
691
+ `.spur/run/worktree-*.json` for a marker whose `command` + `selector` match the current
692
+ invocation and whose `status` is `active` or `retained`. Create-mode runs that did not name their
693
+ tree resolve here.
694
+ 3. **Found by either path** → `cd` into the marker's `path`, skip WT-1/WT-2 (no new worktree), and
695
+ resume the loop from the checkpoint (Steps 1–5 with `--continue` semantics).
696
+ 4. **Not found** → fail loudly: "no resolvable worktree marker for `<command> <selector>` under
697
+ `.spur/run/`; cannot resume a `--worktree` batch without one. Re-run without `--worktree` to
698
+ start a new batch in the main tree, or inspect `.spur/run/` for prior markers." Do **not**
699
+ silently run in the main tree.
700
+
701
+ Name resolution failing at resume (0 or ≥2 hits) has the same abort semantics as a fresh run: it
702
+ names candidates and requires the path form — it does **not** fall through to the command+selector
703
+ fallback, because `<name>` was explicit and unambiguous intent.
704
+
705
+ ### WT-7 — Exclusions (R8)
706
+
707
+ - **`dev-next`** does not get `--worktree` — it dispatches a single step; per-step isolation is not
708
+ worth the worktree cost.
709
+ - **`--mode parallel`** is rejected when combined with `--worktree` — per-task worktrees and
710
+ parallel isolation remain task 0142 Slice A.
711
+ - **No** create-with-name (`--worktree <name>` never creates; an unresolvable name is an error),
712
+ no `--worktree-keep` variant, no auto-cleanup of stale worktrees or markers from prior runs.
713
+
714
+ ### Corpus visibility note
715
+
716
+ While the batch runs, corpus writes (`spur task update`, `spur feature update`) land in the
717
+ **worktree copy**; the operator's main tree still shows pre-run task statuses. This is expected —
718
+ the merge (WT-4) or manual integration (WT-5) propagates the writes back. Worth one line in each
719
+ command doc so it does not read as a bug.
720
+
721
+ ## Still out of scope
722
+
723
+ - **Interactive within-step Q&A** — a headless subprocess `agent.run` agent asking the operator a
724
+ real question. This waits for the workspace module + inbox module + `spur agent` team mode.
725
+ `sp:super-planner` surfaces blockers/HITL only at the **batch boundary** (between task runs), not
726
+ from inside a pipeline step.
727
+
728
+ ## AC traceability
729
+
730
+ | AC | Where satisfied |
731
+ |---|---|
732
+ | R1.1–R1.4 (selector grammar) | Step 1 — selector resolution table |
733
+ | R1.5 (umbrella-parent exclusion) | Step 1 — "Umbrella-parent exclusion" paragraph |
734
+ | R2.1 (freeze at kickoff) | Step 2.1 |
735
+ | R2.2 (topological order) | Step 2.4 (Kahn, WBS-ascending tie-break) |
736
+ | R2.3 (cycle aborts) | Step 2.4 cycle handling |
737
+ | R2.4 (unmet out-of-set dep blocks subtree) | Step 2.3 + Step 4.2 |
738
+ | R2.5 (satisfied out-of-set dep allowed) | Step 2.3 |
739
+ | R3.1 (stop-the-batch default) | Step 4.1 |
740
+ | R3.2 (`--keep-going` skips subtree) | Step 4.2 |
741
+ | R4.1 (each task reuses the pipeline verbatim) | Step 3.1 |
742
+ | R4.2 (`--auto` → profile=auto) | Step 3.2 |
743
+ | R4.3 (`--agent` merged into per-task vars) | Step 3.2 |
744
+ | R5.1 (orchestrator boundary) | "Zero engine code" preamble + Step 3 |
745
+ | R5.2 (structured batch report) | Step 5 |
746
+ | 0411 (bounded feature-sync retry suppression) | Step 3.3c — wrapper lives in pipeline `record` + wrap-up `feature-transition`; driver unchanged |
747
+ | 0510 R2 (feature-derived strict preflight) | Step 1 — "Feature-derived strict preflight (R2, task 0510)" |
748
+ | 0510 R5 (metadata-only host controller) | Step 3.4 + projected `task show` / trace snippets in Step 1, 2.3, 3.1 |
749
+
750
+ ## Parallel Execution
751
+
752
+ When a batch contains tasks with **zero dependency edges between them** and **no file-overlap conflicts**, the orchestrator can fan them out in parallel instead of running them sequentially. This is an **orchestrator-level optimization** — the per-task pipeline (`task-pipeline.yaml`) is unchanged; only the execution order differs.
753
+
754
+ **Decision framework:** `sp:parallel-execution` owns the full fan-out decision logic and patterns. Consult its [fan-out-patterns.md](../../parallel-execution/references/fan-out-patterns.md) before parallelizing. The orchestrator's responsibility is:
755
+ 1. Identify the independent subset from the topo-sorted batch (tasks with no edges to each other).
756
+ 2. Check for file-overlap conflicts (two tasks touching the same `file:line` range must serialize).
757
+ 3. Verify token budget supports N-way fan-out.
758
+ 4. Dispatch via `spur agent run` per task (trigger 4: workspace isolation required for parallel fan-out).
759
+ 5. Synthesize results per the [result-synthesis contract](../../parallel-execution/references/result-synthesis.md).
760
+
761
+ **Parallel vs. sequential:** the default is sequential (topo-sort order). Parallel is an opt-in via `--mode parallel` on `sp:super-planner` or `/sp:dev-parallel`. When in doubt, run sequentially — parallel is only beneficial when tasks are provably independent.
762
+
763
+ **See also:** `sp:parallel-execution` skill, `sp:super-planner` agent (parallel mode), `/sp:dev-parallel` command.
764
+
765
+
766
+ ## Subagent execution disciplines
767
+
768
+ Parallel fan-out and any subagent dispatch obey the four disciplines owned by
769
+ [sp:parallel-execution](../../parallel-execution/SKILL.md) (its "Subagent execution disciplines" section is the SSOT):
770
+
771
+ - **File-handoffs** — pass the artifact as a file path, never bulk context in the dispatch prompt.
772
+ - **Durable progress ledger** — per-task status + result location recorded in a file/the batch report so a resumed or compacted run knows what already ran.
773
+ - **Per-role model selection** — the cheapest model that fits each role (`--agent` pins the executor; the discipline picks the model per role).
774
+ - **Never pre-judge the reviewer** — verify/review subagents receive artifact + contract only; no pre-rated severity, no "do not flag X".
775
+
776
+ ## Checkpoint read on batch resume
777
+
778
+ When resuming an interrupted batch run, read the latest checkpoint from
779
+ `.spur/memory/sessions/` before re-launching:
780
+
781
+ ```bash
782
+ ls -t .spur/memory/sessions/*.md 2>/dev/null | head -1
783
+ ```
784
+
785
+ The checkpoint's YAML frontmatter contains `session_id`, `workflow`, `task_wbs` or `feature_id`,
786
+ `phase`, `last_gate`, `timestamp`, and `next_action`. Surface `next_action` to the operator
787
+ before resuming. The batch driver reads the checkpoint to determine which task was last
788
+ attempted and whether it reached a terminal state. Checkpoints are working memory — the task
789
+ files and the frozen task set are the authoritative state. See
790
+ [cross-cutting.md](cross-cutting.md) § "Session Checkpoint Convention" for the full format.