@gobing-ai/spur 0.3.85 → 0.3.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +0 -25
  3. package/config/plugin-scripts.json +0 -5
  4. package/config/rules/boundary/env-var-hygiene.yaml +41 -0
  5. package/config/transition-shims.json +1 -1
  6. package/config/workflow-candidates.json +41 -0
  7. package/config/workflows/feature-lifecycle.yaml +4 -2
  8. package/config/workflows/feature-verification.yaml +70 -0
  9. package/config/workflows/idea-pipeline.yaml +24 -12
  10. package/config/workflows/task-pipeline.yaml +43 -12
  11. package/config/workflows/wrapup-pipeline.yaml +70 -7
  12. package/package.json +9 -9
  13. package/plugins/sp/README.md +0 -3
  14. package/plugins/sp/hooks/agent-hint.ts +5 -4
  15. package/plugins/sp/hooks/careful-guard.ts +3 -1
  16. package/plugins/sp/hooks/context-post-tool.ts +2 -1
  17. package/plugins/sp/hooks/context-session-start.ts +4 -3
  18. package/plugins/sp/hooks/context-session-stop.ts +2 -1
  19. package/plugins/sp/hooks/pi/guard-extension.ts +4 -3
  20. package/plugins/sp/hooks/task-write-guard.ts +4 -3
  21. package/plugins/sp/lib/idea-handoff.generated.mjs +260 -260
  22. package/plugins/sp/plugin.json +1 -1
  23. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +13 -4
  24. package/plugins/sp/scripts/daily-summary/daily-summary.ts +6 -4
  25. package/plugins/sp/scripts/feature-sync-bounded.mjs +10 -2
  26. package/plugins/sp/scripts/feature-sync-bounded.ts +2 -1
  27. package/plugins/sp/scripts/idea-handoff.mjs +6 -1
  28. package/plugins/sp/scripts/idea-handoff.ts +4 -1
  29. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
  30. package/plugins/sp/scripts/inline-run-setup.ts +185 -2
  31. package/plugins/sp/scripts/pr-reviewing.mjs +8 -1
  32. package/plugins/sp/scripts/pr-reviewing.ts +2 -1
  33. package/plugins/sp/scripts/quality-gate.mjs +8 -1
  34. package/plugins/sp/scripts/quality-gate.ts +2 -1
  35. package/plugins/sp/scripts/surface-drift-inventory.ts +1 -4
  36. package/plugins/sp/scripts/task-evidence-precheck.ts +2 -1
  37. package/plugins/sp/scripts/task-size-precheck.ts +6 -4
  38. package/plugins/sp/scripts/verify-answer-lint.ts +2 -1
  39. package/plugins/sp/scripts/workflow-step-profile.mjs +10 -2
  40. package/plugins/sp/scripts/workflow-step-profile.ts +2 -1
  41. package/plugins/sp/scripts/wrapup-steps.mjs +8 -1
  42. package/plugins/sp/scripts/wrapup-steps.ts +2 -1
  43. package/plugins/sp/skills/spur-cli/references/workflows.md +8 -7
  44. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -5
  45. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
  46. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +2 -2
  47. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +59 -1
  48. package/schemas/state-machine-workflow.schema.json +5 -0
  49. package/schemas/transition-flow-workflow.schema.json +5 -0
  50. package/spur.js +20988 -20652
  51. package/web/_astro/BoardApp.CJiqp5pS.js +1 -0
  52. package/web/_astro/{BoardApp.yW425dRZ.js → BoardApp.yBBcFXWP.js} +4 -4
  53. package/web/_astro/{TaskDetail.CGwJAinW.js → TaskDetail.DqFJbRFc.js} +1 -1
  54. package/web/_astro/{arc.B-qNXzSO.js → arc.DL-BpHoi.js} +1 -1
  55. package/web/_astro/{architectureDiagram-3BPJPVTR.Bdg-xiji.js → architectureDiagram-3BPJPVTR.9IdQYyDq.js} +1 -1
  56. package/web/_astro/{blockDiagram-GPEHLZMM.DFArg3Kp.js → blockDiagram-GPEHLZMM.BKsFCqTl.js} +1 -1
  57. package/web/_astro/{c4Diagram-AAUBKEIU.BkOLcjyx.js → c4Diagram-AAUBKEIU.DhwI0dh1.js} +1 -1
  58. package/web/_astro/channel.CX5453qQ.js +1 -0
  59. package/web/_astro/{chunk-2J33WTMH.DN3c20V4.js → chunk-2J33WTMH.B3QVmQ9S.js} +1 -1
  60. package/web/_astro/{chunk-4BX2VUAB.B64K0Ttd.js → chunk-4BX2VUAB.DBSuqs9F.js} +1 -1
  61. package/web/_astro/{chunk-55IACEB6.C-AJvNwa.js → chunk-55IACEB6.BSYTWAYD.js} +1 -1
  62. package/web/_astro/{chunk-727SXJPM.DMkr46uS.js → chunk-727SXJPM.cWVuxXfS.js} +1 -1
  63. package/web/_astro/{chunk-AQP2D5EJ.4_WGbI64.js → chunk-AQP2D5EJ.DpU_Ob3d.js} +1 -1
  64. package/web/_astro/{chunk-FMBD7UC4.cbFW_lVE.js → chunk-FMBD7UC4.BykFkyji.js} +1 -1
  65. package/web/_astro/{chunk-ND2GUHAM.CDW_-2O1.js → chunk-ND2GUHAM.DwgHlMdY.js} +1 -1
  66. package/web/_astro/{chunk-QZHKN3VN.BoYOt_yg.js → chunk-QZHKN3VN.CusXUGWM.js} +1 -1
  67. package/web/_astro/{classDiagram-4FO5ZUOK.BNT6XpHQ.js → classDiagram-4FO5ZUOK.fx0ObzkN.js} +1 -1
  68. package/web/_astro/{classDiagram-v2-Q7XG4LA2.BNT6XpHQ.js → classDiagram-v2-Q7XG4LA2.fx0ObzkN.js} +1 -1
  69. package/web/_astro/{cose-bilkent-S5V4N54A.CI1DQxR3.js → cose-bilkent-S5V4N54A.Z4HgOlsd.js} +1 -1
  70. package/web/_astro/{cynefin-OW5HDTMX.A8ARH61p.js → cynefin-OW5HDTMX.B5dIZHJu.js} +1 -1
  71. package/web/_astro/{dagre-BM42HDAG.u28ZmqwR.js → dagre-BM42HDAG.DT70Q_Yw.js} +1 -1
  72. package/web/_astro/{diagram-2AECGRRQ.Dx8JcrGc.js → diagram-2AECGRRQ.DqHA3XBF.js} +1 -1
  73. package/web/_astro/{diagram-5GNKFQAL.BXJUG9GY.js → diagram-5GNKFQAL.BmCem957.js} +1 -1
  74. package/web/_astro/{diagram-KO2AKTUF.BtlMuWqW.js → diagram-KO2AKTUF.sn0-hrE0.js} +1 -1
  75. package/web/_astro/{diagram-LMA3HP47.C4T7qaAy.js → diagram-LMA3HP47.BSHe9tVc.js} +1 -1
  76. package/web/_astro/{diagram-OG6HWLK6.D5HECE10.js → diagram-OG6HWLK6.DHIc-86k.js} +1 -1
  77. package/web/_astro/{erDiagram-TEJ5UH35.B_hMR3yw.js → erDiagram-TEJ5UH35.Bxayrs7v.js} +1 -1
  78. package/web/_astro/{flowDiagram-I6XJVG4X.BAmpcYl3.js → flowDiagram-I6XJVG4X.BkzoE_5I.js} +1 -1
  79. package/web/_astro/{ganttDiagram-6RSMTGT7.DV1bSWK-.js → ganttDiagram-6RSMTGT7.okT6CvTo.js} +1 -1
  80. package/web/_astro/{gitGraphDiagram-PVQCEYII.DxYKkKrJ.js → gitGraphDiagram-PVQCEYII.CJuYbhC7.js} +1 -1
  81. package/web/_astro/{infoDiagram-5YYISTIA.BAWY2xMb.js → infoDiagram-5YYISTIA.RqLy7nBo.js} +1 -1
  82. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CZssRn6V.js → ishikawaDiagram-YF4QCWOH.BwIcoagw.js} +1 -1
  83. package/web/_astro/{journeyDiagram-JHISSGLW.C--muARd.js → journeyDiagram-JHISSGLW.UB1VbWtH.js} +1 -1
  84. package/web/_astro/{kanban-definition-UN3LZRKU.D_QCK4et.js → kanban-definition-UN3LZRKU.AaxMKpTk.js} +1 -1
  85. package/web/_astro/{linear.DzrmTtZ0.js → linear.Nv_xOUjP.js} +1 -1
  86. package/web/_astro/{mermaid.core.Da03W3iu.js → mermaid.core.Bc4LqQgX.js} +4 -4
  87. package/web/_astro/{mindmap-definition-RKZ34NQL.6cy-8hR_.js → mindmap-definition-RKZ34NQL.oKUvU_qi.js} +1 -1
  88. package/web/_astro/{pieDiagram-4H26LBE5.C5rS1pdU.js → pieDiagram-4H26LBE5.DQk0oo03.js} +1 -1
  89. package/web/_astro/{quadrantDiagram-W4KKPZXB.w56GZZ6Q.js → quadrantDiagram-W4KKPZXB.BdDjESDa.js} +1 -1
  90. package/web/_astro/{requirementDiagram-4Y6WPE33.CipX3Pwu.js → requirementDiagram-4Y6WPE33.C2u9hUeH.js} +1 -1
  91. package/web/_astro/{sankeyDiagram-5OEKKPKP.C0VVzgJm.js → sankeyDiagram-5OEKKPKP.CDEoiJST.js} +1 -1
  92. package/web/_astro/{sequenceDiagram-3UESZ5HK.BX2dUUbF.js → sequenceDiagram-3UESZ5HK.D_hT_GAT.js} +1 -1
  93. package/web/_astro/{stateDiagram-AJRCARHV.ypCdgODQ.js → stateDiagram-AJRCARHV.DI8RYG0b.js} +1 -1
  94. package/web/_astro/{stateDiagram-v2-BHNVJYJU.In0baEtg.js → stateDiagram-v2-BHNVJYJU.Bkxz4DnP.js} +1 -1
  95. package/web/_astro/{timeline-definition-PNZ67QCA.CJN4Vkvl.js → timeline-definition-PNZ67QCA.DSY-kH3-.js} +1 -1
  96. package/web/_astro/{vennDiagram-CIIHVFJN.Cf8KkIPY.js → vennDiagram-CIIHVFJN.CpaDtuGr.js} +1 -1
  97. package/web/_astro/{wardleyDiagram-YWT4CUSO.DCZBo9xw.js → wardleyDiagram-YWT4CUSO.DujQWvo8.js} +1 -1
  98. package/web/_astro/{xychartDiagram-2RQKCTM6.Cm4v_MiJ.js → xychartDiagram-2RQKCTM6.DcM5Y4b9.js} +1 -1
  99. package/web/index.html +1 -1
  100. package/config/workflows/basic.yaml +0 -146
  101. package/config/workflows/docs-pipeline.yaml +0 -350
  102. package/config/workflows/feature-dev.yaml +0 -288
  103. package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -146
  104. package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
  105. package/web/_astro/BoardApp.CHenHFia.js +0 -1
  106. package/web/_astro/channel.CbDHK5UQ.js +0 -1
@@ -5,6 +5,13 @@
5
5
  import { spawnSync } from "child_process";
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
7
7
  import { join } from "path";
8
+
9
+ // ../ts-libs/packages/utils/dist/env.js
10
+ function getEnvVars() {
11
+ return process.env;
12
+ }
13
+
14
+ // plugins/sp/scripts/wrapup-steps.ts
8
15
  function jqPick(...values) {
9
16
  for (const value of values) {
10
17
  if (value !== null && value !== undefined && value !== false)
@@ -319,7 +326,7 @@ function runFeatureTransition(env, options = {}) {
319
326
  return { status: syncStatus, statusFile: relStatusFile, exitCode: 0 };
320
327
  }
321
328
  var WRAPUP_STEPS_USAGE = "usage: wrapup-steps.ts <resolve|metrics|feature-transition> (env: __runId, tasks, feature, featureGateCmd, spurBin)";
322
- function main(argv, env = process.env, options = {}) {
329
+ function main(argv, env = getEnvVars(), options = {}) {
323
330
  const sub = argv[0];
324
331
  if (sub === "resolve")
325
332
  return resolveTasks(env, options).exitCode;
@@ -31,6 +31,7 @@
31
31
  import { spawnSync } from 'node:child_process';
32
32
  import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
33
33
  import { join } from 'node:path';
34
+ import { getEnvVars } from '@gobing-ai/ts-utils';
34
35
 
35
36
  export interface WrapupStepsEnv {
36
37
  __runId?: string;
@@ -449,7 +450,7 @@ export function runFeatureTransition(env: WrapupStepsEnv, options: WrapupStepsOp
449
450
  export const WRAPUP_STEPS_USAGE =
450
451
  'usage: wrapup-steps.ts <resolve|metrics|feature-transition> (env: __runId, tasks, feature, featureGateCmd, spurBin)';
451
452
 
452
- export function main(argv: string[], env: WrapupStepsEnv = process.env, options: WrapupStepsOptions = {}): number {
453
+ export function main(argv: string[], env: WrapupStepsEnv = getEnvVars(), options: WrapupStepsOptions = {}): number {
453
454
  const sub = argv[0];
454
455
  if (sub === 'resolve') return resolveTasks(env, options).exitCode;
455
456
  if (sub === 'metrics') {
@@ -62,7 +62,7 @@ ordered guards, and may loop back on itself (e.g. implement → check → fix
62
62
  | Node typing | none (states are untyped) | `type: action\|gate\|parallel\|decision` |
63
63
  | Required keys | `name, initialState, states, transitions` | `kind, name, initialNode, nodes, edges` |
64
64
  | `kind` field | optional (defaults to state-machine) | **required** — `kind: transition-flow` |
65
- | Canonical example | implement→check→fix loop (`basic.yaml` — soft status-file probe + bounded fixall) | read→validate→transform→write pipeline |
65
+ | Canonical example | implement→check→fix loop (`task-pipeline.yaml` — quality-gate hop with bounded fixall) | read→validate→transform→write pipeline |
66
66
 
67
67
  **Heuristic:** loops / retries / one-active-state → **state-machine**; pipeline / fan-out / action-per-node → **transition-flow**.
68
68
 
@@ -95,7 +95,7 @@ Use this skill to:
95
95
 
96
96
  The skill's logic divides by **whether the LLM adds value**:
97
97
 
98
- - **Direct CLI** (`validate`, `run`, `list`, `trace`, `continue`, `cancel`, `clean`) — deterministic,
98
+ - **Direct CLI** (`validate`, `run`, `list`, `trace`, `progress`, `continue`, `cancel`, `clean`) — deterministic,
99
99
  single-verb commands. Run them straight. A slash-command wrapper would only forward flags and add
100
100
  drift; **there is no command for these — use the CLI**. The skill still drives them in natural
101
101
  language (interpreting a failed validate, reading a run trace).
@@ -113,6 +113,7 @@ The skill's logic divides by **whether the LLM adds value**:
113
113
  | `clean` | `spur workflow clean` (CLI) | `[--older-than <min>] [--force] [--logs] [--dry-run]` | Bulk-finalize stale `running`/`pending` runs as failed **and** reclaim retained run logs older than `workflow.logRetentionDays` (30d default) |
114
114
  | `list` | `spur workflow list` (CLI) | — | Available workflow **YAML definition files** (not run records) |
115
115
  | `trace` | `spur workflow trace` (CLI) | `[run-id] [--workflow <n>] [--status <s>] [--since <iso>] [--last <n>] [--follow] [--poll <ms>] [--output]` | Run history list or per-run timeline |
116
+ | `progress` | `spur workflow progress` (CLI) | `<run-id>` | The `projectWorkflowProgress` projection for that run — current state, per-action attempts, candidate next transitions, diagnostics. Read-only; the verb renders, `packages/app` derives. Unknown run id exits 1 with `Run <id> not found.` |
116
117
  | `add` | agent procedure | `"<nl-description>" [--kind <state-machine\|transition-flow>] [--file <path>]` | **Mode chosen (confirmed)** → first reconciled against existing workflows (extend an existing flow rather than duplicate) → YAML authored in real schema shape → **validated AND dry-run** (reaches the expected terminal state) → [add](workflows/operations.md#add) |
117
118
  | `refine` | agent procedure | `<workflow-file> [--intent "<goal>"] [--dry-run]` | Smallest change meeting the intent, re-validated and re-dry-run; `--dry-run` emits a diff only → [refine](workflows/operations.md#refine) |
118
119
 
@@ -262,6 +263,7 @@ spur workflow cancel <run-id> [--json]
262
263
  spur workflow clean [--older-than <minutes>] [--force] [--logs] [--dry-run] [--json]
263
264
  spur workflow list [--json]
264
265
  spur workflow trace [run-id] [--workflow <name>] [--status <s>] [--since <iso>] [--last <n>] [--follow] [--poll <ms>] [--output] [--json]
266
+ spur workflow progress <run-id> [--json]
265
267
  ```
266
268
 
267
269
  ### `show` - project a definition
@@ -359,7 +361,7 @@ the workflow's actions (`shell`, custom runners) do that; this skill builds and
359
361
  (transition-flow) wins. Order the specific case before the fallback. Simple shell checks can use
360
362
  `action-ok` before an unconditional retry edge; multi-condition gates prefer a **soft status-file
361
363
  probe** (always exit 0) with ordered shell guards for PASS / FAIL / exhausted (see
362
- `basic.yaml` and `task-pipeline.yaml` quality-gate hop).
364
+ `task-pipeline.yaml`'s quality-gate hop).
363
365
  4. **`env.allow` is an allowlist.** `${env.X}` resolves only if `X` is listed under `env.allow`;
364
366
  otherwise it resolves empty. A workflow that "loses" an environment value usually forgot to allow it.
365
367
  5. **Extensions are fail-closed.** The CLI loads YAML-declared extension modules itself
@@ -400,10 +402,9 @@ the workflow's actions (`shell`, custom runners) do that; this skill builds and
400
402
  custom action/guard runners, the trust-gated extension loader, and CLI-vs-library capability gaps.
401
403
  - `@gobing-ai/ts-dual-workflow-engine` README — authoritative library reference (both drivers,
402
404
  RunLifecycle, persistence, the full event map, every built-in capability).
403
- - `basic.yaml` — the canonical state-machine implement→soft-check→fixall loop
404
- (status-file branching + `qualityGateMaxFixAttempts`); copy real shapes from here.
405
- - `task-pipeline.yaml` — full production pipeline (precheck, quality gate, HITL,
406
- verify, record) when you need the complete reliability pattern set.
405
+ - `task-pipeline.yaml` — the canonical state-machine implement→soft-check→fixall loop and
406
+ full production pipeline (status-file branching, `qualityGateMaxFixAttempts`, quality gate,
407
+ HITL, verify, record); copy real shapes from here.
407
408
 
408
409
  ## Platform Notes
409
410
 
@@ -573,8 +573,6 @@ invariants that keep the pipeline set coherent as new ones are added.
573
573
  | `idea-pipeline.yaml` | Ideation + planning (vague idea or known slug → feature + AC + task batch) | `/sp:dev-idea`, `/sp:dev-plan` | `handoff`, `cancelled` |
574
574
  | `task-pipeline.yaml` | Execution (one task → done) | `/sp:dev-run` | `done`, `failed` |
575
575
  | `wrapup-pipeline.yaml` | Wrap-up (completed tasks → learning + metrics + doc-sync) | `/sp:dev-wrap`, `/sp:dev-wrapall` | `done`, `skipped` |
576
- | `feature-dev.yaml` | Umbrella (brainstorm → plan → execute → feature-verify) | `/sp:dev-runall --feature <id>` (or `--tasks feature:<id>`) | `done`, `failed` |
577
- | `basic.yaml` | Simple (generic implement/check/fix loop) | direct `spur workflow run` | `done`, `failed` |
578
576
  | `feature-lifecycle.yaml` | Feature status FSM (entity lifecycle, not a phase pipeline) | `spur feature update` | `done`, `cancelled` |
579
577
  | `task-lifecycle.yaml` | Task status FSM (entity lifecycle, not a phase pipeline) | `spur task update` | `done`, `cancelled` |
580
578
 
@@ -587,8 +585,9 @@ not replace them.
587
585
  A pipeline may invoke another workflow through a command wrapper or `spur workflow run` **only at a
588
586
  phase boundary** — it must NOT inline another pipeline's state graph. Concretely:
589
587
 
590
- - `feature-dev.yaml`'s `execute-tasks` state may invoke `task-pipeline.yaml` per task via
591
- `spur workflow run` (phase boundary: design → execution).
588
+ - The feature-level batch entry (`/sp:dev-runall --feature <id>`) dispatches `task-pipeline.yaml`
589
+ per task through the CLI command wrapper, not by inlining the task graph (feature roster →
590
+ execution boundary).
592
591
  - `idea-pipeline.yaml`'s `handoff` state may output a command for the operator to run
593
592
  `task-pipeline.yaml` (phase boundary: ideation → execution).
594
593
  - `task-pipeline.yaml`'s `implement` state must NOT contain a nested state machine for
@@ -692,7 +691,7 @@ Field semantics (enforced by `parseCheckpointMetadata` / `checkpointStaleness`):
692
691
  **Writer cadence (0784 R4).** There is exactly one canonical writer: the `task-pipeline` done
693
692
  state's terminal checkpoint (`status: done`, real HEAD, run id from `$__runId`, and `$wbs`-expanded
694
693
  artifact paths). It is a plain `shell` step — checkpoints are working memory, not CLI-gated corpus.
695
- The `feature-dev`, `wrapup-pipeline`, and `idea-pipeline` pipelines used to echo pseudo-checkpoints
694
+ The `wrapup-pipeline`, and `idea-pipeline` pipelines (plus the since-retired `feature-dev`) used to echo pseudo-checkpoints
696
695
  ("checkpoint: <workflow> done ...") that violated the canonical schema; those writers were removed
697
696
  in 0784 — the persisted run row is the authoritative terminal record, and a non-canonical echo
698
697
  cannot be resumed, routed, or reclaimed safely.
@@ -105,7 +105,7 @@ gate will reject the task without it). Use the normal `--section --from-file` co
105
105
  **4. Force-done with an honest reason.**
106
106
 
107
107
  ```bash
108
- SPUR_PROVENANCE_OVERRIDE=1 spur task update <wbs> done --force-done \
108
+ spur task update <wbs> done --force-done --provenance-bypass \
109
109
  --reason "<step> agent.run timed out at <N>s; recovered manually: lint clean, <suite> pass, sections authored by hand"
110
110
  ```
111
111
 
@@ -149,11 +149,11 @@ passed, so provenance denied first and Review L3 denied on the retry.
149
149
 
150
150
  | # | Gate layer | Triggers denial when | Remediation |
151
151
  |---|------------|----------------------|-------------|
152
- | 1 | **Strict-core + verdict artifact** (`spur task check <wbs> --strict-core` + `done-transition-guard.ts`) | The strict-core check fails, or `.spur/run/<wbs>-verdict.json` is **missing** or has a non-PASS aggregate. **Missing artifact is a deny** (not a silent allow — closes the 0349 "done without verdict" class). The aggregate is recomputed from requirement/AC rows; the harsher of stored and computed wins. | Re-run `/sp:dev-verify <wbs>` until PASS (writes the artifact), or explicitly override with `spur task update <wbs> done --force-done --reason "<why>"`. Docs-only pipelines (`docs-pipeline.yaml`) meet the same layer: read-only measured verification
152
+ | 1 | **Strict-core + verdict artifact** (`spur task check <wbs> --strict-core` + `done-transition-guard.ts`) | The strict-core check fails, or `.spur/run/<wbs>-verdict.json` is **missing** or has a non-PASS aggregate. **Missing artifact is a deny** (not a silent allow — closes the 0349 "done without verdict" class). The aggregate is recomputed from requirement/AC rows; the harsher of stored and computed wins. | Re-run `/sp:dev-verify <wbs>` until PASS (writes the artifact), or explicitly override with `spur task update <wbs> done --force-done --reason "<why>"`. Docs-only procedures meet the same layer: read-only measured verification
153
153
  (answer file + `spur task verdict`) writes the standard `.spur/run/<wbs>-verdict.json` artifact
154
154
  under proof-input digest bracketing; missing or non-PASS evidence is a refusal, never a synthetic
155
155
  PASS stub. |
156
- | 2 | **Provenance guard** (`lifecycle-adapter.ts`) | No pipeline-kind run link exists for `<wbs>`. | Run `/sp:dev-run <wbs>` through the full pipeline, use `/sp:dev-run <wbs> --mode implement --auto --next` for the explicit step chain, or record the audited bypass with `SPUR_PROVENANCE_OVERRIDE=1`. |
156
+ | 2 | **Provenance guard** (`lifecycle-adapter.ts`) | No pipeline-kind run link exists for `<wbs>`. | Run `/sp:dev-run <wbs>` through the full pipeline, use `/sp:dev-run <wbs> --mode implement --auto --next` for the explicit step chain, or record the audited bypass with `--provenance-bypass` on `spur task update`. |
157
157
  | 3 | **Review L3** (`task-check.ts`) | `### Review` is empty, placeholder-only, or lacks a populated P1–P4 findings table. | Run `/sp:dev-review <wbs>`; verify cannot write Review because of the Step 10 prohibition above. |
158
158
 
159
159
  When the verdict is **PARTIAL/FAIL**, or any gate layer fails: stop as review-pending — surface
@@ -25,7 +25,11 @@ implements it; remove the entry when the corresponding kind is dropped from the
25
25
 
26
26
  **Actions:** `shell` · `note` · `doctor.probe` · `file.read.into-var` · `hitl.confirm` · `agent.run` · `proof.fingerprint` · `run.artifact` · `command.gate`
27
27
 
28
- **Guards (transitions):** `always` · `shell`
28
+ **Guards (transitions):** `always` · `shell` · `action-ok` · `contract-violation`
29
+
30
+ - `action-ok` — pass iff the prior action on this state/node succeeded (engine builtin).
31
+ - `contract-violation` — pass iff the prior `agent.run` result is a named contract violation
32
+ (`data.outcome === 'contract-violation'`, ADR-118); the report carries `contract` and `observed`.
29
33
 
30
34
  ## What this driver is
31
35
 
@@ -398,6 +402,60 @@ keep their exact content after the stamp prefix. This normalization is contractu
398
402
  **bare local-clock stamps are prohibited** — a hand-appended `[stage 12:31]` form mixes timezones
399
403
  in one file and makes the run unauditable (task 0726 mixed both forms).
400
404
 
405
+ ## Structured trace emission (ADR-117, task 0868)
406
+
407
+ `.spur/run/<run-id>.log` is a human convenience, **not the record of truth**. A run's
408
+ observability is a property of the run, so the inline driver owes the same structured trace the
409
+ engine subprocess writes — and it owes it through the **same writer**, never a parallel
410
+ implementation. The shared writer is `WorkflowActionTraceWriter`
411
+ (`packages/app/src/workflow/action-trace.ts`): the same decorator the engine composition installs
412
+ around `DbWorkflowPersistenceAdapter`, so the two surfaces call one emission path and one run-row
413
+ closure path and cannot drift.
414
+
415
+ The driver reaches it through the existing run delegate (`$SETUP_SCRIPT`,
416
+ `plugins/sp/scripts/inline-run-setup.ts`) — no new entry point, no second resolution chain:
417
+
418
+ - **Every executed action** — after the action settles, whether it ran host-inline or via a native
419
+ subagent — append its provenance line as before, then record the boundary:
420
+
421
+ ```bash
422
+ bun "$SETUP_SCRIPT" --action --run-id "$RUN_ID" --node <state-id> --kind <action-kind> \
423
+ --status <done|failed> --ok <true|false> --duration-ms <measured-ms>
424
+ ```
425
+
426
+ `<state-id>` is the current YAML state id (the `node`), `<action-kind>` the YAML action kind
427
+ (`agent.run`, `shell`, `note`, `doctor.probe`, …). `--status` is `done` when the action settled
428
+ under its declared error policy and `failed` otherwise; `--duration-ms` is the wall clock the
429
+ driver measured around the action. This writes the `action_runs` row (node, kind, status, `ok`,
430
+ `duration_ms`, `run_id`) the engine would have written, so the run's rows are queryable by run id
431
+ (`spur workflow progress <run-id>`, `ActionRunDao`) without reading the text log.
432
+
433
+ - **At the run's declared terminal state** — before the driver reports the run complete, close the
434
+ row so a successful inline run is never left non-terminal for `spur workflow clean` to reap as
435
+ stale:
436
+
437
+ ```bash
438
+ bun "$SETUP_SCRIPT" --close --run-id "$RUN_ID" --status <done|failed|paused>
439
+ ```
440
+
441
+ `--status` is the declared terminal state's verdict, not a guess: a run that reached a terminal
442
+ state is `done`; a run halted by a failing action under its error policy is `failed`.
443
+
444
+ **Best-effort at the action boundary only (ADR-117).** An `--action` persistence failure is
445
+ recorded — the delegate appends a `trace-emission-failed` line to `.spur/run/<run-id>.log` and
446
+ prints `{"ok":false}` on stdout — and the run continues to its declared terminal state; the
447
+ delegate exits `0` for that outcome and the driver must never treat an emission failure as a run
448
+ failure, retry it in a loop, or substitute a hand-written row. The run-row closure (`--close`) is
449
+ bookkeeping, not trace emission, and is **not** best-effort: a missing run row or a persistence
450
+ failure exits `1` with `{"ok":false}` and a named error (a missing row also carries
451
+ `code:"RUN_NOT_FOUND"`), because a silently `running` row is exactly the stale state
452
+ `spur workflow clean` reaps as `failed`. Exit `2` means the invocation itself was malformed
453
+ (missing `--node`/`--kind`/`--status`/`--ok`, a miscased `--ok`, a missing or malformed
454
+ `--duration-ms`, or an unsafe run id) and must be corrected, not ignored.
455
+
456
+ Emission is not optional and not deferred: an inline run that skips it reintroduces the
457
+ 1,011-untraced-runs gap ADR-117 exists to close.
458
+
401
459
  Transition guards are not advisory. Execute the declared guard exactly, in order, with the same
402
460
  resolved variables and artifacts. `--no-lifecycle` remains bookkeeping only; the YAML's task checks,
403
461
  verdict gate, record step, and done guard all remain authoritative.
@@ -161,6 +161,11 @@
161
161
  "options": {
162
162
  "type": "object",
163
163
  "additionalProperties": true
164
+ },
165
+ "onError": {
166
+ "type": "string",
167
+ "enum": ["fail", "continue"],
168
+ "description": "Per-action error handling policy: 'fail' halts the run; 'continue' logs the failure and proceeds so transition guards can route the outcome."
164
169
  }
165
170
  }
166
171
  },
@@ -140,6 +140,11 @@
140
140
  "options": {
141
141
  "type": "object",
142
142
  "additionalProperties": true
143
+ },
144
+ "onError": {
145
+ "type": "string",
146
+ "enum": ["fail", "continue"],
147
+ "description": "Per-action error handling policy: 'fail' halts the run; 'continue' logs the failure and proceeds so edge conditions can route the outcome."
143
148
  }
144
149
  }
145
150
  },