@gobing-ai/spur 0.3.98 → 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 (120) 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 +15 -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/templates/task/brainstorm.md +1 -1
  10. package/config/templates/task/feature-impl.md +1 -1
  11. package/config/templates/task/issue.md +1 -1
  12. package/config/templates/task/meta.md +1 -1
  13. package/config/templates/task/review.md +1 -1
  14. package/config/templates/task/standard.md +1 -1
  15. package/config/workflows/task-pipeline.yaml +178 -18
  16. package/package.json +9 -9
  17. package/plugins/sp/README.md +14 -1
  18. package/plugins/sp/agents/super-reviewer.md +25 -11
  19. package/plugins/sp/commands/dev-changelog.md +5 -4
  20. package/plugins/sp/commands/dev-review-session.md +12 -7
  21. package/plugins/sp/commands/dev-run.md +5 -2
  22. package/plugins/sp/commands/dev-runall.md +5 -2
  23. package/plugins/sp/hooks/agent-lifecycle.ts +155 -0
  24. package/plugins/sp/hooks/fleet-guest-stop.ts +180 -0
  25. package/plugins/sp/hooks/hooks.json +45 -1
  26. package/plugins/sp/hooks/pi/guard-extension.ts +21 -0
  27. package/plugins/sp/lib/history-anatomy.generated.mjs +5 -0
  28. package/plugins/sp/lib/idea-handoff.generated.mjs +68 -10
  29. package/plugins/sp/lib/inline-run.generated.d.mts +1 -0
  30. package/plugins/sp/lib/inline-run.generated.mjs +83 -20
  31. package/plugins/sp/lib/residual-scan.generated.d.mts +5 -0
  32. package/plugins/sp/lib/residual-scan.generated.mjs +45 -5
  33. package/plugins/sp/lib/transcript.ts +160 -0
  34. package/plugins/sp/plugin.json +1 -1
  35. package/plugins/sp/scripts/history-anatomy-cache.mjs +5 -0
  36. package/plugins/sp/scripts/inline-run-setup.mjs +21 -20
  37. package/plugins/sp/scripts/inline-run-setup.ts +25 -31
  38. package/plugins/sp/scripts/persist-out-check.mjs +164 -0
  39. package/plugins/sp/scripts/persist-out-check.ts +185 -0
  40. package/plugins/sp/scripts/residual-scan.mjs +52 -15
  41. package/plugins/sp/scripts/residual-scan.ts +11 -11
  42. package/plugins/sp/scripts/run-summary.mjs +286 -0
  43. package/plugins/sp/scripts/run-summary.ts +241 -0
  44. package/plugins/sp/scripts/session-timeline.mjs +234 -0
  45. package/plugins/sp/scripts/session-timeline.ts +152 -0
  46. package/plugins/sp/scripts/task-diffstat.mjs +17 -5
  47. package/plugins/sp/scripts/task-diffstat.ts +25 -4
  48. package/plugins/sp/skills/code-verification/SKILL.md +8 -0
  49. package/plugins/sp/skills/code-verification/references/verdict-schema.md +17 -1
  50. package/plugins/sp/skills/fleet-join/SKILL.md +63 -0
  51. package/plugins/sp/skills/session-review/SKILL.md +46 -24
  52. package/plugins/sp/skills/spur-cli/SKILL.md +1 -0
  53. package/plugins/sp/skills/spur-cli/references/agent.md +108 -6
  54. package/plugins/sp/skills/spur-cli/references/decision.md +97 -0
  55. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +22 -0
  56. package/plugins/sp/skills/spur-composer/SKILL.md +27 -1
  57. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +40 -7
  58. package/plugins/sp/skills/spur-dev/references/dev-operations.md +28 -16
  59. package/plugins/sp/skills/spur-dev/references/execution-batch.md +278 -42
  60. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +17 -1
  61. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +16 -4
  62. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +139 -10
  63. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +6 -0
  64. package/schemas/decision-catalog.schema.json +226 -0
  65. package/schemas/spur-config.schema.json +25 -0
  66. package/spur.js +8856 -2564
  67. package/web/_astro/BoardApp.Bzr4OXrW.js +324 -0
  68. package/web/_astro/{BoardApp.Dflq5a8x.js → BoardApp.VeoxxLBy.js} +1 -1
  69. package/web/_astro/{TaskDetail.CK7oXddP.js → TaskDetail.LAyR_Pvn.js} +1 -1
  70. package/web/_astro/{arc.BIqkDATc.js → arc.f4e0OYLg.js} +1 -1
  71. package/web/_astro/{architectureDiagram-3BPJPVTR.D0B3SvWv.js → architectureDiagram-3BPJPVTR.SSIvxi-Z.js} +1 -1
  72. package/web/_astro/{blockDiagram-GPEHLZMM.C2YyKQmL.js → blockDiagram-GPEHLZMM.DSXIHOFf.js} +1 -1
  73. package/web/_astro/{c4Diagram-AAUBKEIU.DmtTYrt_.js → c4Diagram-AAUBKEIU.DG9Ubu85.js} +1 -1
  74. package/web/_astro/channel.CUFdtWhm.js +1 -0
  75. package/web/_astro/{chunk-2J33WTMH.HsRXbh7l.js → chunk-2J33WTMH.C7QU88Qh.js} +1 -1
  76. package/web/_astro/{chunk-4BX2VUAB.obE_P-R3.js → chunk-4BX2VUAB.26Z94xBj.js} +1 -1
  77. package/web/_astro/{chunk-55IACEB6.CjWTF5mz.js → chunk-55IACEB6.DYRiBkqw.js} +1 -1
  78. package/web/_astro/{chunk-727SXJPM.DMo799Ax.js → chunk-727SXJPM.D5Vfv5pe.js} +1 -1
  79. package/web/_astro/{chunk-AQP2D5EJ.-cE6JOKZ.js → chunk-AQP2D5EJ.DXHV47bN.js} +1 -1
  80. package/web/_astro/{chunk-FMBD7UC4.D6Rw6HIk.js → chunk-FMBD7UC4.D1suAfOs.js} +1 -1
  81. package/web/_astro/{chunk-ND2GUHAM.d5rBD73Z.js → chunk-ND2GUHAM.D6BmdHEY.js} +1 -1
  82. package/web/_astro/{chunk-QZHKN3VN.r3ox40Q3.js → chunk-QZHKN3VN.rnPU-Ak-.js} +1 -1
  83. package/web/_astro/{classDiagram-4FO5ZUOK.CGGbvjjJ.js → classDiagram-4FO5ZUOK.CvEfXIo1.js} +1 -1
  84. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CGGbvjjJ.js → classDiagram-v2-Q7XG4LA2.CvEfXIo1.js} +1 -1
  85. package/web/_astro/{cose-bilkent-S5V4N54A.CmKtPd4l.js → cose-bilkent-S5V4N54A.DkMisLSm.js} +1 -1
  86. package/web/_astro/{cynefin-OW5HDTMX.QNg5GXs8.js → cynefin-OW5HDTMX.C77Q-pF6.js} +1 -1
  87. package/web/_astro/{dagre-BM42HDAG.B1SabsjC.js → dagre-BM42HDAG.YaV8ce27.js} +1 -1
  88. package/web/_astro/{diagram-2AECGRRQ.RtDbNj89.js → diagram-2AECGRRQ.CUyGEKge.js} +1 -1
  89. package/web/_astro/{diagram-5GNKFQAL.IzZpuM-Y.js → diagram-5GNKFQAL.CRgd8dgj.js} +1 -1
  90. package/web/_astro/{diagram-KO2AKTUF.DPTF_fgv.js → diagram-KO2AKTUF.Bmzys9hq.js} +1 -1
  91. package/web/_astro/{diagram-LMA3HP47.CCyza39P.js → diagram-LMA3HP47.BvmJY4Lw.js} +1 -1
  92. package/web/_astro/{diagram-OG6HWLK6.BXAi8zcZ.js → diagram-OG6HWLK6.0hEhD30d.js} +1 -1
  93. package/web/_astro/{erDiagram-TEJ5UH35.DUDX7_MR.js → erDiagram-TEJ5UH35.Bjt8lrnm.js} +1 -1
  94. package/web/_astro/{flowDiagram-I6XJVG4X.C63QTh3N.js → flowDiagram-I6XJVG4X.CJZTjRDV.js} +1 -1
  95. package/web/_astro/{ganttDiagram-6RSMTGT7.CnlZJSwG.js → ganttDiagram-6RSMTGT7._VqoVafc.js} +1 -1
  96. package/web/_astro/{gitGraphDiagram-PVQCEYII.CTqIrOPC.js → gitGraphDiagram-PVQCEYII.CEDb7tVo.js} +1 -1
  97. package/web/_astro/index.Dg_3bOaJ.css +1 -0
  98. package/web/_astro/{infoDiagram-5YYISTIA.BYMiDFh9.js → infoDiagram-5YYISTIA.CgwifPpK.js} +1 -1
  99. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CRiQmI-z.js → ishikawaDiagram-YF4QCWOH.B99wx2J1.js} +1 -1
  100. package/web/_astro/{journeyDiagram-JHISSGLW.YQgWBsEU.js → journeyDiagram-JHISSGLW.2hRwJbEo.js} +1 -1
  101. package/web/_astro/{kanban-definition-UN3LZRKU.1ECnrrK1.js → kanban-definition-UN3LZRKU.BahJkKNC.js} +1 -1
  102. package/web/_astro/{linear.DzVA71yT.js → linear.DguUUe6V.js} +1 -1
  103. package/web/_astro/{mermaid.core.BK7P2Sr_.js → mermaid.core.CvgfmqVr.js} +4 -4
  104. package/web/_astro/{mindmap-definition-RKZ34NQL.BvXyWZK7.js → mindmap-definition-RKZ34NQL.B3qEA3x9.js} +1 -1
  105. package/web/_astro/{pieDiagram-4H26LBE5.BiXk1TW3.js → pieDiagram-4H26LBE5.FlWA-_ru.js} +1 -1
  106. package/web/_astro/{quadrantDiagram-W4KKPZXB.krvBH8lh.js → quadrantDiagram-W4KKPZXB.9bVn35X7.js} +1 -1
  107. package/web/_astro/{requirementDiagram-4Y6WPE33.ih1gL25c.js → requirementDiagram-4Y6WPE33.ChykIhct.js} +1 -1
  108. package/web/_astro/{sankeyDiagram-5OEKKPKP.CpUxkv4u.js → sankeyDiagram-5OEKKPKP.Z2Y3hkYb.js} +1 -1
  109. package/web/_astro/{sequenceDiagram-3UESZ5HK.clAfeN7y.js → sequenceDiagram-3UESZ5HK.J7JsHWDt.js} +1 -1
  110. package/web/_astro/{stateDiagram-AJRCARHV.DpXWb8-m.js → stateDiagram-AJRCARHV.DAPbiCSS.js} +1 -1
  111. package/web/_astro/{stateDiagram-v2-BHNVJYJU.ClZb9GIH.js → stateDiagram-v2-BHNVJYJU.D2jvSi0q.js} +1 -1
  112. package/web/_astro/{timeline-definition-PNZ67QCA.BLcX5dh7.js → timeline-definition-PNZ67QCA.psW2ci29.js} +1 -1
  113. package/web/_astro/{vennDiagram-CIIHVFJN.oGgpKMpx.js → vennDiagram-CIIHVFJN.DlILYVmg.js} +1 -1
  114. package/web/_astro/{wardleyDiagram-YWT4CUSO.BPHtMtt-.js → wardleyDiagram-YWT4CUSO.xNWwLJnn.js} +1 -1
  115. package/web/_astro/{xychartDiagram-2RQKCTM6.BNZOPYSk.js → xychartDiagram-2RQKCTM6.EU_BuH7V.js} +1 -1
  116. package/web/board-runtime.json +1 -1
  117. package/web/index.html +2 -2
  118. package/web/_astro/BoardApp.CYbe-OTb.js +0 -322
  119. package/web/_astro/channel.JuKVyuhZ.js +0 -1
  120. package/web/_astro/index.CbNz17Sx.css +0 -1
@@ -19,13 +19,17 @@
19
19
  * other probe problem writes `sensitive: true` (and exits 0) so the lane degrades to
20
20
  * safety, never to fast.
21
21
  *
22
+ * The artifact is written atomically (tmp + rename), so redirecting stdout onto the artifact path
23
+ * cannot corrupt it — the rename swaps in the real JSON after the shell's redirect truncates the
24
+ * old inode (1067 R3); that redirect is still refused with a nonzero exit. Normal output is silent.
25
+ *
22
26
  * Node-builtin imports only; the git lookup is a spawnable `GitRunner` so tests can fake
23
27
  * it; `main(argv, env, options)` is the same injection point the workflow wrapper and its
24
28
  * node twin exercise. Only an empty `wbs` (mis-invocation) exits nonzero.
25
29
  */
26
30
 
27
31
  import { spawnSync } from 'node:child_process';
28
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
32
+ import { fstatSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
29
33
  import { join } from 'node:path';
30
34
  import { getEnvVars } from '../lib/env';
31
35
 
@@ -166,10 +170,18 @@ export function runDiffstat(
166
170
  const failSafe = (reason: string): DiffstatResult => {
167
171
  process.stderr.write(`task-diffstat: ${reason} — failing safe (sensitive)\n`);
168
172
  const row: Diffstat = { files: 0, insertions: 0, deletions: 0, paths: [], sensitive: true };
169
- writeFileSync(abs(relResult), `${JSON.stringify(row)}\n`);
173
+ writeArtifact(abs(relResult), row);
170
174
  return { ...row, resultFile: relResult, exitCode: 0 };
171
175
  };
172
176
 
177
+ let stdoutIsArtifact = false; // 1067 R3: stdout redirected onto the artifact itself clobbered it once
178
+ try {
179
+ const [out, art] = [fstatSync(1), statSync(abs(relResult))];
180
+ stdoutIsArtifact = out.dev === art.dev && out.ino === art.ino;
181
+ } catch {
182
+ /* artifact absent or stdout not a regular file — nothing to compare */
183
+ }
184
+
173
185
  let base = '';
174
186
  try {
175
187
  base = readFileSync(abs(join(relRun, `${wbs}-base.sha`)), 'utf8').trim();
@@ -203,8 +215,17 @@ export function runDiffstat(
203
215
  const all = [...paths].filter((p) => p.length > 0).sort();
204
216
  const sensitive = all.some((p) => sensitiveReasonForPath(p) !== null);
205
217
  const row: Diffstat = { files: all.length, insertions, deletions, paths: all, sensitive };
206
- writeFileSync(abs(relResult), `${JSON.stringify(row)}\n`);
207
- return { ...row, resultFile: relResult, exitCode: 0 };
218
+ writeArtifact(abs(relResult), row);
219
+ if (stdoutIsArtifact)
220
+ process.stderr.write('task-diffstat: stdout redirected onto the artifact — kept; redirect stdout elsewhere\n');
221
+ return { ...row, resultFile: relResult, exitCode: stdoutIsArtifact ? 1 : 0 };
222
+ }
223
+
224
+ /** Atomic artifact write (tmp + rename): a stdout redirect onto the path cannot interleave (1067 R3). */
225
+ function writeArtifact(absPath: string, row: Diffstat): void {
226
+ const tmp = `${absPath}.tmp`;
227
+ writeFileSync(tmp, `${JSON.stringify(row)}\n`);
228
+ renameSync(tmp, absPath);
208
229
  }
209
230
 
210
231
  export const TASK_DIFFSTAT_USAGE = 'usage: task-diffstat (env: wbs)';
@@ -315,6 +315,11 @@ node "$RESIDUAL" fold <wbs>
315
315
  > for each dropped MET-matched scenario key and when the new rows match no feature scenario —
316
316
  > treat those warnings as a re-key instruction, not noise.
317
317
 
318
+ > **Keying rule for feature-linked tasks (1086/1089 re-audits).** Key AC rows by the exact feature
319
+ > scenario title (`R1 — …`): feature `Scenario:` titles are declared identities and satisfy L4
320
+ > scenario matching directly. Bare `AC1` fails `ac-identity` on Gherkin AC sections, and checklist
321
+ > labels truncate at the first colon (a `(req: …)` suffix), so the task's own AC title may not resolve.
322
+
318
323
  > **Do not write `## Review` directly, ever.** The `## Review` section is owned by the
319
324
  > `review` coordinator (`/sp:dev-review` → `sp:super-reviewer`), which merges
320
325
  > `functional-review` + `code-verification` review mode + `code-improvement` fragments. The
@@ -336,6 +341,7 @@ Verdict: PASS (or PARTIAL / FAIL)
336
341
 
337
342
  ```markdown
338
343
  Verdict: PASS
344
+ Confidence: HIGH
339
345
 
340
346
  ### Per-Requirement Traceability
341
347
  | Req | Status | Evidence |
@@ -354,6 +360,8 @@ Verdict: PASS
354
360
  ```
355
361
 
356
362
  The per-requirement traceability table MUST use `| Req | Status | Evidence |` (exactly this header, no `R#`/`R`/`Requirement` variant — `Status` in column 2 — and no extra columns between Req and Status). The Acceptance Criteria table MUST use `| AC | Status | Evidence Type | Evidence |`.
363
+ The answer MUST carry exactly one `Confidence: HIGH|MEDIUM|LOW` line (task 1068): the verifier's stated confidence in the verdict — HIGH = fresh executable evidence + re-read anchors this run; MEDIUM = partial reliance on prior evidence, external/delegated outcomes, or an unstable tree; LOW = key evidence not re-executed. A missing or out-of-vocabulary line fails lint (`confidence-missing` / `confidence-value`) and blocks derivation; the value lands in the verdict artifact's `confidence` field and the recorded `## Testing` section.
364
+ The answer MUST cite a source for external API/library claims in Requirement/AC evidence cells: a cell asserting a scoped-package (`@scope/name`), `node_modules/`, semver-version (`x.y.z`), or backticked function-call (`` `name(args)` ``) claim is rejected with `evidence-citation` unless the SAME cell carries a citation — a repo `path:line(-end)` (backticked or bare), the external named-origin form (@origin `path` line N), or a URL. Cross-cell references ("receipt above") do not count; marker-free receipts ("bun test 398 pass / 0 fail") need no citation.
357
365
  **MUST NOT:** place a `Severity` column between `Req` and `Status` in the authoring contract.
358
366
  The parser is tolerant of these variants (defense-in-depth), but the authoring contract is
359
367
  canonical.
@@ -115,7 +115,9 @@ AC ids that do not resolve to one accepted identity — a task AC checklist labe
115
115
  (exact/bare title, `Scenario:` prefix, bracket tags, `AC-N`); paraphrases and ambiguous aliases
116
116
  fail — invalid status (`MET | PARTIAL | UNMET` for requirements;
117
117
  `N/A` additionally allowed for AC), invalid evidence type (`test | command | static-ref |
118
- manual-review | llm-judge | n/a`, or a `+` compound), and empty evidence. Interrupted runs keep the
118
+ manual-review | llm-judge | n/a`, or a `+` compound), and empty evidence. Evidence cells that assert an
119
+ external API/library claim without a same-cell citation are rejected with `evidence-citation` (see
120
+ "Source citations for external claims" below). Interrupted runs keep the
119
121
  rows that pass the lint and complete only the missing IDs on retry.
120
122
 
121
123
  **Feature-credited AC ids are narrower than the lint's accepted set (task 0966 finding).** A row keyed
@@ -136,6 +138,20 @@ the run actually verified. Since 0804 R9 the checker ignores complete parsed cit
136
138
  scanning for subjects, so a citation's filename (including snake_case paths) can never become a
137
139
  false subject — a real absent symbol, nonexistent file, or invalid range still reports.
138
140
 
141
+ ## Source citations for external claims (task 1070)
142
+
143
+ A Requirement/AC evidence cell that asserts an external API/library claim must carry a verifiable
144
+ citation in the SAME cell, or lint rejects the row with `evidence-citation` (blocking derivation).
145
+
146
+ - Claim markers (closed set): a scoped-package mention (`@scope/name`), a `node_modules/` reference,
147
+ a semver version claim (`x.y.z`), or a backticked function-call-shaped token (`` `name(args)` ``).
148
+ - Accepted citation forms (any one clears the cell): a repo `path:line(-end)` (backticked or bare),
149
+ the external named-origin form (@origin `path` line N — line number outside the backticks), or an
150
+ http(s) URL.
151
+ - The rule is cell-local: a citation in another row or a "receipt above" cross-reference does not
152
+ satisfy it. Marker-free receipts (test counts, reviewed-log notes) need no citation. The check is
153
+ presence-only — which citation proves which claim is not deterministically decidable.
154
+
139
155
  ## Checks evidence
140
156
 
141
157
  Wave C verification can emit the following additive `checks[]` rows:
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: fleet-join
3
+ description: >-
4
+ Join a project fleet as a guest occupant and pull its work. Use when the operator asks to
5
+ "join the fleet", "become a fleet guest", "pull fleet inbox work in this session", or when a
6
+ session should receive fleet messages without being supervised. Runs `spur agent join`, then
7
+ loops `spur agent wait --inbox`, and releases the lease with `spur agent leave`.
8
+ ---
9
+ # fleet-join — a live session as a fleet guest (G73, task 1081)
10
+
11
+ A guest is a session that **pulls** work: it is neither supervised nor restarted, it is addressed
12
+ by concrete id only, and it never receives a stage (`requiresCapabilities` stages can never route
13
+ to it — a guest carries no attestation). Joining registers an occupant row plus a heartbeat lease;
14
+ leaving (or an expired lease) releases it and returns any claimed messages to `pending`.
15
+
16
+ ## Join
17
+
18
+ ```bash
19
+ spur agent join --role reviewer --json # id defaults to <role>-g<n>
20
+ spur agent join --role coder --id my-session --json
21
+ ```
22
+
23
+ Pick the role whose work this session can actually do. `--session-id` defaults to
24
+ `CLAUDE_CODE_SESSION_ID`, which is what the Stop hook matches against — without it the hook cannot
25
+ find this session, though the `wait --inbox` loop still works.
26
+
27
+ ## Pull loop
28
+
29
+ `spur agent wait --inbox <id> --timeout 540000` returns **0** the moment queued work exists and
30
+ **1** on timeout. Each call heartbeats the lease, so a looping session stays joined. The Bash tool's
31
+ own ~10-minute limit is why the timeout stays under it: one bounded call per turn, re-armed after
32
+ the work is done.
33
+
34
+ 1. `spur agent wait --inbox <id> --timeout 540000 --json` → exit 0 means work is waiting.
35
+ 2. `spur message inbox --agent <id> --json` → read the messages.
36
+ 3. Do the work the message asks for.
37
+ 4. `spur message reply <msgId> "<result>"` → reply to each one.
38
+ 5. Re-arm: go back to step 1 while the operator wants this session in the fleet.
39
+
40
+ ## Leave
41
+
42
+ ```bash
43
+ spur agent leave --json # the guest joined by this session
44
+ spur agent leave my-session --json # an explicit id
45
+ ```
46
+
47
+ Leaving releases the lease immediately. If the session dies without leaving, the lease expires and
48
+ the reconciler pass retires the guest — pending work is never lost, it returns to `queued`.
49
+
50
+ ## Stop-hook delivery (opportunistic)
51
+
52
+ When the host fires a `Stop` hook and the host supports the block contract, the
53
+ `fleet-guest-stop` plugin hook delivers pending messages at turn end by replying
54
+ `{"decision":"block","reason":"Pending fleet messages: …"}` — only for a session whose id matches a
55
+ joined guest, never twice in one continued turn, and always fail-open. Claude Code supports this;
56
+ Codex `exec` on 0.160.0 does not deliver `Stop` at all (`docs/plans/2026-10-05-codex-guest-spike.md`),
57
+ so the pull loop above — not the hook — is what makes a guest work anywhere.
58
+
59
+ ## Boundaries
60
+
61
+ - Guests are **not** supervised: do not expect restart, pid ownership, or `--drain` injection.
62
+ - Never dispatch a stage to a guest id: `spur agent run --spec <guest-id>` is refused on purpose.
63
+ - Never hold the write slot through a guest: the guest lease is the only heartbeat (R5).
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: session-review
3
- description: "Review the active coding-agent session: separate resolved from open issues with evidence, propose bounded improvements. With --triage, apply pure-doc / 1–2-line fixes inline and file the rest as one or more tasks. Triggers: review this session, wrap-up, triage findings."
3
+ description: "Review the active coding-agent session: separate resolved from open issues with evidence, propose bounded improvements. With --fix auto (alias --triage), apply pure-doc / 1–2-line fixes inline and file the rest as tasks; with --fix all, fix every actionable finding inline and file none. Triggers: review this session, wrap-up, triage findings."
4
4
  license: Apache-2.0
5
- version: 1.1.0
5
+ version: 1.2.0
6
6
  metadata:
7
7
  author: spur
8
8
  platforms: "claude-code,codex,openclaw,opencode,antigravity,pi"
@@ -31,7 +31,8 @@ cross-agent windows, recurrence, trends, or quantitative performance forensics.
31
31
  | Argument | Description | Default |
32
32
  | --- | --- | --- |
33
33
  | `[focus]` | Question or operation to emphasize. It changes ordering, not evidence collection. | full active session |
34
- | `--triage` | Opt into bounded remediation after the report: apply direct fixes (pure docs / one-to-two-line fixes) inline, then file all remaining actionable findings as one or more implement-ready tasks via the CLI-gated corpus surface. | off (report-only) |
34
+ | `--fix <none\|auto\|all>` | Remediation after the report. `none`: report-only. `auto`: apply direct fixes (pure docs / one-to-two-line fixes) inline, then file all remaining actionable findings as one or more implement-ready tasks via the CLI-gated corpus surface. `all`: fix every actionable finding inline and file no task. | `none` |
35
+ | `--triage` | `--triage` is an alias of `--fix auto`; an explicit `--fix` wins when both are passed. | off |
35
36
 
36
37
  ## Evidence boundary
37
38
 
@@ -46,14 +47,16 @@ cross-agent windows, recurrence, trends, or quantitative performance forensics.
46
47
  name the confirmation needed.
47
48
  - State `not available` when compaction or missing output removed evidence. Never reconstruct it from
48
49
  memory or claim a verification that did not run.
49
- - Derive timing only from timestamps and tool-call records visible in the active session. Use
50
- non-overlapping stages whose durations sum to elapsed time; render unavailable duration or call
51
- counts as `n/a` instead of estimating them.
50
+ - Derive timing and tokens only from the host transcript measurement (Protocol step 1): the visible
51
+ conversation carries no timestamps or usage, so never estimate them from it. Use non-overlapping
52
+ stages whose durations sum to elapsed time; render a value as `n/a` only when the measurement
53
+ reports `available: false` or omits it.
52
54
 
53
- ## Triage mode (`--triage`)
55
+ ## Fix modes (`--fix auto|all`)
54
56
 
55
- Report-only stays the default. With `--triage`, run the same evidence pass, then remediate in
56
- three buckets — never skip triage and start fixing from the raw findings list.
57
+ Report-only stays the default. With `--fix auto` or `--fix all`, run the same
58
+ evidence pass, then remediate in three buckets — never skip triage and start fixing from the raw
59
+ findings list. Steps 1–4 below are `--fix auto`; `--fix all` differs only as stated after them.
57
60
 
58
61
  1. **Triage every finding into exactly one bucket:**
59
62
  - **Direct fix** — pure documentation work, or a one-to-two-line fix with obvious, local,
@@ -74,10 +77,22 @@ three buckets — never skip triage and start fixing from the raw findings list.
74
77
  4. **Report** — add a Triage section: applied fixes (path + one-line what + verification) and
75
78
  each created task WBS. The Resolved/Open tables keep their evidence rules unchanged.
76
79
 
80
+ **`--fix all` (defer nothing).** Triage still runs, but the Task bucket is fixed inline instead of
81
+ filed, so the mode creates no task. Fix in dependency order with the same surgical-diff and
82
+ per-fix re-verification rule as direct fixes, then run the project gate once at the end. A finding
83
+ that needs a design or operator decision is asked through the host question tool, never deferred;
84
+ one that still cannot be fixed (failed re-verification, blocked by the environment) stays in Open
85
+ issues with its evidence. Notes stay report-only.
86
+
77
87
  ## Protocol
78
88
 
79
89
  1. **Resolve scope.** Review the active session from the operator's initiating request through the
80
- latest result. Use `[focus]` only to rank relevant material.
90
+ latest result. Use `[focus]` only to rank relevant material. Measure it with
91
+ `node "$(superskill script path sp session-timeline.mjs)" --group "<ranges>"`: one segment per
92
+ operator prompt, read-only from the host transcript. Run it once without `--group` to list the
93
+ segments, then map them to stages (`"1-3,4,5-6"`). It splits each segment into `work` and
94
+ operator `wait` (idle before the next prompt plus AskUserQuestion answer time) and counts tokens
95
+ once per message id.
81
96
  2. **Inventory outcomes.** List requested outcomes and classify each as completed, partial, blocked,
82
97
  or not attempted. Collapse repeated attempts into one outcome.
83
98
  3. **Classify issues.** For every material issue, distinguish resolved, open, or attempted. Record
@@ -108,26 +123,32 @@ State the overall result in one to three sentences, including partial or blocked
108
123
  Summarize elapsed time and, when supported by evidence, productive work, avoidable setup/recovery,
109
124
  and operator wait time. Then render non-overlapping stages:
110
125
 
111
- | Stage | Time | Tool calls | Assessment |
112
- | --- | ---: | ---: | --- |
126
+ | Stage | Time | Wait | Tool calls | Token | Assessment |
127
+ | --- | ---: | ---: | ---: | ---: | --- |
113
128
 
114
129
  Format durations as `M:SS` below one hour and `H:MM:SS` at one hour or above (`1:44`, not `1m44s`;
115
130
  `0:33`, not `33s`). Include a `Total` row when elapsed time is available. Keep operator approval
116
- waits separate from execution bottlenecks. Use `n/a` for any value not supported by the active
117
- session evidence.
131
+ waits separate from execution bottlenecks. Time is the stage `work` and Wait its operator `wait`.
132
+ Token renders `<total> / <non-cached>` in compact units (`28.7M / 717k`) — the measurement's
133
+ `token` field: total = input + cache creation + cache read + output; non-cached = total minus cache
134
+ read (fresh input, cache writes and output). These are measured session counts, not the 0912 baseline token/USD claims that
135
+ step 4 excludes. Use `n/a` for any value not supported by the measurement.
118
136
 
119
137
  ### Resolved issues
120
138
 
121
- | Issue | Root cause | Resolution | Evidence |
122
- | --- | --- | --- | --- |
139
+ | Issue | Root cause | Resolution | Evidence | Confidence |
140
+ | --- | --- | --- | --- | --- |
123
141
 
124
142
  Evidence names the tool result, verification command, or concrete repository state visible in the
125
- session. Do not list ordinary implementation steps as issues.
143
+ session. Do not list ordinary implementation steps as issues. **Confidence** is `HIGH`, `MEDIUM`, or
144
+ `LOW` for that row's claim, with its grounds in the cell (a level without grounds is not
145
+ verification); any API/library statement in a row cites the installed source as `path:line` or a URL,
146
+ and a claim the session could not ground is written `LOW` with the missing evidence named.
126
147
 
127
148
  ### Open issues and risks
128
149
 
129
- | Issue or risk | State | Evidence or confirmation needed |
130
- | --- | --- | --- |
150
+ | Issue or risk | State | Evidence or confirmation needed | Confidence |
151
+ | --- | --- | --- | --- |
131
152
 
132
153
  ### Process and environment improvements
133
154
 
@@ -137,13 +158,13 @@ observation adopted from the 0912 workflow baseline cites its anchor
137
158
  (`docs/reports/i31/0912-workflow-baseline.md`) and owner handoff, and carries no unsupported
138
159
  performance claim.
139
160
 
140
- ### Triage (only when `--triage` was passed)
161
+ ### Triage (only with `--fix auto|all`)
141
162
 
142
163
  | Applied fix / created task | Bucket | What + verification |
143
164
  | --- | --- | --- |
144
165
 
145
- One row per applied fix and one per created task (with its WBS). Omit the section entirely when
146
- `--triage` was not passed.
166
+ One row per applied fix and one per created task (with its WBS; `--fix all` has none). Omit the
167
+ section entirely under `--fix none`.
147
168
 
148
169
  ### Next actions
149
170
 
@@ -157,8 +178,9 @@ improvement. Use `None` when the session is complete and no follow-up is justifi
157
178
  comparison or recurrence classification, or emit a twelve-section forensic report; those belong
158
179
  to imported-history analysis.
159
180
  - Report-only by default: do not create or update corpus items or edit files. The single exception
160
- is `--triage` mode, which permits exactly two mutation classes — direct fixes from the triage
161
- bucket, and the triage tasks. Anything beyond that stays a proposal.
181
+ is `--fix auto|all`: `auto` permits exactly two mutation classes — direct fixes from the triage
182
+ bucket, and the triage tasks; `all` permits one — inline fixes of actionable findings. Anything
183
+ beyond that stays a proposal.
162
184
  - Do not turn a single low-impact observation into a new policy. Report it as a candidate until it
163
185
  recurs or demonstrates a high-impact contract violation.
164
186
 
@@ -49,6 +49,7 @@ Pick the noun, read its reference. Each Tier A and Tier B reference owns that no
49
49
  | **Tier B** | **self** | Self-management verbs: scaffold (`init`), database maintenance (`maintain`), schema migrations (`migrate`), local web server (`serve`), status overview (`status`); `self init` runs post-scaffold validation probes & layout classification | [references/self.md](references/self.md) |
50
50
  | **Tier B** | **history** | Import agent histories, aggregate forensic artifacts, render reports, and run the checkpoint-resumed daily pipeline | [references/history.md](references/history.md) |
51
51
  | **Tier B** | **projects** | Manage the local multi-project registry and start/stop project servers | [references/projects.md](references/projects.md) |
52
+ | **Tier B** | **decision** | Decision-catalog transport: list visible decisions, show a served contract, run a decision to a served outcome, audit catalog health | [references/decision.md](references/decision.md) |
52
53
  | **Tier C** | **help** | Commander-generated help command; not a Spur noun | Generated `--help` |
53
54
 
54
55
  **Execute-First Contract:** Load the noun reference first and execute Tier A or Tier B commands
@@ -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.