@gobing-ai/spur 0.3.99 → 0.4.0

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 (106) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +17 -0
  3. package/config/decisions/task-pipeline.yaml +55 -0
  4. package/config/plugin-scripts.json +10 -0
  5. package/config/rules/boundary/sp-runtime-path.yaml +2 -0
  6. package/config/rules/quality/coverage-gate.yaml +5 -3
  7. package/config/rules/strict/runtime-boundaries.yaml +11 -0
  8. package/config/rules/structure/protected-files.yaml +3 -0
  9. package/config/workflows/task-pipeline.yaml +178 -18
  10. package/package.json +9 -9
  11. package/plugins/sp/README.md +14 -1
  12. package/plugins/sp/agents/super-reviewer.md +25 -11
  13. package/plugins/sp/commands/dev-changelog.md +5 -4
  14. package/plugins/sp/commands/dev-review-session.md +12 -7
  15. package/plugins/sp/commands/dev-run.md +5 -2
  16. package/plugins/sp/commands/dev-runall.md +5 -2
  17. package/plugins/sp/hooks/agent-lifecycle.ts +155 -0
  18. package/plugins/sp/hooks/fleet-guest-stop.ts +180 -0
  19. package/plugins/sp/hooks/hooks.json +45 -1
  20. package/plugins/sp/hooks/pi/guard-extension.ts +21 -0
  21. package/plugins/sp/lib/history-anatomy.generated.mjs +5 -0
  22. package/plugins/sp/lib/idea-handoff.generated.mjs +34 -9
  23. package/plugins/sp/lib/inline-run.generated.d.mts +1 -0
  24. package/plugins/sp/lib/inline-run.generated.mjs +49 -19
  25. package/plugins/sp/lib/transcript.ts +160 -0
  26. package/plugins/sp/plugin.json +1 -1
  27. package/plugins/sp/scripts/history-anatomy-cache.mjs +5 -0
  28. package/plugins/sp/scripts/inline-run-setup.mjs +21 -20
  29. package/plugins/sp/scripts/inline-run-setup.ts +25 -31
  30. package/plugins/sp/scripts/residual-scan.mjs +2 -7
  31. package/plugins/sp/scripts/residual-scan.ts +7 -8
  32. package/plugins/sp/scripts/run-summary.mjs +286 -0
  33. package/plugins/sp/scripts/run-summary.ts +241 -0
  34. package/plugins/sp/scripts/session-timeline.mjs +234 -0
  35. package/plugins/sp/scripts/session-timeline.ts +152 -0
  36. package/plugins/sp/skills/code-verification/SKILL.md +5 -0
  37. package/plugins/sp/skills/fleet-join/SKILL.md +63 -0
  38. package/plugins/sp/skills/session-review/SKILL.md +46 -24
  39. package/plugins/sp/skills/spur-cli/SKILL.md +1 -0
  40. package/plugins/sp/skills/spur-cli/references/agent.md +108 -6
  41. package/plugins/sp/skills/spur-cli/references/decision.md +97 -0
  42. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +22 -0
  43. package/plugins/sp/skills/spur-composer/SKILL.md +27 -1
  44. package/plugins/sp/skills/spur-dev/references/dev-operations.md +28 -16
  45. package/plugins/sp/skills/spur-dev/references/execution-batch.md +64 -7
  46. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +14 -0
  47. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +16 -4
  48. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +32 -7
  49. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +6 -0
  50. package/schemas/decision-catalog.schema.json +226 -0
  51. package/schemas/spur-config.schema.json +25 -0
  52. package/spur.js +7440 -2424
  53. package/web/_astro/BoardApp.Bzr4OXrW.js +324 -0
  54. package/web/_astro/{BoardApp.i4eh_fHA.js → BoardApp.VeoxxLBy.js} +1 -1
  55. package/web/_astro/{TaskDetail.C5Cu8LdB.js → TaskDetail.LAyR_Pvn.js} +1 -1
  56. package/web/_astro/{arc.Bu5XpBEz.js → arc.f4e0OYLg.js} +1 -1
  57. package/web/_astro/{architectureDiagram-3BPJPVTR.9vHQTvhF.js → architectureDiagram-3BPJPVTR.SSIvxi-Z.js} +1 -1
  58. package/web/_astro/{blockDiagram-GPEHLZMM.Cn2Xl7D6.js → blockDiagram-GPEHLZMM.DSXIHOFf.js} +1 -1
  59. package/web/_astro/{c4Diagram-AAUBKEIU.7o0P_QDo.js → c4Diagram-AAUBKEIU.DG9Ubu85.js} +1 -1
  60. package/web/_astro/channel.CUFdtWhm.js +1 -0
  61. package/web/_astro/{chunk-2J33WTMH.C3zZFsNt.js → chunk-2J33WTMH.C7QU88Qh.js} +1 -1
  62. package/web/_astro/{chunk-4BX2VUAB.CNy_M7fO.js → chunk-4BX2VUAB.26Z94xBj.js} +1 -1
  63. package/web/_astro/{chunk-55IACEB6.2TiPnLBL.js → chunk-55IACEB6.DYRiBkqw.js} +1 -1
  64. package/web/_astro/{chunk-727SXJPM.5CGSyKAp.js → chunk-727SXJPM.D5Vfv5pe.js} +1 -1
  65. package/web/_astro/{chunk-AQP2D5EJ.w-ZIgfsK.js → chunk-AQP2D5EJ.DXHV47bN.js} +1 -1
  66. package/web/_astro/{chunk-FMBD7UC4.BkcEyhKa.js → chunk-FMBD7UC4.D1suAfOs.js} +1 -1
  67. package/web/_astro/{chunk-ND2GUHAM.mxKKbBG4.js → chunk-ND2GUHAM.D6BmdHEY.js} +1 -1
  68. package/web/_astro/{chunk-QZHKN3VN.cyA0z9aT.js → chunk-QZHKN3VN.rnPU-Ak-.js} +1 -1
  69. package/web/_astro/{classDiagram-4FO5ZUOK.CqtUzwhX.js → classDiagram-4FO5ZUOK.CvEfXIo1.js} +1 -1
  70. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CqtUzwhX.js → classDiagram-v2-Q7XG4LA2.CvEfXIo1.js} +1 -1
  71. package/web/_astro/{cose-bilkent-S5V4N54A.BExPTopI.js → cose-bilkent-S5V4N54A.DkMisLSm.js} +1 -1
  72. package/web/_astro/{cynefin-OW5HDTMX.Bq2i10Cn.js → cynefin-OW5HDTMX.C77Q-pF6.js} +1 -1
  73. package/web/_astro/{dagre-BM42HDAG.By_HsOuv.js → dagre-BM42HDAG.YaV8ce27.js} +1 -1
  74. package/web/_astro/{diagram-2AECGRRQ.BtU0b7hz.js → diagram-2AECGRRQ.CUyGEKge.js} +1 -1
  75. package/web/_astro/{diagram-5GNKFQAL.D00ztMdT.js → diagram-5GNKFQAL.CRgd8dgj.js} +1 -1
  76. package/web/_astro/{diagram-KO2AKTUF.DMVJWaUN.js → diagram-KO2AKTUF.Bmzys9hq.js} +1 -1
  77. package/web/_astro/{diagram-LMA3HP47.Cxqrjg5-.js → diagram-LMA3HP47.BvmJY4Lw.js} +1 -1
  78. package/web/_astro/{diagram-OG6HWLK6.2lz_1B5h.js → diagram-OG6HWLK6.0hEhD30d.js} +1 -1
  79. package/web/_astro/{erDiagram-TEJ5UH35.BVQEuJ3T.js → erDiagram-TEJ5UH35.Bjt8lrnm.js} +1 -1
  80. package/web/_astro/{flowDiagram-I6XJVG4X.Qlyo90ky.js → flowDiagram-I6XJVG4X.CJZTjRDV.js} +1 -1
  81. package/web/_astro/{ganttDiagram-6RSMTGT7.DRwb8HHZ.js → ganttDiagram-6RSMTGT7._VqoVafc.js} +1 -1
  82. package/web/_astro/{gitGraphDiagram-PVQCEYII.YfoPthfS.js → gitGraphDiagram-PVQCEYII.CEDb7tVo.js} +1 -1
  83. package/web/_astro/index.Dg_3bOaJ.css +1 -0
  84. package/web/_astro/{infoDiagram-5YYISTIA.DvFREck1.js → infoDiagram-5YYISTIA.CgwifPpK.js} +1 -1
  85. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BzCj9Q0-.js → ishikawaDiagram-YF4QCWOH.B99wx2J1.js} +1 -1
  86. package/web/_astro/{journeyDiagram-JHISSGLW.BDMT_AFb.js → journeyDiagram-JHISSGLW.2hRwJbEo.js} +1 -1
  87. package/web/_astro/{kanban-definition-UN3LZRKU.D6Fh3gj9.js → kanban-definition-UN3LZRKU.BahJkKNC.js} +1 -1
  88. package/web/_astro/{linear.Dxw00fGQ.js → linear.DguUUe6V.js} +1 -1
  89. package/web/_astro/{mermaid.core.CsoxUWa0.js → mermaid.core.CvgfmqVr.js} +4 -4
  90. package/web/_astro/{mindmap-definition-RKZ34NQL.CN8oeHLu.js → mindmap-definition-RKZ34NQL.B3qEA3x9.js} +1 -1
  91. package/web/_astro/{pieDiagram-4H26LBE5.C7ADqQ_7.js → pieDiagram-4H26LBE5.FlWA-_ru.js} +1 -1
  92. package/web/_astro/{quadrantDiagram-W4KKPZXB.Cw9lKyHP.js → quadrantDiagram-W4KKPZXB.9bVn35X7.js} +1 -1
  93. package/web/_astro/{requirementDiagram-4Y6WPE33.B1ni-z4q.js → requirementDiagram-4Y6WPE33.ChykIhct.js} +1 -1
  94. package/web/_astro/{sankeyDiagram-5OEKKPKP.BhexdjVr.js → sankeyDiagram-5OEKKPKP.Z2Y3hkYb.js} +1 -1
  95. package/web/_astro/{sequenceDiagram-3UESZ5HK.C3Rm7dpQ.js → sequenceDiagram-3UESZ5HK.J7JsHWDt.js} +1 -1
  96. package/web/_astro/{stateDiagram-AJRCARHV.CkuMt27u.js → stateDiagram-AJRCARHV.DAPbiCSS.js} +1 -1
  97. package/web/_astro/{stateDiagram-v2-BHNVJYJU.Dc_aazUA.js → stateDiagram-v2-BHNVJYJU.D2jvSi0q.js} +1 -1
  98. package/web/_astro/{timeline-definition-PNZ67QCA.ja7sUFhH.js → timeline-definition-PNZ67QCA.psW2ci29.js} +1 -1
  99. package/web/_astro/{vennDiagram-CIIHVFJN.BxIshX_K.js → vennDiagram-CIIHVFJN.DlILYVmg.js} +1 -1
  100. package/web/_astro/{wardleyDiagram-YWT4CUSO.DfHlC0sh.js → wardleyDiagram-YWT4CUSO.xNWwLJnn.js} +1 -1
  101. package/web/_astro/{xychartDiagram-2RQKCTM6.DSnhHwCh.js → xychartDiagram-2RQKCTM6.EU_BuH7V.js} +1 -1
  102. package/web/board-runtime.json +1 -1
  103. package/web/index.html +2 -2
  104. package/web/_astro/BoardApp.BVp-LYi6.js +0 -322
  105. package/web/_astro/channel.6IFIKBhu.js +0 -1
  106. package/web/_astro/index.CbNz17Sx.css +0 -1
@@ -23,14 +23,18 @@ that before using `run` for fan-out dispatch.
23
23
  | ---- | ------- | --------- |
24
24
  | `run <prompt>` | Execute a prompt or slash command via a coding agent | `--agent <name>` `--spec <id>` `--model <name>` `--mode <mode>` `--continue` `--cwd <path>` `--drain` `--json` |
25
25
  | `wait [<specId>]` | Identity-pinned wait for an occupant run to reach a lifecycle state (G4 wave 2; `--role` selector per 0685) | `--role <name>` `--run <runId>` `--until <state>...` `--timeout <ms>` `--json` |
26
+ | `trace <runId>` | Print one execution record's lineage — the run, its dispatch parents, agent session ids and streams (1076 R4, ADR-132) | `--follow` `--timeout <ms>` `--json` |
26
27
  | `list` | List detected coding agents, or agent specs with `--specs` (live run status + member session merged from `spur self serve`) | `--specs` `--server <url>` `--json` |
28
+ | `report` | Report a fleet member lifecycle state (`working`/`idle`/`blocked`) from a host hook (G73 R1/R2, task 1080) | `--state <state>` `--seq <ns>` `--spec <id>` `--json` |
29
+ | `join` | Join the fleet as a guest occupant — a live session that pulls its own work (G73 R2, task 1081) | `--role <name>` `--id <id>` `--session-id <sid>` `--pid <n>` `--executor <name>` `--json` |
30
+ | `leave` | Leave the fleet: release the guest lease, retire the occupant, return claimed messages to pending | `[id]` `--session-id <sid>` `--json` |
27
31
  | `status` | Agent specs with live process status and member session (requires `spur self serve`) | `--server <url>` `--json` |
28
32
  | `doctor [agent]` | Check agent readiness | `--json` `--probe-health` `--force-refresh` |
29
33
  | `usage` | Run-once provider usage capture (codexbar) → quota-owned availability refresh; scheduled externally | `--dry-run` `--source <name>` `--json` |
30
34
  | `start <spec-id>` | Start a supervised agent process (requires `spur self serve`) | `--server <url>` `--json` |
31
35
  | `stop <spec-id>` | Stop a supervised agent process (requires `spur self serve`) | `--server <url>` `--json` |
32
36
 
33
- `list`, `status`, `doctor`, `run`, `wait`, `start`, and `stop` accept `--json` plus `--json-envelope`. The hidden
37
+ `list`, `status`, `doctor`, `run`, `wait`, `trace`, `start`, and `stop` accept `--json` plus `--json-envelope`. The hidden
34
38
  `loop` is a supervisor-internal process surface. **Exit codes:** `0` success, `1` failure, and `2`
35
39
  invalid usage; `run` can also propagate the invoked agent's non-zero result.
36
40
 
@@ -91,7 +95,9 @@ It waits for a wake on the `system_events` ledger — a human request (`message.
91
95
  change (`strategy.changed`), a capacity change (`fleet.capacity.changed`), or a completion receipt
92
96
  (`agent.invoke.exit`) — then drains the inbox into an `agent run` invocation. An idle wake records
93
97
  the hold reason instead of dispatching; with no wake event it still drains every `--poll` ms
94
- (default `2000`). It runs until `SIGINT` / `SIGTERM`.
98
+ (default `2000`). It runs until `SIGINT` / `SIGTERM`, or until its parent `spur serve` disappears —
99
+ the parent is re-read each poll and a change aborts the loop, so a `SIGKILL`ed serve leaves no
100
+ orphan loop (1088 R2).
95
101
 
96
102
  ## `wait` - identity-pinned occupant wait (G4 wave 2)
97
103
 
@@ -146,8 +152,9 @@ supervisor**: each row carries a trailing status column
146
152
  (`running` / `stopped` / `errored` / `unknown`), `pid=<n>` where a process exists, and the member
147
153
  session (0897): the session mode plus a shortened resume id (`resume id=3f9c2a1d`), or `-` when the
148
154
  member has no recorded session. When `spur self serve` is unreachable, the listing falls back to all
149
- `stopped` with a stderr warning. `--server <url>`
150
- (default `http://localhost:3000/api`) targets the supervisor API.
155
+ `stopped` with a stderr warning. `--server <url>` targets the supervisor API; without it the URL is
156
+ this project's serve — the port in the cwd's project-registry entry (`http://localhost:<port>/api`),
157
+ falling back to `http://localhost:3000/api` only when the project has no live entry (1088 R1).
151
158
 
152
159
  ```bash
153
160
  spur agent list --specs
@@ -164,9 +171,77 @@ spur agent status --json # full objects, session carried whole ({ mode, id }
164
171
 
165
172
  Reads the same supervisor feed as `list --specs` (liveness **and** session come from
166
173
  `GET /api/processes`; the served project's ledger is the source of the session state). An
167
- unreachable server reports every spec `stopped` with no session. See
174
+ unreachable server reports every spec `stopped` with no session. `--server <url>`
175
+ (default: this project's registry serve port, `http://localhost:<port>/api`; else
176
+ `http://localhost:3000/api` — 1088 R1) targets it. See
168
177
  [Member sessions](#member-sessions-g66) for what the modes mean.
169
178
 
179
+ ## `report` - fleet lifecycle state from a host hook (G73, task 1080)
180
+
181
+ ```bash
182
+ spur agent report --state working --seq 1759665600000000000
183
+ spur agent report --state blocked --seq 1759665600000000000 --spec proj-worker-1 --json
184
+ ```
185
+
186
+ | Flag | Description |
187
+ | --- | --- |
188
+ | `--state <state>` | Lifecycle state: `working` \| `idle` \| `blocked` (required) |
189
+ | `--seq <ns>` | Monotonic report sequence in nanoseconds (required); not greater than the last accepted one → ignored |
190
+ | `--spec <id>` | Member spec id; defaults to `SPUR_SPEC_ID` |
191
+ | `--json` | Machine-readable result (`accepted`, `observation`) |
192
+ | `--json-envelope` | Wrap the JSON in the standard output envelope |
193
+
194
+ An accepted report writes the cataloged `agent.lifecycle.changed` ledger row — the newest
195
+ accepted row for the member is its current state, so there is no separate state store. The
196
+ dispatch strategy treats `blocked` as unavailable (hold reason `member-blocked`), and the Board
197
+ shows `needs human` on that member.
198
+
199
+ Exit `2` when `--state`/`--seq` is missing or malformed, or when no member id resolves
200
+ (`SPUR_SPEC_ID` unset and no `--spec`). The `sp` plugin's `agent-lifecycle` host hook calls this
201
+ in the background on `SessionStart` (idle), `UserPromptSubmit` (working), `Notification`
202
+ (`permission_prompt` → blocked) and `Stop` (idle); outside a fleet it makes no call at all.
203
+
204
+ ## `join` / `leave` - guest occupancy (G73 R2/R5, task 1081)
205
+
206
+ ```bash
207
+ spur agent join --role reviewer --json # id defaults to <role>-g<n>
208
+ spur agent join --role coder --id my-session --json
209
+ spur agent leave --json # the guest joined by this session
210
+ ```
211
+
212
+ A guest is a live session that **pulls** work: never supervised, never restarted, addressable by
213
+ concrete id only (role/executor selectors count declared members, so a guest is invisible to them).
214
+ `join` writes an occupant row in `coordination_runs`, claims a `guest:<id>` lease on the shared
215
+ claim table (same TTL/heartbeat semantics as the write slot — R5) and writes
216
+ `.spur/run/guests/<id>.json` so a host Stop hook can recognize its own session without a CLI call.
217
+ `leave`, or lease expiry in the reconciler pass, releases the lease, marks the occupant exited,
218
+ returns its claimed messages to `pending` and removes the record.
219
+
220
+ | Flag | Description |
221
+ | --- | --- |
222
+ | `--role <name>` | Layer-1 role (`scribe` \| `coder` \| `reviewer` \| `planner`); required for `join` |
223
+ | `--id <id>` | Guest id (defaults to `<role>-g<n>`); colliding with a declared member or a joined guest exits 2 |
224
+ | `--session-id <sid>` | Host session id (defaults to `CLAUDE_CODE_SESSION_ID`) |
225
+ | `--pid <n>` | Process id to record |
226
+ | `--executor <name>` | Executor name to record (informational — a guest is never dispatched to) |
227
+ | `--json` | Machine-readable result |
228
+
229
+ A guest carrying no executor attestation is never a stage target: `spur agent run --spec <guest-id>`
230
+ is refused (R3), and `requiresCapabilities` stages therefore can never reach one.
231
+
232
+ ## `wait --inbox <id>` - the guest pull primitive (G73 R4)
233
+
234
+ ```bash
235
+ spur agent wait --inbox reviewer-g1 --timeout 540000 --json
236
+ ```
237
+
238
+ Returns `0` as soon as queued inbox work exists for the guest and `1` on timeout or when the id is
239
+ not a joined guest — never a silent hang. Every call heartbeats the guest's lease, so a session that
240
+ keeps looping stays joined; the timeout stays under the Bash tool's ~10-minute limit. The
241
+ `fleet-join` skill loops it (wait → `spur message inbox --agent <id>` → work → `spur message reply`),
242
+ and the `fleet-guest-stop` plugin hook delivers the same messages at a turn boundary on hosts that
243
+ support a blocking Stop hook.
244
+
170
245
  ## `doctor` - readiness check
171
246
 
172
247
  ```bash
@@ -193,6 +268,31 @@ stderr-clean and carries `capabilities`, `capabilityStale: {verifiedAgainst, det
193
268
  normalized `availability` object, and a top-level `usage` per agent row instead. Exit `1` if any
194
269
  checked agent is not ready.
195
270
 
271
+ ## `trace` - one execution record's lineage (1076 R4, ADR-132)
272
+
273
+ ```bash
274
+ spur agent trace <runId> # the run, its parents, its children
275
+ spur agent trace <rootId> --follow # poll until every run in the lineage is terminal
276
+ spur agent trace <runId> --json
277
+ ```
278
+
279
+ `trace` reports ADR-132's execution record as one lineage. A node is a run: a workflow run comes from
280
+ the `runs` table, an agent run from `coordination_runs`, and the dispatch edge is
281
+ `coordination_runs.parent_run_id` — set from a workflow dispatch key (`<runId>/<state>`) or the
282
+ inherited `SPUR_RUN_ID`. `trace <runId>` first walks that edge UP to the root and then DOWN to the
283
+ leaves, so any id in the chain yields the whole picture. Each node carries its status, the agent
284
+ session ids recorded for it (`history_run_session`, exact rows only) and its durable stream under
285
+ `.spur/memory/runs/<runId>.md` when one was written. The stream is the record; the supervisor's ring
286
+ buffer is only a live view.
287
+
288
+ ### Flags
289
+
290
+ | Flag | Purpose |
291
+ | ------ | --------- |
292
+ | `--follow` | Poll until every run in the lineage reaches a terminal status. |
293
+ | `--timeout <ms>` | `--follow` budget (default 600000). On expiry one checkpoint line is printed and the command exits 1; the runs continue. |
294
+ | `--json` | `{ rootRunId, nodes: [{ runId, kind, status, parentRunId, sessionIds, logPath }] }` — root first, depth-first. |
295
+
196
296
  ## `start` - start a supervised process
197
297
 
198
298
  ```bash
@@ -202,7 +302,9 @@ spur agent start worker-1 --json
202
302
 
203
303
  Posts to the `spur self serve` supervisor API
204
304
  (`POST /api/agents/:id/start`) and prints `started <id> (pid=<n>, status=<s>)`. Requires a
205
- reachable `spur self serve`; `--server <url>` (default `http://localhost:3000/api`) targets it. Exit `1`
305
+ reachable `spur self serve`; `--server <url>` targets it, defaulting to this project's registry
306
+ serve port (`http://localhost:<port>/api`, else `http://localhost:3000/api` when the project has no
307
+ live entry — 1088 R1). Exit `1`
206
308
  when the server is unreachable or the start fails.
207
309
 
208
310
  ## `stop` - stop a supervised process
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: spur-cli-decision
3
+ description: "spur-cli noun reference: operate `spur decision` as the read/serve surface over the decision catalog - list registered decisions, show one served contract (parameters, criteria vocabulary, effective maker), run a decision to a served outcome (never writes a workflow resultFile), and status the catalog layers. Backed by @gobing-ai/ts-ai-decision 0.5.16 hub; offline by default via the built-in `typesafe` maker. Introduced by task 1093."
4
+ see_also:
5
+ - spur-cli
6
+ ---
7
+
8
+ # spur decision - decision catalog surface
9
+
10
+ `spur decision` exposes the **decision catalog** (ADR-117 trace discipline, task 1092) to agents
11
+ and scripts: discover which decisions exist, inspect one's contract, serve one outcome, and audit
12
+ catalog health. It is a thin transport over `DecisionService` (`packages/app`); the decision hub
13
+ lives in `@gobing-ai/ts-ai-decision`.
14
+
15
+ **Frozen at exactly four verbs** (`list`, `show`, `run`, `status`) by task 1093's design
16
+ (`docs/design/decision-catalog.md` §3.4) — do not invent additional subcommands.
17
+
18
+ ## Catalog layers
19
+
20
+ Decisions resolve first-wins across three layers:
21
+
22
+ | Layer | Root | Notes |
23
+ | ----- | ---- | ----- |
24
+ | `project` | `<cwd>/.spur/decisions/*.yaml` | Highest precedence |
25
+ | `registered` | programmatic catalog registrations | Agents register in-process |
26
+ | `shared` | `decisions/` under the bundled config root | Shipped catalogs (`config/decisions/` in-repo) |
27
+
28
+ The bundled shared catalog ships `task-triage`, `failure-class`, and `review-failure-class` (all
29
+ `choice` type, param `wbs`). Duplicate ids across layers keep the higher layer; duplicates inside
30
+ one file are a load error.
31
+
32
+ **Config keys** (under `decisions:` in `config.global.yaml` or project config):
33
+ `paths` (extra catalog folders joined to the registered layer), `maker` (default DecisionMaker
34
+ for every decision point), `makers` (per-decision-id overrides). An unregistered maker name is a
35
+ config error, never a silent fallback (design §3.6).
36
+
37
+ ## Verb map
38
+
39
+ | Verb | Purpose | Key flags |
40
+ | ---- | ------- | --------- |
41
+ | `list` | Every visible decision: id, type, layer, catalog file, description | `--layer <layer>`, `--json` |
42
+ | `show <id>` | One served contract: type, parameters, criteria, fallback, minConfidence, effective maker + source | `--json` |
43
+ | `run <id>` | Serve one outcome (closed vocabulary; **never writes a resultFile**) | `--param <pairs...>`, `--evidence <files...>`, `--maker <name>`, `--json` |
44
+ | `status` | Layer counts, registered makers, per-decision maker resolution, load errors | `--json` |
45
+
46
+ All verbs support `--json` (bare payload) and `--json-envelope` (`{ok, data|error}` envelope,
47
+ ADR-091). Failure output for `--json` commands uses the canonical error envelope only under
48
+ `--json-envelope`; plain `--json` keeps the human message on stderr.
49
+
50
+ **Exit codes:** `status` exits 1 on catalog load errors; `show` exits 1 on unknown id; `list` always
51
+ exits 0 (design §3.4 — load errors surface via `status`).
52
+
53
+ **Exit contract for `run` (design §3.4):** any backend-observed outcome — low confidence, model
54
+ error surfaced as fallback, declined — is still a **successful serve** (exit 0, envelope carries
55
+ `source`/`confidence` so callers can see why). Exit 1 is reserved for caller mistakes caught
56
+ **before** the backend: unknown id, unknown parameter, missing required parameter, type-invalid
57
+ parameter, unregistered `--maker`, unreadable evidence file.
58
+
59
+ ## Parameters and evidence
60
+
61
+ `--param k=v` is repeatable and validated against the decision's declared parameters: values for
62
+ `number`/`boolean`/`json` params are parsed accordingly (`json` accepts inline JSON objects);
63
+ string/enum params pass through verbatim. A parameter with no `default` is **required** (hub
64
+ loader derives `required: !hasDefault`). Undeclared keys exit 1.
65
+
66
+ `--evidence <file>` is repeatable; each file is read, **redacted and bounded to
67
+ `DECIDE_EVIDENCE_MAX_CHARS` (2000 chars)** via the same `redactAndBound` used by workflow
68
+ agent execution. Evidence rides the hub's implicit `instructions` input parameter — it reaches the
69
+ maker through the rendered question, never as a raw input key (the hub rejects undeclared input
70
+ keys). An unreadable evidence file exits 1 before any backend call.
71
+
72
+ ## Effective maker resolution
73
+
74
+ Makers resolve `flag → config-decision → config-default → catalog-decision → catalog-default`;
75
+ `decision show` reports the effective maker as `{name, source}` with that source precedence
76
+ (operator config outranks the shipped catalog because only the machine owner knows which backends
77
+ exist there). The final `catalog-default` maker is the hub built-in `typesafe` — decisions served
78
+ without a configured backend still produce deterministic closed-vocabulary outcomes, which is why
79
+ `run` is usable in tests and scripts with no model access.
80
+
81
+ ## Examples
82
+
83
+ ```bash
84
+ spur decision list --json | jq -r '.[].id'
85
+ spur decision show task-triage # inspect contract before serving
86
+ spur decision run task-triage --param wbs=1093 --json
87
+ spur decision run failure-class --param wbs=1093 \
88
+ --evidence .spur/run/<runId>-test-output.txt # redacted evidence -> instructions
89
+ spur decision status --json | jq '.data.errors' # audit catalog health
90
+ ```
91
+
92
+ ## References
93
+
94
+ - Design: `docs/design/decision-catalog.md` (§2 catalog authoring, §3.4 CLI surface)
95
+ - Hub: `@gobing-ai/ts-ai-decision` 0.5.16 (`DecisionService`, catalog schema, maker registry)
96
+ - App service: `packages/app/src/decision/decision-service.ts`
97
+ - Shipped catalogs: `config/decisions/*.yaml`
@@ -448,3 +448,25 @@ spur task path <wbs> [--folder] [--json]
448
448
  spur task run-link <wbs> [--source <src>] [--run-id <id>] [--json]
449
449
  spur task scaffold-tests <wbs> [--file <path>] [--folder] [--json]
450
450
  ```
451
+
452
+ **No `delete` verb — and that is the surface's shape, not an oversight.** The corpus is
453
+ append-only history: a task that is no longer wanted is a *decision*, and the lifecycle already owns
454
+ it. Use
455
+
456
+ ```
457
+ spur task update <wbs> cancelled [--json] # the in-band terminal; the file stays as the record
458
+ ```
459
+
460
+ Removing the file entirely is reserved for a task that should never have existed (a duplicate WBS, a
461
+ mis-scaffolded capture) — a **corpus repair**, not a lifecycle move. Do it as a tracked deletion so
462
+ the removal itself is auditable:
463
+
464
+ ```
465
+ git rm docs/tasks<N>/<wbs>_<slug>.md
466
+ git commit -m "chore(corpus): drop <wbs> — <why it never existed>"
467
+ ```
468
+
469
+ Check for inbound references before deleting (`grep -rn "<wbs>" docs/ config/ plugins/`, plus any
470
+ `dependencies[]` edge via `spur task list --json`). The `deps remove` verb clears an edge. A deleted
471
+ WBS must not stay referenced: a dangling `dependencies[]` entry is a broken traceability edge, not a
472
+ harmless leftover.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: spur-composer
3
- description: "Select, compose and tune spur artifacts — tasks, features, rules, workflows and agent specs. Owns workflow catalog selection, the ephemeral→project→shared ladder, the ADR-115 budgets, trace-driven rule tuning, and applying accepted sp:spur-doctor proposals. Triggers: compose a workflow, tune a rule, apply doctor proposals."
3
+ description: "Select, compose and tune spur artifacts — tasks, features, rules, workflows, agent specs and project Board modules. Owns workflow catalog selection, the ephemeral→project→shared ladder, ADR-115 budgets, trace-driven rule tuning and applying accepted sp:spur-doctor proposals. Triggers: compose a workflow, tune a rule, add a Board module."
4
4
  license: Apache-2.0
5
5
  version: 1.0.0
6
6
  metadata:
@@ -50,6 +50,7 @@ its own output and never runs a recurring loop — evaluation is the doctor's jo
50
50
  | rule | The trace-driven tuning loop (§ Rule tuning loop) | [../spur-cli/references/rules.md](../spur-cli/references/rules.md) · [fine-tuning](../spur-cli/references/rules/fine-tuning.md) |
51
51
  | workflow | Catalog selection, the composition ladder, and the ADR-115 budgets (§ below) | [../spur-cli/references/workflows.md](../spur-cli/references/workflows.md) · [operations](../spur-cli/references/workflows/operations.md) |
52
52
  | agent spec | Read through `spur agent list --specs`; specs are materialized from the fleet declaration at serve start | [../spur-cli/references/agent.md](../spur-cli/references/agent.md) |
53
+ | board module | Declare under `bootstrap.modules`, gated by the serve preflight (§ Board module composition) | [../spur-cli/references/serve.md](../spur-cli/references/serve.md) |
53
54
 
54
55
  Do not drive the planning→execution lifecycle from here — that is `sp:spur-dev`.
55
56
 
@@ -106,6 +107,31 @@ budgets:
106
107
  A new model step in a shared workflow raises its `pipeline-budgets` `modelQueries`; that needs a
107
108
  recorded decision before the shared step.
108
109
 
110
+ ## Board module composition
111
+
112
+ A project adds its own Spur Board modules (ADR-128) by declaration, never by editing the installed
113
+ package. The contract — the `react`/`iframe` union, the `@gobing-ai/spur/board` type, the Vite
114
+ externals and the frame/trust limits — is owned by
115
+ [downstream Board modules §6](../../../../docs/design/downstream-board-modules.md#6-authoring-guide);
116
+ the user walkthrough is
117
+ [docs/help/how_to_customize_project_modules.md](../../../../docs/help/how_to_customize_project_modules.md).
118
+ Link them, never restate them.
119
+
120
+ | Step | Action |
121
+ | --- | --- |
122
+ | select | A built-in module or an existing declaration already covers the need → no new module. An app that already runs and permits framing → `type: iframe`. Otherwise → `type: react`. |
123
+ | compose | Add one declaration to the project `.spur/config.yaml` `bootstrap.modules` list. Stage it with `enabled: false` first: it is fully validated (id shape, duplicates, host/retired collisions) but loads no assets. |
124
+ | build | `react` only: the downstream project builds its own ESM entry plus CSS into the declared `directory`, externalizing exactly the `imports` keys of the installed `web/board-runtime.json`. Building the module is the project's code work, not composer's. |
125
+ | gate | Restart `spur self serve`; the preflight fails startup on any declaration, asset or runtime-version error. Confirm the module in `GET /api/board/modules` and open `/board/modules/<id>`. `spur self serve --json` is a dry port probe and does **not** run this preflight. |
126
+ | enable | Flip to `enabled: true`, restart and reload. There is no hot reload. |
127
+
128
+ - The module is project-owned: it never climbs a shared layer, and it needs no `build:bundle`.
129
+ - Declare only trusted code — importing a `react` entry evaluates it. Spur never starts or proxies an
130
+ `iframe` app, and a frame the child refuses stays blank by design.
131
+ - Changing a built-in module is internal Board work in the spur repository
132
+ ([docs/help/how_to_add_a_new_ui_module.md](../../../../docs/help/how_to_add_a_new_ui_module.md)),
133
+ not a downstream declaration.
134
+
109
135
  ## Rule tuning loop
110
136
 
111
137
  Start from trace evidence, never from a guess:
@@ -40,8 +40,9 @@ each would be scope creep for one-liner procedures.
40
40
 
41
41
  > **`dev-review-session`** is not in this table. It is a thin `Skill()` wrapper over
42
42
  > **`sp:session-review`** for an immediate, inline review of the active conversation. Report-only
43
- > is the default; `--triage` applies pure-doc / one-to-two-line fixes and files remaining actionable
44
- > findings as implement-ready tasks through the task CLI. It launches no workflow or agent and
43
+ > is the default; `--fix auto` (alias `--triage`) applies pure-doc / one-to-two-line fixes and files
44
+ > remaining actionable findings as implement-ready tasks through the task CLI; `--fix all` fixes
45
+ > every actionable finding inline and files none. It launches no workflow or agent and
45
46
  > imports no history. Use it before the active session ends; use history-anatomy for ended sessions,
46
47
  > cross-agent windows, trends, or quantitative forensics.
47
48
 
@@ -72,19 +73,19 @@ each would be scope creep for one-liner procedures.
72
73
  | 2 | review | `dev-review` | `Skill()` | `sp:code-verification` (`review`) + `sp:functional-review` + `sp:code-improvement` | `[--tasks <selector> \| --feature <id>[,<id>] \| --scope <path>[,<path>]] [--agent <inline\|auto\|name>] [--focus <dims>] [--triage] [--worktree [<name>]] [--auto]` |
73
74
  | 3 | verify | `dev-verify` | `Skill()` | `sp:code-verification` (`verify`) | `<wbs> [--agent <inline\|auto\|name>] [--fix <none\|blockers-first\|all>] [--focus <lens>] [--bdd] [--auto] [--force] [--next] [--skip-shippable]` |
74
75
  | 3a | verifyall | `dev-verifyall` | `Skill()` → agent | `sp:spur-dev` (`verifyall`) | `--tasks <selector> [--feature <id>] [--agent <inline\|auto\|name>] [--fix <none\|blockers-first\|all>] [--focus <lens>] [--bdd] [--auto] [--force] [--next] [--json] [--skip-shippable] [--worktree [<name>]]` |
75
- | 4 | run | `dev-run` | `Skill()` | `sp:spur-dev` (`run` / `implement`) | `<wbs> [--mode <full\|implement>] [--agent <inline\|auto\|name>] [--auto] [--next] [--wrap] [--continue] [--worktree [<name>]]` |
76
+ | 4 | run | `dev-run` | `Skill()` | `sp:spur-dev` (`run` / `implement`) | `<wbs> [--mode <full\|implement>] [--agent <inline\|auto\|name>] [--auto] [--next] [--wrap] [--continue] [--worktree [<name>]] [--no-summary]` |
76
77
  | 5 | refine | `dev-refine` | `Skill()` | `sp:spur-dev` (`refine`) | `<wbs> [--focus <mode>] [--description <text>] [--depth <standard\|ready>] [--agent <inline\|auto\|name>] [--auto] [--next]` |
77
78
  | 5a | refineall | `dev-refineall` | `Skill()` | `sp:spur-dev` (`refineall`) | `--feature <id> \| --tasks <selector> [--focus <mode>] [--description <text>] [--depth <standard\|ready>] [--agent <inline\|auto\|name>] [--auto] [--keep-going] [--status <s>] [--json] [--worktree [<name>]]` |
78
79
  | 6 | plan | `dev-plan` | `Skill()` | inline driver (`idea-pipeline`) or async workflow | `"<description>" [--feature <id>] [--parent <feature-id>] [--skip-design] [--agent <inline\|auto\|name>] [--auto]` |
79
80
  | 7 | docs | _(no thin wrapper)_ | `Skill()` | `sp:doc-evolve` | `"<change description>"` |
80
- | 8 | changelog | `dev-changelog` | `inline` | git log + conventional-commit grouping | `[--since <ref>] [--until <ref>] [--version <ver>]` |
81
+ | 8 | changelog | `dev-changelog` | `inline` | git log + conventional-commit grouping | `[<file>] [--since <ref>] [--until <ref>] [--version <ver>]` |
81
82
  | 9 | gitmsg | `dev-gitmsg` | `inline` | bounded diff capture → concern grouping → conventional commit | `[--commit] [--squash] [--all] [--scope <path>]` |
82
83
  | 10 | fixall | `dev-fixall` | `inline` | lint + test fix loop | `[<validation-command>] [--max-retry <n>] [--scope <path>] [--gate-log <path>] [--findings <anchors>]` |
83
84
  | 11 | handover | `dev-handover` | `inline` | structured doc generation | `"<blocker description>"` |
84
85
  | 11a | job-dump | `dev-job-dump` | `Skill()` | `sp:spur-dev` (`job-dump`) | `--file <path>` |
85
86
  | 11b | job-resume | `dev-job-resume` | `Skill()` | `sp:spur-dev` (`job-resume`) | `--file <path>` |
86
87
  | 12 | brainstorm | `dev-brainstorm` | `Skill()` | `sp:brainstorm` (`dev-brainstorm`) | `<topic> [--depth <basic\|detailed\|comprehensive>] [--options <n>] [--agent <inline\|auto\|name>] [--skip-discovery] [--wayfind] [--task [<feature-id>]] [--feature [<parent-id>]] [--next]` |
87
- | 13 | runall | `dev-runall` | `Skill()` → agent | `sp:spur-dev` (`runall`) → `sp:super-planner` | `--tasks <selector> [--feature <id>] [--mode <sequential\|parallel>] [--keep-going] [--auto] [--agent <inline\|auto\|name>] [--json] [--wrap] [--next] [--continue] [--worktree [<name>]]` |
88
+ | 13 | runall | `dev-runall` | `Skill()` → agent | `sp:spur-dev` (`runall`) → `sp:super-planner` | `--tasks <selector> [--feature <id>] [--mode <sequential\|parallel>] [--keep-going] [--auto] [--agent <inline\|auto\|name>] [--json] [--wrap] [--next] [--continue] [--worktree [<name>]] [--no-summary]` |
88
89
  | 13a | parallel | `dev-parallel` | `Skill()` | `sp:parallel-execution` | `--tasks <selector> [--feature <id>] [--mode <fan-out\|review-panel\|investigation>] [--agent <inline\|auto\|name>] [--json]` |
89
90
  | 14 | wrap | `dev-wrap` | `Skill()` | `spur workflow run` (wrapup-pipeline) | `<wbs> [--agent <inline\|auto\|name>] [--auto] [--merge] [--dry-run]` |
90
91
  | 15 | wrapall | `dev-wrapall` | `Skill()` | `spur workflow run` (wrapup-pipeline) | `[--since <iso>] [--feature <id>] [--status <s>] [--agent <inline\|auto\|name>] [--auto] [--merge] [--dry-run]` |
@@ -155,13 +156,28 @@ must not be changed without updating the backing skill.
155
156
  ### 4. run
156
157
 
157
158
  - **Purpose:** Run a task through the execution pipeline (full) or execute a single pipeline step (implement).
158
- - **Inputs:** `<wbs>` (required). `--mode <full|implement>` selects the execution mode. `implement` invokes `sp:code-implementation` inline by default. Interactive `full` resolves the selector uniformly (task 0687): omit ≡ explicit `--agent inline`; full mode reads `task-pipeline.yaml` and drives its actions/guards in the host session — host-controlled and non-subprocess, with 0508 eligibility applying to the resolved-inline selector (eligible `agent.run` stages dispatch once to a native subagent, host fallback)). `--agent auto`, a name, or headless invocation launches the workflow subprocess. `--agent <inline|auto|name>` selects the execution surface (see [SSOT](cross-cutting.md#inline-default-execution-surface)). `--auto` skips the HITL approve gate / confirmations and propagates down the `--next` chain. `--next` controls chaining only and never changes the mode; a pipeline implement stage must invoke `/sp:dev-run <wbs> --mode implement`. On implement success with `--next`, transition `todo → wip → testing` through the FSM (guards honored — no `--no-lifecycle`) + chain to `/sp:dev-verify <wbs> --auto --next`. On a guard failure, stop as review-pending. **Partial-deliverable rule:** if the task ships only part of its requirements (e.g. an R1/R2 split with the rest in a follow-up task), the `## Solution` section must state that explicitly and the verify verdict will record the scope. `--worktree [<name>]` runs the full pipeline inside an isolated git worktree (create or reuse; FF-merge on success, retain on failure) — the batch lifecycle in [execution-batch.md § Worktree isolation](execution-batch.md#worktree-isolation---worktree-name) applied to a batch of one; rejected with `--mode implement`. `--wrap` hands off to `/sp:dev-wrap <wbs>` after the main step; the `--agent` selector is preserved into that handoff when supplied (omission remains omission), and the wrap hop reports its own trigger-3 subprocess override per the wrap contract.
159
+ - **Inputs:** `<wbs>` (required). `--mode <full|implement>` selects the execution mode. `implement` invokes `sp:code-implementation` inline by default. Interactive `full` resolves the selector uniformly (task 0687): omit ≡ explicit `--agent inline`; full mode reads `task-pipeline.yaml` and drives its actions/guards in the host session — host-controlled and non-subprocess, with 0508 eligibility applying to the resolved-inline selector (eligible `agent.run` stages dispatch once to a native subagent, host fallback)). `--agent auto`, a name, or headless invocation launches the workflow subprocess. `--agent <inline|auto|name>` selects the execution surface (see [SSOT](cross-cutting.md#inline-default-execution-surface)). `--auto` skips the HITL approve gate / confirmations and propagates down the `--next` chain. `--next` controls chaining only and never changes the mode; a pipeline implement stage must invoke `/sp:dev-run <wbs> --mode implement`. On implement success with `--next`, transition `todo → wip → testing` through the FSM (guards honored — no `--no-lifecycle`) + chain to `/sp:dev-verify <wbs> --auto --next`. On a guard failure, stop as review-pending. **Partial-deliverable rule:** if the task ships only part of its requirements (e.g. an R1/R2 split with the rest in a follow-up task), the `## Solution` section must state that explicitly and the verify verdict will record the scope. `--worktree [<name>]` runs the full pipeline inside an isolated git worktree (create or reuse; FF-merge on success, retain on failure) — the batch lifecycle in [execution-batch.md § Worktree isolation](execution-batch.md#worktree-isolation---worktree-name) applied to a batch of one; rejected with `--mode implement`. `--no-summary` skips the [execution summary](#execution-summary) printed after a full-mode run. `--wrap` hands off to `/sp:dev-wrap <wbs>` after the main step; the `--agent` selector is preserved into that handoff when supplied (omission remains omission), and the wrap hop reports its own trigger-3 subprocess override per the wrap contract.
159
160
  - **Backing:** `sp:spur-dev` skill — `run` operation for the full pipeline (the spine drives it); `sp:code-implementation` competency skill for the implement step (the spine dispatches to it).
160
161
  - **Modes:**
161
162
  - **`full`** (default): Drive the full pipeline — precheck → implement → test → review → approve(HITL) → verify → record → done. Interactive omit/inline uses [inline-pipeline-driver.md](inline-pipeline-driver.md) (host-controlled; eligible stages may use a native subagent); explicit/headless executor selection invokes `spur workflow run task-pipeline.yaml --vars '{"wbs":"<wbs>"}'` (with `profile: auto` when `--auto`). Both monitor/surface HITL and preserve the YAML gates. `--next` never changes this mode.
162
163
  - **`implement`** (explicit `--mode implement` only): Execute only the implement step. Read the task's `## Requirements` / `## Design` / `## Plan`, write the code that satisfies them, author the `## Solution` change-map section (file:line + what/why per changed file) via `spur task update <wbs> --section Solution --from-file`. This is the implement step the pipeline calls — it is NOT the pipeline driver. With `--next`: on success, transition `todo → wip → testing` through the FSM (guards honored — no `--no-lifecycle`) + chain to `/sp:dev-verify <wbs> --auto --next`; on a guard failure, stop as review-pending. **Partial-deliverable rule:** if the task ships only part of its requirements (e.g. an R1/R2 split with the rest in a follow-up task), the `## Solution` and `## Review` sections MUST carry a `⚠️ PARTIAL` marker naming the deferred part and the follow-up WBS — see `plugins/sp/commands/dev-run.md` → "Section ownership".
163
164
  - **Delegation:** `Skill(skill="sp:spur-dev", args="run-inline $ARGUMENTS")` for interactive full omit/inline (resolved-inline semantics, task 0687); `Skill(skill="sp:spur-dev", args="run $ARGUMENTS")` for explicit/headless full mode; `Skill(skill="sp:code-implementation", args="$ARGUMENTS")` for implement mode.
164
165
 
166
+ #### Execution summary
167
+
168
+ Default on for full-mode `dev-run` and `dev-runall`; `--no-summary` skips it. At command start record
169
+ `SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)`. When the run settles (done, failed or paused), write each run's
170
+ `spur workflow progress <run-id> --json` to `$TMPDIR/<run-id>-progress.json` and print, verbatim under an
171
+ `### Execution summary` heading:
172
+ `node "$(superskill script path sp run-summary.mjs)" --since "$SINCE" --progress <file> --markdown`
173
+ (`dev-runall`: one `--progress <wbs>=<file>` per attempted run). One file renders one row per state visit;
174
+ several render one row per run; a `driver overhead` row carries host work outside every stage window so rows
175
+ sum to `Total` (omitted when parallel runs overlap). Columns: Stage | Status | Time | Wait | Tool calls |
176
+ Token (total / non-cached) — total counts every input class plus output; non-cached drops cache reads.
177
+ Stage windows with no host transcript record (subprocess executors) render `n/a`, never 0. The summary is
178
+ reporting only: a failure prints `Execution summary: n/a (<reason>)` and never changes the run's outcome.
179
+ Not printed for `--mode implement` (no workflow run).
180
+
165
181
  ### 5. refine
166
182
 
167
183
  - **Purpose:** Refine a task's requirements via structured Q&A — clarify scope, elicit missing details, tighten acceptance criteria before execution. Optional **implement-ready** depth freezes Design/Requirements/Plan so another agent can implement without inventing design, and is also the path for **evaluating and correcting an existing task** — a review-triage filing, a stale backlog capture, or any task whose claims and proposed fixes may no longer hold.
@@ -429,7 +445,7 @@ gate commands, pending questions and approval requirements.>
429
445
  ### 13. runall
430
446
 
431
447
  - **Purpose:** Run a batch of tasks through their pipelines in dependency-correct order — resolve a set, topo-sort, run each via `task-pipeline.yaml`, inspect verdicts, apply the failure policy, emit a batch report.
432
- - **Inputs:** `--tasks <selector>` (required — explicit WBS list, status pseudo-list, `feature:<id>`, or `ready`). `--mode <sequential|parallel>` (default `sequential`; `parallel` fans out a proven-independent subset per `execution-batch.md` § Parallel Execution). `--keep-going` skips a failed task's in-batch dependents and continues independents (default halts on first failure). `--auto` sets `profile=auto` on each per-task run (skips the HITL approve gate). `--feature <id>` is sugar for `--tasks feature:<id>`; when the effective selector is feature-derived, the batch runs `spur feature check <id> --strict --json` **once** before task resolution — a non-zero strict check aborts with verdict `aborted`, zero attempted tasks, and the structured findings (task 0510 R2); scoped: `L4.scenario-unverified` (expected pre-run state of any not-yet-run feature) is reported verbatim but does not abort — any other strict error aborts. Explicit WBS/status/`ready` selectors add no feature check. The orchestrator loop itself continues in this session; interactive sequential omit/`inline` uses the host driver — host-controlled with eligible `agent.run` stages dispatching once to a native subagent and host fallback (task 0508) — while `--agent auto`/a name, parallel mode, and headless invocation keep the isolated per-task workflow subprocess boundary. `--agent <inline|auto|name>` pins the executor for the per-task stages (see [SSOT](cross-cutting.md#inline-default-execution-surface)); the value crosses into per-task `vars.agent`, not the orchestrator. `--json` emits the report as JSON. `--wrap` triggers `wrapup-pipeline.yaml` **once for the batch** over the `done` subset only (execution-batch.md Step 6: `vars.feature` only when every frozen task is `done`/`cancelled`; empty done subset skips with a reason), `--next` chains each task to terminal status then runs the same batch-once wrap, `--continue` resumes from checkpoint.
448
+ - **Inputs:** `--tasks <selector>` (required — explicit WBS list, status pseudo-list, `feature:<id>`, or `ready`). `--mode <sequential|parallel>` (default `sequential`; `parallel` fans out a proven-independent subset per `execution-batch.md` § Parallel Execution). `--keep-going` skips a failed task's in-batch dependents and continues independents (default halts on first failure). `--auto` sets `profile=auto` on each per-task run (skips the HITL approve gate). `--feature <id>` is sugar for `--tasks feature:<id>`; when the effective selector is feature-derived, the batch runs `spur feature check <id> --strict --json` **once** before task resolution — a non-zero strict check aborts with verdict `aborted`, zero attempted tasks, and the structured findings (task 0510 R2); scoped: `L4.scenario-unverified` (expected pre-run state of any not-yet-run feature) is reported verbatim but does not abort — any other strict error aborts. Explicit WBS/status/`ready` selectors add no feature check. The orchestrator loop itself continues in this session; interactive sequential omit/`inline` uses the host driver — host-controlled with eligible `agent.run` stages dispatching once to a native subagent and host fallback (task 0508) — while `--agent auto`/a name, parallel mode, and headless invocation keep the isolated per-task workflow subprocess boundary. `--agent <inline|auto|name>` pins the executor for the per-task stages (see [SSOT](cross-cutting.md#inline-default-execution-surface)); the value crosses into per-task `vars.agent`, not the orchestrator. `--json` emits the report as JSON. `--wrap` triggers `wrapup-pipeline.yaml` **once for the batch** over the `done` subset only (execution-batch.md Step 6: `vars.feature` only when every frozen task is `done`/`cancelled`; empty done subset skips with a reason), `--next` chains each task to terminal status then runs the same batch-once wrap, `--continue` resumes from checkpoint. `--no-summary` skips the [execution summary](#execution-summary) printed after the batch report (one row per attempted run).
433
449
  - **Three orthogonal axes (do not confuse):** `--keep-going` = batch failure policy (halt vs skip dependents); `--continue` = resume from checkpoint against the batch's original frozen identity (execution-batch.md § Batch continuation, task 0919); `--next` = per-task lifecycle chaining (advance status on a verdict — `dev-verify`/`dev-verifyall` only). `routing-table.md` offers `--continue` and `--next` as competing options for the same situation only when the batch was interrupted mid-run; otherwise they address different problems.
434
450
  - **Backing:** `sp:spur-dev` skill, `runall` operation → delegates the driver loop to the **`sp:super-planner`** agent (the batch orchestrator).
435
451
  - **Behavior:** The orchestrator reads [execution-batch.md](execution-batch.md) and drives: resolve selector → freeze set → topo-sort by `dependencies[]` (Kahn, WBS-ascending tie-break; cycle aborts) → resolve out-of-set deps by status (done → allow, else → block subtree) → run each task via `spur workflow run task-pipeline.yaml --async` + `spur workflow trace` polling → inspect terminal state + `.spur/run/<wbs>-verdict.json` → stop-the-batch default or `--keep-going` subtree skip → emit batch report. Per-task pipeline is invoked **verbatim** — no new FSM, no step edits. `--auto`/`--agent` are the only flags that cross the orchestrator→pipeline boundary (both into per-task `--vars`).
@@ -519,7 +535,7 @@ is the procedure. The backing is a combination of git CLI, `spur` CLI, and agent
519
535
  ### 8. changelog
520
536
 
521
537
  - **Purpose:** Generate a structured changelog from git commits between two refs.
522
- - **Inputs:** `--since <ref>` (default: last tag), `--until <ref>` (default: `HEAD`), `--version <ver>` (default: auto-detect from latest tag).
538
+ - **Inputs:** `<file>` (optional positional, e.g. `CHANGELOG.md`; default: stdout), `--since <ref>` (default: last tag), `--until <ref>` (default: `HEAD`), `--version <ver>` (default: auto-detect from latest tag).
523
539
  - **Backing:** `inline` — git log + conventional-commit grouping.
524
540
  - **Behavior:**
525
541
  1. Resolve `--since`: if not given, use the most recent tag (`git describe --tags --abbrev=0`). If no tags exist, use the repo root commit.
@@ -529,14 +545,10 @@ is the procedure. The backing is a combination of git CLI, `spur` CLI, and agent
529
545
  5. Format as markdown:
530
546
  - `keepachangelog` (default): `## [<version>] - <date>` header, then category headings per the keepachangelog convention — `### Added` / `### Fixed` / `### Changed` / `### Removed` / `### Other` — mapped from conventional-commit types.
531
547
  - `simple`: flat bulleted list grouped by type heading (`### feat`, `### fix`, …).
532
- 6. Print the changelog to stdout. If the operator wants it in `CHANGELOG.md`, they redirect or paste.
533
- 7. Verify the emitted section against git and close with one line: `Verification: HIGH|MEDIUM|LOW — <first failing check and deviation, or 'all checks passed'>`.
534
- - **Coverage** — bullet count equals the commit count in `<since>..<until>`, and every short hash appears exactly once.
535
- - **Mapping** — per-category bullet counts match the conventional-type counts (`feat`→`Added`, `fix`→`Fixed`, other recognized types→`Changed`, unrecognized→`Other`).
536
- - **Header** — `[<version>] - <date>` matches the resolved `--version` (or detected tag) and the header date.
537
- - **Citations** — a bullet that asserts API/library behavior or a version/dependency claim beyond its commit summary carries a source citation: an official docs URL, or a local `path:line` verified against the installed manifest/lockfile (`package.json`, lockfile, or the cited file). A commit hash is provenance for what shipped, not a citation for what the API/library does; an uncited claim is flagged inline on the bullet (`(uncited: <claim>)`) rather than silently dropped.
538
- - **HIGH** — all checks pass with every API/library claim cited. **MEDIUM** — coverage intact but mapping or header deviates, or at least one API/library claim lacks a source citation. **LOW** — coverage broken (missing/duplicate hashes), the range cannot be reconciled against git, or a citation fails verification (dead URL, or a `path:line` that does not support the claim).
539
- - **Invariants:** Never mutates `CHANGELOG.md` directly — the command surface is stdout-only; writing it to a file (e.g. appending to `CHANGELOG.md`) is the operator's redirect choice, never the command's.
548
+ 6. Emit: with `<file>`, insert the section into that file above its newest `## [` release heading (or after its title when none exists) and touch nothing else in the file; without `<file>`, print it to stdout.
549
+ 7. Self-check before emitting: every short hash in `<since>..<until>` appears exactly once. Fix any miss or duplicate; do not report a verdict.
550
+ 8. Reply in at most three lines: the range, bullet counts per category, and the output target.
551
+ - **Invariants:** Output is derived from `git log` only. No quality gates — no lint/test/`spur-check`/rule runs, citation or manifest lookups, confidence ratings, or edits beyond the one inserted section. The file is left uncommitted.
540
552
 
541
553
  ### 9. gitmsg
542
554
 
@@ -424,7 +424,8 @@ one or more tasks sit on an operator question (escalate, 0933) or an approval ga
424
424
  (cycle / unknown selector) — emit a structured report. The report is the orchestrator's sole
425
425
  output; it does not mutate the corpus (the pipeline's `record` step already wrote per-task
426
426
  results). A `paused` task is non-terminal — the report marks it `paused` (never `done`) and names
427
- the resume command.
427
+ the resume command. Unless `--no-summary`, follow it with the measured
428
+ [execution summary](dev-operations.md#execution-summary), one `--progress <wbs>=<file>` per attempted run.
428
429
 
429
430
  ```
430
431
  ## Batch Report — <selector>
@@ -744,6 +745,34 @@ existing batch loop (Steps 1–5) with the worktree as process cwd. `spur workfl
744
745
  from the process (`apps/cli/src/commands/workflow.ts:124`), so no CLI change is needed — `cd` into
745
746
  the worktree directory before launching the loop.
746
747
 
748
+ #### Evidence staging before the first task (create and reuse mode)
749
+
750
+ `.spur/memory/evidence/` and `.spur/run/` are **untracked per-tree** planes, so a fresh worktree
751
+ starts with neither while the invoking tree holds every earlier task's verdict. Both
752
+ `preflightFeature` (`plugins/sp/scripts/wrapup-steps.ts` → `spur feature check --strict --as done`
753
+ with cwd = the worktree) and `runMetrics` resolve verdict artifacts relative to the tree they run
754
+ in, so without this step an already-`done` linked task reads as missing evidence
755
+ (`L4.evidence-not-recoverable`, `L4.scenario-unverified`) and its metrics row degrades to
756
+ `UNKNOWN`.
757
+
758
+ Stage the invoking tree's verdict artifacts into the worktree **before the first task runs** —
759
+ from the invoking tree (cwd), with `$WT` the resolved absolute worktree path. `cp -n` is
760
+ load-bearing: reuse mode may be re-entering a tree that already owns divergence-checked evidence,
761
+ and this step must never clobber it. Only `*-verdict.json` travels in each direction; receipts and
762
+ other scratch artifacts are not staged, so WT-4a's identity classification is unaffected.
763
+
764
+ ```bash
765
+ # Run from the INVOKING tree; $WT is the worktree (create- or reuse-mode) absolute path.
766
+ for d in .spur/memory/evidence .spur/run; do
767
+ mkdir -p "$WT/$d"
768
+ for f in "$d"/*-verdict.json; do [ -f "$f" ] && cp -n "$f" "$WT/$d/"; done
769
+ done
770
+ ```
771
+
772
+ The position relative to the dependency install is immaterial (this is a plain copy into an
773
+ untracked plane); the position relative to the **first task** is not — a preflight or metrics read
774
+ that runs before this step sees an empty evidence plane.
775
+
747
776
  #### Name resolution (`--worktree <name>`)
748
777
 
749
778
  <a id="name-resolution---worktree-name"></a>
@@ -988,8 +1017,10 @@ if [ -n "$FINAL" ]; then
988
1017
  fi # NO prune/remove/branch delete
989
1018
  # Pre-removal evidence assertion (task 1067 R1, E71 enforcement): refuse to remove the
990
1019
  # worktree while it still owns evidence absent from (or divergent from) the invoking tree —
991
- # the D63 landing skipped persist-out and silently lost verdicts + receipts. Task-file
992
- # WBSes / run ids ride the same TASK_FILE_ARGS resolved for WT-4a (run ids: --run-id <id>).
1020
+ # the D63 landing skipped persist-out and silently lost verdicts + receipts. WBSes ride the
1021
+ # same TASK_FILE_ARGS resolved for WT-4a; run ids are a separate `--run-id <id>` per run and
1022
+ # belong to THIS check only — `inline-run-setup.ts --persist-out` accepts `--from` and
1023
+ # `--task-file` alone and exits 2 on `--run-id` (1089 driver note).
993
1024
  CHECK_SCRIPT=plugins/sp/scripts/persist-out-check.ts; [ -f config/plugin-scripts.json -a -f "$CHECK_SCRIPT" ] || CHECK_SCRIPT="$(superskill script path sp persist-out-check.mjs 2>/dev/null)"
994
1025
  bun "$CHECK_SCRIPT" --from "$WT_PATH" "${TASK_FILE_ARGS[@]}" \
995
1026
  || { echo "WT-4 halt: worktree evidence would be abandoned - persist-out missing or stale (WT-5)" >&2; write_marker retained; exit 1; }
@@ -1179,16 +1210,42 @@ ordered divergence recipe, run by the operator — the driver never merges, reba
1179
1210
  conflicts itself. Other halt causes (task failure, HITL pause) keep the hint as printed:
1180
1211
 
1181
1212
  ```
1182
- # 1. integrate as a merge commit — never a rebase; task evidence cites the branch's commit SHAs
1183
- git checkout <base-ref> && git merge --no-ff --no-commit <branch>
1213
+ # 1. decide whether conflicts need resolving FIRST, because that decides the merge form:
1214
+ # git merge-tree $(git merge-base <base-ref> <branch>) <base-ref> <branch> | grep -q '^<<<<<<<' \
1215
+ # && echo "conflicts — use the two-phase form below" || echo "clean — use the atomic form"
1216
+ #
1217
+ # 1a. CLEAN (the common case): integrate as ONE atomic merge commit. Never a rebase — task
1218
+ # evidence cites the branch's commit SHAs. This is the file's sanctioned merge commit.
1219
+ # Do NOT split this into a two-phase merge + commit when nothing needs resolving: that spans
1220
+ # the whole gate run, and anything in that window that touches the repository's git state
1221
+ # (a test, a hook, a tool invocation) discards MERGE_HEAD silently. The failure is invisible
1222
+ # until the history is read — it produced a single-parent commit in run ada5a36c (task 1090),
1223
+ # which then needed an extra empty ancestry merge to repair. A cheap probe could not
1224
+ # reproduce it (MERGE_HEAD survived `bun run lint`, the rule preset and a test-file subset),
1225
+ # so the cross-command dependency is removed rather than diagnosed.
1226
+ git checkout <base-ref> && git merge --no-ff -m "<prepared message>" <branch>
1227
+
1228
+ # 1b. CONFLICTING: the two-phase form is correct here, because the resolution work must land
1229
+ # between the merge and the commit. Everything in steps 2–4 belongs to this branch only.
1230
+ # A divergent branch cannot fast-forward, so the merge flag from 1a is not needed here.
1231
+ # git checkout <base-ref> && git merge --no-commit <branch>
1184
1232
  # 2. resolve source conflicts by hand; generated files are then regenerated with the project's
1185
1233
  # generator, never hand-merged
1186
1234
  # (this repo: bun run build:plugin-lib && bun run --filter @gobing-ai/spur build:bundle)
1187
1235
  # 3. stage every resolved path and regenerated bundle (git add …) — an unmerged or
1188
1236
  # unstaged path makes step 5 abort
1189
1237
  # 4. run qualityGateCmd once, after ALL conflicts are resolved
1190
- # 5. commit the merge with the prepared message file
1191
- git commit -F <message-file>
1238
+ # 5. commit the merge with the prepared message file — CHECK THE PARENTS FIRST (step 5a)
1239
+ # git commit -F <message-file>
1240
+ #
1241
+ # 5a. ALWAYS verify the merge really is a merge before treating it as landed:
1242
+ # git log -1 --format='%h parents=%p' # must print TWO parents
1243
+ # One parent means MERGE_HEAD was lost somewhere between steps 1 and 5 — the content is on
1244
+ # the base ref but the branch is not an ancestor, so `git branch -d` would refuse and the
1245
+ # retained worktree's provenance would not be readable from the base ref. Recover by
1246
+ # re-running step 1a against the already-landed tree (a content-free merge commit restores
1247
+ # ancestry).
1248
+
1192
1249
  # 6. persist evidence out (WT-4a), then WT-4b/4c cleanup, and set the marker to merged
1193
1250
  inline-run-setup --persist-out --from <worktree> --task-file …
1194
1251
  ```
@@ -163,6 +163,20 @@ The pipeline (`kind: state-machine`) runs the work loop:
163
163
  precheck → implement → test [→ test-fix → test-recheck] → review → approve(HITL) → verify → record → done
164
164
  ```
165
165
 
166
+ Two branches reach the bounded repair hop, not one (session finding after 1088):
167
+
168
+ - `test`/`test-recheck` FAIL → `test-fail-triage` (0943's failure-class router) → `test-fix`;
169
+ - `review` without a `Verdict: PASS` in its answer → `review-fail-triage` (the review-side twin) →
170
+ `test-fix`, bounded by the SAME `qualityGateMaxFixAttempts` counter. A FAIL review therefore has
171
+ an on-graph, bounded path to remediation instead of dead-ending the run, and the fix re-enters
172
+ quality → review → verify on a freshly captured proof digest.
173
+
174
+ The completion gate also reads the verifier's confidence (session finding after 1088): the verify
175
+ answer's `Confidence: HIGH|MEDIUM|LOW` line reaches the verdict artifact, and both `verify → record`
176
+ and `record → done` refuse a `LOW` (or absent) level unless the run was launched with
177
+ `--vars '{"ackLowConfidence":"true"}'`. A PASS the verifier would not stand behind is an operator
178
+ risk decision, so it takes an operator acknowledgement — not a silent certification.
179
+
166
180
  Agentic steps use the pure slash inputs declared by each YAML `agent.run` action (ADR-043). The
167
181
  workflow engine dispatches them; the interactive driver invokes their backing skills in-session.
168
182
  The `test` hop is primarily