@gobing-ai/spur 0.4.0 → 0.4.1

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