@gobing-ai/spur 0.3.98 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +17 -0
  3. package/config/decisions/task-pipeline.yaml +55 -0
  4. package/config/plugin-scripts.json +15 -0
  5. package/config/rules/boundary/sp-runtime-path.yaml +2 -0
  6. package/config/rules/quality/coverage-gate.yaml +5 -3
  7. package/config/rules/strict/runtime-boundaries.yaml +11 -0
  8. package/config/rules/structure/protected-files.yaml +3 -0
  9. package/config/templates/task/brainstorm.md +1 -1
  10. package/config/templates/task/feature-impl.md +1 -1
  11. package/config/templates/task/issue.md +1 -1
  12. package/config/templates/task/meta.md +1 -1
  13. package/config/templates/task/review.md +1 -1
  14. package/config/templates/task/standard.md +1 -1
  15. package/config/workflows/task-pipeline.yaml +178 -18
  16. package/package.json +9 -9
  17. package/plugins/sp/README.md +14 -1
  18. package/plugins/sp/agents/super-reviewer.md +25 -11
  19. package/plugins/sp/commands/dev-changelog.md +5 -4
  20. package/plugins/sp/commands/dev-review-session.md +12 -7
  21. package/plugins/sp/commands/dev-run.md +5 -2
  22. package/plugins/sp/commands/dev-runall.md +5 -2
  23. package/plugins/sp/hooks/agent-lifecycle.ts +155 -0
  24. package/plugins/sp/hooks/fleet-guest-stop.ts +180 -0
  25. package/plugins/sp/hooks/hooks.json +45 -1
  26. package/plugins/sp/hooks/pi/guard-extension.ts +21 -0
  27. package/plugins/sp/lib/history-anatomy.generated.mjs +5 -0
  28. package/plugins/sp/lib/idea-handoff.generated.mjs +68 -10
  29. package/plugins/sp/lib/inline-run.generated.d.mts +1 -0
  30. package/plugins/sp/lib/inline-run.generated.mjs +83 -20
  31. package/plugins/sp/lib/residual-scan.generated.d.mts +5 -0
  32. package/plugins/sp/lib/residual-scan.generated.mjs +45 -5
  33. package/plugins/sp/lib/transcript.ts +160 -0
  34. package/plugins/sp/plugin.json +1 -1
  35. package/plugins/sp/scripts/history-anatomy-cache.mjs +5 -0
  36. package/plugins/sp/scripts/inline-run-setup.mjs +21 -20
  37. package/plugins/sp/scripts/inline-run-setup.ts +25 -31
  38. package/plugins/sp/scripts/persist-out-check.mjs +164 -0
  39. package/plugins/sp/scripts/persist-out-check.ts +185 -0
  40. package/plugins/sp/scripts/residual-scan.mjs +52 -15
  41. package/plugins/sp/scripts/residual-scan.ts +11 -11
  42. package/plugins/sp/scripts/run-summary.mjs +286 -0
  43. package/plugins/sp/scripts/run-summary.ts +241 -0
  44. package/plugins/sp/scripts/session-timeline.mjs +234 -0
  45. package/plugins/sp/scripts/session-timeline.ts +152 -0
  46. package/plugins/sp/scripts/task-diffstat.mjs +17 -5
  47. package/plugins/sp/scripts/task-diffstat.ts +25 -4
  48. package/plugins/sp/skills/code-verification/SKILL.md +8 -0
  49. package/plugins/sp/skills/code-verification/references/verdict-schema.md +17 -1
  50. package/plugins/sp/skills/fleet-join/SKILL.md +63 -0
  51. package/plugins/sp/skills/session-review/SKILL.md +46 -24
  52. package/plugins/sp/skills/spur-cli/SKILL.md +1 -0
  53. package/plugins/sp/skills/spur-cli/references/agent.md +108 -6
  54. package/plugins/sp/skills/spur-cli/references/decision.md +97 -0
  55. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +22 -0
  56. package/plugins/sp/skills/spur-composer/SKILL.md +27 -1
  57. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +40 -7
  58. package/plugins/sp/skills/spur-dev/references/dev-operations.md +28 -16
  59. package/plugins/sp/skills/spur-dev/references/execution-batch.md +278 -42
  60. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +17 -1
  61. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +16 -4
  62. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +139 -10
  63. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +6 -0
  64. package/schemas/decision-catalog.schema.json +226 -0
  65. package/schemas/spur-config.schema.json +25 -0
  66. package/spur.js +8856 -2564
  67. package/web/_astro/BoardApp.Bzr4OXrW.js +324 -0
  68. package/web/_astro/{BoardApp.Dflq5a8x.js → BoardApp.VeoxxLBy.js} +1 -1
  69. package/web/_astro/{TaskDetail.CK7oXddP.js → TaskDetail.LAyR_Pvn.js} +1 -1
  70. package/web/_astro/{arc.BIqkDATc.js → arc.f4e0OYLg.js} +1 -1
  71. package/web/_astro/{architectureDiagram-3BPJPVTR.D0B3SvWv.js → architectureDiagram-3BPJPVTR.SSIvxi-Z.js} +1 -1
  72. package/web/_astro/{blockDiagram-GPEHLZMM.C2YyKQmL.js → blockDiagram-GPEHLZMM.DSXIHOFf.js} +1 -1
  73. package/web/_astro/{c4Diagram-AAUBKEIU.DmtTYrt_.js → c4Diagram-AAUBKEIU.DG9Ubu85.js} +1 -1
  74. package/web/_astro/channel.CUFdtWhm.js +1 -0
  75. package/web/_astro/{chunk-2J33WTMH.HsRXbh7l.js → chunk-2J33WTMH.C7QU88Qh.js} +1 -1
  76. package/web/_astro/{chunk-4BX2VUAB.obE_P-R3.js → chunk-4BX2VUAB.26Z94xBj.js} +1 -1
  77. package/web/_astro/{chunk-55IACEB6.CjWTF5mz.js → chunk-55IACEB6.DYRiBkqw.js} +1 -1
  78. package/web/_astro/{chunk-727SXJPM.DMo799Ax.js → chunk-727SXJPM.D5Vfv5pe.js} +1 -1
  79. package/web/_astro/{chunk-AQP2D5EJ.-cE6JOKZ.js → chunk-AQP2D5EJ.DXHV47bN.js} +1 -1
  80. package/web/_astro/{chunk-FMBD7UC4.D6Rw6HIk.js → chunk-FMBD7UC4.D1suAfOs.js} +1 -1
  81. package/web/_astro/{chunk-ND2GUHAM.d5rBD73Z.js → chunk-ND2GUHAM.D6BmdHEY.js} +1 -1
  82. package/web/_astro/{chunk-QZHKN3VN.r3ox40Q3.js → chunk-QZHKN3VN.rnPU-Ak-.js} +1 -1
  83. package/web/_astro/{classDiagram-4FO5ZUOK.CGGbvjjJ.js → classDiagram-4FO5ZUOK.CvEfXIo1.js} +1 -1
  84. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CGGbvjjJ.js → classDiagram-v2-Q7XG4LA2.CvEfXIo1.js} +1 -1
  85. package/web/_astro/{cose-bilkent-S5V4N54A.CmKtPd4l.js → cose-bilkent-S5V4N54A.DkMisLSm.js} +1 -1
  86. package/web/_astro/{cynefin-OW5HDTMX.QNg5GXs8.js → cynefin-OW5HDTMX.C77Q-pF6.js} +1 -1
  87. package/web/_astro/{dagre-BM42HDAG.B1SabsjC.js → dagre-BM42HDAG.YaV8ce27.js} +1 -1
  88. package/web/_astro/{diagram-2AECGRRQ.RtDbNj89.js → diagram-2AECGRRQ.CUyGEKge.js} +1 -1
  89. package/web/_astro/{diagram-5GNKFQAL.IzZpuM-Y.js → diagram-5GNKFQAL.CRgd8dgj.js} +1 -1
  90. package/web/_astro/{diagram-KO2AKTUF.DPTF_fgv.js → diagram-KO2AKTUF.Bmzys9hq.js} +1 -1
  91. package/web/_astro/{diagram-LMA3HP47.CCyza39P.js → diagram-LMA3HP47.BvmJY4Lw.js} +1 -1
  92. package/web/_astro/{diagram-OG6HWLK6.BXAi8zcZ.js → diagram-OG6HWLK6.0hEhD30d.js} +1 -1
  93. package/web/_astro/{erDiagram-TEJ5UH35.DUDX7_MR.js → erDiagram-TEJ5UH35.Bjt8lrnm.js} +1 -1
  94. package/web/_astro/{flowDiagram-I6XJVG4X.C63QTh3N.js → flowDiagram-I6XJVG4X.CJZTjRDV.js} +1 -1
  95. package/web/_astro/{ganttDiagram-6RSMTGT7.CnlZJSwG.js → ganttDiagram-6RSMTGT7._VqoVafc.js} +1 -1
  96. package/web/_astro/{gitGraphDiagram-PVQCEYII.CTqIrOPC.js → gitGraphDiagram-PVQCEYII.CEDb7tVo.js} +1 -1
  97. package/web/_astro/index.Dg_3bOaJ.css +1 -0
  98. package/web/_astro/{infoDiagram-5YYISTIA.BYMiDFh9.js → infoDiagram-5YYISTIA.CgwifPpK.js} +1 -1
  99. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CRiQmI-z.js → ishikawaDiagram-YF4QCWOH.B99wx2J1.js} +1 -1
  100. package/web/_astro/{journeyDiagram-JHISSGLW.YQgWBsEU.js → journeyDiagram-JHISSGLW.2hRwJbEo.js} +1 -1
  101. package/web/_astro/{kanban-definition-UN3LZRKU.1ECnrrK1.js → kanban-definition-UN3LZRKU.BahJkKNC.js} +1 -1
  102. package/web/_astro/{linear.DzVA71yT.js → linear.DguUUe6V.js} +1 -1
  103. package/web/_astro/{mermaid.core.BK7P2Sr_.js → mermaid.core.CvgfmqVr.js} +4 -4
  104. package/web/_astro/{mindmap-definition-RKZ34NQL.BvXyWZK7.js → mindmap-definition-RKZ34NQL.B3qEA3x9.js} +1 -1
  105. package/web/_astro/{pieDiagram-4H26LBE5.BiXk1TW3.js → pieDiagram-4H26LBE5.FlWA-_ru.js} +1 -1
  106. package/web/_astro/{quadrantDiagram-W4KKPZXB.krvBH8lh.js → quadrantDiagram-W4KKPZXB.9bVn35X7.js} +1 -1
  107. package/web/_astro/{requirementDiagram-4Y6WPE33.ih1gL25c.js → requirementDiagram-4Y6WPE33.ChykIhct.js} +1 -1
  108. package/web/_astro/{sankeyDiagram-5OEKKPKP.CpUxkv4u.js → sankeyDiagram-5OEKKPKP.Z2Y3hkYb.js} +1 -1
  109. package/web/_astro/{sequenceDiagram-3UESZ5HK.clAfeN7y.js → sequenceDiagram-3UESZ5HK.J7JsHWDt.js} +1 -1
  110. package/web/_astro/{stateDiagram-AJRCARHV.DpXWb8-m.js → stateDiagram-AJRCARHV.DAPbiCSS.js} +1 -1
  111. package/web/_astro/{stateDiagram-v2-BHNVJYJU.ClZb9GIH.js → stateDiagram-v2-BHNVJYJU.D2jvSi0q.js} +1 -1
  112. package/web/_astro/{timeline-definition-PNZ67QCA.BLcX5dh7.js → timeline-definition-PNZ67QCA.psW2ci29.js} +1 -1
  113. package/web/_astro/{vennDiagram-CIIHVFJN.oGgpKMpx.js → vennDiagram-CIIHVFJN.DlILYVmg.js} +1 -1
  114. package/web/_astro/{wardleyDiagram-YWT4CUSO.BPHtMtt-.js → wardleyDiagram-YWT4CUSO.xNWwLJnn.js} +1 -1
  115. package/web/_astro/{xychartDiagram-2RQKCTM6.BNZOPYSk.js → xychartDiagram-2RQKCTM6.EU_BuH7V.js} +1 -1
  116. package/web/board-runtime.json +1 -1
  117. package/web/index.html +2 -2
  118. package/web/_astro/BoardApp.CYbe-OTb.js +0 -322
  119. package/web/_astro/channel.JuKVyuhZ.js +0 -1
  120. package/web/_astro/index.CbNz17Sx.css +0 -1
@@ -142,7 +142,10 @@ the human/native presentation layer — labels are display addresses only, never
142
142
  safety checks succeed, create/adopt and `cd` into the execution tree; confirm absolute cwd,
143
143
  branch, base SHA, and ownership. An invalid/empty target, unsupported mode, ambiguous ownership,
144
144
  or stale target stops without creating a tree or discarding work. All subsequent tools, agents,
145
- corpus writes, and run artifacts use the confirmed execution tree.
145
+ corpus writes, and run artifacts use the confirmed execution tree — **re-selected per call, not
146
+ inherited**: a host shell call starts in an arbitrary directory and carries no cwd from the
147
+ previous call, so every later call runs inside the **Per-call execution-tree pin** protocol
148
+ below (task 1058), which consumes the identity confirmed here.
146
149
  8. **Publish the workflow inventory (R4), BEFORE reading the YAML.** Resolve the selected workflow
147
150
  through the same project/bundled resolver as execution and run
148
151
  `spur workflow show <resolved-file> --no-logo --format todo --json`. Validate the projection with
@@ -156,6 +159,11 @@ the human/native presentation layer — labels are display addresses only, never
156
159
  resolved definition identity against the bound `__definitionDigest`; a mismatch is identity drift
157
160
  and fails closed (step 8 already caught projection-side drift; this re-checks the same definition
158
161
  the interpreter will execute).
162
+ - **Runtime-written vars are re-read, never cached.** A var a later state writes (`proofDigest`,
163
+ `proofDigestNow`, `taskSpecPath`, `featureSpecPath`, `taskPriority`, `mode`, `gateFindings`) is a
164
+ run artifact, not a YAML default: re-resolve it from `.spur/run/`/`.spur/memory/runs/` at every
165
+ state boundary, and never let a host-side copy of the invocation vars shadow it — a stale empty
166
+ `proofDigest` silently fails the bound `run.artifact` equivalence and the `record` proof guard.
159
167
  - **Layer 2** = the active state's `onEnter` actions (`kind` + resolved `input`/`command`), from
160
168
  the YAML read here, shown only for the active state.
161
169
  - **Refresh cadence** = stage boundaries only (when the current state changes after a transition),
@@ -186,6 +194,12 @@ the human/native presentation layer — labels are display addresses only, never
186
194
  This is required for the normal `testing → done` provenance guard. Planning pipelines have no
187
195
  task lifecycle link and skip this task-specific action.
188
196
 
197
+ **Run it from the task's execution tree.** Every tree has an isolated `.spur` DB, so a link
198
+ recorded from the invoking tree never satisfies a worktree's guard — the exact failure that
199
+ forced audited `--provenance-bypass` on tasks 1029/1030 (E93). Under `--worktree`, enter the
200
+ worktree first and run this command (and every lifecycle-touching command: `task record`,
201
+ `task update <wbs> testing|done`) from the worktree root.
202
+
189
203
  ### Idea-pipeline quick start (0887 R1/R2)
190
204
 
191
205
  Minimum files to read for `/sp:dev-idea` inline runs — then drive `idea-pipeline.yaml`:
@@ -215,6 +229,91 @@ Expected artifacts per stage (all run-scoped under `.spur/run/<run-id>-*`):
215
229
  | ready-prepare | `-idea-ready.json` |
216
230
  | handoff-finalize | `-idea-handoff.md` |
217
231
 
232
+ ## Per-call execution-tree pin (task 1058)
233
+
234
+ A host shell tool call may start in an arbitrary directory; no earlier call's `cd` is input to the
235
+ next call. During inline run `95522d21` that drift silently landed corpus writes in the invoking
236
+ tree instead of the run worktree. Every host-session corpus command — reads, writes, absolute
237
+ section-input files and output artifacts — therefore selects the confirmed execution tree **within
238
+ the same tool call**, in a self-contained subshell. The identity is never re-derived: it is the
239
+ tree path, branch and resolved Spur invocation confirmed at bootstrap isolation (step 7) and, for
240
+ batch worktrees, recorded in the WT-3 marker (execution-batch.md). The resolved invocation is
241
+ field 4 of the dispatch payload — never a bare `spur`.
242
+
243
+ Template — every host shell call follows this shape; paths are quoted because trees may contain
244
+ spaces:
245
+
246
+ ```bash
247
+ (
248
+ cd -- "$SPUR_TREE" || { echo "tree missing: expected $SPUR_TREE" >&2; exit 1; }
249
+ ACTUAL_TREE="$(pwd -P)"
250
+ [ "$ACTUAL_TREE" = "$SPUR_TREE" ] || { echo "tree mismatch: expected $SPUR_TREE, got $ACTUAL_TREE" >&2; exit 1; }
251
+ GIT_TOP="$(git rev-parse --show-toplevel)" || exit 1
252
+ [ "$GIT_TOP" = "$SPUR_TREE" ] || { echo "toplevel mismatch: expected $SPUR_TREE, got $GIT_TOP" >&2; exit 1; }
253
+ ACTUAL_BRANCH="$(git branch --show-current)"
254
+ [ "$ACTUAL_BRANCH" = "$SPUR_BRANCH" ] || { echo "branch mismatch: expected $SPUR_BRANCH, got $ACTUAL_BRANCH" >&2; exit 1; }
255
+ exec <resolved-spur-invocation> task show <wbs> --json # the already-resolved CLI command
256
+ )
257
+ ```
258
+
259
+ Rules (R1):
260
+
261
+ - **Pin reads as well as writes.** `task show`, `feature show`, `workflow show` and `task check`
262
+ use the same subshell — a read from the wrong tree is a wrong answer, not a harmless one.
263
+ - **Validate the returned `filePath`.** A pinned `task show --json` must return a `filePath`
264
+ inside the expected configured corpus/tree. Configured task folders may intentionally live
265
+ outside the default `docs/tasks` — check membership in the configured folder set, not one
266
+ literal path.
267
+ - **Prove sections with a fresh pinned read.** After a section write, re-run a pinned
268
+ `task show <wbs> --json` and assert the new body there; a heading-only grep is not proof of
269
+ section contents.
270
+ - **Resolve section input/output paths before changing tree.** `--section <name> --from-file
271
+ <path>`, `answerFile`/`expectFile` and run artifacts are resolved against the confirmed tree
272
+ (dispatch field 5) before the subshell `cd`s.
273
+ - **Fail closed before writing.** A failed `cd` or any identity mismatch exits nonzero naming
274
+ expected vs actual, before the CLI runs — there is no designated exit code; consumers read
275
+ nonzero and the named expected/actual trees. A PWD assertion alone cannot select a tree: `cd`
276
+ first, then verify the physical path and repository identity.
277
+ - **Native tool cwd options are additive.** A tool-level cwd/`cd` pin may additionally set the
278
+ call's directory, but never replaces the identity checks above.
279
+ - **Host-only scope.** Engine `shell` actions already bind `context.workdir`
280
+ (`packages/app/src/workflow/actions/shell.ts:98`) — leave them alone. No service-wide
281
+ `process.chdir`, no new public flag, no helper framework.
282
+
283
+ The `spur …` command examples in this driver (run setup, workflow inventory, `task run-link`,
284
+ fingerprint capture, record/done sequencing) abbreviate the pinned subshell form for readability;
285
+ each executes inside the template above with the confirmed tree, branch and resolved invocation
286
+ substituted. A dispatched subagent inherits the same duty: its own tool calls may each start in
287
+ an arbitrary directory, so every shell call the delegate makes re-pins the execution tree with
288
+ the identity supplied in the dispatch payload — never re-derived.
289
+
290
+ ## Chunked implement dispatch contract (cap-limited worker models, task 1066)
291
+
292
+ Cap-limited worker executors die on monolithic implement briefs: the output cap is consumed by
293
+ one giant thinking block before any file action lands. Recognized failure signature —
294
+ consecutive implement dispatches end with `stopReason=length` and **zero output** (no tool
295
+ call, no file write), optionally with a `contact_supervisor` call mid-death. The trigger is
296
+ the observed signature, never a model name: any executor that exhibits it is treated as
297
+ cap-limited for the rest of the run.
298
+
299
+ Before dispatching an `implement` stage to a worker executor:
300
+
301
+ 1. **Chunk the brief.** Split the implement scope into small, independent dispatches ordered
302
+ by dependency (e.g. test cases → doc rewrite → task sections), one bounded objective per
303
+ dispatch. Never pack the whole task plan into one dispatch.
304
+ 2. **Cap thinking.** Instruct the worker to keep every thinking block under ~150 words and act
305
+ between thoughts; never plan the whole task in one block.
306
+ 3. **Fallback ladder.** A worker still dying with the signature: re-chunk the remaining scope
307
+ smaller once; still failing → execute the stage host-inline (the driver's inline stage
308
+ fallback) and record the fallback in the run log.
309
+
310
+ Evidence (2026-10-02, task 1059 implement stage, runall-D63-2ebbd97c): 4/4 single-shot
311
+ implement dispatches died `stopReason=length` on a 16k-output-cap worker; the same scope in
312
+ three chunked dispatches with the thinking cap succeeded 3/3. See the pitfalls 2026-10-02
313
+ entry and `.spur/memory/runs/runall-D63-2ebbd97c.md`.
314
+
315
+ ## Comprehensive-check retention and evidence (R7/R8)
316
+
218
317
  ## Comprehensive-check retention and evidence (R7/R8)
219
318
 
220
319
  **R7 — comprehensive checks stay at their owning boundaries.** Quick readiness and plan projection are
@@ -248,9 +347,13 @@ or terminal close. Measure its actual duration and preserve its declared failure
248
347
 
249
348
  ```bash
250
349
  bun "$SETUP_SCRIPT" --action --run-id "$RUN_ID" --node <state-id> --kind <action-kind> \
251
- --status <done|failed> --ok <true|false> --duration-ms <measured-ms>
350
+ --status <done|failed> --ok <true|false> --duration-ms <measured-ms> [--estimated]
252
351
  ```
253
352
 
353
+ `--duration-ms` is the wall clock measured around the action. Pass `--estimated` when you did
354
+ **not** time it — for example a duration reconstructed after a subagent returned — so the row is
355
+ labelled instead of presented as measured. `--estimated` is valid only with `--action`.
356
+
254
357
  For a multi-action state, `--actions-file` may emit the measured boundaries together before leaving
255
358
  that state. Follow [Structured trace emission](#structured-trace-emission-adr-117-task-0868) for the
256
359
  payload, delegated `decide` emission and best-effort failure handling. Never retry a failed batch
@@ -263,6 +366,14 @@ Action semantics come from the YAML and the workflow action contract:
263
366
  - `note` — append the expanded message to the inline run log.
264
367
  - `doctor.probe` — run the declared Spur doctor once, persist its status file, and apply any
265
368
  `setVars` result (including a resolved executor) before the next action or state.
369
+ **Role-map mode has no inline surface (task 1088 session finding).** A `doctor.probe` that
370
+ declares `roles` resolves them through `AgentService.resolve`, which is reachable only in-process
371
+ (the engine composition injects the service); no CLI verb exposes it, so the host session cannot
372
+ reproduce `__executor.<role>` pins. The driver therefore probes the host-session agent instead,
373
+ writes that verdict to the declared status file, and records one run-log line naming the
374
+ substitution. Nothing downstream degrades: `__executor.<role>` pins are consumed by the
375
+ subprocess dispatch path, and the inline path executes its stages in the host session without
376
+ reading them. Never fabricate pins to satisfy the shape, and never skip the status file.
266
377
  - `file.read.into-var` — read the declared file into the declared run variable before subsequent
267
378
  actions/guards.
268
379
  - `hitl.confirm` — under `profile=auto`, follow the YAML's auto-skip transition. Otherwise pause,
@@ -296,7 +407,8 @@ Action semantics come from the YAML and the workflow action contract:
296
407
  `verify-verdict`: verdict `PASS`), `proofBinding: current` is honored against a freshly captured
297
408
  proof digest — capture it with `bun "$SETUP_SCRIPT" --fingerprint --task-file <task path>
298
409
  [--feature-file <feature path>]`, the same entry point used at Run setup, which prints the engine
299
- `sha256:<hex>` digest. Run it from the worktree root (cwd feeds the git-tree half of the digest)
410
+ `sha256:<hex>` digest. Run it inside a per-call pin subshell for the confirmed execution tree
411
+ (the cwd feeds the git-tree half of the digest)
300
412
  and pass the same `--feature-file` the run folded in — omitting it, or running from elsewhere,
301
413
  yields a different digest and the mismatch surfaces later as a refused `run.artifact`
302
414
  registration — and the run-scoped review-completion marker exists — then appends one provenance
@@ -314,6 +426,10 @@ Action semantics come from the YAML and the workflow action contract:
314
426
 
315
427
  **Native-subagent dispatch (R2 eligibility, evaluated before each action):**
316
428
 
429
+ Before dispatching an `implement` stage to a worker executor, apply the chunked implement
430
+ dispatch contract (§ Chunked implement dispatch contract): chunk the brief, cap thinking
431
+ blocks, and know the fallback ladder before spending a dispatch.
432
+
317
433
  1. The invocation is one of the two interactive inline full-pipeline surfaces (`dev-run --mode full`
318
434
  or sequential `dev-runall`) and the resolved selector is inline — i.e. `--agent` **omitted**
319
435
  (0687 R1 default) or `--agent inline` passed explicitly (0687 R2 generalized from omit-only).
@@ -391,7 +507,10 @@ boundary, and a delegate left to re-derive them re-derives them against its own
391
507
  that invocation and MUST NOT rely on a bare `spur`: a competing `spur` earlier on the delegate's
392
508
  PATH otherwise wins. Setting `SPUR_BIN` alone does **not** change bare-command resolution — only
393
509
  using the supplied invocation does. Spur-owned scripted calls take it through the existing
394
- `--spur-bin` flag rather than a new mechanism.
510
+ `--spur-bin` flag rather than a new mechanism. A delegate's tool calls may each start in an
511
+ arbitrary directory, so every shell call the delegate makes re-pins the execution tree per the
512
+ **Per-call execution-tree pin** protocol using this supplied identity — never an inherited cwd
513
+ and never a re-derivation.
395
514
  5. The **resolved absolute output path** (`answerFile`/`expectFile`, resolved as above) and the
396
515
  **owning stage's artifact contract** — for a verify stage, the compact contract below.
397
516
  6. **The implement-stage acceptance-evidence requirement** — for the implement stage and any stage
@@ -526,23 +645,29 @@ The driver reaches it through the existing run delegate (`$SETUP_SCRIPT`,
526
645
 
527
646
  ```bash
528
647
  bun "$SETUP_SCRIPT" --action --run-id "$RUN_ID" --node <state-id> --kind <action-kind> \
529
- --status <done|failed> --ok <true|false> --duration-ms <measured-ms>
648
+ --status <done|failed> --ok <true|false> --duration-ms <measured-ms> [--estimated]
530
649
  ```
531
650
 
532
651
  `<state-id>` is the current YAML state id (the `node`), `<action-kind>` the YAML action kind
533
652
  (`agent.run`, `shell`, `note`, `doctor.probe`, …). `--status` is `done` when the action settled
534
653
  under its declared error policy and `failed` otherwise; `--duration-ms` is the wall clock the
535
- driver measured around the action. This writes the `action_runs` row (node, kind, status, `ok`,
536
- `duration_ms`, `run_id`) the engine would have written, so the run's rows are queryable by run id
537
- (`spur workflow progress <run-id>`, `ActionRunDao`) without reading the text log. The writer
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
538
663
  back-dates the row's `started_at` from its own `completed_at` minus the measured duration
539
664
  (0887 R8), so `completed_at − started_at == duration_ms` exactly; a back-date failure is
540
665
  recorded (`action.backdate`) and never affects the run.
541
666
 
542
667
  - **A state with several actions (1007 R5)** — emit the whole state's boundaries in one call
543
668
  instead of one `--action` invocation per action. Write a JSON array
544
- (`[{node,kind,status,ok,durationMs}, …]` — same fields the `--action` flags carry) to a temp
545
- file and pass it with `--actions-file`:
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`:
546
671
 
547
672
  ```bash
548
673
  bun "$SETUP_SCRIPT" --actions-file <actions.json> --run-id "$RUN_ID"
@@ -590,6 +715,8 @@ The driver reaches it through the existing run delegate (`$SETUP_SCRIPT`,
590
715
  `--status` is the declared terminal state's verdict, not a guess: a run that reached a terminal
591
716
  state is `done`; a run halted by a failing action under its error policy is `failed`. On success
592
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`.
593
720
 
594
721
  **Zero-row done closes are a named failure (task 0975 R2).** A run closed `done` with **zero**
595
722
  `action_runs` rows (`actionRows:0`) finalizes the row but exits `1` with
@@ -636,6 +763,8 @@ tasks (0617, 0619) because the sections were hand-written **before** the verdict
636
763
  ```bash
637
764
  # verdict artifact first (shape: {wbs, verdict, requirements:[{id,status,evidence}], checks:[], source})
638
765
  # then the record hop; then re-write Testing/Solution if record's backfill is thinner than intended.
766
+ # Both calls are per-call pinned (task 1058): the record hop selects the execution tree inside
767
+ # the same tool call — never through a cwd a previous call happened to leave behind.
639
768
  spur task update <wbs> wip --no-lifecycle
640
769
  spur task record <wbs> --solution-from-diff --transition testing
641
770
  ```
@@ -43,6 +43,12 @@ creation.
43
43
  spur feature create "<name>" [--parent <id>]
44
44
  ```
45
45
 
46
+ Without `--parent`, choose one before creating: list the tree (`spur feature list --json`) and nest the
47
+ feature under the existing feature that owns the surface or its design satellite. A new root feature
48
+ is a last resort that needs an explicit operator decision. When a parent is full (the 9-child limit),
49
+ nest under the closest owning child, and use `spur feature move <id> --parent <id> --dry-run` to
50
+ relocate an existing feature.
51
+
46
52
  The feature file lands in `docs/features/<ID>_<slug>.md`. Immediately author the `## Goal`
47
53
  (single sentence) and `## Scope` (in/out bullets).
48
54
 
@@ -0,0 +1,226 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft/2020-12/schema",
3
+ "title": "AI decision catalog",
4
+ "description": "Declarative decision catalog for @gobing-ai/ts-ai-decision (task 0089). Strict: unknown keys are rejected at every level. Cross-field checks (fallback vocabulary, ${params.*} references, defaults, reserved instructions parameter) run at load time.",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "decisions"],
8
+ "properties": {
9
+ "$schema": {
10
+ "type": "string",
11
+ "description": "Bundled package-specifier ref to this schema; validated when present."
12
+ },
13
+ "version": {
14
+ "const": 1
15
+ },
16
+ "defaults": {
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "maker": {
21
+ "type": "string",
22
+ "pattern": "^[a-z][a-z0-9-]*$",
23
+ "description": "Registered maker name (built-ins: typesafe | fm-local | laya-local)."
24
+ },
25
+ "model": {
26
+ "type": "string",
27
+ "description": "Forwarded as the ask-level model; omit for the backend default."
28
+ },
29
+ "minConfidence": {
30
+ "type": "number",
31
+ "minimum": 0,
32
+ "maximum": 1
33
+ }
34
+ }
35
+ },
36
+ "decisions": {
37
+ "type": "object",
38
+ "minProperties": 1,
39
+ "propertyNames": {
40
+ "pattern": "^[a-z][a-z0-9_-]*$"
41
+ },
42
+ "additionalProperties": {
43
+ "$ref": "#/$defs/decision"
44
+ }
45
+ }
46
+ },
47
+ "$defs": {
48
+ "jsonValue": {
49
+ "anyOf": [
50
+ {
51
+ "type": "string"
52
+ },
53
+ {
54
+ "type": "number"
55
+ },
56
+ {
57
+ "type": "boolean"
58
+ },
59
+ {
60
+ "type": "null"
61
+ },
62
+ {
63
+ "type": "array",
64
+ "items": {
65
+ "$ref": "#/$defs/jsonValue"
66
+ }
67
+ },
68
+ {
69
+ "type": "object",
70
+ "additionalProperties": {
71
+ "$ref": "#/$defs/jsonValue"
72
+ }
73
+ }
74
+ ]
75
+ },
76
+ "jevEntry": {
77
+ "description": "A Jev entry: description string, JSON object, JSON array, or null (left undescribed). Description string leaves support ${params.*} templating.",
78
+ "anyOf": [
79
+ {
80
+ "type": "string"
81
+ },
82
+ {
83
+ "type": "null"
84
+ },
85
+ {
86
+ "type": "array",
87
+ "items": {
88
+ "$ref": "#/$defs/jsonValue"
89
+ }
90
+ },
91
+ {
92
+ "type": "object",
93
+ "additionalProperties": {
94
+ "$ref": "#/$defs/jsonValue"
95
+ }
96
+ }
97
+ ]
98
+ },
99
+ "paramType": {
100
+ "enum": ["string", "number", "boolean", "enum", "json"]
101
+ },
102
+ "param": {
103
+ "anyOf": [
104
+ {
105
+ "$ref": "#/$defs/paramType"
106
+ },
107
+ {
108
+ "type": "object",
109
+ "additionalProperties": false,
110
+ "required": ["type"],
111
+ "properties": {
112
+ "type": {
113
+ "$ref": "#/$defs/paramType"
114
+ },
115
+ "default": {},
116
+ "values": {
117
+ "type": "array",
118
+ "items": {
119
+ "type": "string"
120
+ },
121
+ "minItems": 1,
122
+ "description": "Accepted strings for type: enum (at least one)."
123
+ },
124
+ "description": {
125
+ "type": "string"
126
+ }
127
+ }
128
+ }
129
+ ]
130
+ },
131
+ "choiceCriteria": {
132
+ "type": "object",
133
+ "minProperties": 2,
134
+ "additionalProperties": {
135
+ "$ref": "#/$defs/jevEntry"
136
+ }
137
+ },
138
+ "scoreCriteria": {
139
+ "type": "array",
140
+ "minItems": 2,
141
+ "items": {
142
+ "$ref": "#/$defs/jevEntry"
143
+ }
144
+ },
145
+ "noulCriteria": {
146
+ "type": "object",
147
+ "additionalProperties": false,
148
+ "properties": {
149
+ "true": {
150
+ "$ref": "#/$defs/jevEntry"
151
+ },
152
+ "false": {
153
+ "$ref": "#/$defs/jevEntry"
154
+ }
155
+ }
156
+ },
157
+ "decision": {
158
+ "type": "object",
159
+ "additionalProperties": false,
160
+ "required": ["type", "fallback"],
161
+ "properties": {
162
+ "type": {
163
+ "enum": ["choice", "score", "noul"]
164
+ },
165
+ "description": {
166
+ "type": "string",
167
+ "description": "Documentation only; never sent to the model."
168
+ },
169
+ "instructions": {
170
+ "$ref": "#/$defs/jevEntry",
171
+ "description": "Templated Jev entry; defaults to \"${params.instructions}\"."
172
+ },
173
+ "parameters": {
174
+ "type": "object",
175
+ "propertyNames": {
176
+ "pattern": "^[a-z][a-zA-Z0-9_]*$"
177
+ },
178
+ "additionalProperties": {
179
+ "$ref": "#/$defs/param"
180
+ }
181
+ },
182
+ "criteria": {
183
+ "description": "choice: label map (>= 2 labels); score: ordered rubric (>= 2 levels); noul: optional { true?, false? }.",
184
+ "anyOf": [
185
+ {
186
+ "$ref": "#/$defs/choiceCriteria"
187
+ },
188
+ {
189
+ "$ref": "#/$defs/scoreCriteria"
190
+ },
191
+ {
192
+ "$ref": "#/$defs/noulCriteria"
193
+ }
194
+ ]
195
+ },
196
+ "fallback": {
197
+ "description": "choice: a declared label; score: integer level in [0, levels-1]; noul: boolean.",
198
+ "anyOf": [
199
+ {
200
+ "type": "string"
201
+ },
202
+ {
203
+ "type": "number"
204
+ },
205
+ {
206
+ "type": "boolean"
207
+ }
208
+ ]
209
+ },
210
+ "minConfidence": {
211
+ "type": "number",
212
+ "minimum": 0,
213
+ "maximum": 1
214
+ },
215
+ "maker": {
216
+ "type": "string",
217
+ "pattern": "^[a-z][a-z0-9-]*$",
218
+ "description": "Registered maker name; checked by the hub, not the loader."
219
+ },
220
+ "model": {
221
+ "type": "string"
222
+ }
223
+ }
224
+ }
225
+ }
226
+ }
@@ -287,6 +287,31 @@
287
287
  }
288
288
  }
289
289
  },
290
+ "decisions": {
291
+ "type": "object",
292
+ "description": "Decision-catalog resolution and DecisionMaker selection (feature P / ADR-134). The whole section is optional.",
293
+ "properties": {
294
+ "paths": {
295
+ "type": "array",
296
+ "items": {
297
+ "type": "string"
298
+ },
299
+ "description": "Extra catalog folders for the registered decision layer (absolute, project-relative, or 'bundled:<sub>' against the installed package's config root)."
300
+ },
301
+ "maker": {
302
+ "type": "string",
303
+ "pattern": "^[a-z][a-z0-9-]*$",
304
+ "description": "Default DecisionMaker for every decision point (built-ins: typesafe | fm-local | laya-local). Must be registered; an unknown name is a config error, never a silent fallback."
305
+ },
306
+ "makers": {
307
+ "type": "object",
308
+ "additionalProperties": {
309
+ "type": "string"
310
+ },
311
+ "description": "Per-decision-point maker overrides, keyed by decision id; outrank decisions.maker."
312
+ }
313
+ }
314
+ },
290
315
  "redaction": {
291
316
  "type": "object",
292
317
  "properties": {