@gobing-ai/spur 0.4.0 → 0.4.2

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 (172) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +4 -2
  3. package/config/decisions/gates.yaml +36 -0
  4. package/config/decisions/history-anatomy.yaml +35 -0
  5. package/config/decisions/idea-pipeline.yaml +38 -0
  6. package/config/plugin-scripts.json +15 -0
  7. package/config/rules/docs/sourced-api-claims.yaml +60 -0
  8. package/config/rules/recommended-pre-check.yaml +8 -3
  9. package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
  10. package/config/script-placement-baseline.json +2 -2
  11. package/config/templates/AGENTS.md +1 -1
  12. package/config/workflows/feature-lifecycle.yaml +9 -2
  13. package/config/workflows/history-anatomy.yaml +22 -0
  14. package/config/workflows/idea-pipeline.yaml +75 -0
  15. package/config/workflows/task-pipeline.yaml +216 -57
  16. package/config/workflows/wrapup-pipeline.yaml +107 -14
  17. package/package.json +10 -10
  18. package/plugins/sp/README.md +12 -3
  19. package/plugins/sp/agents/super-planner.md +16 -6
  20. package/plugins/sp/agents/super-reviewer.md +14 -0
  21. package/plugins/sp/bunfig.toml +8 -0
  22. package/plugins/sp/commands/dev-brainstorm.md +29 -16
  23. package/plugins/sp/commands/dev-find-way.md +80 -0
  24. package/plugins/sp/commands/dev-idea.md +6 -0
  25. package/plugins/sp/commands/dev-plan.md +6 -0
  26. package/plugins/sp/commands/dev-review-session.md +4 -1
  27. package/plugins/sp/commands/dev-run.md +12 -2
  28. package/plugins/sp/commands/dev-runall.md +32 -15
  29. package/plugins/sp/hooks/context-owner.ts +56 -0
  30. package/plugins/sp/hooks/context-post-tool.ts +23 -0
  31. package/plugins/sp/hooks/context-session-start.ts +49 -27
  32. package/plugins/sp/hooks/context-session-stop.ts +131 -30
  33. package/plugins/sp/hooks/pi/guard-extension.ts +21 -8
  34. package/plugins/sp/lib/idea-handoff.generated.mjs +43 -5
  35. package/plugins/sp/lib/inline-run-app.ts +28 -0
  36. package/plugins/sp/lib/inline-run.generated.d.mts +5 -0
  37. package/plugins/sp/lib/inline-run.generated.mjs +351 -46
  38. package/plugins/sp/lib/plan-projection.generated.d.mts +47 -0
  39. package/plugins/sp/lib/plan-projection.generated.mjs +368 -0
  40. package/plugins/sp/lib/quality-gate.generated.d.mts +35 -1
  41. package/plugins/sp/lib/quality-gate.generated.mjs +30296 -71
  42. package/plugins/sp/lib/residual-scan.generated.d.mts +19 -0
  43. package/plugins/sp/lib/residual-scan.generated.mjs +66 -0
  44. package/plugins/sp/lib/run-summary-core.ts +561 -0
  45. package/plugins/sp/lib/transcript.ts +150 -7
  46. package/plugins/sp/plugin.json +1 -1
  47. package/plugins/sp/references/roles.md +1 -1
  48. package/plugins/sp/scripts/batch-plan.mjs +488 -0
  49. package/plugins/sp/scripts/batch-plan.ts +145 -0
  50. package/plugins/sp/scripts/batch-preflight.mjs +51 -3
  51. package/plugins/sp/scripts/batch-preflight.ts +72 -1
  52. package/plugins/sp/scripts/commit-guard.mjs +216 -0
  53. package/plugins/sp/scripts/commit-guard.ts +249 -0
  54. package/plugins/sp/scripts/inline-run-dispatch.mjs +130 -0
  55. package/plugins/sp/scripts/inline-run-dispatch.ts +135 -0
  56. package/plugins/sp/scripts/inline-run-setup.mjs +562 -80
  57. package/plugins/sp/scripts/inline-run-setup.ts +52 -78
  58. package/plugins/sp/scripts/persist-out-check.mjs +90 -41
  59. package/plugins/sp/scripts/persist-out-check.ts +112 -57
  60. package/plugins/sp/scripts/pr-reviewing.mjs +19 -5
  61. package/plugins/sp/scripts/pr-reviewing.ts +34 -9
  62. package/plugins/sp/scripts/quality-gate.mjs +30213 -61
  63. package/plugins/sp/scripts/quality-gate.ts +21 -5
  64. package/plugins/sp/scripts/residual-scan.mjs +86 -31
  65. package/plugins/sp/scripts/residual-scan.ts +28 -30
  66. package/plugins/sp/scripts/run-summary.mjs +432 -14
  67. package/plugins/sp/scripts/run-summary.ts +41 -177
  68. package/plugins/sp/scripts/session-timeline.mjs +109 -11
  69. package/plugins/sp/scripts/session-timeline.ts +57 -19
  70. package/plugins/sp/scripts/task-diffstat.mjs +32 -29
  71. package/plugins/sp/scripts/task-diffstat.ts +34 -34
  72. package/plugins/sp/scripts/wrapup-steps.mjs +150 -2
  73. package/plugins/sp/scripts/wrapup-steps.ts +203 -1
  74. package/plugins/sp/skills/brainstorm/SKILL.md +9 -47
  75. package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
  76. package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +1 -0
  77. package/plugins/sp/skills/code-implementation/SKILL.md +48 -1
  78. package/plugins/sp/skills/code-verification/SKILL.md +3 -4
  79. package/plugins/sp/skills/code-verification/references/secu-review.md +3 -0
  80. package/plugins/sp/skills/code-verification/references/verdict-schema.md +68 -0
  81. package/plugins/sp/skills/fleet-join/SKILL.md +1 -1
  82. package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
  83. package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
  84. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  85. package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
  86. package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
  87. package/plugins/sp/skills/session-review/SKILL.md +40 -7
  88. package/plugins/sp/skills/source-driven-development/SKILL.md +5 -0
  89. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +5 -3
  90. package/plugins/sp/skills/spur-cli/SKILL.md +5 -2
  91. package/plugins/sp/skills/spur-cli/references/agent.md +20 -15
  92. package/plugins/sp/skills/spur-cli/references/decision.md +16 -8
  93. package/plugins/sp/skills/spur-cli/references/message.md +7 -7
  94. package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
  95. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +19 -3
  96. package/plugins/sp/skills/spur-cli/references/tasks.md +10 -7
  97. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +7 -0
  98. package/plugins/sp/skills/spur-cli/references/workflows.md +7 -2
  99. package/plugins/sp/skills/spur-composer/SKILL.md +1 -1
  100. package/plugins/sp/skills/spur-dev/SKILL.md +20 -14
  101. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +33 -3
  102. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
  103. package/plugins/sp/skills/spur-dev/references/dev-operations.md +37 -17
  104. package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
  105. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +199 -0
  106. package/plugins/sp/skills/spur-dev/references/execution-batch.md +156 -1024
  107. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +133 -0
  108. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +6 -0
  109. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +471 -0
  110. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +379 -0
  111. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +44 -12
  112. package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
  113. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +340 -165
  114. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
  115. package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
  116. package/schemas/state-machine-workflow.schema.json +23 -0
  117. package/spur.js +42402 -26889
  118. package/web/_astro/{BoardApp.VeoxxLBy.js → BoardApp.B3GQZvj8.js} +1 -1
  119. package/web/_astro/BoardApp.BT7L2kSb.js +324 -0
  120. package/web/_astro/TaskDetail.Cxov1_N_.js +1 -0
  121. package/web/_astro/{arc.f4e0OYLg.js → arc.vaT7tPOB.js} +1 -1
  122. package/web/_astro/{architectureDiagram-3BPJPVTR.SSIvxi-Z.js → architectureDiagram-3BPJPVTR.DRMmTjkN.js} +1 -1
  123. package/web/_astro/{blockDiagram-GPEHLZMM.DSXIHOFf.js → blockDiagram-GPEHLZMM.cla0LO_q.js} +1 -1
  124. package/web/_astro/{c4Diagram-AAUBKEIU.DG9Ubu85.js → c4Diagram-AAUBKEIU.D4YAIBKn.js} +1 -1
  125. package/web/_astro/channel.D4nG9YLf.js +1 -0
  126. package/web/_astro/{chunk-2J33WTMH.C7QU88Qh.js → chunk-2J33WTMH.BrSJbMRZ.js} +1 -1
  127. package/web/_astro/{chunk-4BX2VUAB.26Z94xBj.js → chunk-4BX2VUAB.CuLMgKF5.js} +1 -1
  128. package/web/_astro/{chunk-55IACEB6.DYRiBkqw.js → chunk-55IACEB6.zkvgu54F.js} +1 -1
  129. package/web/_astro/{chunk-727SXJPM.D5Vfv5pe.js → chunk-727SXJPM.DxWkACh8.js} +1 -1
  130. package/web/_astro/{chunk-AQP2D5EJ.DXHV47bN.js → chunk-AQP2D5EJ.42XrmIlH.js} +1 -1
  131. package/web/_astro/{chunk-FMBD7UC4.D1suAfOs.js → chunk-FMBD7UC4.Bao2wMK9.js} +1 -1
  132. package/web/_astro/{chunk-ND2GUHAM.D6BmdHEY.js → chunk-ND2GUHAM.BhydojD1.js} +1 -1
  133. package/web/_astro/{chunk-QZHKN3VN.rnPU-Ak-.js → chunk-QZHKN3VN.CmZ82g-H.js} +1 -1
  134. package/web/_astro/{classDiagram-4FO5ZUOK.CvEfXIo1.js → classDiagram-4FO5ZUOK.Blr83Gt_.js} +1 -1
  135. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CvEfXIo1.js → classDiagram-v2-Q7XG4LA2.Blr83Gt_.js} +1 -1
  136. package/web/_astro/{cose-bilkent-S5V4N54A.DkMisLSm.js → cose-bilkent-S5V4N54A.qmfv0SPn.js} +1 -1
  137. package/web/_astro/{cynefin-OW5HDTMX.C77Q-pF6.js → cynefin-OW5HDTMX.BtELhXPR.js} +1 -1
  138. package/web/_astro/{dagre-BM42HDAG.YaV8ce27.js → dagre-BM42HDAG.AWmaAvXo.js} +1 -1
  139. package/web/_astro/{diagram-2AECGRRQ.CUyGEKge.js → diagram-2AECGRRQ.DdH34KDi.js} +1 -1
  140. package/web/_astro/{diagram-5GNKFQAL.CRgd8dgj.js → diagram-5GNKFQAL.BmNPix1k.js} +1 -1
  141. package/web/_astro/{diagram-KO2AKTUF.Bmzys9hq.js → diagram-KO2AKTUF.BNQd_Ppo.js} +1 -1
  142. package/web/_astro/{diagram-LMA3HP47.BvmJY4Lw.js → diagram-LMA3HP47.BAPgDi0e.js} +1 -1
  143. package/web/_astro/{diagram-OG6HWLK6.0hEhD30d.js → diagram-OG6HWLK6.DZ4zyLdC.js} +1 -1
  144. package/web/_astro/{erDiagram-TEJ5UH35.Bjt8lrnm.js → erDiagram-TEJ5UH35.Q1TySMmw.js} +1 -1
  145. package/web/_astro/{flowDiagram-I6XJVG4X.CJZTjRDV.js → flowDiagram-I6XJVG4X.BXhFBicG.js} +1 -1
  146. package/web/_astro/{ganttDiagram-6RSMTGT7._VqoVafc.js → ganttDiagram-6RSMTGT7.vHSgcuo2.js} +1 -1
  147. package/web/_astro/{gitGraphDiagram-PVQCEYII.CEDb7tVo.js → gitGraphDiagram-PVQCEYII.Bi1f73C7.js} +1 -1
  148. package/web/_astro/index.BhSwN8yY.css +1 -0
  149. package/web/_astro/{infoDiagram-5YYISTIA.CgwifPpK.js → infoDiagram-5YYISTIA.DPvXZEWm.js} +1 -1
  150. package/web/_astro/{ishikawaDiagram-YF4QCWOH.B99wx2J1.js → ishikawaDiagram-YF4QCWOH.Cz829fsV.js} +1 -1
  151. package/web/_astro/{journeyDiagram-JHISSGLW.2hRwJbEo.js → journeyDiagram-JHISSGLW.enRnB9Me.js} +1 -1
  152. package/web/_astro/{kanban-definition-UN3LZRKU.BahJkKNC.js → kanban-definition-UN3LZRKU.DnTNk6d2.js} +1 -1
  153. package/web/_astro/{linear.DguUUe6V.js → linear.BzGnBwNL.js} +1 -1
  154. package/web/_astro/{mermaid.core.CvgfmqVr.js → mermaid.core.CSbTpIVz.js} +4 -4
  155. package/web/_astro/{mindmap-definition-RKZ34NQL.B3qEA3x9.js → mindmap-definition-RKZ34NQL.DGJDy8zT.js} +1 -1
  156. package/web/_astro/{pieDiagram-4H26LBE5.FlWA-_ru.js → pieDiagram-4H26LBE5.HSPdi5_8.js} +1 -1
  157. package/web/_astro/{quadrantDiagram-W4KKPZXB.9bVn35X7.js → quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js} +1 -1
  158. package/web/_astro/{requirementDiagram-4Y6WPE33.ChykIhct.js → requirementDiagram-4Y6WPE33.CnBCtFwa.js} +1 -1
  159. package/web/_astro/{sankeyDiagram-5OEKKPKP.Z2Y3hkYb.js → sankeyDiagram-5OEKKPKP.5M8VWAu8.js} +1 -1
  160. package/web/_astro/{sequenceDiagram-3UESZ5HK.J7JsHWDt.js → sequenceDiagram-3UESZ5HK.DN41XN-U.js} +1 -1
  161. package/web/_astro/{stateDiagram-AJRCARHV.DAPbiCSS.js → stateDiagram-AJRCARHV.ErOO3W8j.js} +1 -1
  162. package/web/_astro/{stateDiagram-v2-BHNVJYJU.D2jvSi0q.js → stateDiagram-v2-BHNVJYJU.B_ZHICM-.js} +1 -1
  163. package/web/_astro/{timeline-definition-PNZ67QCA.psW2ci29.js → timeline-definition-PNZ67QCA.CWdosr07.js} +1 -1
  164. package/web/_astro/{vennDiagram-CIIHVFJN.DlILYVmg.js → vennDiagram-CIIHVFJN.BHzYjNV5.js} +1 -1
  165. package/web/_astro/{wardleyDiagram-YWT4CUSO.xNWwLJnn.js → wardleyDiagram-YWT4CUSO.h8x4IwPX.js} +1 -1
  166. package/web/_astro/{xychartDiagram-2RQKCTM6.EU_BuH7V.js → xychartDiagram-2RQKCTM6.RTapW2X8.js} +1 -1
  167. package/web/index.html +2 -2
  168. package/plugins/sp/commands/dev-parallel.md +0 -31
  169. package/web/_astro/BoardApp.Bzr4OXrW.js +0 -324
  170. package/web/_astro/TaskDetail.LAyR_Pvn.js +0 -1
  171. package/web/_astro/channel.CUFdtWhm.js +0 -1
  172. package/web/_astro/index.Dg_3bOaJ.css +0 -1
@@ -19,6 +19,17 @@ the interpreter and trace instructions must match. Never hand-edit the installed
19
19
 
20
20
  **Retirement criterion (0755 R5, D8 decision D7):** the per-task interpreter retires once the engine covers per-task execution for `/sp:dev-runall` with real terminal runs **and** the parity check (this doc's documented action/guard set ≡ the resolved action/guard set of every `.spur/workflows/*.yaml`) is green. Recording the criterion is part of this task; acting on it is not — that is a separate A3-gate decision.
21
21
 
22
+ ## Driver state checklist (re-read this after a compaction)
23
+
24
+ - **Run id** — the run row `inline-run-setup` persisted (`.spur/run/<run-id>-inline-setup.json`
25
+ sidecar, `.spur/memory/runs/<run-id>.md` + `.state.json`); never re-derive it from the transcript.
26
+ - **Markers under `.spur/run/`** — `<run-id>-inline-setup.json` (frozen invocation identity),
27
+ `<run-id>-tree-start.json` (commit-guard fingerprint), `<wbs>-verdict.json` (per-stage verdict),
28
+ `<wbs>-question.md` / `<wbs>-escalation.md` (operator pause), `<run-id>-event-trace.md`.
29
+ - **Re-read per state** — this file (Run setup → YAML interpreter → Record & done),
30
+ [structured-trace-emission.md](structured-trace-emission.md) at the first trace event.
31
+ - **Authoritative state** — the run record and verdict artifacts, never the host todo list.
32
+
22
33
  ## Supported action and guard set (0755 R2 parity contract)
23
34
 
24
35
  The action and guard kinds this driver implements. The parity check
@@ -51,7 +62,7 @@ command, skill, script, or second workflow.
51
62
 
52
63
  **Inline HITL defer semantics (0911):** bundled pipeline human gates ship `decision: {mode:
53
64
  never}`, so the inline driver always prompts the operator for those gates regardless of
54
- `workflow.hitlDecisionMaker` — the decision policy never answers inline pipeline gates. A local
65
+ `workflows.hitlDecisionMaker` — the decision policy never answers inline pipeline gates. A local
55
66
  workflow override (`.spur/workflows/*.yaml`) may declare `mode: evidence`, but the driver does
56
67
  not claim full inline parity for the DecisionMaker path: evidence-mode auto-answering inside the
57
68
  inline driver is an explicit non-goal (subprocess runs own that behavior). No hot reload: config
@@ -59,21 +70,25 @@ and YAML changes require restarting the invoking process.
59
70
 
60
71
  ## Run setup
61
72
 
62
- **Shared startup contract (task 0814 R1/R3/R4/R6/R7).** The order is load-bearing: publish a compact
63
- **bootstrap checklist** immediately (host-preparation rows, never copied workflow states); run quick
64
- deterministic readiness (admission, not an implementation certificate) before any isolation; when
65
- `--worktree` is valid, create/adopt and switch to the execution tree; then publish the **workflow
66
- inventory** (the CLI todo projection) — and only then read the full YAML for comprehensive/model work.
67
- Comprehensive checks stay at their owning boundaries and run after the plan is visible and after
68
- isolation when requested (R7); quick readiness and plan projection dispatch zero models and execute
69
- zero workflow actions (R8). Use the stable label helpers
70
- (`columnLabel` / `buildStepLabels` / `labelChild` in `packages/app/src/workflow/step-reporter.ts`) for
71
- the human/native presentation layer — labels are display addresses only, never an execution key.
73
+ **Shared startup contract (task 0814 R1/R3/R4/R6/R7; publish-first 1105 R1).** The order is
74
+ load-bearing: publish the **generated plan** first — resolve the selected workflow through the same
75
+ project/bundled resolver as execution, run `spur workflow show <resolved-file> --no-logo --format
76
+ todo --json`, and publish the `.plan` rows verbatim (the projection reads the definition and executes
77
+ nothing, so it is safe before readiness and isolation); run quick deterministic readiness (admission,
78
+ not an implementation certificate) next; when `--worktree` is valid, create/adopt and switch to the
79
+ execution tree; then bind the published plan to the run's persisted `__definitionDigest` — and only
80
+ then read the full YAML for comprehensive/model work. Comprehensive checks stay at their owning
81
+ boundaries and run after the plan is visible and after isolation when requested (R7); quick readiness
82
+ and plan projection dispatch zero models and execute zero workflow actions (R8). Labels come from the
83
+ projection (or `columnLabel` in `packages/app/src/workflow/step-reporter.ts` for the `steps[]`
84
+ fallback) — labels are display addresses only, never an execution key.
72
85
 
73
86
  1. Resolve the command inputs, `--auto`, and any explicit `--vars` — **without reading the selected
74
87
  YAML yet**. An explicit non-inline executor selection chooses the subprocess workflow path.
75
88
  2. Allocate a collision-resistant inline run id (`uuidgen`, with a timestamp/pid fallback), create
76
- `.spur/run/` for attempt staging and `.spur/memory/runs/` for retained records, and use the two-file run record (task 0927) — append lines to
89
+ `.spur/run/` and the run's scratch subdirectory `.spur/run/<run-id>/` for attempt staging (action
90
+ batches, captured stage output, temporary status/answer files) plus `.spur/memory/runs/` for
91
+ retained records, and use the two-file run record (task 0927) — append lines to
77
92
  `.spur/memory/runs/<run-id>.md` and read machine state from `.spur/memory/runs/<run-id>.state.json`.
78
93
  The run id lives in those two files and the persisted run row — never stage it in an ad-hoc
79
94
  `*.env` file: the `no-env-files` gate rule flags any `.env` name, so run-scoped staging files
@@ -93,6 +108,20 @@ the human/native presentation layer — labels are display addresses only, never
93
108
  || { echo "inline run setup failed closed — checker not found; run 'superskill install sp --marketplace gobing-ai/spur'" >&2; exit 1; }
94
109
  ```
95
110
 
111
+ **Commit fingerprint (1129 R1).** Fingerprint the tree the task commit will land in — before the
112
+ first task write — because Record & done stages through this snapshot, so a concurrent writer's
113
+ file cannot ride into the task commit. The artifact is cwd-relative under `.spur/run/` (gitignored
114
+ per tree), so the snapshot and the commit it guards must name the SAME tree: without `--worktree`
115
+ take it here in the setup window; with `--worktree` take it immediately **after step 7's
116
+ isolation, from the worktree root** (still before the first task write) — a snapshot taken here
117
+ would land in the invoking tree, and the worktree's commit would find none (1129 review N1).
118
+
119
+ ```bash
120
+ RUN_ID="<marker-id>" # the WT-3 marker's id (execution-batch.md), or the run id allocated at setup
121
+ GUARD=plugins/sp/scripts/commit-guard.ts; [ -f config/plugin-scripts.json -a -f "$GUARD" ] || GUARD="$(superskill script path sp commit-guard.mjs 2>/dev/null)";
122
+ bun "$GUARD" start --run "$RUN_ID" # <this tree>/.spur/run/$RUN_ID-tree-start.json = { head, dirty[] }
123
+ ```
124
+
96
125
  The delegate uses the source app service when the SPUR_BIN chain identifies a checkout;
97
126
  otherwise it loads the generated application bundle shipped with the plugin. Both setup paths
98
127
  obtains the selected definition from the existing CLI `workflow show --format todo --json`
@@ -128,10 +157,21 @@ the human/native presentation layer — labels are display addresses only, never
128
157
  `session` and the Codex key `session_id` (in that order). If neither is available, allocate
129
158
  `host-session-<run-id>` and record that fallback in the log; provenance must never be blank or
130
159
  guessed from an executor subprocess.
131
- 5. **Publish the bootstrap checklist (R1).** Render host-preparation rows into the host todo list
132
- before any expensive check: `A, Quick readiness`, `B, Prepare Git`, `C, Publish workflow plan`,
133
- `D, Comprehensive checking`. These are host preparation, never copied workflow states; they are
134
- not silently reassigned to unrelated workflow states when the workflow view later appears.
160
+ 5. **Publish the generated plan (R1, 1105) — the run's first action.** Run `spur workflow show
161
+ <resolved-file> --no-logo --format todo --json` and publish the projection into the host todo list
162
+ before any expensive check:
163
+ - **Annotated workflow (the normal case):** publish every `.plan` row's `text` **verbatim** —
164
+ `A Prepare`, `A1 Quick readiness`, `A2 Prepare Git`, `A3 Publish plan`, `B Implement`, … — in
165
+ plan order. Never hand-write rows, never copy the YAML state table into prose, and never
166
+ renumber: the labels carry identity.
167
+ - **Unannotated workflow (`.plan` is `null`):** publish the flat `steps[]` inventory in
168
+ declaration order with `columnLabel` labels and their `initial` / `terminal` / `failure` /
169
+ `pause` / `loopBack` / `conditional` markers. Never re-derive this list from the YAML.
170
+ - **No workflow plan at all** (skill-only refine/verify batches): keep the existing host
171
+ preparation rows; do not fabricate a workflow YAML.
172
+
173
+ Publishing executes nothing, so it safely precedes readiness and isolation, which keep their
174
+ existing order below.
135
175
  6. **Quick deterministic readiness (R2), before isolation.** Evaluate `quickReadiness` from
136
176
  `plugins/sp/scripts/batch-preflight.ts` with the operation, status, filtered-set size, the
137
177
  matrix-selected required/present sections, and content-policy findings. Record the outcome
@@ -146,15 +186,12 @@ the human/native presentation layer — labels are display addresses only, never
146
186
  inherited**: a host shell call starts in an arbitrary directory and carries no cwd from the
147
187
  previous call, so every later call runs inside the **Per-call execution-tree pin** protocol
148
188
  below (task 1058), which consumes the identity confirmed here.
149
- 8. **Publish the workflow inventory (R4), BEFORE reading the YAML.** Resolve the selected workflow
150
- through the same project/bundled resolver as execution and run
151
- `spur workflow show <resolved-file> --no-logo --format todo --json`. Validate the projection with
152
- `parseWorkflowInventory` and bind it to the run's persisted `__definitionDigest` with
189
+ 8. **Bind the published plan to the run digest (R4) — plan row A3.** Validate the step-5 projection
190
+ with `parseWorkflowInventory` and bind it to the run's persisted `__definitionDigest` with
153
191
  `assertInventoryIdentity` — a drift or projection failure stops the run before any
154
- comprehensive/model work, never executing with a misleading plan.
155
- - **Layer 1** = that projection's `steps[]`: the declared state inventory in declaration order
156
- with `initial` / `terminal` / `failure` / `pause` / `loopBack` / `conditional` markers. Mark
157
- the active state. Never re-derive this list from the YAML.
192
+ comprehensive/model work, never executing with a misleading plan. The A rows published at step 5
193
+ track this setup order: A1 = quick readiness (step 6), A2 = Git/isolation (step 7), A3 = this
194
+ binding; mark each row completed as its step settles.
158
195
  9. **Read the selected YAML and overlay its `vars` defaults** with the invocation values. Compare the
159
196
  resolved definition identity against the bound `__definitionDigest`; a mismatch is identity drift
160
197
  and fails closed (step 8 already caught projection-side drift; this re-checks the same definition
@@ -176,15 +213,25 @@ the human/native presentation layer — labels are display addresses only, never
176
213
  ended 0/11 with precheck and implement still open).
177
214
  - **Source of truth** = the CLI projection for layer 1; the YAML read here for layer 2.
178
215
  Never hand-copy or hand-derive the state list into the driver, a command, a skill, or a script.
179
- - **Stable labels (task 0814 R5).** Top-level declaration indexes map to A, B, … Z, AA, AB …;
180
- visible children restart numbering under their parent (A1, A2, B1 …). Labels never replace the
181
- canonical step id, and are re-derived identically on retry/resume against the same definition.
182
- - **Truthful progress (task 0814 R6).** Publish pending/active state before the visible item
183
- starts, then update it immediately after the observed item completes and before the next item
184
- starts. Keep completed, skipped, failed, blocked, paused, and unattempted outcomes distinct;
185
- never mark skipped/conditional work completed merely to clear the UI. If the host has no
186
- suitable native todo tool, or it fails, use an explicit Markdown fallback with the same labels
187
- and truth — never a fabricated successful tool invocation.
216
+ - **Stable labels (task 0814 R5; A–Z / 1–9 rule, 1105 R1).** Labels come from the published
217
+ `.plan`: single letters A–Z for top-level rows, digits 1–9 for children under one letter. The
218
+ projection enforces that cap instead of rolling to two-letter labels (`planLetter` throws past
219
+ Z — split into waves or stop; never render `AA`-style addresses). An `on-entry` state is
220
+ inserted as the next digit the moment the host enters it (`insertOnEntry` in
221
+ `packages/app/src/workflow/plan-projection.ts`), appended after its letter's existing children;
222
+ re-entering an already-published loop state keeps its label and appends the re-entry note
223
+ ` — attempt N`. Labels never replace the canonical step id, and are re-derived identically on
224
+ retry/resume against the same definition.
225
+ - **Truthful progress (task 0814 R6; frozen status mapping, 1105 R1).** Publish pending/active
226
+ state before the visible item starts, then update it immediately after the observed item
227
+ completes and before the next item starts. Frozen status mapping: `pending → pending`,
228
+ `active → in_progress`, `completed → completed`,
229
+ `skipped|failed|unattempted|blocked → pending` + ` [<outcome>]` — the four engine-decided
230
+ outcomes stay visible on a pending row, never cleared. Keep completed, skipped, failed, blocked,
231
+ paused, and unattempted outcomes distinct; never mark skipped/conditional work completed merely
232
+ to clear the UI. Re-entry appends ` — attempt N`. If the host has no suitable native todo tool,
233
+ or it fails, use the `renderProgressMarkdown` fallback with the same labels and truth — never a
234
+ fabricated successful tool invocation.
188
235
  10. For task execution only, record lifecycle provenance before entering the FSM:
189
236
 
190
237
  ```bash
@@ -200,6 +247,25 @@ the human/native presentation layer — labels are display addresses only, never
200
247
  worktree first and run this command (and every lifecycle-touching command: `task record`,
201
248
  `task update <wbs> testing|done`) from the worktree root.
202
249
 
250
+ ### Host todo update styles (1105 R2)
251
+
252
+ The same published plan is updated differently per host family. Both styles keep plan order and are
253
+ bookkeeping for the one projection — never a second source of truth:
254
+
255
+ | Host | Tool(s) | Update style |
256
+ | --- | --- | --- |
257
+ | Claude Code | `TaskCreate` / `TaskUpdate` | per-item — create each item once, then update by id; append inserted (`on-entry`) steps at the end (the label carries identity, so list position is display only) |
258
+ | pi | `todo` | per-item — same create-once/update-by-id discipline |
259
+ | Codex | `update_plan` | full-list — rewrite the whole list in plan order on every update |
260
+ | Gemini | `write_todos` | full-list — rewrite the whole list in plan order |
261
+ | OpenCode | `todowrite` | full-list — rewrite the whole list in plan order |
262
+ | Grok | `todo_write` | full-list — rewrite the whole list in plan order |
263
+ | omp | host todo tool | maps plan letters to `phase` |
264
+ | any host without a native tool | `renderProgressMarkdown` | Markdown fallback with the same labels and truth |
265
+
266
+ Per-item hosts update the one row whose outcome changed; full-list hosts re-render every row from the
267
+ current plan state. Both render the frozen status mapping above identically.
268
+
203
269
  ### Idea-pipeline quick start (0887 R1/R2)
204
270
 
205
271
  Minimum files to read for `/sp:dev-idea` inline runs — then drive `idea-pipeline.yaml`:
@@ -276,6 +342,10 @@ Rules (R1):
276
342
  first, then verify the physical path and repository identity.
277
343
  - **Native tool cwd options are additive.** A tool-level cwd/`cd` pin may additionally set the
278
344
  call's directory, but never replaces the identity checks above.
345
+ - **Dispatch cwd is mandatory when the tool accepts one.** A subagent dispatch whose tool exposes a
346
+ `cwd` (pi `subagent`) passes the execution-tree cwd. Omitted, the child starts in the host tree:
347
+ it can edit main, and the timeout watchdog diffs the wrong tree (H15 2026-10-07: "changed
348
+ tracked files: none" on a worker that changed 5 worktree files).
279
349
  - **Host-only scope.** Engine `shell` actions already bind `context.workdir`
280
350
  (`packages/app/src/workflow/actions/shell.ts:98`) — leave them alone. No service-wide
281
351
  `process.chdir`, no new public flag, no helper framework.
@@ -314,8 +384,6 @@ entry and `.spur/memory/runs/runall-D63-2ebbd97c.md`.
314
384
 
315
385
  ## Comprehensive-check retention and evidence (R7/R8)
316
386
 
317
- ## Comprehensive-check retention and evidence (R7/R8)
318
-
319
387
  **R7 — comprehensive checks stay at their owning boundaries.** Quick readiness and plan projection are
320
388
  admission and visibility, not a substitute for the owning gates. After the plan is visible and after
321
389
  isolation when requested, retain the full task/feature integrity, size, evidence-channel,
@@ -363,6 +431,19 @@ Action semantics come from the YAML and the workflow action contract:
363
431
 
364
432
  - `shell` — run the expanded command in the project working tree with resolved vars exported as
365
433
  environment variables. A non-zero result follows the action's existing failure policy.
434
+ **Long commands must not hold the host call (session finding 2026-10-09).** A `shell` action whose
435
+ command can outlive the host's per-call limit — the full quality gate, a repo-wide suite, any
436
+ `bun run spur-check`-class gate — is started **detached and polled**, never held in one foreground
437
+ call: redirect to a run-scoped file and disown it
438
+ (`nohup <cmd> > .spur/run/<wbs>-gate.out 2>&1 < /dev/null & disown`), then observe it in bounded
439
+ calls (`pgrep`, the declared `.status` file, the log tail). A call killed at the cap leaves the
440
+ substrate's own FAIL receipt — for the quality gate, `.spur/run/<wbs>-check-receipt.json` — and the
441
+ recheck's `no-progress` guard honours it, so a cap-kill reads as a red gate the next time. Preserve
442
+ the signal-induced receipt under a distinct name (`<wbs>-check-receipt.false-fail-<signal>.json`)
443
+ and say so in the run log before re-running: never delete it silently, and never report a
444
+ cap-killed attempt as a genuine gate failure. Measured this session: one foreground gate call
445
+ (25.6 min host-wide lock wait + 7.4 min runtime) was SIGTERM-killed at the host cap and produced
446
+ exactly that false FAIL receipt.
366
447
  - `note` — append the expanded message to the inline run log.
367
448
  - `doctor.probe` — run the declared Spur doctor once, persist its status file, and apply any
368
449
  `setVars` result (including a resolved executor) before the next action or state.
@@ -398,8 +479,10 @@ Action semantics come from the YAML and the workflow action contract:
398
479
  `answerFile`; assert `expectFile`; honor `escalationFile` (a non-empty file after exit 0 means
399
480
  the agent paused on an operator question — treat the attempt as succeeded-with-escalation and
400
481
  skip `requireDiff` for that attempt only); enforce `requireDiff` against a pre-action git snapshot,
401
- including the task-scope guard; honor declared error policy. `timeoutMs` is recorded as not
402
- applicable because the host session has no independent kill boundary.
482
+ including the task-scope guard; honor declared error policy. A host-inline action records
483
+ `timeoutMs` as not applicable because the host session has no independent kill boundary; a
484
+ dispatched subagent passes it to the host when the dispatch tool accepts a timeout (see
485
+ § Timeout boundary).
403
486
  - `run.artifact` — the engine's ledger registration has **no inline execution surface** (0808 R4).
404
487
  The inline equivalent is a documented **registration-equivalent convention**: before the record
405
488
  state mutates the task, the host validates the same refusal conditions inline — the declared
@@ -470,6 +553,49 @@ reformulate the prose into a command, spawn a subagent for it, or silently promo
470
553
  Beyond the deterministic `estimate_hours` floor in condition 5, no token estimate, model heuristic,
471
554
  or configuration switch is added.
472
555
 
556
+ **Contingent fallback (task 1134 R1/R2) — capacity is not capability.** That fallback is correct for
557
+ capability-shaped failures and **wrong when the exhausted resource is the host session's own
558
+ provider quota**: re-selecting the same exhausted provider cannot succeed by construction, and the
559
+ run collapses into one serial host worker with no executor diversity. So classify the pre-dispatch
560
+ failure before falling back:
561
+
562
+ ```bash
563
+ DISPATCH_SCRIPT=plugins/sp/scripts/inline-run-dispatch.ts; [ -f config/plugin-scripts.json -a -f "$DISPATCH_SCRIPT" ] || DISPATCH_SCRIPT="$(superskill script path sp inline-run-dispatch.mjs 2>/dev/null)"
564
+ bun "$DISPATCH_SCRIPT" --dispatch-failure --run-id "$RUN_ID" --stage <state-id> --decision <escalate|stop> \
565
+ --text-file <bounded failure record> [--executor <name>] [--agent <a>] [--model <m>]
566
+ ```
567
+
568
+ The helper wraps the upstream `@gobing-ai/ts-ai-runner` classifier (Spur matches **no** provider
569
+ code or quota vocabulary of its own) and writes `.spur/run/<run-id>-dispatch-fallback.json` as
570
+ `{stage, class, reason?, resetAt?, decision, executor, attribution, observedAt}`. Read `.class` from
571
+ that file — never judge the prose yourself.
572
+
573
+ - **`class: capability`** (generic 429, rate limit, overload, auth failure, timeout, missing
574
+ permission, missing capability, non-dispatch-eligible prose, below the size floor) → today's
575
+ fallback, unchanged: execute the stage once in the host session and log
576
+ `stage <id> executed inline in session <session-id>`.
577
+ - **`class: capacity`** (a confirmed exhausted usage allowance or credit balance) → the stage is
578
+ **never** re-executed in the orchestrating host session. Hand it to the subprocess dispatch path
579
+ (`--agent auto|name` resolves an executor through the registry and can pick another rung); when
580
+ that path is unavailable, terminate the run at `failed` with `terminalReason: failed-agent`,
581
+ naming the executor and the record's `resetAt` when it carries one. Record
582
+ `stage <id> capacity exhaustion: <executor> <reason> reset <resetAt|unknown> — escalate|stop`.
583
+
584
+ **Attribution before dispatch (task 1134 R3).** Immediately before each inline `agent.run` stage,
585
+ record the executor identity the stage will use, so a later classified exhaustion can be attributed
586
+ to a rung configuration can disable:
587
+
588
+ ```bash
589
+ bun "$DISPATCH_SCRIPT" --attribution --run-id "$RUN_ID" --stage <state-id> \
590
+ [--executor <name>] [--agent <name>] [--model <m>] # omit --executor for the no-attribution marker
591
+ ```
592
+
593
+ This appends one line to `.spur/run/<run-id>-attribution.jsonl` and one run-log line
594
+ (`stage <id> attribution: <executor|none>`). When no named executor resolves, pass no `--executor`:
595
+ the ledger records the explicit `executor: null` marker — never a silent drop. An unattributed stage
596
+ is observable but cannot write configuration (executor-availability §4), and `spur agent doctor
597
+ --json` counts those stages as `inlineFailFast.unattributedStages`.
598
+
473
599
  **Pre-dispatch permission check (2026-09-15 subagent-dispatch evaluation).** Claude Code exposes no
474
600
  dry-run permission API, so the contract is **name-capabilities + fail-fast blocker** — never a
475
601
  permission-probing subsystem. Before dispatching a stage, the driver names the stage's required
@@ -482,7 +608,7 @@ prompt the host cannot see is invisible until join. Record one run-log line per
482
608
  stage <id> permission precheck: ok | <missing capability>
483
609
  ```
484
610
 
485
- Read-only investigation fan-out (`/sp:dev-parallel --mode investigation`) dispatches read-only
611
+ Read-only investigation fan-out (N-way investigation) dispatches read-only
486
612
  worker shapes by construction — see the [pre-dispatch permission
487
613
  rule](../../parallel-execution/references/fan-out-patterns.md).
488
614
 
@@ -515,13 +641,30 @@ boundary, and a delegate left to re-derive them re-derives them against its own
515
641
  **owning stage's artifact contract** — for a verify stage, the compact contract below.
516
642
  6. **The implement-stage acceptance-evidence requirement** — for the implement stage and any stage
517
643
  whose YAML action declares `requireDiff`. A `requireDiff` stage is the one whose delegate writes
518
- the deliverable, so its handoff carries three components:
644
+ the deliverable, so its handoff carries four components:
519
645
  (a) **the task's AC identities verbatim** — the exact `Scenario:` titles / checklist text read by
520
646
  the driver from the task file, never paraphrased;
521
647
  (b) **required evidence** — the pasted output of the narrow targeted tests the delegate ran, and
522
648
  a `file:line` change map written into the task's `## Solution` section;
523
649
  (c) the reminder that **a delegate success message is not evidence** — post-join validation reads
524
- the artifacts and the diff, not the delegate's claims.
650
+ the artifacts and the diff, not the delegate's claims;
651
+ (d) **the public-surface probe requirement (task 1135 R1/R2/R6).** Before the stage reports done
652
+ it runs at least one end-to-end probe of each new or changed **public surface** — a CLI
653
+ verb/flag, an HTTP route, or a plugin script entry — against a throwaway project root (never the
654
+ repo corpus, never the execution tree's tracked files), and reports the exact command plus its
655
+ observed output as acceptance evidence. A service-layer test suite is **not** a substitute. The
656
+ probe is bounded: 120 s per invocation, output capped at 8 KiB, no network beyond the CLI under
657
+ test, and a probe failure is evidence, never a corpus mutation.
658
+ A stage that changed no public surface returns instead the single line
659
+ `probe: not-applicable (no public surface changed)`.
660
+ The stage's own changed-path static self-check (task 1135 R8) also runs here: `spur rule run`
661
+ with the configured pre-check preset over the changed paths, `bun run script-contract-check`
662
+ when a plugin script was touched, and the paired-script parity test when `package.json` changed —
663
+ so an env-var-hygiene violation, a stale generated twin, or a half-updated paired chain is
664
+ named by the stage rather than discovered by the pipeline `test` hop.
665
+ **A stage that returns neither probe evidence nor the not-applicable line is not silently
666
+ complete (task 1135 R2): the driver records the stage `done` with a `probe-missing` marker in
667
+ the run log and carries the same marker into that task's batch-report row.**
525
668
  Component (a) is read from the task by the driver; this extends the payload contract and is NOT a
526
669
  new YAML key.
527
670
 
@@ -533,6 +676,26 @@ execution tree, and an ad-hoc git worktree created for a comparison is removed b
533
676
  reports. The G65 batch's implement dispatches left an 867 MB worktree plus a trail of `/tmp` scratch
534
677
  files that outlived the run (2026-09-15).
535
678
 
679
+ **Delegate search discipline.** A delegate searches with bounded scope: `rg -g '!node_modules' …`
680
+ (or an explicit narrow path), never `grep -r .` / `grep -rn --include=*` from a repository root —
681
+ those walk `node_modules` and `.git` and can burn many minutes at full CPU in the wrong tree
682
+ (measured 2026-10-09: a delegate's `grep -rn --include=* .` from the invoking tree root ran 4+ min
683
+ before the orchestrator killed it). The dispatch payload's execution-tree cwd is the only tree a
684
+ delegate searches or edits; a search it cannot bound is a search it should not run.
685
+
686
+ **Driver scratch lives with the run (task 1136 R6).** Every driver scratch file — action-batch
687
+ JSON, captured stage output, temporary status/answer files — lives under `.spur/run/<run-id>/`,
688
+ never `/tmp` or `$TMPDIR`: the OS may reap a temp directory mid-run, and a reaped scratch file
689
+ surfaces as an unrelated downstream failure. Run-scoped files directly under `.spur/run/` keep
690
+ their existing `<run-id>-*` naming (the `no-env-files` rule still bans `.env` names).
691
+
692
+ **Declared vars are checked before a shell action runs (task 1136 R6).** Before executing a `shell`
693
+ action whose command references a declared workflow var (for example `$qualityGateCmd`), the driver
694
+ confirms the var is exported and non-empty; when it is not, the action fails by name —
695
+ `shell action requires $qualityGateCmd but it is empty/unset` — and the command never runs. The
696
+ check is scoped to vars the workflow declares with a value: legitimately empty optional vars
697
+ (`$featureSpecPath` for an orphan task, `$gateFindings` on a green gate) pass unchanged. This turnsa silently empty `$qualityGateCmd` into a named failure instead of a shell that runs nothing.
698
+
536
699
  **Verify-stage artifact contract.** A verify handoff names
537
700
  [`code-verification/references/verdict-schema.md`](../../code-verification/references/verdict-schema.md)
538
701
  as the canonical answer schema and carries this compact form verbatim:
@@ -546,6 +709,9 @@ Evidence Type = test | command | static-ref | manual-review | llm-judge | n/a
546
709
  AC rows use the task's exact AC identities (verbatim `Scenario:` titles / checklist text).
547
710
  A behavioral AC marked MET requires executable evidence (test | command);
548
711
  static-ref or llm-judge alone cannot carry it.
712
+ Evidence anchors are repo-root-relative (`packages/app/src/foo.ts:42`); a basename
713
+ (`foo.ts:42`) is an unresolved anchor and blocks the done projection (`L4.anchor-unresolved`) —
714
+ see `code-verification/references/verdict-schema.md` § Basename-only anchors are unresolved anchors.
549
715
  ```
550
716
 
551
717
  **AC table shape (0948 R9).** The AC table must be exactly **4 columns** and **cell 3 must hold a
@@ -597,11 +763,34 @@ grading its own work defeats the stage. A continuation stage also dispatches fre
597
763
  same-task subagent exists (the earlier stage ran host-inline or below the dispatch floor), when a
598
764
  host-owned gate sat between the stages, or when the platform cannot address completed subagents.
599
765
 
600
- **Timeout boundary (task 0727):** a dispatched subagent is governed by
601
- **the host platform's subagent limit, not the YAML timeoutMs** — `timeoutMs` stays not-applicable
602
- for host execution only — and before dispatch the driver must
603
- **record the governing timeout boundary and its source before dispatch** in the run log
604
- (e.g. `host timeout <ms> (<platform subagent limit|yaml timeoutMs>)`). If the dispatch reaches that
766
+ **Implement and test-fix workers never run the full gate (task 1142 R3).** Workers use the light tier as their iteration check (task 1142 R3) in exact order:
767
+ 1. the **changed-path matrix** (`code-implementation/SKILL.md` § Changed-path targeted checks) over the paths they touched;
768
+ 2. the resolved `quality-gate` script in `light` mode (source-repo probe, else the installed twin) (biome, per-workspace typecheck, rule presets, related tests).
769
+ Neither step takes the full-gate lock. The pipeline's `test` and `test-recheck` hops remain the only full gate. Carry
770
+ `--gate-log <path>` through to the child **verbatim**: never paraphrase it into "re-run the named
771
+ gate command". That paraphrase is not cosmetic — it sent a worker into a full gate run instead of
772
+ targeted iteration checks. The pipeline's next `test` (recheck)
773
+ stage delivers the deciding verdict.
774
+
775
+ **Null or timed-out dispatch (task 1134 R9).** The dispatch bound stays the existing YAML
776
+ `timeoutMs` (`implementTimeoutMs` / `stepTimeoutMs`), passed to the host dispatch as today — R9 adds
777
+ no new bound. A dispatch that returns **no result**, or that hits that bound, is a **recorded stage
778
+ failure**, not a fallback trigger: record the stage failed with `dispatch: null-result|timeout`,
779
+ follow the stage's normal failure edge (implement's default `fail` policy routes the run to
780
+ `failed`), and start **no** host-inline attempt of that stage. R1–R4 cover a *pre-dispatch* capacity
781
+ refusal (the executor must shift); R9 covers the *dispatched-but-unproductive* case.
782
+
783
+ **Timeout boundary (task 0727, amended by task 1108):** when the host's dispatch tool accepts a
784
+ per-dispatch timeout (pi: subagent `timeoutMs`, default 30 min — pi-subagents `docs/tool-reference.md`
785
+ line 97), the driver passes the stage's resolved YAML
786
+ `timeoutMs` (implement → `implementTimeoutMs`) and records `host timeout <ms> (yaml timeoutMs)`.
787
+ When it does not, a dispatched subagent is governed by
788
+ **the host platform's subagent limit, not the YAML timeoutMs**, and the driver records
789
+ `host timeout <ms> (platform subagent limit)`. `timeoutMs` stays not-applicable for host-inline
790
+ execution only. Either way the driver must
791
+ **record the governing timeout boundary and its source before dispatch** in the run log. Rationale:
792
+ the operator sets one budget in one place; a host silently imposing its own 30 min default is what
793
+ killed the P1 implement dispatches (1096, 1099). If the dispatch reaches that
605
794
  boundary, **a dispatch timeout is a started-subagent failure**: the no-replay rule above and the
606
795
  stage's declared YAML error policy govern (implement's default `fail` policy routes the run to
607
796
  `failed`); it is never a host re-execution. Recovery follows the
@@ -624,139 +813,48 @@ Run-log stamps (task 0727): every appended line is prefixed with an **ISO-8601 U
624
813
  (`YYYY-MM-DDTHH:MM:SSZ`, e.g. `2026-08-31T17:51:11Z`); the exact-template provenance lines above
625
814
  keep their exact content after the stamp prefix. This normalization is contractual:
626
815
  **bare local-clock stamps are prohibited** — a hand-appended `[stage 12:31]` form mixes timezones
627
- in one file and makes the run unauditable (task 0726 mixed both forms).
816
+ in one file and makes the run unauditable (task 0726).
628
817
 
629
818
  ## Structured trace emission (ADR-117, task 0868)
630
819
 
631
- `.spur/memory/runs/<run-id>.md` is the human half of the two-file run record — evidence, **not the record
632
- of truth**. A run's
633
- observability is a property of the run, so the inline driver owes the same structured trace the
634
- engine subprocess writes — and it owes it through the **same writer**, never a parallel
635
- implementation. The shared writer is `WorkflowActionTraceWriter`
636
- (`packages/app/src/workflow/action-trace.ts`): the same decorator the engine composition installs
637
- around `DbWorkflowPersistenceAdapter`, so the two surfaces call one emission path and one run-row
638
- closure path and cannot drift.
820
+ Read [structured-trace-emission.md](structured-trace-emission.md) when the first trace event is
821
+ emitted — the action vocabulary, the emit shape and the parse/close contract.
639
822
 
640
- The driver reaches it through the existing run delegate (`$SETUP_SCRIPT`,
641
- `plugins/sp/scripts/inline-run-setup.ts`) — no new entry point, no second resolution chain:
823
+ ## Post-gate tree freeze (task 1135 R3/R4/R5)
642
824
 
643
- - **Every executed action** — after the action settles, whether it ran host-inline or via a native
644
- subagent — append its provenance line as before, then record the boundary:
825
+ The quality gate's `proofDigest` certifies a specific tree. Two rules protect it, and both are
826
+ **ordering plus early detection** — a host session cannot intercept a file write, so the contract is:
645
827
 
646
- ```bash
647
- bun "$SETUP_SCRIPT" --action --run-id "$RUN_ID" --node <state-id> --kind <action-kind> \
648
- --status <done|failed> --ok <true|false> --duration-ms <measured-ms> [--estimated]
649
- ```
650
-
651
- `<state-id>` is the current YAML state id (the `node`), `<action-kind>` the YAML action kind
652
- (`agent.run`, `shell`, `note`, `doctor.probe`, …). `--status` is `done` when the action settled
653
- under its declared error policy and `failed` otherwise; `--duration-ms` is the wall clock the
654
- driver measured around the action, and `--estimated` marks a duration the driver did **not**
655
- time (a value reconstructed after the action returned). The row's provenance stamp rides
656
- `action_runs.result_json` as `{provenance:'host-reported', estimated}`, and the projection
657
- exposes it per attempt — `provenance` is `host-reported` for every inline row and `unknown`
658
- for an engine-written or pre-stamp row, so the Board can label a host-reported duration
659
- instead of presenting it as measured. This writes the `action_runs` row (node, kind, status,
660
- `ok`, `duration_ms`, `run_id`) the engine would have written, so the run's rows are queryable
661
- by run id (`spur workflow progress <run-id>`, `ActionRunDao`) without reading the text log.
662
- The writer
663
- back-dates the row's `started_at` from its own `completed_at` minus the measured duration
664
- (0887 R8), so `completed_at − started_at == duration_ms` exactly; a back-date failure is
665
- recorded (`action.backdate`) and never affects the run.
666
-
667
- - **A state with several actions (1007 R5)** — emit the whole state's boundaries in one call
668
- instead of one `--action` invocation per action. Write a JSON array
669
- (`[{node,kind,status,ok,durationMs,estimated?}, …]` — the `--action` fields, `estimated`
670
- optional and `false` when absent) to a temp file and pass it with `--actions-file`:
671
-
672
- ```bash
673
- bun "$SETUP_SCRIPT" --actions-file <actions.json> --run-id "$RUN_ID"
674
- ```
828
+ 1. **Every tree edit belongs to `implement`, or to the bounded `test-fix` remediation hop** — stray-file
829
+ cleanup, anchor qualification, contract pins, doc edits, and task-section writes other than the
830
+ pipeline-owned `## Testing`/`## Review`/`## Solution`. After `test` captures `proofDigest`, no
831
+ other state edits the tree. A genuine later correction goes through `test-fix → test-recheck`,
832
+ which re-captures the digest and re-snapshots the tree state.
833
+ 2. **Review joins the proof bracket.** The pipeline now compares `proofDigestNow` against
834
+ `proofDigest` at **review** entry as well as verify and record, so a drifted tree stops the run
835
+ before a review dispatch is spent. The mismatch routes through the existing tripwire to `failed`;
836
+ there is no new re-certify path.
675
837
 
676
- Every row is recorded through the same writer as `--action` (one `action_runs` row per entry);
677
- the batch is validated in full before the first write, so an invalid row or unreadable file
678
- exits `1` with `{"ok":false}` and leaves **no** partial rows — fix the batch and re-emit. On
679
- success it prints `{"ok":true,"runId":…,"recorded":<n>}` and exits `0`. Row emission stays
680
- best-effort exactly like `--action`: if a row's write fails mid-batch, the failure is recorded
681
- to the run record and the call reports `{"ok":false,…,"error":…}` but still exits `0` — the run
682
- continues; never retry the batch or backfill by hand. `--actions-file` is exclusive with the
683
- other mode flags (`--action`, `--decide`, `--close`, …): mixing them is a usage error (exit 2),
684
- and a mixed call must be corrected, not silently split.
685
-
686
- - **A `decide` action (0941)** — the driver never executes the DecisionMaker itself; it delegates
687
- to the same app runner the engine registers, which writes the resultFile row (schemaVersion 1)
688
- and returns the decision, then the delegate records the `action_runs` row (`kind=decide`)
689
- through the same writer as every other action:
838
+ **A mismatch names the drifted paths (R5).** At gate entry the pipeline snapshots the non-corpus tree
839
+ state (`git status --porcelain=v1 -uall` plus `git diff --name-only HEAD`, excluding
840
+ `docs/tasks*` / `docs/features*`) into `.spur/run/<run-id>-gate-paths.txt`. On a mismatch the
841
+ `proof.fingerprint` action diffs the current state against that file and reports the changed paths in
842
+ both the tripwire event and the run log (`proof-compare-failed … drifted paths: …`), so the operator
843
+ reads *which file* moved, not only the `D1`/`D2` pair. An **untracked** file outside those exclusions
844
+ is part of the certified set: the digest's git-tree half stages everything outside the excluded
845
+ globs, so a stray file added after the gate is real drift and must be named (the behaviour is
846
+ documented beside `DEFAULT_EXCLUDE_GLOBS` in `proof-input-fingerprint.ts`).
690
847
 
691
- ```bash
692
- bun "$SETUP_SCRIPT" --decide --run-id "$RUN_ID" --node <state-id> --options-json <options-file>
693
- ```
694
-
695
- The options JSON mirrors the YAML `decide` options (`id`, `method: choice|noul`, `question`,
696
- `choices`/`default`, optional `evidence`, optional `minConfidence`, `resultFile`); paths resolve
697
- against the project workdir. The decision never pauses and never fails the run for model
698
- problems: a degraded outcome (feature switch off, no backend, error, timeout, low confidence)
699
- prints `ok:true` with `degraded:true`, the declared `reason`, and `value = default`, and exits
700
- `0` — route on the resultFile's `.value` with the declared file guards. Every row carries
701
- `source: model|default` (0976 R2), and the delegate appends
702
- `decide node=<id> value=<v> source=<s> reason=<r>` to the run log, so a declared-default
703
- fallback is never read as a model decision. Only an invalid options
704
- schema exits `1` (fail closed), and usage errors exit `2`. The `action_runs` trace row is
705
- best-effort exactly like `--action`.
706
-
707
- - **At the run's declared terminal state** — before the driver reports the run complete, close the
708
- row so a successful inline run is never left non-terminal for `spur workflow clean` to reap as
709
- stale:
710
-
711
- ```bash
712
- bun "$SETUP_SCRIPT" --close --run-id "$RUN_ID" --status <done|failed|paused>
713
- ```
848
+ ## Record & done sequencing
714
849
 
715
- `--status` is the declared terminal state's verdict, not a guess: a run that reached a terminal
716
- state is `done`; a run halted by a failing action under its error policy is `failed`. On success
717
- the close reports the recorded evidence: `{"ok":true,"runId":…,"actionRows":<n>}`.
718
- After the close, the invoking command prints the measured
719
- [execution summary](dev-operations.md#execution-summary) for `$RUN_ID` unless `--no-summary`.
720
-
721
- **Zero-row done closes are a named failure (task 0975 R2).** A run closed `done` with **zero**
722
- `action_runs` rows (`actionRows:0`) finalizes the row but exits `1` with
723
- `{"ok":false,"code":"NO_ACTION_ROWS","actionRows":0}` — a run that claims success without a
724
- single recorded action is exactly the untraced-runs gap ADR-117 closes, so the driver must
725
- surface the code in its final report instead of reporting a clean close. The failure must NOT
726
- be repaired by backfilling rows: the run row is already terminal, and hand-written rows are
727
- forbidden (ADR-117) — record the finding and let the task's evidence show the gap. A `failed`
728
- close with zero rows stays a clean `0` (a halt before the first boundary legitimately records
729
- nothing), and any `done` close with `actionRows ≥ 1` exits `0`.
730
-
731
- **Best-effort at the action boundary only (ADR-117).** An `--action` persistence failure is
732
- recorded — the delegate appends a `trace-emission-failed` line to `.spur/memory/runs/<run-id>.md` and
733
- prints `{"ok":false}` on stdout — and the run continues to its declared terminal state; the
734
- delegate exits `0` for that outcome and the driver must never treat an emission failure as a run
735
- failure, retry it in a loop, or substitute a hand-written row. The run-row closure (`--close`) is
736
- bookkeeping, not trace emission, and is **not** best-effort: a missing run row or a persistence
737
- failure exits `1` with `{"ok":false}` and a named error (a missing row also carries
738
- `code:"RUN_NOT_FOUND"`), because a silently `running` row is exactly the stale state
739
- `spur workflow clean` reaps as `failed`. Exit `2` means the invocation itself was malformed
740
- (missing `--node`/`--kind`/`--status`/`--ok`, a miscased `--ok`, a missing or malformed
741
- `--duration-ms`, or an unsafe run id) and must be corrected, not ignored.
742
-
743
- Emission is not optional and not deferred: an inline run that skips it reintroduces the
744
- 1,011-untraced-runs gap ADR-117 exists to close.
745
-
746
- Transition guards are not advisory. Execute the declared guard exactly, in order, with the same
747
- resolved variables and artifacts. `--no-lifecycle` remains bookkeeping only; the YAML's task checks,
748
- verdict gate, record step, and done guard all remain authoritative.
749
-
750
- ## Record & done sequencing (dogfood 2026-08-21, feature A3)
751
-
752
- Order matters for the `testing → done` hop. The A3 batch hit the same clobbering spiral on two
753
- tasks (0617, 0619) because the sections were hand-written **before** the verdict artifact existed:
850
+ Order matters for the `testing → done` hop: the sections must not be hand-written **before** the
851
+ verdict artifact exists (tasks 0617, 0619 — the clobbering spiral).
754
852
 
755
853
  1. **Write the verdict artifact first.** `spur task record --solution-from-diff --transition testing`
756
854
  reads `.spur/run/<wbs>-verdict.json` (default attempt output) on every invocation and atomically retains the recorded verdict under `.spur/memory/evidence/`. A missing or malformed artifact
757
855
  yields **UNKNOWN**: bare Testing receives a "No requirements recorded" stub, while already-authored
758
- Testing is preserved. `--solution-from-diff` backfills only a bare Solution. The A3 clobbering above
759
- describes the historical behavior, corrected by the authored-Testing safeguard. Creating the
856
+ Testing is preserved. `--solution-from-diff` backfills only a bare Solution. The clobbering above is
857
+ the historical behavior, corrected by the authored-Testing safeguard (tasks 0617, 0619). Creating the
760
858
  artifact first (PASS, with requirement rows keyed by scenario title) remains the standard order;
761
859
  re-running record after a real verdict arrives refreshes Testing from that verdict.
762
860
 
@@ -775,6 +873,12 @@ tasks (0617, 0619) because the sections were hand-written **before** the verdict
775
873
  2. **Done-probe before done.** Run the check projected to `done` (`spur task check <wbs> --as done`
776
874
  via the `TaskCheckService` probe pattern) — it surfaces `L3.unchecked-checklist` (flip `- [ ]` → `- [x]`)
777
875
  and `L3.required-section-placeholder` before the transition, not after.
876
+ **Anchor repair is the YAML implement stage's job, not the driver's** (task 1109 R4):
877
+ `task-pipeline.yaml` runs `$spurBin task migrate-anchors --wbs $wbs --json ; exit 0` after the
878
+ format step and before `test` captures the proof digest, so unique basename anchors are already
879
+ repo-relative by the time this probe runs. The driver does not hand-patch anchors: anything the
880
+ scoped pass leaves (ambiguous basenames, line drift, `L4.anchor-subject-mismatch`) is reported by
881
+ this probe and belongs to the implementer to re-anchor.
778
882
  3. **Solution change-map anchor rule (L4.anchor-subject-mismatch).** A Solution change-map table must
779
883
  list **one `file:line` per row**. A ·-joined paragraph makes every anchor's "subject" the other
780
884
  anchors and trips the L4 subject check. Since 0804 R9, subject extraction ignores complete parsed
@@ -782,6 +886,77 @@ tasks (0617, 0619) because the sections were hand-written **before** the verdict
782
886
  (`docs/help/cmd_example.md:12`) is checked exactly like any other row — cite an **existing file**
783
887
  with a **valid line or line range** whose content names the requirement's subject. A real absent
784
888
  symbol, nonexistent file or invalid range still reports; never replace a citable row with prose.
889
+ 4. **Stage through the commit guard (1129 R2/R3/R5).** The per-task commit that follows the record
890
+ hop stages only the files THIS run wrote — `git add -A` and `git add .` are **forbidden** in
891
+ every driver commit step (they staged a concurrent session's files in `a94f9f431` and
892
+ `36f274590`). `check` first and record a non-empty `foreign` list in the run report; `stage`
893
+ exits 2 when it refuses a listed path (foreign) and 3 when a target is unmerged or still carries
894
+ conflict markers — both mean "resolve it, do not commit":
895
+
896
+ ```bash
897
+ GUARD=plugins/sp/scripts/commit-guard.ts; [ -f config/plugin-scripts.json -a -f "$GUARD" ] || GUARD="$(superskill script path sp commit-guard.mjs 2>/dev/null)";
898
+ bun "$GUARD" check --run "$RUN_ID"
899
+ bun "$GUARD" stage --run "$RUN_ID" -- <files this run wrote> \
900
+ || { echo "record halt: commit-guard refused a path (foreign, unmerged or conflict-marked); nothing committed" >&2; exit 1; }
901
+ git commit -m "<type>(<scope>): <wbs> <summary>"
902
+ ```
903
+
904
+ 5. **Print the run's execution summary (1146).** The terminal `--close` returns `summaryFile`; print
905
+ that file's contents verbatim under an `### Execution summary` heading, and pass `--no-summary`
906
+ through to `--close` when the operator asked for it. The window is the run row's `started_at` →
907
+ close time — the driver records no `SINCE` and assembles no measurement of its own
908
+ (structured-trace-emission.md § the close step owns the contract).
909
+
910
+ ## Running the full gate (task 0872 / 1127 R7)
911
+
912
+ The full gate serializes behind a host-wide lock and legitimately runs 10-25 minutes. Two rules keep
913
+ it from becoming a hazard (both violated once on 2026-10-10, blocking every other session for ~11
914
+ minutes and aborting the run's own verification):
915
+
916
+ - **Never wrap it in an external `timeout`, and never treat its runtime as a hang.** An outer bound
917
+ that fires mid-suite leaves the holder alive holding the lock while the caller believes it is gone.
918
+ - **Run it detached and poll the log**, so losing the foreground call cannot lose the holder:
919
+
920
+ ```bash
921
+ nohup bash -c 'bun run spur-check > /tmp/gate.log 2>&1; echo "EXIT=$?" >> /tmp/gate.log' &
922
+ ```
923
+
924
+ - **Never kill a gate holder whose pid is alive.** The waiter line `waiting for holder (… held Nms)`
925
+ is printed only after `isProcessAlive(held.claim.pid)` succeeded, so a printed holder is a live one
926
+ by construction; only the lock's own dead-pid reclaim is authorised to clear a claim.
927
+
928
+ ## Runtime interface contracts (from run evidence)
929
+
930
+ Each item below cost a driver cycle to discover. They are contracts, not tips.
931
+
932
+ - **`--decide --options-json <file>` takes one option object, never an array.** Shape:
933
+ `{id, decision, params?, evidence? | instructions?, resultFile}`; `evidence` and `instructions` are
934
+ mutually exclusive (`CatalogDecideOptionsSchema`). A JSON array parses as neither dialect and
935
+ surfaces as `decide: invalid options — Invalid input` with no field detail. `resultFile` resolves
936
+ against the confirmed tree's cwd, so pass a worktree-relative path such as
937
+ `.spur/run/<wbs>-triage.decision`.
938
+ - **`task-diffstat.ts` reads `wbs` from the environment and writes an artifact.** Invoke it as
939
+ `wbs=<wbs> node "$(superskill script path sp task-diffstat.mjs)"`; stdout stays empty, so assert success on
940
+ `.spur/run/<wbs>-diffstat.json` and never on command output. Never the bare unguarded
941
+ source-repo path form — the script-contract check forbids it on a shipped surface because it
942
+ shadows the installed twin in a consumer.
943
+ - **The verify answer grammar is exact.** One `Verdict:` line, one `Confidence:` line, then
944
+ `### Per-Requirement Traceability` (`| Req | Status | Evidence |`) and
945
+ `### Acceptance Criteria Verification` (`| AC | Status | Evidence Type | Evidence |`). Requirement
946
+ statuses are `MET | PARTIAL | UNMET`; AC statuses add `N/A`; every AC row id must match a declared
947
+ scenario title verbatim, including its `(req: ...)` suffix. A `MET` row whose evidence hedges
948
+ (`likely`, `probably`, `seems`, `appears`, `possibly`, `presumably`) is rejected as
949
+ `evidence-hedged`, and a level the rows do not earn is rejected as `confidence-unwarranted` or
950
+ `confidence-understated`. `spur task verdict` lints before writing, so a rejected answer leaves no
951
+ artifact and exits non-zero.
952
+ - **Harness tooling refuses direct task-file writes.** Resolve a merge conflict in a task file by
953
+ choosing a git side, then applying the corpus delta through `spur task update` (for example
954
+ `--feature <id>`), never by editing the file.
955
+ - **A skill body has a byte budget** (20,000 B for `SKILL.md`); procedure detail belongs in
956
+ `references/`. Adding a clause to a SKILL.md can fail `R44 — skill BODY budgets`.
957
+ - **`.spur/run/` is volatile** (ADR-131, as amended): its contents are execution scratch. Anything
958
+ that outlives the run cites the durable owner (`.spur/memory/evidence/`, `.spur/memory/runs/`) or
959
+ the tracked Testing section.
785
960
 
786
961
  ## Failure contract
787
962