@gobing-ai/spur 0.4.26 → 0.4.28

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.
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.4.26",
10
+ "version": "0.4.28",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -88,6 +88,8 @@ rules:
88
88
  exclude:
89
89
  # the CLI migration journal — 43 DDL statements (verified load-bearing)
90
90
  - "packages/domain/src/migrations.ts"
91
+ # 0037's embedded mirror, generated verbatim from released drizzle SQL (task 1312)
92
+ - "packages/domain/src/migration-sql.generated.ts"
91
93
 
92
94
  - id: raw-sql-only-in-domain
93
95
  description: >
@@ -44,6 +44,8 @@ rules:
44
44
  - "**/schema/**"
45
45
  - "**/schema.ts"
46
46
  - "**/migrations.ts"
47
+ # generated migration SQL; scripts/commands/emit-migration-sql.test.ts pins it (task 1312)
48
+ - "packages/domain/src/migration-sql.generated.ts"
47
49
  - "**/db.ts"
48
50
  ## Entity declarations — contracts are transport DTOs tested by the single contract.test.ts
49
51
  - "packages/contracts/src/**"
@@ -139,6 +139,15 @@ vars:
139
139
  # The gate observes, it does not mutate: `implement` already ran `$formatCmd` on its way out,
140
140
  # and a formatter inside the gate would rewrite the tree the receipt is keyed by.
141
141
  qualityGateCmd: "${spurBin:-spur} self exec check"
142
+ # 1319 R1: per-spawn bound for EVERY command the gate runs (full attempt, recheck probe, light
143
+ # sub-checks, changed-path read). `command.gate` exports workflow vars as env, so this var reaches
144
+ # `spur task gate` as `gateTimeoutMs` with no new flag. A hit is BLOCKED (exit 2), never a red
145
+ # gate, and writes no check receipt. 15 min: above a healthy full gate, far below a hang.
146
+ gateTimeoutMs: "900000"
147
+ # 1319 R2(c): outer backstop on the gate ACTION itself. Only a finite `timeoutMs` gives
148
+ # `ts-runtime` process-group ownership, which reaps grandchildren the gate's own `spawnSync`
149
+ # cannot reach. Must stay above lock wait (2 h, quality-gate.ts) + 5 x gateTimeoutMs + probe.
150
+ gateBackstopMs: "14400000"
142
151
  # Cheap red-detector run before the full gate on **recheck visits only**; empty ⇒ no probe.
143
152
  # A project overriding qualityGateCmd should override this too. TRUSTED CONFIG ONLY.
144
153
  gateProbeCmd: "${spurBin:-spur} self exec lint"
@@ -163,6 +172,9 @@ vars:
163
172
  # anchors directly in the /sp:dev-fixall input, so the fix agent starts AT the failing
164
173
  # file:line instead of re-deriving it from a fresh gate run (0482 R3).
165
174
  gateFindings: ""
175
+ # 1319 R5: lint findings from the previous review visit, echoed into the review prompt on the
176
+ # inline retry. Empty on the happy path, so the prompt pays nothing when nothing is wrong.
177
+ reviewLint: ""
166
178
  # The quality-gate mode this visit runs — `run` on the first gate visit of the run,
167
179
  # `recheck` after a remediation hop, `deferred` under the opt-in batch policy. Written by
168
180
  # the gate's own shell and projected into this var (args cannot branch) — D65 1239 R2.
@@ -332,9 +344,19 @@ states:
332
344
  # Best-effort: `; exit 0` (not `&&`) so a missing/failing formatter in a seeded
333
345
  # non-Bun project cannot abort a run mid-implement. `${vars.qualityGateCmd}` at
334
346
  # `gate` is the gate that actually decides.
335
- - kind: shell
347
+ # 1319 R3: the formatter is a bounded `command.gate` instead of an unbounded `shell`.
348
+ # Best-effort semantics stay (`softFail: true`: a missing/failing/timed-out formatter
349
+ # never aborts the run — the quality gate is the real gate), but a formatter that hangs
350
+ # can no longer hold the step open forever. `shell` cannot carry a bound upstream
351
+ # (ts-runtime PipeProcessOptions has no timeout), so the step kind changes, not the intent.
352
+ - kind: command.gate
336
353
  options:
337
- command: 'sh -c "$formatCmd" ; exit 0'
354
+ id: implement-format
355
+ executable: "sh"
356
+ args: ["-c", "${vars.formatCmd}"]
357
+ resultFile: .spur/run/${vars.__runId}-implement-format.status
358
+ softFail: true
359
+ timeoutMs: "${vars.stepTimeoutMs}"
338
360
  # 1109 R3: qualify this task's unique basename anchors to repo-relative paths.
339
361
  # Placement is load-bearing: it runs AFTER implement's mutations (and the format
340
362
  # above) and BEFORE the gate reads the tree, so the receipt is keyed to the final
@@ -518,6 +540,9 @@ states:
518
540
  args: ["task", "gate", "${vars.wbs}", "${vars.gateMode}", "--no-logo"]
519
541
  resultFile: .spur/run/${vars.__runId}-gate.status
520
542
  softFail: true
543
+ # 1319 R2(c): group-owned backstop. The gate's own per-spawn bound (R1) bounds
544
+ # each command; this bounds the action and reaps any surviving descendant.
545
+ timeoutMs: "${vars.gateBackstopMs}"
521
546
  # (d) record what the PREVIOUS gate visit's failure-class answer turned out to be
522
547
  # (1263 R3a): a recheck visit means the last class was `fix` and the repair hop ran, so
523
548
  # this visit's gate status says whether it was right. First write wins, the step never
@@ -635,6 +660,18 @@ states:
635
660
  approval decision point. Cost at this decision point (1122): open P1-P3 review
636
661
  findings block `done`; only P4 rows and DEFER-ed P3 rows are wrap residuals.
637
662
  onEnter:
663
+ # 1319 R5(b)/Q9: surface the previous visit's verdict-lint findings for THIS visit's prompt.
664
+ # Empty on the first visit (and after a clean lint), capped so the prompt stays inside its
665
+ # composition budget, and it names the declared R/AC ids because the lint already does.
666
+ - kind: shell
667
+ options:
668
+ command: >-
669
+ mkdir -p .spur/run; L=".spur/run/$wbs-verdict-lint.txt"; O=".spur/run/$__runId-review-lint.txt";
670
+ if [ -s "$L" ]; then head -c 600 "$L" > "$O"; else : > "$O"; fi; exit 0
671
+ - kind: file.read.into-var
672
+ options:
673
+ path: .spur/run/${vars.__runId}-review-lint.txt
674
+ var: reviewLint
638
675
  - kind: agent.run
639
676
  options:
640
677
  # 0710 R2: review always runs on a fresh session — no implementation-session
@@ -664,7 +701,7 @@ states:
664
701
  Answer contract: super-reviewer Output Format. Emit the machine-linted artifact, not a prose summary.
665
702
  `## Review`: deriveReviewConfidence. Captured answer (`spur task verdict`) has one `Confidence: HIGH|MEDIUM|LOW`
666
703
  line (deriveVerifyConfidence), never the section's disposition level.
667
- End with one `Verdict: PASS|PARTIAL|FAIL` line (plain, final) after
704
+ End with one `Verdict: PASS|PARTIAL|FAIL` line after
668
705
  `| Req | Status | Evidence |` (one row per requirement; MET | PARTIAL | UNMET) and
669
706
  `| AC | Status | Evidence Type | Evidence |` (one row per AC; MET | PARTIAL | UNMET | N/A).
670
707
  Evidence types (`+` ok): test | command | static-ref | manual-review | llm-judge | n/a.
@@ -672,6 +709,7 @@ states:
672
709
  a declared `AC-N` alias; a linked scenario covers its task; a task-local criterion needs no feature credit.
673
710
  Open P1-P3 blocks completion.
674
711
  Also write it to `.spur/run/${vars.__runId}-review-cert.md`.
712
+ ${vars.reviewLint}
675
713
  # The captured review answer is the run's certification input (and the
676
714
  # `task verdict` input below): expectFile fails the step when no answer landed.
677
715
  answerFile: .spur/run/${vars.__runId}-review-answer.txt
@@ -712,9 +750,12 @@ states:
712
750
  options:
713
751
  id: review-verdict
714
752
  executable: "${vars.spurBin}"
715
- args: ["task", "verdict", "${vars.wbs}", "--from-answer", ".spur/run/${vars.__runId}-review-cert.md"]
753
+ args: ["task", "verdict", "${vars.wbs}", "--from-answer", ".spur/run/${vars.__runId}-review-cert.md", "--replay"]
716
754
  resultFile: .spur/run/${vars.__runId}-review-verdict.status
717
755
  softFail: true
756
+ # 1319 R4: the replay runs up to 5 cited suites at 120 s each; the action bound is
757
+ # the same headroom the review hop gets, so a slow cited suite cannot pin the visit.
758
+ timeoutMs: "900000"
718
759
  # (e) one fold, one token: PASS requires the typed verdict PASS, the artifact's own
719
760
  # PASS, and a clean review gate (open P1-P3 finding or an uncited claim blocks it).
720
761
  # Fail-closed: any missing/corrupt input leaves FAIL, so the transitions can route a
@@ -759,6 +800,14 @@ states:
759
800
  options:
760
801
  command: >-
761
802
  mkdir -p .spur/run; A=APPROVED; if [ "$profile" = auto ]; then [ "$__hitlAnswer" = no ] && A=DENIED; else [ "$__hitlAnswer" = yes ] || A=DENIED; fi; printf '%s\n' "$A" > ".spur/run/$wbs-approval.status"; exit 0
803
+ # 1319 R5(b): one inline re-review token, decided AFTER every fold above so deleting the
804
+ # stale cert/answer cannot make a fold read the previous visit's verdict. Run-scoped, so a
805
+ # stale file from an earlier run can never trigger a retry; `-retried` makes it once only.
806
+ # A second lint failure leaves the token NONE and routes to remediate exactly as today.
807
+ - kind: shell
808
+ options:
809
+ command: >-
810
+ if [ -s ".spur/run/$wbs-verdict-lint.txt" ] && [ ! -f ".spur/run/$__runId-review-lint-retried" ]; then touch ".spur/run/$__runId-review-lint-retried"; printf 'RETRY\n' > ".spur/run/$__runId-review-lint-retry"; rm -f ".spur/run/$__runId-review-cert.md" ".spur/run/$__runId-review-answer.txt"; else printf 'NONE\n' > ".spur/run/$__runId-review-lint-retry"; fi; exit 0
762
811
 
763
812
  - id: done
764
813
  description: >
@@ -1050,6 +1099,16 @@ transitions:
1050
1099
  kind: shell
1051
1100
  options:
1052
1101
  command: 'test "$(cat ".spur/run/$wbs-approval.status" 2>/dev/null)" = DENIED'
1102
+ # 1319 R5(c): one inline re-review when the answer LINT failed, before any remediate hop is
1103
+ # spent. Declared after the denial route and before `retry-exhausted` because a lint retry is
1104
+ # not a repair attempt (it does not consume the shared fix-attempt counter). First match wins.
1105
+ - from: review
1106
+ to: review
1107
+ description: The verdict lint rejected the answer's shape — re-issue the review prompt once with the lint findings instead of buying a remediate hop.
1108
+ guard:
1109
+ kind: shell
1110
+ options:
1111
+ command: 'test "$(cat ".spur/run/$__runId-review-lint-retry" 2>/dev/null)" = RETRY'
1053
1112
  - from: review
1054
1113
  to: failed
1055
1114
  terminalReason: retry-exhausted
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.4.26",
3
+ "version": "0.4.28",
4
4
  "description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
5
5
  "keywords": [
6
6
  "spur",
@@ -50,7 +50,7 @@
50
50
  "dev": "bun run src/index.ts",
51
51
  "build": "bun run ../../scripts/spur-dev.ts build-cli",
52
52
  "build:binaries": "bun run ../../scripts/spur-dev.ts build-binaries",
53
- "build:bundle": "bun run ../../scripts/spur-dev.ts bundle-web && bun run ../../scripts/spur-dev.ts emit-board-types && bun build src/index.ts --target=bun --outfile spur.js --external @johnhenry/backend-webgpu && bun run ../../scripts/spur-dev.ts bundle-config config && bun run ../../scripts/spur-dev.ts bundle-laya-worker && bun run ../../scripts/spur-dev.ts bundle-plugins",
53
+ "build:bundle": "bun run ../../scripts/spur-dev.ts bundle-web && bun run ../../scripts/spur-dev.ts emit-board-types && bun run ../../scripts/spur-dev.ts emit-migration-sql && bun build src/index.ts --target=bun --outfile spur.js --external @johnhenry/backend-webgpu && bun run ../../scripts/spur-dev.ts bundle-config config && bun run ../../scripts/spur-dev.ts bundle-laya-worker && bun run ../../scripts/spur-dev.ts bundle-plugins",
54
54
  "test": "bun test tests --coverage --coverage-dir=../../.coverage --pass-with-no-tests",
55
55
  "typecheck": "tsc --noEmit",
56
56
  "prepack": "bun run build:bundle",
@@ -17,9 +17,9 @@ Wraps the **sp:spur-dev** skill.
17
17
  | `--tasks` `<selector>` | Task selector to run. | required |
18
18
  | `--feature` `<id>` | Restrict to a feature. | omitted |
19
19
  | `--mode` `<sequential\|parallel\|auto>` | Batch execution order. `auto` computes the frozen batch's dependency waves and runs parallel only when a wave holds ≥2 independent tasks (see below). | sequential |
20
- | `--keep-going` | Continue past per-task failures. | off |
21
- | `--auto` | Skip objective HITL gates. | off |
22
- | `--agent` `<inline\|auto\|name>` | Who runs each task's pipeline stages. Interactive sequential omit/`inline` uses the host-session driver with unified inline semantics (task 0687 — see below). `auto`, a name, parallel mode, and headless invocation use subprocesses. The opt-in `fleet` selector (0942) maps each task run to `executor: 'fleet'` so `agent.run` stages dispatch through the fleet control plane (ADR-126) instead of a subprocess. | omit |
20
+ | `--keep-going` | Continue past per-task failures (implied by `--auto`). | off |
21
+ | `--auto` | Skip objective HITL gates, and treat the batch as unattended: `--auto` implies `--keep-going` (a failed task is `failed`, its in-batch dependents `skipped`, independents keep running). | off |
22
+ | `--agent` `<inline\|auto\|name>` | Who runs each task's pipeline stages. Interactive sequential omit/`inline` uses the host-session driver with unified inline semantics (task 0687 — see below). `auto`, a name, parallel mode, and headless invocation use subprocesses; `auto` is resolved **once per batch** — the first run's implement `result.executor` is pinned for every later task, and only a `failed-agent` ending re-resolves it, exactly once (1318 R2/R3). The opt-in `fleet` selector (0942) maps each task run to `executor: 'fleet'` so `agent.run` stages dispatch through the fleet control plane (ADR-126) instead of a subprocess. | omit |
23
23
  | `--json` | Emit structured JSON. | off |
24
24
  | `--wrap` | Run the wrap hop **once for the batch** over the `done` subset only (Step 6 of execution-batch.md): `vars.feature` is passed only when every frozen task is `done`/`cancelled`; an empty done subset skips the wrap with a reason. The `--agent` selector is preserved into the `/sp:dev-wrap` handoff when supplied; omission remains omission. | off |
25
25
  | `--next` | Chain-to-completion via the next-router. | off |
@@ -50,8 +50,9 @@ see `execution-parallel-isolation.md`; `auto` picks one per batch — see below)
50
50
  (parallel-mode worker bound — at most <n> task pipelines at once, default 2; a dependent task starts
51
51
  only after its in-set dependencies are **integrated** onto the base ref), `--keep-going`
52
52
  (batch failure policy — skip a failed task's in-batch dependents, continue independents; default
53
- halts on first failure), `--auto`
54
- (sets `profile=auto` on each per-task run, skipping the HITL approve gate), `--agent <inline|auto|name>`
53
+ halts on first failure, and `--auto` implies `--keep-going`), `--auto`
54
+ (sets `profile=auto` on each per-task run, skipping the HITL approve gate; makes the batch
55
+ unattended, so it also implies `--keep-going`), `--agent <inline|auto|name>`
55
56
  (names who runs the pipeline's stages — interactive sequential omit/`inline` uses the host driver;
56
57
  `auto` or a name is merged into each per-task `vars.agent` and `vars.implementAgent`; `fleet` maps each task run to `executor: 'fleet'` instead (fleet dispatch, 0942); parallel mode
57
58
  retains isolated subprocesses, ADR-047), `--json` (emit the
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.4.26",
3
+ "version": "0.4.28",
4
4
  "description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics."
5
5
  }
@@ -191,6 +191,12 @@ Neither step takes the full project gate. The full gate is named only for the pi
191
191
  creep into every plugin edit.
192
192
  - Run the applicable rows, then stop: the single full project check belongs to the pipeline's
193
193
  `gate` state (`task-pipeline.yaml` `${vars.qualityGateCmd}`).
194
+
195
+ **Focused runs and file mutation (1319 R6/R9).** Focused verification is `spur task gate <wbs> light` —
196
+ never a hand-rolled `bunfig.toml`, never the root-cwd `bun test`. Mutate with `Edit`/`Write`; a
197
+ `python3 - <<PY`, `sed -i` or `perl -pi` file mutation is a **defect** (it carries scaffolding through
198
+ the context instead of a diff, and a silent no-op replace is indistinguishable from success).
199
+ Contract: `docs/design/gate-bounds-and-pause-attribution.md`.
194
200
  - **Mirrored surfaces: an edit lands on all of them.** When the change is one instance of a repeated
195
201
  pattern — the same wiring rule on two runbooks, a signature in both a generator and its generated
196
202
  `.d.mts`, a command chain in both `spur-check` and `spur-check:full` — grep for the siblings and
@@ -64,6 +64,13 @@ loop are the dominant verification cost on a long chain.
64
64
  Do not re-run the full suite per iteration, and do not run a full gate before the narrow target is
65
65
  green. Target: full `spur-check` runs ≤2 per task.
66
66
 
67
+ ## Temp dirs: never under `.spur/run` (task 1319 R9)
68
+
69
+ A test that needs a scratch directory creates it under the OS temp dir (`mkdtempSync(join(tmpdir(),
70
+ ...))`), never under `.spur/run`. That path is the run's own evidence plane: test fixtures written
71
+ there inflate the run directory (the credit-sentry tree reached 528 MB with 3656 entries in one
72
+ task's directory), slow every glob over it, and re-trigger the pipeline's dirty-tree precheck.
73
+
67
74
  ## Composition with the discipline + implementation skills
68
75
 
69
76
  - **`sp:test-driven-development`** — they compose: TDD *designs* the tests (red-green-refactor, behavior naming,
@@ -607,10 +607,10 @@ principles determine which gates route around HITL and which still pause.
607
607
  `--auto`. Irreversible is irreversible.
608
608
 
609
609
  **Exception — the worktree batch success path (`execution-batch.md` § WT-4).** The
610
- `--worktree --auto` full-batch success sequence — `git merge --ff-only "$BRANCH"`, `git worktree
610
+ `--worktree --auto` full-batch success sequence — `git merge --ff-only --no-edit "$BRANCH"`, `git worktree
611
611
  remove`, `git branch -d "$BRANCH"` — does **not** pause, even though it performs a merge and a
612
612
  branch deletion. This is the single carve-out from Principle #6, and it is safe by construction:
613
- `git merge --ff-only` refuses rather than rewriting history when the base ref has moved, and
613
+ `git merge --ff-only --no-edit` refuses rather than rewriting history when the base ref has moved, and
614
614
  `git branch -d` (lowercase) refuses to delete a branch that is not fully merged. Both fail closed,
615
615
  so no work can be lost — the property #6 exists to protect. When FF is impossible, WT-4 falls
616
616
  through to the WT-5 retention path, which leaves the worktree and branch intact. Every other
@@ -481,7 +481,7 @@ gate commands, pending questions and approval requirements.>
481
481
  ### 13. runall
482
482
 
483
483
  - **Purpose:** Run a batch of tasks through their pipelines in dependency-correct order — resolve a set, topo-sort, run each via `task-pipeline.yaml`, inspect verdicts, apply the failure policy, emit a batch report.
484
- - **Inputs:** `--tasks <selector>` (required — explicit WBS list, status pseudo-list, `feature:<id>`, or `ready`). `--mode <sequential|parallel>` (default `sequential`; `parallel` fans out a proven-independent subset per `execution-batch.md` § Parallel Execution). `--keep-going` skips a failed task's in-batch dependents and continues independents (default halts on first failure). `--auto` sets `profile=auto` on each per-task run (skips the HITL approve gate). `--feature <id>` is sugar for `--tasks feature:<id>`; when the effective selector is feature-derived, the batch runs `spur feature check <id> --fix --json` then `spur feature check <id> --strict --json` **once** before task resolution — a non-zero strict check aborts with verdict `aborted`, zero attempted tasks, and the structured findings (task 0510 R2); scoped: `L4.scenario-unverified` and `L4.verifying-incomplete-tasks` (expected pre-run states of a not-yet-run or reopened feature) are reported verbatim but do not abort — the non-aborting set is `NON_ABORTING_PREFLIGHT_CODES` behind `spur task preflight --classify-feature` and `--fix` repairs are listed under `autoRepairs` — any other strict error aborts. Explicit WBS/status/`ready` selectors add no feature check. The orchestrator loop itself continues in this session; interactive sequential omit/`inline` uses the host driver — host-controlled with eligible `agent.run` stages dispatching once to a native subagent and host fallback (task 0508) — while `--agent auto`/a name, parallel mode, and headless invocation keep the isolated per-task workflow subprocess boundary. `--agent <inline|auto|name>` pins the executor for the per-task stages (see [SSOT](cross-cutting.md#inline-default-execution-surface)); the value crosses into per-task `vars.agent`, not the orchestrator. `--json` emits the report as JSON. `--wrap` triggers `wrapup-pipeline.yaml` **once for the batch** over the `done` subset only (execution-batch.md Step 6: `vars.feature` only when every frozen task is `done`/`cancelled`; empty done subset skips with a reason), `--next` chains each task to terminal status then runs the same batch-once wrap, `--continue` resumes from checkpoint. `--no-summary` skips the [execution summary](#execution-summary) printed after the batch report (one row per attempted run).
484
+ - **Inputs:** `--tasks <selector>` (required — explicit WBS list, status pseudo-list, `feature:<id>`, or `ready`). `--mode <sequential|parallel>` (default `sequential`; `parallel` fans out a proven-independent subset per `execution-batch.md` § Parallel Execution). `--keep-going` skips a failed task's in-batch dependents and continues independents (default off; `--auto` implies `--keep-going`, so an unattended batch never stops for a human — 1318 R1). `--auto` sets `profile=auto` on each per-task run (skips the HITL approve gate). `--feature <id>` is sugar for `--tasks feature:<id>`; when the effective selector is feature-derived, the batch runs `spur feature check <id> --fix --json` then `spur feature check <id> --strict --json` **once** before task resolution — a non-zero strict check aborts with verdict `aborted`, zero attempted tasks, and the structured findings (task 0510 R2); scoped: `L4.scenario-unverified` and `L4.verifying-incomplete-tasks` (expected pre-run states of a not-yet-run or reopened feature) are reported verbatim but do not abort — the non-aborting set is `NON_ABORTING_PREFLIGHT_CODES` behind `spur task preflight --classify-feature` and `--fix` repairs are listed under `autoRepairs` — any other strict error aborts. Explicit WBS/status/`ready` selectors add no feature check. The orchestrator loop itself continues in this session; interactive sequential omit/`inline` uses the host driver — host-controlled with eligible `agent.run` stages dispatching once to a native subagent and host fallback (task 0508) — while `--agent auto`/a name, parallel mode, and headless invocation keep the isolated per-task workflow subprocess boundary. `--agent <inline|auto|name>` pins the executor for the per-task stages (see [SSOT](cross-cutting.md#inline-default-execution-surface)); the value crosses into per-task `vars.agent`, not the orchestrator. `--json` emits the report as JSON. `--wrap` triggers `wrapup-pipeline.yaml` **once for the batch** over the `done` subset only (execution-batch.md Step 6: `vars.feature` only when every frozen task is `done`/`cancelled`; empty done subset skips with a reason), `--next` chains each task to terminal status then runs the same batch-once wrap, `--continue` resumes from checkpoint. `--no-summary` skips the [execution summary](#execution-summary) printed after the batch report (one row per attempted run).
485
485
  - **Three orthogonal axes (do not confuse):** `--keep-going` = batch failure policy (halt vs skip dependents); `--continue` = resume from checkpoint against the batch's original frozen identity (execution-batch.md § Batch continuation, task 0919); `--next` = per-task lifecycle chaining (advance status on a verdict — `dev-verify`/`dev-verifyall` only). `routing-table.md` offers `--continue` and `--next` as competing options for the same situation only when the batch was interrupted mid-run; otherwise they address different problems.
486
486
  - **Backing:** `sp:spur-dev` skill, `runall` operation → delegates the driver loop to the **`sp:super-planner`** agent (the batch orchestrator).
487
487
  - **Behavior:** The orchestrator reads [execution-batch.md](execution-batch.md) and drives: resolve selector → freeze set → topo-sort by `dependencies[]` (Kahn, WBS-ascending tie-break; cycle aborts) → resolve out-of-set deps by status (done → allow, else → block subtree) → run each task via `spur workflow run task-pipeline.yaml --async` + `spur workflow trace` polling → inspect terminal state + `.spur/run/<wbs>-verdict.json` → stop-the-batch default or `--keep-going` subtree skip → emit batch report. Per-task pipeline is invoked **verbatim** — no new FSM, no step edits. `--auto`/`--agent` are the only flags that cross the orchestrator→pipeline boundary (both into per-task `--vars`).
@@ -20,21 +20,29 @@ results). A `paused` task is non-terminal — the report marks it `paused` (neve
20
20
  the resume command. Unless `--no-summary`, follow it with the measured
21
21
  [execution summary](dev-operations.md#execution-summary), one `--progress <wbs>=<file>` per attempted run.
22
22
 
23
+ Under `--auto` the report's mode field reads `Mode: --auto (keep-going)`, and `--auto` implies `--keep-going`:
24
+ an unattended batch never stops for a human (1318 R1). For an `--agent auto` or
25
+ `--agent <name>` batch, print the header `Executor pin: <name> (from <wbs>|--agent)` and fill the
26
+ per-task `Executor` column with the pinned executor, or `A → B (failed-agent)` for a run that was
27
+ re-dispatched once on a new executor (1318 R2–R4). Leave that line and column out for host-inline
28
+ runs, which have no executor to pin.
29
+
23
30
  ```
24
31
  ## Batch Report — <selector>
25
32
 
26
33
  **Selector:** <value>
27
34
  **Plan:** <n> tasks (ordered: <wbs-list>) · <m> blocked · <p> not-attempted
28
- **Mode:** stop-the-batch | --keep-going | --auto
35
+ **Mode:** stop-the-batch | --keep-going | --auto (keep-going)
36
+ **Executor pin:** <name> (from <wbs>|--agent) ← `--agent auto`/`--agent <name>` runs only
29
37
  **Verdict:** clean | halted | aborted
30
38
 
31
- | WBS | Status | Reason |
32
- |-----|--------|--------|
33
- | 0040 | done | — |
34
- | 0042 | failed | verify verdict PARTIAL (see .spur/run/0042-verdict.json) |
35
- | 0050 | not-attempted | batch halted after 0042 (stop-the-batch) |
36
- | 0051 | skipped | dependency 0040 failed (--keep-going) |
37
- | 0060 | blocked | unmet out-of-set dep: 0099 is wip |
39
+ | WBS | Status | Executor | Reason |
40
+ |-----|--------|----------|--------|
41
+ | 0040 | done | claude | — |
42
+ | 0042 | failed | codex-a → codex-b (failed-agent) | verify verdict PARTIAL (see .spur/run/0042-verdict.json) |
43
+ | 0050 | not-attempted | — | batch halted after 0042 (stop-the-batch) |
44
+ | 0051 | skipped | — | dependency 0040 failed (--keep-going) |
45
+ | 0060 | blocked | — | unmet out-of-set dep: 0099 is wip |
38
46
 
39
47
  **Next:** <one-line action — pick up halted run / resolve 0099 / all green, feature H1 complete>
40
48
 
@@ -23,6 +23,9 @@ see_also:
23
23
  [execution-batch-continuation.md](execution-batch-continuation.md) for `--continue`.
24
24
  - **Never re-derive the plan** — the frozen plan is the persisted report; a new selector match is
25
25
  `not-admitted`, not a plan change.
26
+ - **Fresh session per segment** — when host context nears its limit, stop after a task terminal and
27
+ resume in a new session with `--continue`; the persisted report and markers carry the state. A
28
+ near-full context slows every later turn and degrades judgment.
26
29
 
27
30
  ## What this file owns
28
31
 
@@ -146,6 +149,13 @@ recorded in the batch report, and independent tasks keep running. The batch does
146
149
  - **without `--auto`:** unchanged halt behavior — the batch stops on the failing task (`--keep-going`
147
150
  still skips only its in-batch dependents).
148
151
 
152
+ **`--auto` implies `--keep-going` (1318 R1).** An unattended batch never stops for a human: under
153
+ `--auto` every per-task failure follows §4.2 whether or not the flag is passed — a failed,
154
+ cancelled, `stale-evidence` or worker-lost (1317) task is recorded `failed`, its in-batch
155
+ dependents are `skipped` with `dependency <wbs> failed`, and independent tasks keep running. The
156
+ refine pass above is only the extra step before that, not an exception. Without `--auto` the §4.1
157
+ stop-the-batch default is unchanged. No new flag.
158
+
149
159
  `--tasks <value>` (or the effective value after normalization) resolves to a frozen set of task WBS numbers. Resolution happens **once, at
150
160
  kickoff** — the driver never re-queries `spur task list` to recompute membership mid-batch (R2.1).
151
161
 
@@ -408,6 +418,18 @@ The host session remains the orchestrator for interactive sequential omit/inline
408
418
  owns explicit-executor and parallel paths; there the flag pins the per-task step executor, not the
409
419
  orchestrator.
410
420
 
421
+ **Executor pin (1318 R2/R3).** `--agent auto` is resolved **once per batch**, not once per task:
422
+ after the first task run, read the resolved implement executor from that run's implement `agent.run`
423
+ trace event — `spur workflow trace <run> --json` → `.events[] | select(.node=="implement" and
424
+ .actionKind=="agent.run") | .result.executor` — and dispatch every later task with
425
+ `--agent <that executor>`. The report's header records `Executor pin: <name> (from <wbs>)`; an
426
+ explicit `--agent <name>` records `(from --agent)` and needs no resolution. A task whose run ends
427
+ with `terminalReason == "failed-agent"` is re-dispatched **exactly once** with `--agent auto`; a
428
+ different resolved executor then becomes the pin for the remaining tasks, and a second
429
+ `failed-agent` records the task `failed` under the failure policy with the pin kept. No other terminal reason
430
+ is ever retried on a new executor. `--agent inline` (host driver) has no executor
431
+ to pin, so R2/R3 do not apply.
432
+
411
433
  ### 3.3 Terminal-state inspection
412
434
 
413
435
  Each pipeline run ends in one of two terminal states:
@@ -525,10 +547,11 @@ belongs to a subagent or the operator, not to the batch controller's report loop
525
547
 
526
548
  ## Step 4 — Failure policy (R3)
527
549
 
528
- ### 4.1 Stop-the-batch (default) (R3.1)
550
+ ### 4.1 Stop-the-batch (default unless `--auto`) (R3.1)
529
551
 
530
- By default, the **first** pipeline failure halts the batch. Remaining tasks in the plan are reported
531
- as `not-attempted`. The report lists succeeded, failed, and not-attempted tasks.
552
+ By default — i.e. without `--auto` (1318 R1) — the **first** pipeline failure halts the batch.
553
+ Remaining tasks in the plan are reported as `not-attempted`. The report lists succeeded, failed,
554
+ and not-attempted tasks.
532
555
 
533
556
  **Run-failed / task-done (observed 2026-10-19, feature P1).** A run can fail *after* the task is
534
557
  already finished: the `done` state's close commands run last, and a lint or anchor rule can refuse
@@ -540,9 +563,10 @@ contract, not to a run whose last recorded state was superseded. Surface it in t
540
563
  way (never silently continue), and never re-run the pipeline just to flip a run row from `failed` to
541
564
  `done` — a second run re-executes model stages for a task that is already closed.
542
565
 
543
- ### 4.2 `--keep-going` (R3.2)
566
+ ### 4.2 `--keep-going` (R3.2), or implied by `--auto`
544
567
 
545
- With `--keep-going`, a failed task does **not** halt the batch. Instead:
568
+ With `--keep-going` — passed explicitly, or implied by `--auto` (1318 R1) — a failed task does
569
+ **not** halt the batch. Instead:
546
570
 
547
571
  - The failed task's **in-batch dependents** (tasks in the plan that transitively depend on it) are
548
572
  marked `skipped` with the failed dependency as the reason — they cannot run because their dep did
@@ -62,7 +62,7 @@ task from the main tree; integrations are **serialized** (one at a time) in comp
62
62
  integrate(t):
63
63
  git -C "$WT" rebase "$BASE_REF" # replay onto the CURRENT base tip (re-read per integration)
64
64
  ├─ rebase fails → git -C "$WT" rebase --abort → conflict path (R4) below
65
- └─ rebase clean → git checkout "$BASE_REF" → zero-commit guard → git merge --ff-only "$BRANCH"
65
+ └─ rebase clean → git checkout "$BASE_REF" → zero-commit guard → git merge --ff-only --no-edit "$BRANCH"
66
66
  → WT-4a verdict persist → WT-4b holders → WT-4c registry
67
67
  → git worktree remove → existence-guarded git branch -D → marker merged
68
68
  ```
@@ -82,7 +82,7 @@ commands:
82
82
 
83
83
  ```
84
84
  resume: cd <worktree-path> && git rebase <BASE_REF> # the operator resolves, never the driver
85
- merge: git checkout <BASE_REF> && git merge --ff-only <branch>
85
+ merge: git checkout <BASE_REF> && git merge --ff-only --no-edit <branch>
86
86
  discard: git worktree remove <worktree-path> && git branch -D <branch> && spur projects remove <worktree-path>
87
87
  ```
88
88
 
@@ -64,6 +64,17 @@ default. See the [inline-default execution-surface contract](cross-cutting.md#in
64
64
 
65
65
  > **Single-run & parse discipline (suite run cost control).** Run full quality/test suites (`bun run check` / `spur-check`) at most ONCE per task iteration (task 0436 R2). Parse failure details from the single retained command output rather than re-running full suites repeatedly to inspect errors. Re-run targeted/narrow test files (e.g. `bun test <file> --test-name-pattern <pattern>`) while iterating on fixes, and re-run the full suite only when all targeted fixes pass.
66
66
 
67
+ ## File mutation in a driven run (task 1319 R6/R9)
68
+
69
+ The driver and every dispatched implement stage mutate files with `Edit`/`Write`. A `python3 - <<PY`
70
+ heredoc, `sed -i` or `perl -pi` file mutation is a **defect**: it carries scaffolding through the
71
+ context instead of a diff, and a silent no-op replace cannot be told apart from success. The
72
+ credit-sentry run (2026-11-06→08) spent 291 such edits, 248 of them on source and tests, while the
73
+ same work needed one edit call each.
74
+
75
+ Focused verification inside implement is `spur task gate <wbs> light` — never a hand-rolled
76
+ `bunfig.toml` and never the root-cwd `bun test`. The full gate belongs to the pipeline's `gate` state.
77
+
67
78
  ## Section ownership — one writer per evidence section (F92 0593 R1)
68
79
 
69
80
  | Section | Writer |
@@ -74,7 +74,9 @@ git merge-base --is-ancestor "$BASE_TIP" "$BATCH_TIP" || ANCESTRY_RC=$?
74
74
  git log --oneline "$BASE_TIP..$BATCH_TIP" >&2 # batch-side divergent commits
75
75
  git log --oneline "$BATCH_TIP..$BASE_TIP" >&2 # base-side divergent commits
76
76
  exit 1; } # -> WT-5: tree + branch retained, marker stays "active"
77
- git merge --ff-only "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
77
+ git merge --ff-only --no-edit "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
78
+ # `--no-edit` is load-bearing in agent environments (task 1320): a host policy blocks a bare
79
+ # `git merge` because it could open an editor, even for a fast-forward that writes no message.
78
80
  [ $? -eq 0 ] \
79
81
  || { echo "WT-4 halt: FF-merge failed - the concurrent-writer race survives every precheck; marker stays \"active\"" >&2; exit 1; } # -> WT-5
80
82
  # Landed verification before anything else runs: the captured BATCH_TIP must be an ancestor of
@@ -231,14 +233,14 @@ socket, and `lsof` port discovery failing must not hide the PIDs.
231
233
  #### Reuse mode — merge, retain
232
234
 
233
235
  The fail-stop merge sequence runs identically — same guard, captured `BATCH_TIP`/`BASE_TIP`,
234
- ancestry check immediately before FF, `git merge --ff-only`, landed verification (per-call pinned
236
+ ancestry check immediately before FF, `git merge --ff-only --no-edit`, landed verification (per-call pinned
235
237
  to the invoking tree, task 1058) — but no WT-4a/4b/4c and no removal: the worktree and branch are
236
238
  **retained**. Pre-merge halts leave the marker `active`; a persistence failure after the landed
237
239
  merge records `retained` + `mergeCommit`:
238
240
 
239
241
  ```bash
240
242
  # Same fail-stop sequence as create mode (zero-commit guard -> BATCH_TIP/BASE_TIP -> ancestry
241
- # rc check -> git merge --ff-only -> landed verify); local refs only — no fetch.
243
+ # rc check -> git merge --ff-only --no-edit -> landed verify); local refs only — no fetch.
242
244
  MARKER=".spur/run/worktree-<marker-id>.json"
243
245
  git checkout "$BASE_REF" \
244
246
  || { echo "WT-4 halt: checkout $BASE_REF failed - nothing merged, marker stays \"active\"" >&2; exit 1; } # -> WT-5
@@ -255,7 +257,9 @@ git merge-base --is-ancestor "$BASE_TIP" "$BATCH_TIP" || ANCESTRY_RC=$?
255
257
  git log --oneline "$BASE_TIP..$BATCH_TIP" >&2
256
258
  git log --oneline "$BATCH_TIP..$BASE_TIP" >&2
257
259
  exit 1; } # -> WT-5: tree + branch retained, marker stays "active"
258
- git merge --ff-only "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
260
+ git merge --ff-only --no-edit "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
261
+ # `--no-edit` is load-bearing in agent environments (task 1320): a host policy blocks a bare
262
+ # `git merge` because it could open an editor, even for a fast-forward that writes no message.
259
263
  [ $? -eq 0 ] \
260
264
  || { echo "WT-4 halt: FF-merge failed - the concurrent-writer race survives every precheck; marker stays \"active\"" >&2; exit 1; }
261
265
  LANDED_BASE_TIP="$(git rev-parse "$BASE_REF")" \
@@ -301,7 +305,7 @@ invoking tree there too — after the Step 5 persistence, before the success mar
301
305
  appends the same stale-links warning to the persisted batch report, and the success marker still
302
306
  records `merged`.
303
307
 
304
- **Fast-forward only.** `git merge --ff-only` is the sole mutation of the terminal sequence; every
308
+ **Fast-forward only.** `git merge --ff-only --no-edit` is the sole mutation of the terminal sequence; every
305
309
  other step is a read, a check, a persistence write, or a cleanup, and every tip/ancestry read uses
306
310
  local refs — no fetch runs anywhere in this lifecycle. If the fresh `BASE_TIP` is not an ancestor of
307
311
  `BATCH_TIP`, the ancestry check halts before the merge: the report names both tips and both
@@ -412,7 +416,7 @@ landed-but-incomplete: the batch merge LANDED as <batch-tip-sha> on <base-ref>;
412
416
  Resume, merge, or discard:
413
417
 
414
418
  resume: cd <worktree-path> && <command> --continue --worktree <worktree-path>
415
- merge: git checkout <base-ref> && git merge <branch> # resolve conflicts manually
419
+ merge: git checkout <base-ref> && git merge --no-edit <branch> # resolve conflicts manually
416
420
  discard: git worktree remove <worktree-path> && git branch -D <branch> && spur projects remove <worktree-path>
417
421
  ```
418
422
 
@@ -187,6 +187,21 @@ generator runs. In this repo that is `apps/cli/config/` (from `bun run bundle:co
187
187
  directory (observed 2026-10-12, A91 batch). Run the generator once after a create-mode install, and
188
188
  again after a rebase or merge that touches the canonical workflow sources it is built from.
189
189
 
190
+ **A fresh worktree cannot commit until its tool config is trusted (task 1320).** `proto` refuses to
191
+ load a project's `.prototools` in a path it has not seen, so the repo's `cog` commit-msg hook cannot
192
+ run and WT-3b's commit exits 1 with:
193
+
194
+ Unable to proceed, the plugin for cog is configured in <worktree>/.prototools, which has not been
195
+ trusted, so it was not loaded.
196
+
197
+ Trust it once per created tree, best-effort and only when the project ships the file (a missing
198
+ `.prototools` or a `proto` that does not prompt is not an error — the commit is what proves it):
199
+
200
+ cd "../<worktree-dir>" && [ ! -f .prototools ] || proto trust .prototools
201
+
202
+ The trust decision is per absolute path, so a reuse-mode tree that was already trusted needs nothing
203
+ and a re-cut tree always does.
204
+
190
205
  #### Reuse mode (`--worktree <name>`)
191
206
 
192
207
  Creation is **skipped entirely**. `$BRANCH` is the resolved worktree's already-checked-out branch; a
@@ -138,6 +138,11 @@ Only declared where the command already has at least one HITL gate the flag can
138
138
  taste gates (idea-eval, design-approval) — every gate there is reversible corpus writes. A gate
139
139
  without an actionable recommendation (missing eval recommendation, FAIL design check) still pauses.
140
140
 
141
+ **Batch policy** (`dev-runall`): `--auto` implies `--keep-going` — an unattended batch never stops
142
+ for a human. With `--agent auto` the driver also pins ONE executor for the batch — `Executor pin:
143
+ <name>`, read from the first run's implement `result.executor` — and only a `failed-agent` ending
144
+ re-resolves it, exactly once (1318 R1–R3).
145
+
141
146
  ### `--keep-going` — batch failure policy: skip dependents, continue independents
142
147
 
143
148
  **Anchor:** `#flag-keep-going`.
@@ -146,6 +151,10 @@ Batch operation only (`dev-refineall`, `dev-runall`). When a task in the batch
146
151
  fails, skip its in-batch dependents and continue the independent ones, instead of the default
147
152
  halt-on-first-failure. Never silently retried; the failure is still reported.
148
153
 
154
+ **Default:** off, except under `--auto` — `--auto` implies `--keep-going` (1318 R1), because an
155
+ unattended batch that stops to wait for a human is the defect it was supposed to avoid. To stop at
156
+ the first failure, run without `--auto`.
157
+
149
158
  ### `--defer-gate` — opt-in parallel-batch gate policy (task 1111 / H15)
150
159
 
151
160
  **Applies to:** `/sp-dev-runall --mode parallel`, and `--mode auto` when it resolves to parallel.