@gobing-ai/spur 0.4.10 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +22 -0
  3. package/config/rules/quality/coverage-gate.yaml +2 -2
  4. package/config/rules/surface/check-cli-surface.yaml +4 -0
  5. package/config/workflows/feature-verification.yaml +14 -5
  6. package/config/workflows/idea-pipeline.yaml +92 -12
  7. package/config/workflows/pr-review.yaml +72 -224
  8. package/config/workflows/task-pipeline.yaml +456 -830
  9. package/config/workflows/wayfinder-resolution.yaml +20 -3
  10. package/config/workflows/wrapup-pipeline.yaml +22 -9
  11. package/package.json +1 -1
  12. package/plugins/sp/README.md +2 -2
  13. package/plugins/sp/agents/super-planner.md +2 -2
  14. package/plugins/sp/agents/super-reviewer.md +17 -8
  15. package/plugins/sp/bunfig.toml +3 -3
  16. package/plugins/sp/commands/dev-changelog.md +1 -1
  17. package/plugins/sp/commands/dev-gitmsg.md +2 -2
  18. package/plugins/sp/commands/dev-handover.md +1 -1
  19. package/plugins/sp/commands/dev-idea.md +23 -13
  20. package/plugins/sp/commands/dev-job-dump.md +1 -1
  21. package/plugins/sp/commands/dev-plan.md +17 -6
  22. package/plugins/sp/commands/dev-pr-review.md +27 -17
  23. package/plugins/sp/commands/dev-review.md +1 -1
  24. package/plugins/sp/commands/dev-run.md +7 -6
  25. package/plugins/sp/commands/dev-runall.md +1 -1
  26. package/plugins/sp/plugin.json +1 -1
  27. package/plugins/sp/skills/code-implementation/SKILL.md +7 -7
  28. package/plugins/sp/skills/code-refactoring/SKILL.md +1 -1
  29. package/plugins/sp/skills/code-verification/SKILL.md +1 -1
  30. package/plugins/sp/skills/code-verification/references/secu-review.md +8 -4
  31. package/plugins/sp/skills/code-verification/references/verdict-schema.md +23 -10
  32. package/plugins/sp/skills/doc-evolve/SKILL.md +1 -1
  33. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  34. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +7 -7
  35. package/plugins/sp/skills/pr-reviewing/SKILL.md +157 -210
  36. package/plugins/sp/skills/source-driven-development/SKILL.md +14 -6
  37. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +4 -4
  38. package/plugins/sp/skills/spur-check/SKILL.md +9 -8
  39. package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
  40. package/plugins/sp/skills/spur-cli/references/builder.md +86 -13
  41. package/plugins/sp/skills/spur-cli/references/self.md +37 -2
  42. package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +1 -1
  43. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -3
  44. package/plugins/sp/skills/spur-cli/references/tasks.md +1 -1
  45. package/plugins/sp/skills/spur-cli/references/workflows.md +5 -3
  46. package/plugins/sp/skills/spur-dev/SKILL.md +3 -3
  47. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +9 -8
  48. package/plugins/sp/skills/spur-dev/references/dev-operations.md +11 -11
  49. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +4 -3
  50. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +4 -4
  51. package/plugins/sp/skills/spur-dev/references/execution-batch.md +13 -11
  52. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +31 -22
  53. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +5 -5
  54. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +4 -4
  55. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +3 -2
  56. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +4 -4
  57. package/schemas/spur-config.schema.json +19 -1
  58. package/spur.js +32388 -31906
  59. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +0 -997
  60. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +0 -178
@@ -1,997 +0,0 @@
1
- ---
2
- name: inline-pipeline-driver
3
- description: "Interactive host-session interpreter for Spur state-machine pipelines: execute the existing FSM without a workflow agent subprocess while preserving actions, guards, artifacts, and provenance."
4
- owner: spur-dev-maintainers
5
- retirement-criterion: "The per-task interpreter retires once the engine covers per-task execution for /sp:dev-runall with real terminal runs and the parity check (scripts/commands/inline-pipeline-parity-check.ts) is green (D8 decision D7). Batch orchestration wrapper may remain."
6
- see_also:
7
- - spur-dev
8
- - execution-workflow
9
- - execution-batch
10
- ---
11
-
12
- # Inline Pipeline Driver
13
-
14
- **Installed-copy drift (1046):** Superskill converts `/sp:dev-*` command spellings to
15
- `/sp-dev-*` for Codex. After `superskill install sp`, this adapter-only difference remains;
16
- the interpreter and trace instructions must match. Never hand-edit the installed reference.
17
-
18
- **Owner:** `spur-dev-maintainers` (per task 0755 R1). Reach the named owner via the frontmatter; no need to read the originating task.
19
-
20
- **Retirement criterion (0755 R5, D8 decision D7):** the per-task interpreter retires once the engine covers per-task execution for `/sp:dev-runall` with real terminal runs **and** the parity check (this doc's documented action/guard set ≡ the resolved action/guard set of every `.spur/workflows/*.yaml`) is green. Recording the criterion is part of this task; acting on it is not — that is a separate A3-gate decision.
21
-
22
- ## Driver state checklist (re-read this after a compaction)
23
-
24
- - **Run id** — the run row `spur workflow inline begin` persisted (`.spur/run/<run-id>-inline-setup.json`
25
- sidecar, `.spur/memory/runs/<run-id>.md` + `.state.json`); never re-derive it from the transcript.
26
- - **Markers under `.spur/run/`** — `<run-id>-inline-setup.json` (frozen invocation identity),
27
- `<run-id>-tree-start.json` (commit-guard fingerprint), `<wbs>-verdict.json` (per-stage verdict),
28
- `<wbs>-question.md` / `<wbs>-escalation.md` (operator pause), `<run-id>-event-trace.md`.
29
- - **Re-read per state** — this file (Run setup → YAML interpreter → Record & done),
30
- [structured-trace-emission.md](structured-trace-emission.md) at the first trace event.
31
- - **Authoritative state** — the run record and verdict artifacts, never the host todo list.
32
-
33
- ## Supported action and guard set (0755 R2 parity contract)
34
-
35
- The action and guard kinds this driver implements. The parity check
36
- (`scripts/commands/inline-pipeline-parity-check.ts`) compares this set against
37
- the resolved actions and guards of every `.spur/workflows/*.yaml`; any element present
38
- in one and absent in the other fails the check. Add a new kind here when the driver
39
- implements it; remove the entry when the corresponding kind is dropped from the YAML.
40
-
41
- **Actions:** `shell` · `note` · `doctor.probe` · `file.read.into-var` · `hitl.confirm` · `hitl.input` · `agent.run` · `proof.fingerprint` · `run.artifact` · `command.gate` · `decide`
42
-
43
- **Guards (transitions):** `always` · `shell` · `action-ok` · `contract-violation`
44
-
45
- - `action-ok` — pass iff the prior action on this state/node succeeded (engine builtin).
46
- - `contract-violation` — pass iff the prior `agent.run` result is a named contract violation
47
- (`data.outcome === 'contract-violation'`, ADR-118); the report carries `contract` and `observed`.
48
-
49
- ## What this driver is
50
-
51
- This driver is the interactive control-inversion path granted by ADR-047. It applies when an
52
- interactive `/sp:dev-run --mode full`, sequential `/sp:dev-runall`, `/sp:dev-idea`, or
53
- `/sp:dev-plan` invocation omits `--agent` or passes `--agent inline`. A named executor,
54
- `--agent auto`, parallel batch mode, `spur workflow run`, and `spur agent run` keep the existing
55
- subprocess path.
56
-
57
- The selected project runtime definition — `task-pipeline.yaml` or `idea-pipeline.yaml`, resolved through the
58
- project→registered→shared model (ADR-113) — remains the sole
59
- FSM definition. The driver MUST read that file
60
- at invocation time. It must not copy the state list, actions, guards, or transition order into a
61
- command, skill, script, or second workflow.
62
-
63
- **Inline HITL defer semantics (0911):** bundled pipeline human gates ship `decision: {mode:
64
- never}`, so the inline driver always prompts the operator for those gates regardless of
65
- `workflows.hitlDecisionMaker` — the decision policy never answers inline pipeline gates. A local
66
- workflow override (`.spur/workflows/*.yaml`) may declare `mode: evidence`, but the driver does
67
- not claim full inline parity for the DecisionMaker path: evidence-mode auto-answering inside the
68
- inline driver is an explicit non-goal (subprocess runs own that behavior). No hot reload: config
69
- and YAML changes require restarting the invoking process.
70
-
71
- ## Run setup
72
-
73
- **Shared startup contract (task 0814 R1/R3/R4/R6/R7; publish-first 1105 R1).** The order is
74
- load-bearing: publish the **generated plan** first — resolve the selected workflow through the same
75
- project/bundled resolver as execution, run `spur workflow show <resolved-file> --no-logo --format
76
- todo --json`, and publish the `.plan` rows verbatim (the projection reads the definition and executes
77
- nothing, so it is safe before readiness and isolation); run quick deterministic readiness (admission,
78
- not an implementation certificate) next; when `--worktree` is valid, create/adopt and switch to the
79
- execution tree; then bind the published plan to the run's persisted `__definitionDigest` — and only
80
- then read the full YAML for comprehensive/model work. Comprehensive checks stay at their owning
81
- boundaries and run after the plan is visible and after isolation when requested (R7); quick readiness
82
- and plan projection dispatch zero models and execute zero workflow actions (R8). Labels come from the
83
- projection (or `columnLabel` in `packages/app/src/workflow/step-reporter.ts` for the `steps[]`
84
- fallback) — labels are display addresses only, never an execution key.
85
-
86
- 1. Resolve the command inputs, `--auto`, and any explicit `--vars` — **without reading the selected
87
- YAML yet**. An explicit non-inline executor selection chooses the subprocess workflow path.
88
- 2. Allocate a collision-resistant inline run id (`uuidgen`, with a timestamp/pid fallback), create
89
- `.spur/run/` and the run's scratch subdirectory `.spur/run/<run-id>/` for attempt staging (action
90
- batches, captured stage output, temporary status/answer files) plus `.spur/memory/runs/` for
91
- retained records, and use the two-file run record (task 0927) — append lines to
92
- `.spur/memory/runs/<run-id>.md` and read machine state from `.spur/memory/runs/<run-id>.state.json`.
93
- The run id lives in those two files and the persisted run row — never stage it in an ad-hoc
94
- `*.env` file: the `no-env-files` gate rule flags any `.env` name, so run-scoped staging files
95
- use `<run-id>-*` names with `.md`/`.json`/`.txt`/`.status` extensions only.
96
- 3. **Authoritative run identity (task 0804 R1, fail-closed).** Persist the run row through the
97
- internal delegate before any stage executes — this is what makes bound `run.artifact` record
98
- accept the inline run (0785 R3):
99
-
100
- ```bash
101
- spur workflow inline begin --run-id "$RUN_ID" --file <selected-pipeline-yaml> \
102
- || { echo "inline run setup failed closed — see the spur workflow inline begin error above" >&2; exit 1; }
103
- ```
104
-
105
- **Commit fingerprint (1129 R1).** Fingerprint the tree the task commit will land in — before the
106
- first task write — because Record & done stages through this snapshot, so a concurrent writer's
107
- file cannot ride into the task commit. The artifact is cwd-relative under `.spur/run/` (gitignored
108
- per tree), so the snapshot and the commit it guards must name the SAME tree: without `--worktree`
109
- take it here in the setup window; with `--worktree` take it immediately **after step 7's
110
- isolation, from the worktree root** (still before the first task write) — a snapshot taken here
111
- would land in the invoking tree, and the worktree's commit would find none (1129 review N1).
112
-
113
- ```bash
114
- RUN_ID="<marker-id>" # the WT-3 marker's id (execution-batch.md), or the run id allocated at setup
115
- spur workflow commit-guard start --run "$RUN_ID" # <this tree>/.spur/run/$RUN_ID-tree-start.json = { head, dirty[] }
116
- ```
117
-
118
- `spur workflow inline begin` (the installed CLI transport; the plugin requires spur) obtains the
119
- selected definition from the CLI's `workflow show --format todo --json` projection — through
120
- `--spur-bin`/`SPUR_BIN` when set, else the running spur — and revalidates its
121
- schema/name/digest before preserving its source layer. It create-or-attaches the row,
122
- and writes the run-record state `.spur/memory/runs/<run-id>.state.json` plus the `.md` run-start
123
- header (task 0927; a legacy pre-0927 run keeps its `<run-id>-inline-setup.json` sidecar).
124
- Seed `__runId` and `__definitionDigest`
125
- from that state so proof capture and bound registration verify against the persisted identity.
126
- A non-zero exit (invalid projection, missing row identity, changed definition) stops the run —
127
- never continue unbound and never fabricate a PASS. (The delegate persists the authoritative
128
- RUN row only — the 0808 inline record is the registration-equivalent convention below, not a
129
- setup-time artifact-ledger insert.)
130
-
131
- **Frozen invocation identity (task 0809 R4).** The definition parsed and hashed at setup is
132
- the definition for the ENTIRE run: keep one invocation-time parsed definition and never
133
- re-resolve or reseed `__definitionDigest` at record because a workflow YAML changed. Two
134
- digests serve different purposes: `proof.digest` is the freshly captured current-input
135
- fingerprint; `proof.definitionDigest` identifies the workflow actually interpreted (the setup
136
- identity). A source-only edit of a TRACKED workflow YAML before capture is part of current
137
- input proof — the Git fingerprint covers tracked working-tree files, and ignored/external
138
- workflow files are NOT part of it (their executed identity remains the setup digest).
139
- Post-capture changes to fingerprinted inputs invalidate that proof and take the normal
140
- certification loop — stale post-capture evidence is refused, never reconciled. If execution
141
- must switch to a different definition, or the executed identity cannot be established: stop
142
- before record, preserve the run log and evidence, and start a FRESH inline run with a fresh
143
- run id and fresh gate/review/verify certification. Never mutate old run/proof identities,
144
- manufacture a paused engine snapshot, or call `continuePaused` for a running inline row;
145
- task text, Git attribution and `--auto` are not consent to stamp `resumeDefinitionDigest` —
146
- explicit consent for actual paused engine runs stays owned by task 0784.
147
- 4. Resolve the host session id from `.spur/context/.session.json`, accepting the normalized hook key
148
- `session` and the Codex key `session_id` (in that order). If neither is available, allocate
149
- `host-session-<run-id>` and record that fallback in the log; provenance must never be blank or
150
- guessed from an executor subprocess.
151
- 5. **Publish the generated plan (R1, 1105) — the run's first action.** Run `spur workflow show
152
- <resolved-file> --no-logo --format todo --json` and publish the projection into the host todo list
153
- before any expensive check:
154
- - **Annotated workflow (the normal case):** publish every `.plan` row's `text` **verbatim** —
155
- `A Prepare`, `A1 Quick readiness`, `A2 Prepare Git`, `A3 Publish plan`, `B Implement`, … — in
156
- plan order. Never hand-write rows, never copy the YAML state table into prose, and never
157
- renumber: the labels carry identity.
158
- - **Unannotated workflow (`.plan` is `null`):** publish the flat `steps[]` inventory in
159
- declaration order with `columnLabel` labels and their `initial` / `terminal` / `failure` /
160
- `pause` / `loopBack` / `conditional` markers. Never re-derive this list from the YAML.
161
- - **No workflow plan at all** (skill-only refine/verify batches): keep the existing host
162
- preparation rows; do not fabricate a workflow YAML.
163
-
164
- Publishing executes nothing, so it safely precedes readiness and isolation, which keep their
165
- existing order below.
166
- 6. **Quick deterministic readiness (R2), before isolation.** Evaluate `quickReadiness` from
167
- `spur task preflight <wbs> --operation <op>` with the operation, status, filtered-set size, the
168
- matrix-selected required/present sections, and content-policy findings. Record the outcome
169
- (runnable / needs-refinement / blocked / skipped / invalid) in the run log. Admission decision
170
- only — no model, no full tests/lint, no live-data probe, no feature mutation, no corpus-wide
171
- relational check.
172
- 7. **Isolation (R3), only when `--worktree` is valid.** After quick readiness and the required Git
173
- safety checks succeed, create/adopt and `cd` into the execution tree; confirm absolute cwd,
174
- branch, base SHA, and ownership. An invalid/empty target, unsupported mode, ambiguous ownership,
175
- or stale target stops without creating a tree or discarding work. All subsequent tools, agents,
176
- corpus writes, and run artifacts use the confirmed execution tree — **re-selected per call, not
177
- inherited**: a host shell call starts in an arbitrary directory and carries no cwd from the
178
- previous call, so every later call runs inside the **Per-call execution-tree pin** protocol
179
- below (task 1058), which consumes the identity confirmed here.
180
- 8. **Bind the published plan to the run digest (R4) — plan row A3.** Validate the step-5 projection
181
- with `parseWorkflowInventory` and bind it to the run's persisted `__definitionDigest` with
182
- `assertInventoryIdentity` — a drift or projection failure stops the run before any
183
- comprehensive/model work, never executing with a misleading plan. The A rows published at step 5
184
- track this setup order: A1 = quick readiness (step 6), A2 = Git/isolation (step 7), A3 = this
185
- binding; mark each row completed as its step settles.
186
- 9. **Read the selected YAML and overlay its `vars` defaults** with the invocation values. Compare the
187
- resolved definition identity against the bound `__definitionDigest`; a mismatch is identity drift
188
- and fails closed (step 8 already caught projection-side drift; this re-checks the same definition
189
- the interpreter will execute).
190
- - **Runtime-written vars are re-read, never cached.** A var a later state writes (`proofDigest`,
191
- `proofDigestNow`, `taskSpecPath`, `featureSpecPath`, `taskPriority`, `mode`, `gateFindings`) is a
192
- run artifact, not a YAML default: re-resolve it from `.spur/run/`/`.spur/memory/runs/` at every
193
- state boundary, and never let a host-side copy of the invocation vars shadow it — a stale empty
194
- `proofDigest` silently fails the bound `run.artifact` equivalence and the `record` proof guard.
195
- - **Layer 2** = the active state's `onEnter` actions (`kind` + resolved `input`/`command`), from
196
- the YAML read here, shown only for the active state.
197
- - **Refresh cadence** = stage boundaries only (when the current state changes after a transition),
198
- never per action.
199
- - **Transition reconciliation (task 0727)** = at every stage boundary the host must
200
- **mark the finished stage completed and the next stage in_progress** in the host todo list.
201
- This reconciliation is **host-owned and execution-surface-independent**: it fires identically
202
- whether the stage ran via native subagent, host-inline execution, or the post-dispatch host
203
- fallback, so a run can never terminate with earlier stages stuck `in_progress` (task 0726
204
- ended 0/11 with precheck and implement still open).
205
- - **Source of truth** = the CLI projection for layer 1; the YAML read here for layer 2.
206
- Never hand-copy or hand-derive the state list into the driver, a command, a skill, or a script.
207
- - **Stable labels (task 0814 R5; A–Z / 1–9 rule, 1105 R1).** Labels come from the published
208
- `.plan`: single letters A–Z for top-level rows, digits 1–9 for children under one letter. The
209
- projection enforces that cap instead of rolling to two-letter labels (`planLetter` throws past
210
- Z — split into waves or stop; never render `AA`-style addresses). An `on-entry` state is
211
- inserted as the next digit the moment the host enters it (`insertOnEntry` in
212
- `packages/app/src/workflow/plan-projection.ts`), appended after its letter's existing children;
213
- re-entering an already-published loop state keeps its label and appends the re-entry note
214
- ` — attempt N`. Labels never replace the canonical step id, and are re-derived identically on
215
- retry/resume against the same definition.
216
- - **Truthful progress (task 0814 R6; frozen status mapping, 1105 R1).** Publish pending/active
217
- state before the visible item starts, then update it immediately after the observed item
218
- completes and before the next item starts. Frozen status mapping: `pending → pending`,
219
- `active → in_progress`, `completed → completed`,
220
- `skipped|failed|unattempted|blocked → pending` + ` [<outcome>]` — the four engine-decided
221
- outcomes stay visible on a pending row, never cleared. Keep completed, skipped, failed, blocked,
222
- paused, and unattempted outcomes distinct; never mark skipped/conditional work completed merely
223
- to clear the UI. Re-entry appends ` — attempt N`. If the host has no suitable native todo tool,
224
- or it fails, use the `renderProgressMarkdown` fallback with the same labels and truth — never a
225
- fabricated successful tool invocation.
226
- 10. For task execution only, record lifecycle provenance before entering the FSM:
227
-
228
- ```bash
229
- spur task run-link <wbs> --source inline-full --run-id <run-id> --json
230
- ```
231
-
232
- This is required for the normal `testing → done` provenance guard. Planning pipelines have no
233
- task lifecycle link and skip this task-specific action.
234
-
235
- **Run it from the task's execution tree.** Every tree has an isolated `.spur` DB, so a link
236
- recorded from the invoking tree never satisfies a worktree's guard — the exact failure that
237
- forced audited `--provenance-bypass` on tasks 1029/1030 (E93). Under `--worktree`, enter the
238
- worktree first and run this command (and every lifecycle-touching command: `task record`,
239
- `task update <wbs> testing|done`) from the worktree root.
240
-
241
- ### Host todo update styles (1105 R2)
242
-
243
- The same published plan is updated differently per host family. Both styles keep plan order and are
244
- bookkeeping for the one projection — never a second source of truth:
245
-
246
- | Host | Tool(s) | Update style |
247
- | --- | --- | --- |
248
- | Claude Code | `TaskCreate` / `TaskUpdate` | per-item — create each item once, then update by id; append inserted (`on-entry`) steps at the end (the label carries identity, so list position is display only) |
249
- | pi | `todo` | per-item — same create-once/update-by-id discipline |
250
- | Codex | `update_plan` | full-list — rewrite the whole list in plan order on every update |
251
- | Gemini | `write_todos` | full-list — rewrite the whole list in plan order |
252
- | OpenCode | `todowrite` | full-list — rewrite the whole list in plan order |
253
- | Grok | `todo_write` | full-list — rewrite the whole list in plan order |
254
- | omp | host todo tool | maps plan letters to `phase` |
255
- | any host without a native tool | `renderProgressMarkdown` | Markdown fallback with the same labels and truth |
256
-
257
- Per-item hosts update the one row whose outcome changed; full-list hosts re-render every row from the
258
- current plan state. Both render the frozen status mapping above identically.
259
-
260
- ### Idea-pipeline quick start (0887 R1/R2)
261
-
262
- Minimum files to read for `/sp:dev-idea` inline runs — then drive `idea-pipeline.yaml`:
263
-
264
- - `.spur/workflows/idea-pipeline.yaml` (the machine) and this driver.
265
- - `plugins/sp/skills/spur-dev/references/idea-evaluation.md` (report template incl. the mandatory
266
- `## Requirement inventory`) and `references/ac-style-guide.md` (scenario `# covers:` form).
267
- - `references/dev-operations.md` § idea for the stage-by-stage surface.
268
-
269
- **Persist the verbatim idea FIRST.** Before executing the `start` state, write the operator's
270
- idea argument (or `--from-file` contents) **unmodified** to
271
- `.spur/run/<run-id>-idea-input.md`; the run precheck fails when that file is empty or missing,
272
- and every model-bearing stage prompt treats it as the authoritative ask. `--from-file` and the
273
- positional idea are mutually exclusive — exactly one must be present.
274
-
275
- Expected artifacts per stage (all run-scoped under `.spur/run/<run-id>-*`):
276
-
277
- | Stage | Artifacts |
278
- | ----- | --------- |
279
- | start | `-idea-input.md` (verbatim idea), `-idea-precheck-doctor.status` |
280
- | discovery | `-idea-eval-report.md` (with `## Requirement inventory`), `-idea-needs-design.json` |
281
- | feature-create | `-idea-feature-id.txt`, `-idea-goal.md`, `-idea-scope.md` |
282
- | ac-generate | `-idea-ac-content.md`, `-idea-ac-check.status` |
283
- | system-design | `-idea-design-review.md`, `-idea-design-check.status` |
284
- | decompose | `-idea-task-batch.json`, `-idea-task-order.json` |
285
- | batch-create-run | `-idea-batch-create-result.json`, `-idea-batch-create.done`/`.failed` |
286
- | ready-prepare | `-idea-ready.json` |
287
- | handoff-finalize | `-idea-handoff.md` |
288
-
289
- ## Per-call execution-tree pin (task 1058)
290
-
291
- A host shell tool call may start in an arbitrary directory; no earlier call's `cd` is input to the
292
- next call. During inline run `95522d21` that drift silently landed corpus writes in the invoking
293
- tree instead of the run worktree. Every host-session corpus command — reads, writes, absolute
294
- section-input files and output artifacts — therefore selects the confirmed execution tree **within
295
- the same tool call**, in a self-contained subshell. The identity is never re-derived: it is the
296
- tree path, branch and resolved Spur invocation confirmed at bootstrap isolation (step 7) and, for
297
- batch worktrees, recorded in the WT-3 marker (execution-batch.md). The resolved invocation is
298
- field 4 of the dispatch payload — never a bare `spur`.
299
-
300
- Template — every host shell call follows this shape; paths are quoted because trees may contain
301
- spaces:
302
-
303
- ```bash
304
- (
305
- cd -- "$SPUR_TREE" || { echo "tree missing: expected $SPUR_TREE" >&2; exit 1; }
306
- ACTUAL_TREE="$(pwd -P)"
307
- [ "$ACTUAL_TREE" = "$SPUR_TREE" ] || { echo "tree mismatch: expected $SPUR_TREE, got $ACTUAL_TREE" >&2; exit 1; }
308
- GIT_TOP="$(git rev-parse --show-toplevel)" || exit 1
309
- [ "$GIT_TOP" = "$SPUR_TREE" ] || { echo "toplevel mismatch: expected $SPUR_TREE, got $GIT_TOP" >&2; exit 1; }
310
- ACTUAL_BRANCH="$(git branch --show-current)"
311
- [ "$ACTUAL_BRANCH" = "$SPUR_BRANCH" ] || { echo "branch mismatch: expected $SPUR_BRANCH, got $ACTUAL_BRANCH" >&2; exit 1; }
312
- exec <resolved-spur-invocation> task show <wbs> --json # the already-resolved CLI command
313
- )
314
- ```
315
-
316
- Rules (R1):
317
-
318
- - **Pin reads as well as writes.** `task show`, `feature show`, `workflow show` and `task check`
319
- use the same subshell — a read from the wrong tree is a wrong answer, not a harmless one.
320
- - **Validate the returned `filePath`.** A pinned `task show --json` must return a `filePath`
321
- inside the expected configured corpus/tree. Configured task folders may intentionally live
322
- outside the default `docs/tasks` — check membership in the configured folder set, not one
323
- literal path.
324
- - **Prove sections with a fresh pinned read.** After a section write, re-run a pinned
325
- `task show <wbs> --json` and assert the new body there; a heading-only grep is not proof of
326
- section contents.
327
- - **Resolve section input/output paths before changing tree.** `--section <name> --from-file
328
- <path>`, `answerFile`/`expectFile` and run artifacts are resolved against the confirmed tree
329
- (dispatch field 5) before the subshell `cd`s.
330
- - **Fail closed before writing.** A failed `cd` or any identity mismatch exits nonzero naming
331
- expected vs actual, before the CLI runs — there is no designated exit code; consumers read
332
- nonzero and the named expected/actual trees. A PWD assertion alone cannot select a tree: `cd`
333
- first, then verify the physical path and repository identity.
334
- - **Native tool cwd options are additive.** A tool-level cwd/`cd` pin may additionally set the
335
- call's directory, but never replaces the identity checks above.
336
- - **Dispatch cwd is mandatory when the tool accepts one.** A subagent dispatch whose tool exposes a
337
- `cwd` (pi `subagent`) passes the execution-tree cwd. Omitted, the child starts in the host tree:
338
- it can edit main, and the timeout watchdog diffs the wrong tree (H15 2026-10-07: "changed
339
- tracked files: none" on a worker that changed 5 worktree files).
340
- - **Host-only scope.** Engine `shell` actions already bind `context.workdir`
341
- (`packages/app/src/workflow/actions/shell.ts:98`) — leave them alone. No service-wide
342
- `process.chdir`, no new public flag, no helper framework.
343
-
344
- The `spur …` command examples in this driver (run setup, workflow inventory, `task run-link`,
345
- fingerprint capture, record/done sequencing) abbreviate the pinned subshell form for readability;
346
- each executes inside the template above with the confirmed tree, branch and resolved invocation
347
- substituted. A dispatched subagent inherits the same duty: its own tool calls may each start in
348
- an arbitrary directory, so every shell call the delegate makes re-pins the execution tree with
349
- the identity supplied in the dispatch payload — never re-derived.
350
-
351
- ## Chunked implement dispatch contract (cap-limited worker models, task 1066)
352
-
353
- Cap-limited worker executors die on monolithic implement briefs: the output cap is consumed by
354
- one giant thinking block before any file action lands. Recognized failure signature —
355
- consecutive implement dispatches end with `stopReason=length` and **zero output** (no tool
356
- call, no file write), optionally with a `contact_supervisor` call mid-death. The trigger is
357
- the observed signature, never a model name: any executor that exhibits it is treated as
358
- cap-limited for the rest of the run.
359
-
360
- Before dispatching an `implement` stage to a worker executor:
361
-
362
- 1. **Chunk the brief.** Split the implement scope into small, independent dispatches ordered
363
- by dependency (e.g. test cases → doc rewrite → task sections), one bounded objective per
364
- dispatch. Never pack the whole task plan into one dispatch.
365
- 2. **Cap thinking.** Instruct the worker to keep every thinking block under ~150 words and act
366
- between thoughts; never plan the whole task in one block.
367
- 3. **Fallback ladder.** A worker still dying with the signature: re-chunk the remaining scope
368
- smaller once; still failing → execute the stage host-inline (the driver's inline stage
369
- fallback) and record the fallback in the run log.
370
-
371
- Evidence (2026-10-02, task 1059 implement stage, runall-D63-2ebbd97c): 4/4 single-shot
372
- implement dispatches died `stopReason=length` on a 16k-output-cap worker; the same scope in
373
- three chunked dispatches with the thinking cap succeeded 3/3. See the pitfalls 2026-10-02
374
- entry and `.spur/memory/runs/runall-D63-2ebbd97c.md`.
375
-
376
- ## Comprehensive-check retention and evidence (R7/R8)
377
-
378
- **R7 — comprehensive checks stay at their owning boundaries.** Quick readiness and plan projection are
379
- admission and visibility, not a substitute for the owning gates. After the plan is visible and after
380
- isolation when requested, retain the full task/feature integrity, size, evidence-channel,
381
- provenance, capability, dependency, quality, review, and verification gates exactly where their
382
- owners declare them. Prefer deterministic checks; invoke semantic model work only for an identified
383
- unresolved requirement/design/evidence question and record its reason in the run log. Reuse a
384
- cached observation only while its relevant inputs (task content, effective status, matrix, dependency
385
- snapshot, cwd) remain unchanged; refresh after branch/tree changes, task writes, dependency
386
- completion, or resume. Never run a shadow copy of a workflow precheck state in the host — if a
387
- workflow defines a precheck state, its result updates that state, not a host-side duplicate.
388
-
389
- **R8 — matched before/after evidence, no invented claims.** Record a timestamped event trace under
390
- `.spur/run/<run-id>-event-trace.md` (render via `renderEventTrace` in
391
- `packages/app/src/workflow/workflow-inventory.ts`) naming event ordering, time-to-first-visible
392
- checklist, time-to-workflow-inventory, confirmed execution cwd, and CLI/process/model invocation
393
- counts. Quick readiness and plan projection must dispatch zero models and execute zero workflow
394
- actions — record that as observed. Record unavailable measurements as `unknown`; never present a
395
- simulated run as a real verified outcome. Event order and provenance are evidence; wall-clock/token
396
- savings are observations, never fabricated pass conditions.
397
-
398
- ## YAML interpreter
399
-
400
- Start at `initialState`. For each current state, execute its `onEnter` actions in declaration order,
401
- then evaluate outgoing transitions in declaration order and take the first passing guard. Stop only
402
- at a declared terminal state or a surfaced HITL pause. The `iterationBound` remains mandatory.
403
-
404
- After each executed action settles, record its boundary before the next action, transition guard,
405
- or terminal close. Measure its actual duration and preserve its declared failure policy:
406
-
407
- ```bash
408
- spur workflow inline action --run-id "$RUN_ID" --node <state-id> --kind <action-kind> \
409
- --status <done|failed> --ok <true|false> --duration-ms <measured-ms> [--estimated]
410
- ```
411
-
412
- `--duration-ms` is the wall clock measured around the action. Pass `--estimated` when you did
413
- **not** time it — for example a duration reconstructed after a subagent returned — so the row is
414
- labelled instead of presented as measured. `--estimated` is valid only on `inline action`.
415
-
416
- For a multi-action state, `spur workflow inline actions --file <json>` may emit the measured boundaries together before leaving
417
- that state. Follow [Structured trace emission](#structured-trace-emission-adr-117-task-0868) for the
418
- payload, delegated `decide` emission and best-effort failure handling. Never retry a failed batch
419
- or backfill at close; a zero-row done close fails with `NO_ACTION_ROWS`.
420
-
421
- Action semantics come from the YAML and the workflow action contract:
422
-
423
- - `shell` — run the expanded command in the project working tree with resolved vars exported as
424
- environment variables. A non-zero result follows the action's existing failure policy.
425
- **Long commands must not hold the host call (session finding 2026-10-09).** A `shell` action whose
426
- command can outlive the host's per-call limit — the full quality gate, a repo-wide suite, any
427
- `bun run spur-check`-class gate — is started **detached and polled**, never held in one foreground
428
- call: redirect to a run-scoped file and disown it
429
- (`nohup <cmd> > .spur/run/<wbs>-gate.out 2>&1 < /dev/null & disown`), then observe it in bounded
430
- calls (`pgrep`, the declared `.status` file, the log tail). A call killed at the cap leaves the
431
- substrate's own FAIL receipt — for the quality gate, `.spur/run/<wbs>-check-receipt.json` — and the
432
- recheck's `no-progress` guard honours it, so a cap-kill reads as a red gate the next time. Preserve
433
- the signal-induced receipt under a distinct name (`<wbs>-check-receipt.false-fail-<signal>.json`)
434
- and say so in the run log before re-running: never delete it silently, and never report a
435
- cap-killed attempt as a genuine gate failure. Measured 2026-10-10 (before the gate-lock removal):
436
- one foreground gate call (25.6 min host-wide lock wait + 7.4 min runtime) was SIGTERM-killed at the
437
- host cap and produced exactly that false FAIL receipt.
438
- - `note` — append the expanded message to the inline run log.
439
- - `doctor.probe` — run the declared Spur doctor once, persist its status file, and apply any
440
- `setVars` result (including a resolved executor) before the next action or state.
441
- **Role-map mode has no inline surface (task 1088 session finding).** A `doctor.probe` that
442
- declares `roles` resolves them through `AgentService.resolve`, which is reachable only in-process
443
- (the engine composition injects the service); no CLI verb exposes it, so the host session cannot
444
- reproduce `__executor.<role>` pins. The driver therefore probes the host-session agent instead,
445
- writes that verdict to the declared status file, and records one run-log line naming the
446
- substitution. Nothing downstream degrades: `__executor.<role>` pins are consumed by the
447
- subprocess dispatch path, and the inline path executes its stages in the host session without
448
- reading them. Never fabricate pins to satisfy the shape, and never skip the status file.
449
- - `file.read.into-var` — read the declared file into the declared run variable before subsequent
450
- actions/guards.
451
- - `hitl.confirm` — under `profile=auto`, follow the YAML's auto-skip transition. Otherwise pause,
452
- surface the prompt, and resume from the same state with the operator's answer.
453
- **Host-session rendering:** ask the gate as ONE `AskUserQuestion`
454
- [decision brief](decision-brief.md), never a typed yes/no. Read the state's evidence (the
455
- artifacts its prompt names and the recorded `.status` files) and derive the recommendation;
456
- map options to the `yes` / `no` / `cancel` answers the guards route on, recommended option
457
- first, each with a one-line reason from that evidence. When a `no` needs operator feedback
458
- (design-approval's `## Operator feedback`), take it from the operator's notes/"Other" text
459
- and write it into the named artifact yourself before resuming. Never ask them to edit the file.
460
- - `hitl.input` — pause, surface the declared prompt (the agent's operator question, 0933), and
461
- resume from the same state with the operator's answer written into the declared var (default
462
- `__hitlInput`); the subsequent guards route on answer presence exactly as the engine does.
463
- **Host-session exception (0933 R5):** because a host session can talk to the operator
464
- directly, do NOT pause the run at `hitl.input` — ask the question in the session, write the
465
- answer into the declared var, and continue. The same 2-escalation bound applies: after the
466
- bound the ask routes to `failed` like the engine does.
467
- - `agent.run` — execute the action's input in the host session. Task execution may use the native
468
- subagent eligibility below; idea/plan never dispatch a native subagent unless the operator
469
- explicitly requested delegation. Do not call `spur agent run` or re-enter a full pipeline. Preserve the YAML options: capture
470
- `answerFile`; assert `expectFile`; honor `escalationFile` (a non-empty file after exit 0 means
471
- the agent paused on an operator question — treat the attempt as succeeded-with-escalation and
472
- skip `requireDiff` for that attempt only); enforce `requireDiff` against a pre-action git snapshot,
473
- including the task-scope guard; honor declared error policy. A host-inline action records
474
- `timeoutMs` as not applicable because the host session has no independent kill boundary; a
475
- dispatched subagent passes it to the host when the dispatch tool accepts a timeout (see
476
- § Timeout boundary).
477
- - `run.artifact` — the engine's ledger registration has **no inline execution surface** (0808 R4).
478
- The inline equivalent is a documented **registration-equivalent convention**: before the record
479
- state mutates the task, the host validates the same refusal conditions inline — the declared
480
- artifact exists at the resolved path and is canonical-valid for the run's wbs (for
481
- `verify-verdict`: verdict `PASS`), `proofBinding: current` is honored against a freshly captured
482
- proof digest — capture it with `spur workflow inline fingerprint --task-file <task path>
483
- [--feature-file <feature path>]`, the same entry point used at Run setup, which prints the engine
484
- `sha256:<hex>` digest. Run it inside a per-call pin subshell for the confirmed execution tree
485
- (the cwd feeds the git-tree half of the digest)
486
- and pass the same `--feature-file` the run folded in — omitting it, or running from elsewhere,
487
- yields a different digest and the mismatch surfaces later as a refused `run.artifact`
488
- registration — and the run-scoped review-completion marker exists — then appends one provenance
489
- line to `.spur/memory/runs/<run-id>.md` naming the equivalence (artifact kind, path, verdict, digest) and
490
- proceeds to `spur task record`. A failed validation stops at the state and follows the failure
491
- contract; the step is never silently skipped. Artifact-provenance consumers read that run-log
492
- line on the inline path — there is no ledger row. The validation also includes **run/definition
493
- identity agreement from authoritative evidence** (task 0809 R4): the verdict's `proof.runId` and
494
- `proof.definitionDigest` must agree with the run-record state `.spur/memory/runs/<run-id>.state.json`
495
- (legacy pre-0927 runs: `<run-id>-inline-setup.json`) and the persisted run row; if that identity
496
- is absent or conflicts, STOP — recreating a row is
497
- not a diagnostic operation. The app-service bound-artifact fixture (which writes a real engine
498
- ledger row) is service-level test evidence for this identity mechanics, not evidence that the
499
- inline host writes a ledger.
500
-
501
- **Native-subagent dispatch (R2 eligibility, evaluated before each action):**
502
-
503
- Before dispatching an `implement` stage to a worker executor, apply the chunked implement
504
- dispatch contract (§ Chunked implement dispatch contract): chunk the brief, cap thinking
505
- blocks, and know the fallback ladder before spending a dispatch.
506
-
507
- 1. The invocation is one of the two interactive inline full-pipeline surfaces (`dev-run --mode full`
508
- or sequential `dev-runall`) and the resolved selector is inline — i.e. `--agent` **omitted**
509
- (0687 R1 default) or `--agent inline` passed explicitly (0687 R2 generalized from omit-only).
510
- Explicit inline and omitted resolve identically; a named executor, `auto`, parallel mode,
511
- `spur workflow run`, and `spur agent run` keep the subprocess path.
512
- 2. The YAML action kind is `agent.run` and its input is a pure slash command. Shell, note, file,
513
- guard, and operator-interaction actions remain host-executed.
514
- 3. The current state/action has no operator-confirmation action, `pause: true`, approve/taste/ask
515
- decision, or other operator prompt.
516
- 4. The platform exposes a native subagent that shares the working tree and has read, write, shell,
517
- and Spur task/run-artifact access.
518
- 5. **Size floor (2026-09-15 subagent-dispatch evaluation).** The task file's frontmatter
519
- `estimate_hours` — when present — is **above** the dispatch floor of **1 hour**. A task at or
520
- below the floor is cheaper to execute than to delegate: the stage runs host-inline and the run
521
- log carries `stage <id> executed inline in session <session-id> (below dispatch floor:
522
- estimate_hours <n> <= 1)`. The field is authored at decomposition time (batch item
523
- `estimate_hours` → frontmatter) or set via `spur task update <wbs> --estimate-hours <n>`; a task
524
- with no `estimate_hours` passes this condition unchanged. The driver reads the frontmatter value
525
- directly — never estimates size itself.
526
-
527
- **Diffstat arm (verify only, 1033 R1).** When the current state id is `verify`, condition 5
528
- fails when the triage diffstat file `.spur/run/<wbs>-diffstat.json` exists and shows a
529
- small, non-sensitive diff: `([.files, .insertions, .deletions] | all(type == "number")) and
530
- .files <= 3 and (.insertions + .deletions) <= 60 and
531
- .sensitive == false` (literal thresholds; the driver reads the file with `jq` — it never
532
- estimates size itself). A missing, unparsable or `sensitive: true` diffstat leaves condition 5
533
- as above, so the failure mode is more isolation, never less. A missing or null count also leaves
534
- condition 5 as above. Below the floor on this arm, the
535
- run log carries `stage verify executed inline in session <session-id> (below dispatch floor:
536
- diffstat files <f> lines <n>)`. Only `verify` eligibility changes: `implement` and `review`
537
- keep the estimate floor, and the pipeline state graph is unchanged.
538
-
539
- All five pass → dispatch. Any pre-dispatch failure → execute the stage **once** in the host session.
540
- An `agent.run` whose `input` is free-form prose rather than a pure slash command fails condition 2
541
- and is never dispatch-eligible: the driver executes it in the host session and logs it with the
542
- existing host-fallback line `stage <id> executed inline in session <session-id>` — it does not
543
- reformulate the prose into a command, spawn a subagent for it, or silently promote it to dispatch.
544
- Beyond the deterministic `estimate_hours` floor in condition 5, no token estimate, model heuristic,
545
- or configuration switch is added.
546
-
547
- **Contingent fallback (task 1134 R1/R2) — capacity is not capability.** That fallback is correct for
548
- capability-shaped failures and **wrong when the exhausted resource is the host session's own
549
- provider quota**: re-selecting the same exhausted provider cannot succeed by construction, and the
550
- run collapses into one serial host worker with no executor diversity. So classify the pre-dispatch
551
- failure before falling back:
552
-
553
- ```bash
554
- spur workflow inline dispatch-failure --run-id "$RUN_ID" --stage <state-id> --decision <escalate|stop> \
555
- --text-file <bounded failure record> [--executor <name>] [--agent <a>] [--model <m>]
556
- ```
557
-
558
- The helper wraps the upstream `@gobing-ai/ts-ai-runner` classifier (Spur matches **no** provider
559
- code or quota vocabulary of its own) and writes `.spur/run/<run-id>-dispatch-fallback.json` as
560
- `{stage, class, reason?, resetAt?, decision, executor, attribution, observedAt}`. Read `.class` from
561
- that file — never judge the prose yourself.
562
-
563
- - **`class: capability`** (generic 429, rate limit, overload, auth failure, timeout, missing
564
- permission, missing capability, non-dispatch-eligible prose, below the size floor) → today's
565
- fallback, unchanged: execute the stage once in the host session and log
566
- `stage <id> executed inline in session <session-id>`.
567
- - **`class: capacity`** (a confirmed exhausted usage allowance or credit balance) → the stage is
568
- **never** re-executed in the orchestrating host session. Hand it to the subprocess dispatch path
569
- (`--agent auto|name` resolves an executor through the registry and can pick another rung); when
570
- that path is unavailable, terminate the run at `failed` with `terminalReason: failed-agent`,
571
- naming the executor and the record's `resetAt` when it carries one. Record
572
- `stage <id> capacity exhaustion: <executor> <reason> reset <resetAt|unknown> — escalate|stop`.
573
-
574
- **Attribution before dispatch (task 1134 R3).** Immediately before each inline `agent.run` stage,
575
- record the executor identity the stage will use, so a later classified exhaustion can be attributed
576
- to a rung configuration can disable:
577
-
578
- ```bash
579
- spur workflow inline attribution --run-id "$RUN_ID" --stage <state-id> \
580
- [--executor <name>] [--agent <name>] [--model <m>] # omit --executor for the no-attribution marker
581
- ```
582
-
583
- This appends one line to `.spur/run/<run-id>-attribution.jsonl` and one run-log line
584
- (`stage <id> attribution: <executor|none>`). When no named executor resolves, pass no `--executor`:
585
- the ledger records the explicit `executor: null` marker — never a silent drop. An unattributed stage
586
- is observable but cannot write configuration (executor-availability §4), and `spur agent doctor
587
- --json` counts those stages as `inlineFailFast.unattributedStages`.
588
-
589
- **Pre-dispatch permission check (2026-09-15 subagent-dispatch evaluation).** Claude Code exposes no
590
- dry-run permission API, so the contract is **name-capabilities + fail-fast blocker** — never a
591
- permission-probing subsystem. Before dispatching a stage, the driver names the stage's required
592
- capabilities in the dispatch payload: the slash command (field 2), the resolved Spur invocation
593
- (field 4), and any shell actions the YAML action declares. The delegate is instructed to **return a
594
- blocker immediately on the first missing permission** rather than stalling — a worker parked at a
595
- prompt the host cannot see is invisible until join. Record one run-log line per dispatch:
596
-
597
- ```text
598
- stage <id> permission precheck: ok | <missing capability>
599
- ```
600
-
601
- Read-only investigation fan-out (N-way investigation) dispatches read-only
602
- worker shapes by construction — see the [pre-dispatch permission
603
- rule](../../parallel-execution/references/fan-out-patterns.md).
604
-
605
- **Dispatch and join:** before dispatch, capture the same pre-action git snapshot used by
606
- `requireDiff` enforcement, and resolve `answerFile`/`expectFile` against the worktree root — the
607
- resolved absolute path, not the YAML's relative string, is what the dispatched agent is instructed
608
- to write and what post-join validation reads. Resolving once at the dispatch boundary fixes every
609
- surface at once; a relative path would resolve against whatever cwd the writer process happens to
610
- have.
611
-
612
- **Dispatch payload (task 0818 R2).** Send exactly these six fields. The earlier "send only the
613
- stage id, the slash command, and the no-recursion notice" restriction is **deliberately replaced**:
614
- the execution-tree cwd, the Spur invocation, and the output path are all already resolved at this
615
- boundary, and a delegate left to re-derive them re-derives them against its own cwd and PATH.
616
-
617
- 1. The stage id.
618
- 2. The YAML's **exact** pure slash command — unchanged, never reformulated.
619
- 3. `execution surface already resolved: native subagent; do not dispatch this stage again`.
620
- 4. The **confirmed execution-tree cwd** (absolute) and the **resolved absolute Spur invocation** —
621
- `vars.spurBin`, i.e. `resolveSpurBin()`'s `<runtime> <mainModule>` form
622
- (`apps/cli/src/workflow/resolve-spur-bin.ts`). The delegate MUST run every Spur command through
623
- that invocation and MUST NOT rely on a bare `spur`: a competing `spur` earlier on the delegate's
624
- PATH otherwise wins. Setting `SPUR_BIN` alone does **not** change bare-command resolution — only
625
- using the supplied invocation does. Spur-owned scripted calls take it through the existing
626
- `--spur-bin` flag rather than a new mechanism. A delegate's tool calls may each start in an
627
- arbitrary directory, so every shell call the delegate makes re-pins the execution tree per the
628
- **Per-call execution-tree pin** protocol using this supplied identity — never an inherited cwd
629
- and never a re-derivation.
630
- 5. The **resolved absolute output path** (`answerFile`/`expectFile`, resolved as above) and the
631
- **owning stage's artifact contract** — for a verify stage, the compact contract below.
632
- 6. **The implement-stage acceptance-evidence requirement** — for the implement stage and any stage
633
- whose YAML action declares `requireDiff`. A `requireDiff` stage is the one whose delegate writes
634
- the deliverable, so its handoff carries four components:
635
- (a) **the task's AC identities verbatim** — the exact `Scenario:` titles / checklist text read by
636
- the driver from the task file, never paraphrased;
637
- (b) **required evidence** — the pasted output of the narrow targeted tests the delegate ran, and
638
- a `file:line` change map written into the task's `## Solution` section;
639
- (c) the reminder that **a delegate success message is not evidence** — post-join validation reads
640
- the artifacts and the diff, not the delegate's claims;
641
- (d) **the public-surface probe requirement (task 1135 R1/R2/R6).** Before the stage reports done
642
- it runs at least one end-to-end probe of each new or changed **public surface** — a CLI
643
- verb/flag, an HTTP route, or a plugin script entry — against a throwaway project root (never the
644
- repo corpus, never the execution tree's tracked files), and reports the exact command plus its
645
- observed output as acceptance evidence. A service-layer test suite is **not** a substitute. The
646
- probe is bounded: 120 s per invocation, output capped at 8 KiB, no network beyond the CLI under
647
- test, and a probe failure is evidence, never a corpus mutation.
648
- A stage that changed no public surface returns instead the single line
649
- `probe: not-applicable (no public surface changed)`.
650
- The stage's own changed-path static self-check (task 1135 R8) also runs here: `spur rule run`
651
- with the configured pre-check preset over the changed paths, `bun run script-contract-check`
652
- when a plugin script was touched, and the paired-script parity test when `package.json` changed —
653
- so an env-var-hygiene violation, a stale generated twin, or a half-updated paired chain is
654
- named by the stage rather than discovered by the pipeline `test` hop.
655
- **That pre-check is returned evidence, not a background step:** the stage reports its output (or
656
- `precheck: not-run` with the reason), and a stage that returns neither the output nor the
657
- not-run line is recorded `done` with a `precheck-missing` marker beside the `probe-missing` one.
658
- Measured cost of skipping it (2026-10-16, task 1215): one full quality-gate run plus a
659
- `test-fix` → `test-recheck` cycle on a `process.env` in a new test that the pre-check preset
660
- flags in seconds.
661
- **A stage that returns neither probe evidence nor the not-applicable line is not silently
662
- complete (task 1135 R2): the driver records the stage `done` with a `probe-missing` marker in
663
- the run log and carries the same marker into that task's batch-report row.**
664
- Component (a) is read from the task by the driver; this extends the payload contract and is NOT a
665
- new YAML key.
666
-
667
- Nothing else: no task/session transcripts, no machine-specific session paths. The WBS/path already
668
- carried by the slash command remains the task handoff.
669
-
670
- **Delegate hygiene.** A dispatched stage cleans up after itself: temporary artifacts stay inside the
671
- execution tree, and an ad-hoc git worktree created for a comparison is removed before the stage
672
- reports. The G65 batch's implement dispatches left an 867 MB worktree plus a trail of `/tmp` scratch
673
- files that outlived the run (2026-09-15).
674
-
675
- **Delegate search discipline.** A delegate searches with bounded scope: `rg -g '!node_modules' …`
676
- (or an explicit narrow path), never `grep -r .` / `grep -rn --include=*` from a repository root —
677
- those walk `node_modules` and `.git` and can burn many minutes at full CPU in the wrong tree
678
- (measured 2026-10-09: a delegate's `grep -rn --include=* .` from the invoking tree root ran 4+ min
679
- before the orchestrator killed it). The dispatch payload's execution-tree cwd is the only tree a
680
- delegate searches or edits; a search it cannot bound is a search it should not run.
681
-
682
- **Driver scratch lives with the run (task 1136 R6).** Every driver scratch file — action-batch
683
- JSON, captured stage output, temporary status/answer files — lives under `.spur/run/<run-id>/`,
684
- never `/tmp` or `$TMPDIR`: the OS may reap a temp directory mid-run, and a reaped scratch file
685
- surfaces as an unrelated downstream failure. Run-scoped files directly under `.spur/run/` keep
686
- their existing `<run-id>-*` naming (the `no-env-files` rule still bans `.env` names).
687
-
688
- **Declared vars are checked before a shell action runs (task 1136 R6).** Before executing a `shell`
689
- action whose command references a declared workflow var (for example `$qualityGateCmd`), the driver
690
- confirms the var is exported and non-empty; when it is not, the action fails by name —
691
- `shell action requires $qualityGateCmd but it is empty/unset` — and the command never runs. The
692
- check is scoped to vars the workflow declares with a value: legitimately empty optional vars
693
- (`$featureSpecPath` for an orphan task, `$gateFindings` on a green gate) pass unchanged. This turnsa silently empty `$qualityGateCmd` into a named failure instead of a shell that runs nothing.
694
-
695
- **Verify-stage artifact contract.** A verify handoff names
696
- [`code-verification/references/verdict-schema.md`](../../code-verification/references/verdict-schema.md)
697
- as the canonical answer schema and carries this compact form verbatim:
698
-
699
- ```text
700
- Verdict: PASS|PARTIAL|FAIL top-level, one line
701
- | Req | Status | Evidence | Status = MET | PARTIAL | UNMET
702
- (N/A and PASS are NOT valid requirement statuses)
703
- | AC | Status | Evidence Type | Evidence | Status = MET | PARTIAL | UNMET | N/A (justified)
704
- Evidence Type = test | command | static-ref | manual-review | llm-judge | n/a
705
- AC rows use the task's exact AC identities (verbatim `Scenario:` titles / checklist text).
706
- A behavioral AC marked MET requires executable evidence (test | command);
707
- static-ref or llm-judge alone cannot carry it.
708
- Evidence anchors are repo-root-relative (`packages/app/src/foo.ts:42`); a basename
709
- (`foo.ts:42`) is an unresolved anchor and blocks the done projection (`L4.anchor-unresolved`) —
710
- see `code-verification/references/verdict-schema.md` § Basename-only anchors are unresolved anchors.
711
- ```
712
-
713
- **AC table shape (0948 R9).** The AC table must be exactly **4 columns** and **cell 3 must hold a
714
- single evidence-type token** from the allowlist above. The token is *isolated* — never merged or
715
- concatenated with another token or with prose (`static-reftest` is a lint failure, not a shorthand
716
- for "static-ref + test"). Evidence detail belongs in cell 4. The verify-answer linter rejects a
717
- merged token, and `spur task verdict` then derives the wrong artifact — this exact shape failed the
718
- 0926 verify lint once before it was canonicalized, so it is stated here rather than left implicit in
719
- the header row.
720
-
721
- **Review-stage artifact contract.** A review handoff carries the Review output contract owned by
722
- `plugins/sp/agents/super-reviewer.md` — native `P1 (blocker)` / `P2 (major)` / `P3 (minor)` /
723
- `P4 (advisory)` priority cells and section-relative headings — **not** the verify answer schema.
724
-
725
- This is an invocation and handoff fix, not a runtime PATH-injection subsystem: it makes no guarantee
726
- about arbitrary bare commands in host shells or agent-generated shells.
727
-
728
- Dispatch exactly one native subagent and wait for it; the inline FSM
729
- must not advance actions or guards concurrently (one writer at a time). After join, validate
730
- `answerFile`, `expectFile`, `requireDiff`, task scope, and the action's error policy from the shared
731
- filesystem — a subagent success message is not evidence. On success append exactly:
732
-
733
- ```text
734
- stage <id> executed via subagent <agent-id> (host session <session-id>)
735
- ```
736
-
737
- Host fallback retains exactly `stage <id> executed inline in session <session-id>`. If launch fails
738
- before the subagent starts, log the reason and use host fallback. If a started subagent fails or
739
- leaves invalid artifacts, do **not** replay the stage in the host — follow the YAML error policy so
740
- partial mutations are not duplicated.
741
-
742
- **Resume over re-dispatch for worker-role continuation (2026-09-15 subagent-dispatch evaluation).**
743
- Some stages continue the *same* task's work product after a finding: `test-fix` after a failing
744
- `test`, an implement rework after review findings. A cold re-dispatch makes the next worker
745
- re-ingest the task, the diff, and every skill file it already loaded. When the host platform
746
- supports addressing a completed subagent again (Claude Code: send a follow-up message to the same
747
- agent — its context survives completion), the driver SHOULD resume the prior same-task worker
748
- subagent for that continuation stage instead of dispatching a fresh one, carrying the same
749
- six-field payload plus the finding that triggered the continuation. Provenance uses the resumed
750
- form:
751
-
752
- ```text
753
- stage <id> executed via subagent <agent-id> (resumed; host session <session-id>)
754
- ```
755
-
756
- The resume rule applies to **worker-role** stages only (implement, test-fix). Reviewer and verify
757
- stages always dispatch **fresh**: their value is independent eyes on the diff, and a resumed worker
758
- grading its own work defeats the stage. A continuation stage also dispatches fresh when no prior
759
- same-task subagent exists (the earlier stage ran host-inline or below the dispatch floor), when a
760
- host-owned gate sat between the stages, or when the platform cannot address completed subagents.
761
-
762
- **Implement and test-fix workers never run the full gate (task 1142 R3).** Workers use the light tier as their iteration check (task 1142 R3) in exact order:
763
- 1. the **changed-path matrix** (`code-implementation/SKILL.md` § Changed-path targeted checks) over the paths they touched;
764
- 2. `spur task gate <wbs> light` (biome, per-workspace typecheck, rule presets, related tests).
765
- Neither step takes the full gate. The pipeline's `test` and `test-recheck` hops remain the only full gate. Carry
766
- `--gate-log <path>` through to the child **verbatim**: never paraphrase it into "re-run the named
767
- gate command". That paraphrase is not cosmetic — it sent a worker into a full gate run instead of
768
- targeted iteration checks. The pipeline's next `test` (recheck)
769
- stage delivers the deciding verdict.
770
-
771
- **Null or timed-out dispatch (task 1134 R9).** The dispatch bound stays the existing YAML
772
- `timeoutMs` (`implementTimeoutMs` / `stepTimeoutMs`), passed to the host dispatch as today — R9 adds
773
- no new bound. A dispatch that returns **no result**, or that hits that bound, is a **recorded stage
774
- failure**, not a fallback trigger: record the stage failed with `dispatch: null-result|timeout`,
775
- follow the stage's normal failure edge (implement's default `fail` policy routes the run to
776
- `failed`), and start **no** host-inline attempt of that stage. R1–R4 cover a *pre-dispatch* capacity
777
- refusal (the executor must shift); R9 covers the *dispatched-but-unproductive* case.
778
-
779
- **Timeout boundary (task 0727, amended by task 1108):** when the host's dispatch tool accepts a
780
- per-dispatch timeout (pi: subagent `timeoutMs`, default 30 min — pi-subagents `docs/tool-reference.md`
781
- line 97), the driver passes the stage's resolved YAML
782
- `timeoutMs` (implement → `implementTimeoutMs`) and records `host timeout <ms> (yaml timeoutMs)`.
783
- When it does not, a dispatched subagent is governed by
784
- **the host platform's subagent limit, not the YAML timeoutMs**, and the driver records
785
- `host timeout <ms> (platform subagent limit)`. `timeoutMs` stays not-applicable for host-inline
786
- execution only. Either way the driver must
787
- **record the governing timeout boundary and its source before dispatch** in the run log. Rationale:
788
- the operator sets one budget in one place; a host silently imposing its own 30 min default is what
789
- killed the P1 implement dispatches (1096, 1099). If the dispatch reaches that
790
- boundary, **a dispatch timeout is a started-subagent failure**: the no-replay rule above and the
791
- stage's declared YAML error policy govern (implement's default `fail` policy routes the run to
792
- `failed`); it is never a host re-execution. Recovery follows the
793
- [timed-out implement runbook](execution-workflow.md)'s inline-path equivalent:
794
- **resume from the partial tree, never restart the stage inline** — no
795
- `<runId>-implement-partial.md` artifact is written on this path, so the partial working tree
796
- itself is the recovery input.
797
-
798
- **Host-owned interaction:** the host alone executes operator-confirmation actions, owns
799
- `pause: true`, and surfaces approve/taste/ask decisions. A subagent that discovers missing authority
800
- or an operator decision returns a blocker; the host pauses at the current state and presents it. The
801
- subagent cannot approve, infer consent, or recursively invoke the full pipeline.
802
-
803
- After every successful inline `agent.run` action append exactly one provenance line (inline or
804
- subagent form above) to `.spur/memory/runs/<run-id>.md`, where `<id>` is the current YAML state id. Also
805
- log start/failure and the ignored timeout value so an inline run remains auditable without
806
- fabricating an `AgentRunTracedResult`.
807
-
808
- Run-log stamps (task 0727): every appended line is prefixed with an **ISO-8601 UTC** timestamp
809
- (`YYYY-MM-DDTHH:MM:SSZ`, e.g. `2026-08-31T17:51:11Z`); the exact-template provenance lines above
810
- keep their exact content after the stamp prefix. This normalization is contractual:
811
- **bare local-clock stamps are prohibited** — a hand-appended `[stage 12:31]` form mixes timezones
812
- in one file and makes the run unauditable (task 0726).
813
-
814
- ## Structured trace emission (ADR-117, task 0868)
815
-
816
- Read [structured-trace-emission.md](structured-trace-emission.md) when the first trace event is
817
- emitted — the action vocabulary, the emit shape and the parse/close contract.
818
-
819
- ## Post-gate tree freeze (task 1135 R3/R4/R5)
820
-
821
- The quality gate's `proofDigest` certifies a specific tree. Two rules protect it, and both are
822
- **ordering plus early detection** — a host session cannot intercept a file write, so the contract is:
823
-
824
- 1. **Every tree edit belongs to `implement`, or to the bounded `test-fix` remediation hop** — stray-file
825
- cleanup, anchor qualification, contract pins, doc edits, and task-section writes other than the
826
- pipeline-owned `## Testing`/`## Review`/`## Solution`. After `test` captures `proofDigest`, no
827
- other state edits the tree. A genuine later correction goes through `test-fix → test-recheck`,
828
- which re-captures the digest and re-snapshots the tree state.
829
- 2. **Review joins the proof bracket.** The pipeline now compares `proofDigestNow` against
830
- `proofDigest` at **review** entry as well as verify and record, so a drifted tree stops the run
831
- before a review dispatch is spent. The mismatch routes through the existing tripwire to `failed`;
832
- there is no new re-certify path.
833
-
834
- **A host-driven pass that skips the `test` hop has no digest-bound receipt.** `proofDigest` is
835
- captured by the `test` state's `proof.fingerprint` action, so hops driven out of band — an operator
836
- implementing/verifying/recording by hand, or an audit of a task whose gate the host ran directly —
837
- leave no `.spur/run/<wbs>-check-receipt.json`. The deciding artifacts are then
838
- `.spur/run/<wbs>-test-gate.status` plus its gate log. Two consequences, in order of preference:
839
-
840
- 1. **Drive the pipeline** for anything that must be certified: only a digest-bound full receipt
841
- satisfies a review/verify/record boundary (`sp-spur-check`), and the `test` hop owns the capture.
842
- 2. **When a host-driven pass is unavoidable, say so in the verdict** — name the status file and gate
843
- log as the evidence, and mark the reliance explicitly in the evidence cell (a host-driven gate is
844
- not re-runnable proof from a checked path; the `sp-code-verification` ladder is). Claiming a
845
- receipt that was never written is the failure mode this note exists to prevent (observed across
846
- four A91 tasks, 2026-10-12).
847
-
848
- **A mismatch names the drifted paths (R5).** At gate entry the pipeline snapshots the non-corpus tree
849
- state (`git status --porcelain=v1 -uall` plus `git diff --name-only HEAD`, excluding
850
- `docs/tasks*` / `docs/features*`) into `.spur/run/<run-id>-gate-paths.txt`. On a mismatch the
851
- `proof.fingerprint` action diffs the current state against that file and reports the changed paths in
852
- both the tripwire event and the run log (`proof-compare-failed … drifted paths: …`), so the operator
853
- reads *which file* moved, not only the `D1`/`D2` pair. An **untracked** file outside those exclusions
854
- is part of the certified set: the digest's git-tree half stages everything outside the excluded
855
- globs, so a stray file added after the gate is real drift and must be named (the behaviour is
856
- documented beside `DEFAULT_EXCLUDE_GLOBS` in `proof-input-fingerprint.ts`).
857
-
858
- ## Record & done sequencing
859
-
860
- Order matters for the `testing → done` hop: the sections must not be hand-written **before** the
861
- verdict artifact exists (tasks 0617, 0619 — the clobbering spiral).
862
-
863
- 1. **Write the verdict artifact first.** `spur task record <wbs> --solution-from-diff --transition testing --no-lifecycle`
864
- reads `.spur/run/<wbs>-verdict.json` (default attempt output) on every invocation and atomically retains the recorded verdict under `.spur/memory/evidence/`. A missing or malformed artifact
865
- yields **UNKNOWN**: bare Testing receives a "No requirements recorded" stub, while already-authored
866
- Testing is preserved. `--solution-from-diff` backfills only a bare Solution. The clobbering above is
867
- the historical behavior, corrected by the authored-Testing safeguard (tasks 0617, 0619). Creating the
868
- artifact first (PASS, with requirement rows keyed by scenario title) remains the standard order;
869
- re-running record after a real verdict arrives refreshes Testing from that verdict.
870
-
871
- ```bash
872
- # verdict artifact first (shape: {wbs, verdict, requirements:[{id,status,evidence}], checks:[], source})
873
- # then the record hop; then re-write Testing/Solution if record's backfill is thinner than intended.
874
- # Both calls are per-call pinned (task 1058): the record hop selects the execution tree inside
875
- # the same tool call — never through a cwd a previous call happened to leave behind.
876
- spur task update <wbs> wip --no-lifecycle
877
- spur task record <wbs> --solution-from-diff --transition testing --no-lifecycle
878
- ```
879
-
880
- The engine now preserves an already-authored Testing when the verdict is UNKNOWN (task-service
881
- `record` fallback-only, mirroring the Review 0593 precedent) — but the order above is still the
882
- contract for the standard pipeline.
883
- 2. **Done-probe before done.** Run the check projected to `done` (`spur task check <wbs> --as done`
884
- via the `TaskCheckService` probe pattern) — it surfaces `L3.unchecked-checklist` (flip `- [ ]` → `- [x]`)
885
- and `L3.required-section-placeholder` before the transition, not after.
886
- **Anchor repair is the YAML implement stage's job, not the driver's** (task 1109 R4):
887
- `task-pipeline.yaml` runs `$spurBin task migrate-anchors --wbs $wbs --json ; exit 0` after the
888
- format step and before `test` captures the proof digest, so unique basename anchors are already
889
- repo-relative by the time this probe runs. The driver does not hand-patch anchors: anything the
890
- scoped pass leaves (ambiguous basenames, line drift, `L4.anchor-subject-mismatch`) is reported by
891
- this probe and belongs to the implementer to re-anchor.
892
- 3. **Solution change-map anchor rule (L4.anchor-subject-mismatch).** A Solution change-map table must
893
- list **one `file:line` per row**. A ·-joined paragraph makes every anchor's "subject" the other
894
- anchors and trips the L4 subject check. Since 0804 R9, subject extraction ignores complete parsed
895
- citation spans, so a path's underscores no longer manufacture a subject: an underscore path row
896
- (`docs/help/cmd_example.md:12`) is checked exactly like any other row — cite an **existing file**
897
- with a **valid line or line range** whose content names the requirement's subject. A real absent
898
- symbol, nonexistent file or invalid range still reports; never replace a citable row with prose.
899
- 4. **Stage through the commit guard (1129 R2/R3/R5).** The per-task commit that follows the record
900
- hop stages only the files THIS run wrote — `git add -A` and `git add .` are **forbidden** in
901
- every driver commit step (they staged a concurrent session's files in `a94f9f431` and
902
- `36f274590`). `check` first and record a non-empty `foreign` list in the run report; `stage`
903
- exits 2 when it refuses a listed path (foreign) and 3 when a target is unmerged or still carries
904
- conflict markers — both mean "resolve it, do not commit":
905
-
906
- ```bash
907
- spur workflow commit-guard check --run "$RUN_ID"
908
- spur workflow commit-guard stage --run "$RUN_ID" -- <files this run wrote> \
909
- || { echo "record halt: commit-guard refused a path (foreign, unmerged or conflict-marked); nothing committed" >&2; exit 1; }
910
- git commit -m "<type>(<scope>): <wbs> <summary>"
911
- ```
912
-
913
- 5. **Print the run's execution summary (1146).** The terminal `spur workflow inline close` returns `summaryFile`; print
914
- that file's contents verbatim under an `### Execution summary` heading, and pass `--no-summary`
915
- through to `inline close` when the operator asked for it. The window is the run row's `started_at` →
916
- close time — the driver records no `SINCE` and assembles no measurement of its own
917
- (structured-trace-emission.md § the close step owns the contract).
918
-
919
- ## Running the full gate (task 0872 / 1127 R7)
920
-
921
- The full gate legitimately runs minutes, not seconds — measured 1.8-3.1 min for `bun run spur-check`
922
- on an idle 10-core host (2026-10-13), and far longer when other agents' suites run concurrently.
923
- Five rules keep it from becoming a hazard (the first two
924
- were both violated once on 2026-10-10, aborting the run's own verification):
925
-
926
- - **Never wrap it in an external `timeout`, and never treat its runtime as a hang.** An outer bound
927
- that fires mid-suite leaves a half-finished suite behind while the caller believes it is gone.
928
- - **Run it detached and poll the log**, so losing the foreground call cannot lose the run:
929
-
930
- ```bash
931
- nohup bash -c 'bun run spur-check > /tmp/gate.log 2>&1; echo "EXIT=$?" >> /tmp/gate.log' &
932
- ```
933
-
934
- - **Preserve a signal-induced receipt.** A cap-killed attempt leaves a red receipt that the next
935
- recheck's `no-progress` guard honours, so it reads as a genuine failure unless the receipt is kept
936
- under a distinct name and the run log says so.
937
- - **Clear the killed attempt's process tree before re-running.** A cap-signalled gate leaves its
938
- `bun test --test-worker` children alive: the parent dies, the workers keep burning ~100% CPU, and
939
- each retry is slower than the last until the cap kills that one too and mints another leak.
940
- Observed 2026-10-13: five leaked workers (ages 23 min, 37 min, 50 min, 2 h 17 m, 4 h 18 m) drove
941
- consecutive attempts from the measured 1.8-3.1 min up to 13-40 min, and the real defect of the
942
- session only became visible once they were cleared. Find them by age and cwd, then clear the tree
943
- before the next run; leave a tree alone whose cwd is another session's live run:
944
-
945
- ```bash
946
- ps -Ao pid=,ppid=,etime=,%cpu=,command= | grep -E "run-tests\.ts|bun test --test-worker" | grep -v grep
947
- kill -TERM <pid> <ppid>; sleep 4; kill -KILL <pid> <ppid> # escalate only if TERM is ignored
948
- ```
949
-
950
- - **One gate per tree.** Nothing serializes gate runs since task 1173 deleted the lock, so confirm
951
- no live gate is already running in your tree before starting one: two suites in one tree contend
952
- for CPU and for the shared fixtures, and the second one's result is uninterpretable. Measured
953
- 2026-10-13: a duplicate gate launched into a tree already running another session's
954
- `bun run spur-check` burned 40 min at the host cap and left an orphaned worker behind.
955
-
956
- > Historical (task 1127, removed in task 1173): these rules were once stated in terms of a host-wide
957
- > `full-gate.lock`. The lock mechanism was deleted (`refactor(gate): remove gate-lock mechanism and
958
- > simplify quality-gate`), so nothing serializes gate runs today — the guidance above stands on its
959
- > own without it.
960
-
961
- ## Runtime interface contracts (from run evidence)
962
-
963
- Each item below cost a driver cycle to discover. They are contracts, not tips.
964
-
965
- - **`spur workflow inline decide --options-json <file>` takes one option object, never an array.** Shape:
966
- `{id, decision, params?, evidence? | instructions?, resultFile}`; `evidence` and `instructions` are
967
- mutually exclusive (`CatalogDecideOptionsSchema`). A JSON array parses as neither dialect and
968
- surfaces as `decide: invalid options — Invalid input` with no field detail. `resultFile` resolves
969
- against the confirmed tree's cwd, so pass a worktree-relative path such as
970
- `.spur/run/<wbs>-triage.decision`.
971
- - **`spur task diffstat <wbs>` writes an artifact, not output.** stdout stays empty, so assert
972
- success on `.spur/run/<wbs>-diffstat.json` and never on command output; a missing or malformed
973
- run base still exits 0 with `sensitive: true` (fail safe).
974
- - **The verify answer grammar is exact.** One `Verdict:` line, then
975
- `### Per-Requirement Traceability` (`| Req | Status | Evidence |`) and
976
- `### Acceptance Criteria Verification` (`| AC | Status | Evidence Type | Evidence |`). Requirement
977
- statuses are `MET | PARTIAL | UNMET`; AC statuses add `N/A`; every AC row id must match a declared
978
- scenario title verbatim, including its `(req: ...)` suffix. A `MET` row whose evidence hedges
979
- (`likely`, `probably`, `seems`, `appears`, `possibly`, `presumably`) is rejected as
980
- `evidence-hedged`. No `Confidence:` line exists (F511, task 1207) — legacy answers that carry one
981
- parse with the value ignored. `spur task verdict` lints before writing, so a rejected answer leaves no
982
- artifact and exits non-zero.
983
- - **Harness tooling refuses direct task-file writes.** Resolve a merge conflict in a task file by
984
- choosing a git side, then applying the corpus delta through `spur task update` (for example
985
- `--feature <id>`), never by editing the file.
986
- - **A skill body has a byte budget** (20,000 B for `SKILL.md`); procedure detail belongs in
987
- `references/`. Adding a clause to a SKILL.md can fail `R44 — skill BODY budgets`.
988
- - **`.spur/run/` is volatile** (ADR-131, as amended): its contents are execution scratch. Anything
989
- that outlives the run cites the durable owner (`.spur/memory/evidence/`, `.spur/memory/runs/`) or
990
- the tracked Testing section.
991
-
992
- ## Failure contract
993
-
994
- Never silently fall back from this interactive inline path to `agent.default`. If the driver cannot
995
- read the YAML, allocate provenance, execute an action, or evaluate a guard, stop at that state and
996
- report the run id, state id, original error, and the concrete resume/retry command. The working tree
997
- and run artifacts are the recovery input.