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