@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/config.example.yaml +22 -0
- package/config/rules/quality/coverage-gate.yaml +2 -2
- package/config/rules/surface/check-cli-surface.yaml +4 -0
- package/config/workflows/feature-verification.yaml +14 -5
- package/config/workflows/idea-pipeline.yaml +92 -12
- package/config/workflows/pr-review.yaml +72 -224
- package/config/workflows/task-pipeline.yaml +456 -830
- package/config/workflows/wayfinder-resolution.yaml +20 -3
- package/config/workflows/wrapup-pipeline.yaml +22 -9
- package/package.json +1 -1
- package/plugins/sp/README.md +2 -2
- package/plugins/sp/agents/super-planner.md +2 -2
- package/plugins/sp/agents/super-reviewer.md +17 -8
- package/plugins/sp/bunfig.toml +3 -3
- package/plugins/sp/commands/dev-changelog.md +1 -1
- package/plugins/sp/commands/dev-gitmsg.md +2 -2
- package/plugins/sp/commands/dev-handover.md +1 -1
- package/plugins/sp/commands/dev-idea.md +23 -13
- package/plugins/sp/commands/dev-job-dump.md +1 -1
- package/plugins/sp/commands/dev-plan.md +17 -6
- package/plugins/sp/commands/dev-pr-review.md +27 -17
- package/plugins/sp/commands/dev-review.md +1 -1
- package/plugins/sp/commands/dev-run.md +7 -6
- package/plugins/sp/commands/dev-runall.md +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/code-implementation/SKILL.md +7 -7
- package/plugins/sp/skills/code-refactoring/SKILL.md +1 -1
- package/plugins/sp/skills/code-verification/SKILL.md +1 -1
- package/plugins/sp/skills/code-verification/references/secu-review.md +8 -4
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +23 -10
- package/plugins/sp/skills/doc-evolve/SKILL.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +7 -7
- package/plugins/sp/skills/pr-reviewing/SKILL.md +157 -210
- package/plugins/sp/skills/source-driven-development/SKILL.md +14 -6
- package/plugins/sp/skills/spec-decomposition/references/decomposition.md +4 -4
- package/plugins/sp/skills/spur-check/SKILL.md +9 -8
- package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
- package/plugins/sp/skills/spur-cli/references/builder.md +86 -13
- package/plugins/sp/skills/spur-cli/references/self.md +37 -2
- package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +1 -1
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +1 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +5 -3
- package/plugins/sp/skills/spur-dev/SKILL.md +3 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +9 -8
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +11 -11
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +4 -3
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +4 -4
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +13 -11
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +31 -22
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +5 -5
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +4 -4
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +3 -2
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +4 -4
- package/schemas/spur-config.schema.json +19 -1
- package/spur.js +32388 -31906
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +0 -997
- 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.
|