@gobing-ai/spur 0.3.84 → 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 (109) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +11 -6
  3. package/config/pipeline-budgets.json +0 -25
  4. package/config/plugin-scripts.json +0 -5
  5. package/config/rules/boundary/env-var-hygiene.yaml +41 -0
  6. package/config/transition-shims.json +1 -1
  7. package/config/workflow-candidates.json +41 -0
  8. package/config/workflows/feature-lifecycle.yaml +4 -2
  9. package/config/workflows/feature-verification.yaml +70 -0
  10. package/config/workflows/idea-pipeline.yaml +25 -13
  11. package/config/workflows/task-pipeline.yaml +43 -12
  12. package/config/workflows/wrapup-pipeline.yaml +71 -8
  13. package/package.json +9 -9
  14. package/plugins/sp/README.md +0 -3
  15. package/plugins/sp/agents/super-planner.md +2 -1
  16. package/plugins/sp/hooks/agent-hint.ts +5 -4
  17. package/plugins/sp/hooks/careful-guard.ts +3 -1
  18. package/plugins/sp/hooks/context-post-tool.ts +2 -1
  19. package/plugins/sp/hooks/context-session-start.ts +4 -3
  20. package/plugins/sp/hooks/context-session-stop.ts +2 -1
  21. package/plugins/sp/hooks/pi/guard-extension.ts +4 -3
  22. package/plugins/sp/hooks/task-write-guard.ts +4 -3
  23. package/plugins/sp/lib/idea-handoff.generated.mjs +260 -260
  24. package/plugins/sp/plugin.json +1 -1
  25. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +13 -4
  26. package/plugins/sp/scripts/daily-summary/daily-summary.ts +6 -4
  27. package/plugins/sp/scripts/feature-sync-bounded.mjs +10 -2
  28. package/plugins/sp/scripts/feature-sync-bounded.ts +2 -1
  29. package/plugins/sp/scripts/idea-handoff.mjs +6 -1
  30. package/plugins/sp/scripts/idea-handoff.ts +4 -1
  31. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
  32. package/plugins/sp/scripts/inline-run-setup.ts +185 -2
  33. package/plugins/sp/scripts/pr-reviewing.mjs +8 -1
  34. package/plugins/sp/scripts/pr-reviewing.ts +2 -1
  35. package/plugins/sp/scripts/quality-gate.mjs +8 -1
  36. package/plugins/sp/scripts/quality-gate.ts +2 -1
  37. package/plugins/sp/scripts/surface-drift-inventory.ts +2 -5
  38. package/plugins/sp/scripts/task-evidence-precheck.ts +2 -1
  39. package/plugins/sp/scripts/task-size-precheck.ts +6 -4
  40. package/plugins/sp/scripts/verify-answer-lint.ts +2 -1
  41. package/plugins/sp/scripts/workflow-step-profile.mjs +10 -2
  42. package/plugins/sp/scripts/workflow-step-profile.ts +2 -1
  43. package/plugins/sp/scripts/wrapup-steps.mjs +8 -1
  44. package/plugins/sp/scripts/wrapup-steps.ts +2 -1
  45. package/plugins/sp/skills/spur-cli/references/workflows.md +8 -7
  46. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -5
  47. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
  48. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +2 -2
  49. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +59 -1
  50. package/schemas/state-machine-workflow.schema.json +5 -0
  51. package/schemas/transition-flow-workflow.schema.json +5 -0
  52. package/spur.js +21393 -20831
  53. package/web/_astro/BoardApp.CJiqp5pS.js +1 -0
  54. package/web/_astro/{BoardApp.D4mBUGLT.js → BoardApp.yBBcFXWP.js} +60 -60
  55. package/web/_astro/{TaskDetail.DhCWaOPR.js → TaskDetail.DqFJbRFc.js} +1 -1
  56. package/web/_astro/{arc.D3X0nfq-.js → arc.DL-BpHoi.js} +1 -1
  57. package/web/_astro/{architectureDiagram-3BPJPVTR.BQDoiGme.js → architectureDiagram-3BPJPVTR.9IdQYyDq.js} +1 -1
  58. package/web/_astro/{blockDiagram-GPEHLZMM.BjHekBbM.js → blockDiagram-GPEHLZMM.BKsFCqTl.js} +1 -1
  59. package/web/_astro/{c4Diagram-AAUBKEIU.BvHt171l.js → c4Diagram-AAUBKEIU.DhwI0dh1.js} +1 -1
  60. package/web/_astro/channel.CX5453qQ.js +1 -0
  61. package/web/_astro/{chunk-2J33WTMH.CwxGXlDR.js → chunk-2J33WTMH.B3QVmQ9S.js} +1 -1
  62. package/web/_astro/{chunk-4BX2VUAB.Dia1YtwU.js → chunk-4BX2VUAB.DBSuqs9F.js} +1 -1
  63. package/web/_astro/{chunk-55IACEB6.Oz9epCXa.js → chunk-55IACEB6.BSYTWAYD.js} +1 -1
  64. package/web/_astro/{chunk-727SXJPM.DKFYJjYJ.js → chunk-727SXJPM.cWVuxXfS.js} +1 -1
  65. package/web/_astro/{chunk-AQP2D5EJ.AD7w5oVz.js → chunk-AQP2D5EJ.DpU_Ob3d.js} +1 -1
  66. package/web/_astro/{chunk-FMBD7UC4.D0OqTj52.js → chunk-FMBD7UC4.BykFkyji.js} +1 -1
  67. package/web/_astro/{chunk-ND2GUHAM.CU5ppR7j.js → chunk-ND2GUHAM.DwgHlMdY.js} +1 -1
  68. package/web/_astro/{chunk-QZHKN3VN.Bu1rFztw.js → chunk-QZHKN3VN.CusXUGWM.js} +1 -1
  69. package/web/_astro/{classDiagram-4FO5ZUOK.C0Pgm5yE.js → classDiagram-4FO5ZUOK.fx0ObzkN.js} +1 -1
  70. package/web/_astro/{classDiagram-v2-Q7XG4LA2.C0Pgm5yE.js → classDiagram-v2-Q7XG4LA2.fx0ObzkN.js} +1 -1
  71. package/web/_astro/{cose-bilkent-S5V4N54A.C0ecLb1e.js → cose-bilkent-S5V4N54A.Z4HgOlsd.js} +1 -1
  72. package/web/_astro/{cynefin-OW5HDTMX.CpStVmhO.js → cynefin-OW5HDTMX.B5dIZHJu.js} +1 -1
  73. package/web/_astro/{dagre-BM42HDAG.BPWN3W9f.js → dagre-BM42HDAG.DT70Q_Yw.js} +1 -1
  74. package/web/_astro/{diagram-2AECGRRQ.DnXbEot4.js → diagram-2AECGRRQ.DqHA3XBF.js} +1 -1
  75. package/web/_astro/{diagram-5GNKFQAL.BYJsgUiI.js → diagram-5GNKFQAL.BmCem957.js} +1 -1
  76. package/web/_astro/{diagram-KO2AKTUF.CU4Bqprx.js → diagram-KO2AKTUF.sn0-hrE0.js} +1 -1
  77. package/web/_astro/{diagram-LMA3HP47.BQLOANIu.js → diagram-LMA3HP47.BSHe9tVc.js} +1 -1
  78. package/web/_astro/{diagram-OG6HWLK6.Cn1UVhqj.js → diagram-OG6HWLK6.DHIc-86k.js} +1 -1
  79. package/web/_astro/{erDiagram-TEJ5UH35.C8FdZyvA.js → erDiagram-TEJ5UH35.Bxayrs7v.js} +1 -1
  80. package/web/_astro/{flowDiagram-I6XJVG4X.QSZCR8Uo.js → flowDiagram-I6XJVG4X.BkzoE_5I.js} +1 -1
  81. package/web/_astro/{ganttDiagram-6RSMTGT7.O1Py31MY.js → ganttDiagram-6RSMTGT7.okT6CvTo.js} +1 -1
  82. package/web/_astro/{gitGraphDiagram-PVQCEYII.DoeJRBfY.js → gitGraphDiagram-PVQCEYII.CJuYbhC7.js} +1 -1
  83. package/web/_astro/{index.TPF1FerP.css → index.CcU5weKX.css} +1 -1
  84. package/web/_astro/{infoDiagram-5YYISTIA.9npLJecW.js → infoDiagram-5YYISTIA.RqLy7nBo.js} +1 -1
  85. package/web/_astro/{ishikawaDiagram-YF4QCWOH.DXanvOr3.js → ishikawaDiagram-YF4QCWOH.BwIcoagw.js} +1 -1
  86. package/web/_astro/{journeyDiagram-JHISSGLW.DxbVjxAv.js → journeyDiagram-JHISSGLW.UB1VbWtH.js} +1 -1
  87. package/web/_astro/{kanban-definition-UN3LZRKU.BDk6_iq0.js → kanban-definition-UN3LZRKU.AaxMKpTk.js} +1 -1
  88. package/web/_astro/{linear.C9cCDjT3.js → linear.Nv_xOUjP.js} +1 -1
  89. package/web/_astro/{mermaid.core.CpHRe5nH.js → mermaid.core.Bc4LqQgX.js} +4 -4
  90. package/web/_astro/{mindmap-definition-RKZ34NQL.D-6C85bn.js → mindmap-definition-RKZ34NQL.oKUvU_qi.js} +1 -1
  91. package/web/_astro/{pieDiagram-4H26LBE5.B3Oxodty.js → pieDiagram-4H26LBE5.DQk0oo03.js} +1 -1
  92. package/web/_astro/{quadrantDiagram-W4KKPZXB.QZ_9Hshu.js → quadrantDiagram-W4KKPZXB.BdDjESDa.js} +1 -1
  93. package/web/_astro/{requirementDiagram-4Y6WPE33.BimvuK53.js → requirementDiagram-4Y6WPE33.C2u9hUeH.js} +1 -1
  94. package/web/_astro/{sankeyDiagram-5OEKKPKP.D5Kkpx2C.js → sankeyDiagram-5OEKKPKP.CDEoiJST.js} +1 -1
  95. package/web/_astro/{sequenceDiagram-3UESZ5HK.B4YR6fAE.js → sequenceDiagram-3UESZ5HK.D_hT_GAT.js} +1 -1
  96. package/web/_astro/{stateDiagram-AJRCARHV.Bt2wkZok.js → stateDiagram-AJRCARHV.DI8RYG0b.js} +1 -1
  97. package/web/_astro/{stateDiagram-v2-BHNVJYJU.YZ4X7dfQ.js → stateDiagram-v2-BHNVJYJU.Bkxz4DnP.js} +1 -1
  98. package/web/_astro/{timeline-definition-PNZ67QCA.DmiTBL8w.js → timeline-definition-PNZ67QCA.DSY-kH3-.js} +1 -1
  99. package/web/_astro/{vennDiagram-CIIHVFJN.KNJV40mf.js → vennDiagram-CIIHVFJN.CpaDtuGr.js} +1 -1
  100. package/web/_astro/{wardleyDiagram-YWT4CUSO.BVAaKgWH.js → wardleyDiagram-YWT4CUSO.DujQWvo8.js} +1 -1
  101. package/web/_astro/{xychartDiagram-2RQKCTM6.D5Jx7zB2.js → xychartDiagram-2RQKCTM6.DcM5Y4b9.js} +1 -1
  102. package/web/index.html +2 -2
  103. package/config/workflows/basic.yaml +0 -146
  104. package/config/workflows/docs-pipeline.yaml +0 -350
  105. package/config/workflows/feature-dev.yaml +0 -288
  106. package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -146
  107. package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
  108. package/web/_astro/BoardApp.DFPYpI5O.js +0 -1
  109. package/web/_astro/channel.BlQwxyoI.js +0 -1
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.3.84",
10
+ "version": "0.3.86",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -95,7 +95,7 @@ agent:
95
95
  # claude-*-5 via claude, grok-4.6 via grok, gemini-3.x via agy) —
96
96
  # consistently outperforms the same model routed through a third-party CLI,
97
97
  # and belongs at the capable rungs. `portable` models (glm-5.x,
98
- # deepseek-v4-*, …) are provider-agnostic and best carried by omp or pi at
98
+ # deepseek-v4-*, …) are provider-agnostic and best carried by pi at
99
99
  # the cheap/standard rungs. The ladder below mixes both classes on purpose;
100
100
  # measure pairings with the history plane before promoting a portable model
101
101
  # into a capable rung. Live tiers: cheap | standard | capable-1 | capable-2
@@ -106,19 +106,24 @@ agent:
106
106
  # capable-1.
107
107
  #
108
108
  # Because this is the global layer, a project that wants one different model
109
- # writes only `- name: omp` + `model: …` — the agent and tier come from
110
- # here.
109
+ # writes only `- name: pi-dsv4-flash` + `model: …` — the agent and tier come
110
+ # from here.
111
111
  executors:
112
112
  # cheap rung is optional: with none declared, scribe-role work starts
113
113
  # one rung up at the cheapest standard executor. Add one when
114
114
  # transcript-heavy mechanical commands (changelog/gitmsg/handover) get
115
115
  # noisy on cost.
116
116
  # - name: minimax
117
- # agent: omp
117
+ # agent: pi
118
118
  # model: minimax/MiniMax-M3
119
119
  # tier: cheap
120
- - name: omp
121
- agent: omp
120
+ # Standard rung. An `agent:` here is a live DISPATCH target, not a label:
121
+ # role routing (`agent.default: coder`, `--agent auto`, every workflow step
122
+ # declaring `agent: auto`) lands on the cheapest eligible standard executor,
123
+ # so listing an agent makes Spur delegate work to it. Keep this rung on an
124
+ # agent the supported install targets actually carry.
125
+ - name: pi-dsv4-flash
126
+ agent: pi
122
127
  model: opencode/deepseek-v4-flash
123
128
  tier: standard
124
129
  - name: pi
@@ -28,17 +28,6 @@
28
28
  "source": "Post-merge count (0607 R2: doc-sync + learning-capture consolidated, 2 -> 1). Real-run reading (R5): median 253s, max-sane 2319s (n=44). Budget = 60 min ceiling.",
29
29
  "decision": null
30
30
  },
31
- "docs-pipeline": {
32
- "modelQueries": 2,
33
- "wallClockMs": null,
34
- "tokenCostUsd": null,
35
- "source": "No fixture (0607 option a) and only dry/short runs recorded in history (max 1s, n=4) — wall-clock budget unenforced until a real docs-pipeline run exists. Query budget 2 matches the live workflow definition's query list ['draft','verify'] (added by task 0704; 0607 R3 recorded 1, which is stale against the definition). 0754 R6: FIX, not raise — the value was wrong against the definition, the workflow's declared query count did not change.",
36
- "decision": {
37
- "date": "2026-09-03",
38
- "wbs": "0754",
39
- "note": "align docs-pipeline.modelQueries to the live workflow definition's query list ['draft','verify']; not a measured-cost raise — the workflow's declared query count was already 2 (added by 0704); the budget was the stale side, not the workflow. Per D8 Decision 11."
40
- }
41
- },
42
31
  "pr-review": {
43
32
  "modelQueries": 0,
44
33
  "wallClockMs": null,
@@ -53,26 +42,12 @@
53
42
  "source": "Coverage entry (0826 R2): the live definition's model-query count (extractResolvedWorkflowFacts, 2026-09-11). No real-run measurement yet: wall-clock and cost are unenforced until measured, never 0.",
54
43
  "decision": null
55
44
  },
56
- "feature-dev": {
57
- "modelQueries": 2,
58
- "wallClockMs": null,
59
- "tokenCostUsd": null,
60
- "source": "Coverage entry (0826 R2): the live definition's model-query count (extractResolvedWorkflowFacts, 2026-09-11). No real-run measurement yet: wall-clock and cost are unenforced until measured, never 0.",
61
- "decision": null
62
- },
63
45
  "wayfinder-resolution": {
64
46
  "modelQueries": 2,
65
47
  "wallClockMs": null,
66
48
  "tokenCostUsd": null,
67
49
  "source": "Coverage entry (0826 R2): the live definition's model-query count (extractResolvedWorkflowFacts, 2026-09-11). No real-run measurement yet: wall-clock and cost are unenforced until measured, never 0.",
68
50
  "decision": null
69
- },
70
- "basic": {
71
- "modelQueries": 1,
72
- "wallClockMs": null,
73
- "tokenCostUsd": null,
74
- "source": "Coverage entry (0826 R2): the live definition's model-query count (extractResolvedWorkflowFacts, 2026-09-11). No real-run measurement yet: wall-clock and cost are unenforced until measured, never 0.",
75
- "decision": null
76
51
  }
77
52
  }
78
53
  }
@@ -26,11 +26,6 @@
26
26
  "contract": "standard",
27
27
  "twin": "dogfood-testing/validate-report.mjs"
28
28
  },
29
- {
30
- "rel": "feature-dev-precheck.ts",
31
- "contract": "standard",
32
- "twin": "feature-dev-precheck.mjs"
33
- },
34
29
  {
35
30
  "rel": "feature-sync-bounded.ts",
36
31
  "contract": "standard",
@@ -0,0 +1,41 @@
1
+ $schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
2
+ # Environment-variable hygiene — env access funnels through the config gateway (task 0902).
3
+ #
4
+ # Contract:
5
+ # - `@gobing-ai/ts-utils` (`packages/utils/src/env.ts` in ts-libs) is the single
6
+ # implementation source; `packages/config/src/index.ts` re-exports `getEnvVar` /
7
+ # `getEnvVars` / `setEnvVar` / `removeEnvVar` / `getAppOptions` as the app-side funnel,
8
+ # and NO file in this repo touches `process.env` directly — packages/config included.
9
+ # Env flows into the app as typed config or injected `env` records (ADR-027). Runtime
10
+ # options belong in `.spur/config.yaml` `bootstrap.options` via `getAppOptions`;
11
+ # dev-only knobs belong in consts.
12
+ # - Plugin scripts/hooks import the same functions from `@gobing-ai/ts-utils`; the
13
+ # `superskill script convert` `.mjs` twins bundle them in (self-contained on any
14
+ # install target).
15
+ # - `apps/web` uses `import.meta.env` (Vite browser plane) — not process env, never matches.
16
+ # - Which variables exist and who consumes them is documented in `.env.example`.
17
+ # (The former per-var lease table moved there; the rule now enforces the funnel,
18
+ # not a file-by-var inventory.)
19
+ #
20
+ # Scope: apps/**, packages/**, scripts/**, plugins/sp/** — src AND tests. No exclusions:
21
+ # even the gateway package itself is free of direct access.
22
+ include:
23
+ - "apps/**/src/**/*.{ts,tsx}"
24
+ - "apps/**/tests/**/*.{ts,tsx}"
25
+ - "packages/**/src/**/*.{ts,tsx}"
26
+ - "packages/**/tests/**/*.{ts,tsx}"
27
+ - "scripts/**/*.ts"
28
+ - "plugins/sp/**/*.ts"
29
+ rules:
30
+ - id: env-var-hygiene
31
+ description: >
32
+ No direct `process.env` or `Bun.env` access outside the packages/config gateway.
33
+ Read via `getEnvVar` (single var, undefined-only fallback), seed/compose via
34
+ `getEnvVars()` (live record — also the sanctioned form for setting/deleting
35
+ parent→child contract markers), or take an injected `env` record parameter.
36
+ Aliasing (`const env = process.env`) is caught too — it must touch `process.env`.
37
+ severity: error
38
+ evaluator:
39
+ type: rg
40
+ config:
41
+ pattern: "process\\.env|\\bBun\\.env\\b"
@@ -1,5 +1,5 @@
1
1
  {
2
- "note": "Transition-shim manifest (task 0541, feature B2). Each entry records one compatibility path marked @transition-shim(<id>) in source. Every field is required. The gate (bun run transition-shim-check, inside `spur-check`) is two-sided: an unregistered marker fails, and a listed entry whose marker is gone fails. Emptying this file is the definition of the transition being complete (docs/04_DESIGN.md §2.5). Removal conditions must be objectively checkable against the repository, not 'when convenient'. Shims are registered by the tasks that create them (0536/0537/0538/0542).",
2
+ "note": "Transition-shim manifest (task 0541, feature B2). Each entry records one compatibility path marked @transition-shim(<id>) in source. Every field is required. The gate (bun run transition-shim-check, in the feature-scoped `spur-check-feature` pass since task 0872) is two-sided: an unregistered marker fails, and a listed entry whose marker is gone fails. Emptying this file is the definition of the transition being complete (docs/04_DESIGN.md §2.5). Removal conditions must be objectively checkable against the repository, not 'when convenient'. Shims are registered by the tasks that create them (0536/0537/0538/0542).",
3
3
  "entries": [
4
4
  {
5
5
  "id": "agent-bare-binary-name",
@@ -0,0 +1,41 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "note": "Workflow graph-change promotion candidates (task 0873, feature D62; ADR-076 amendment). A candidate is shadow-run against recorded real-run inputs and is promoted into the canonical definition or deleted by its named deadline — never left as a standing parallel <name>2.yaml. `verdict` is null while pending and is filled by `bun scripts/spur-dev.ts promotion evaluate <id>`; `promotion check` (spur-check-feature) fails any candidate still present past its deadline and any unreferenced parallel definition in config/workflows/.",
4
+ "candidates": [
5
+ {
6
+ "id": "wrapup-contract-violation-pilot-routing",
7
+ "canonical": "wrapup-pipeline",
8
+ "deadline": "2026-10-17",
9
+ "createdAt": "2026-09-17",
10
+ "rationale": "ADR-118 pilot graph change shipped by task 0871 (doc-sync contract-violation routing to the shell-only repair state). Registered by task 0876 to take the promotion decision on its first real-run measurement (R4/R5): wrapup-pipeline run fadca099-25a7-4884-a4ae-923cd0239775 (done, state-machine, definition sha256:cad7ad8b..., v4, created 2026-09-17T15:14:46Z, 14h28m after 0871 landed at 00:46:46Z - the only post-landing terminal non-dry wrapup run) routed doc-sync->learnings-append on the default edge: the doc-sync agent.run exited clean (exitCode 0) and produced its declared expectFile capture (.spur/run/fadca099-...-wrapup-learnings.md), so data.outcome was success, ContractViolationGuardRunner passed:false (packages/app/src/workflow/guards/contract-violation.ts:26), zero workflow.agent.contract-violation system_events, zero failed actions (executor-failure path not taken), no repair status file. Measured absence on real traffic - never fabricated. The change projects delta.agentRunCount=1 (unchanged): the repair edge adds no model hop (repair is a shell that records the miss; ADR-118 forbids re-dispatch), so the ADR-076 cost bar cannot reward it; its justification is ADR-118 routing correctness, evidenced here by the measured absence on the first real run. Disposition (resolve delete, or hold as the pattern-spread precedent for 0873's gate) belongs to the D62 owner before the deadline.",
11
+ "measurement": {
12
+ "workflow": "wrapup-pipeline",
13
+ "runIds": ["fadca099-25a7-4884-a4ae-923cd0239775"]
14
+ },
15
+ "delta": {
16
+ "agentRunCount": 1
17
+ },
18
+ "verdict": {
19
+ "decision": "delete",
20
+ "evaluatedAt": "2026-09-17T15:33:27.274Z",
21
+ "agentRunCount": {
22
+ "runs": 1,
23
+ "mean": 1,
24
+ "median": 1,
25
+ "min": 1,
26
+ "max": 1
27
+ },
28
+ "agentRunDurationMs": {
29
+ "runs": 1,
30
+ "mean": 505937,
31
+ "median": 505937,
32
+ "min": 505937,
33
+ "max": 505937
34
+ },
35
+ "candidateAgentRunCount": 1,
36
+ "canonicalAgentRunCount": 1,
37
+ "reason": "candidate projects 1 agent.run action(s), not fewer than the canonical wrapup-pipeline count of 1 (1 real run(s), median 1 agent.run action(s)/run, median 505937 ms/run) — ADR-076 rejected a graph adding a model hop"
38
+ }
39
+ }
40
+ ]
41
+ }
@@ -62,11 +62,13 @@ transitions:
62
62
  - from: verifying
63
63
  to: done
64
64
  description: >
65
- Feature verified — complete. Runs strict check (AC validated, traceability clean).
65
+ Feature verified — complete. Requires the feature-scoped verification pass
66
+ (ADR-119) to have recorded PASS, then strict check (AC validated,
67
+ traceability clean).
66
68
  guard:
67
69
  kind: shell
68
70
  options:
69
- command: '$spurBin feature check $featureId --strict --as done'
71
+ command: 'test "$(cat .spur/run/$featureId-feature-verification.status 2>/dev/null)" = PASS && $spurBin feature check $featureId --strict --as done'
70
72
 
71
73
  # Rework: verifying → active (mandatory History entry)
72
74
  - from: verifying
@@ -0,0 +1,70 @@
1
+ # Feature-scoped verification pass (ADR-119, feature D62, task 0872).
2
+ #
3
+ # Owns a lifecycle boundary no per-task graph owns: the repo-wide checks whose
4
+ # invariant spans the repository rather than a single task's diff — corpus
5
+ # consistency, contract baselines, dependency/schema drift, the frozen History
6
+ # surface, and the repo-wide test set. They run once per feature against a
7
+ # settled tree, deliberately the slow pass, and `feature-lifecycle`'s
8
+ # verifying→done guard refuses to complete the feature until this pass records
9
+ # PASS (ADR-119 consequence: a feature is not done until it passes).
10
+ #
11
+ # Run: spur workflow run feature-verification.yaml --vars '{"featureId":"D62"}'
12
+ #
13
+ # Shape: verify -> done | failed. The shell action writes the verdict to
14
+ # `.spur/run/<featureId>-feature-verification.status` and always exits 0; the
15
+ # transition guard reads the status so a missing, corrupt or FAIL verdict routes
16
+ # to `failed` (fail-closed), never a silent PASS.
17
+ #
18
+ # Reliability (aligned with task-pipeline / ADR-043): no agent.run nodes; the
19
+ # verification command is a trusted config var executed via `sh -c` (same
20
+ # surface as task-pipeline's qualityGateCmd — never interpolate untrusted input).
21
+ "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
22
+ kind: state-machine
23
+ name: feature-verification
24
+ version: "1"
25
+ description: "Feature-scoped verification pass (ADR-119): runs the repo-wide check set once per feature against a settled tree and records PASS/FAIL."
26
+ iterationBound: 2
27
+ initialState: verify
28
+ terminalStates:
29
+ - done
30
+ - failed
31
+ failureStates:
32
+ - failed
33
+ vars:
34
+ featureId: "X"
35
+ spurBin: "spur"
36
+ verificationCmd: "bun run spur-check-feature"
37
+
38
+ states:
39
+ - id: verify
40
+ description: >
41
+ Run the repo-wide check set, record the verdict, always exit 0.
42
+ onEnter:
43
+ - kind: shell
44
+ options:
45
+ command: >-
46
+ mkdir -p .spur/run;
47
+ sh -c "$verificationCmd" > ".spur/run/$featureId-feature-verification.log" 2>&1
48
+ && printf 'PASS\n' > ".spur/run/$featureId-feature-verification.status"
49
+ || printf 'FAIL\n' > ".spur/run/$featureId-feature-verification.status";
50
+ exit 0
51
+
52
+ - id: done
53
+ description: Terminal — repo-wide checks passed.
54
+
55
+ - id: failed
56
+ description: Terminal — repo-wide checks failed or the verdict is missing/corrupt.
57
+
58
+ transitions:
59
+ - from: verify
60
+ to: done
61
+ description: Repo-wide checks passed — the feature-scoped verification pass records PASS.
62
+ guard:
63
+ kind: shell
64
+ options:
65
+ command: 'test "$(cat .spur/run/$featureId-feature-verification.status 2>/dev/null)" = PASS'
66
+ - from: verify
67
+ to: failed
68
+ description: Repo-wide checks failed (or the verdict is missing/corrupt) — stop at failed.
69
+ guard:
70
+ kind: always
@@ -33,7 +33,7 @@
33
33
  # lets profile=auto route around the idea-eval taste gate (default "false")
34
34
  # CLI --approve-taste sets both design_approved and idea_approved to true.
35
35
  # spurBin — PATH-independent spur invocation (overridden by CLI at run start)
36
- # agent — agent for agent.run steps (default: omp)
36
+ # agent — agent for agent.run steps (default: auto → `agent.default` in config)
37
37
  #
38
38
  # Reliability (aligned with task-pipeline / ADR-043):
39
39
  # - Soft agent doctor at start → failed via transitions (not raw lifecycle abort)
@@ -568,12 +568,14 @@ transitions:
568
568
  - from: ac-generate
569
569
  to: system-design
570
570
  description: "profile=auto, check passed, design route — run system design."
571
- # (warn) 4 test segments: profile gate + captured status + route signal jointly own the
571
+ # (warn) 5 commands (named status capture + 4 conditions): profile gate + captured status + route signal jointly own the
572
572
  # route; guards never re-run the CLI (0769), so there is nothing smaller to extract.
573
573
  guard:
574
574
  kind: shell
575
575
  options:
576
- command: 'test "$profile" = auto && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false'
576
+ command: >-
577
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
578
+ test "$profile" = auto && test "$ac_status" = PASS && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false
577
579
  # Auto-skip 2: pass + skip-design route → decompose
578
580
  - from: ac-generate
579
581
  to: decompose
@@ -588,22 +590,26 @@ transitions:
588
590
  - from: ac-generate
589
591
  to: ac-generate
590
592
  description: "profile=auto, check failed, retry cap not reached — re-run ac-generate."
591
- # (warn) 4 test segments: profile gate + captured status + captured retry count; the
593
+ # (warn) 5 commands (named status capture + 4 conditions): profile gate + captured status + captured retry count; the
592
594
  # retry loop is the smallest honest formulation of cap<3 routing (0769).
593
595
  guard:
594
596
  kind: shell
595
597
  options:
596
- command: 'test "$profile" = auto && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" != PASS && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3'
598
+ command: >-
599
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
600
+ test "$profile" = auto && test "$ac_status" != PASS && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3
597
601
  # Auto-skip 4: check failed, retry cap reached → escalate to failed
598
602
  - from: ac-generate
599
603
  to: failed
600
604
  description: "profile=auto, check failed after 3 retries — escalate to failed."
601
- # (warn) 4 test segments: mirror of the retry guard with cap>=3; keeping escalation and
605
+ # (warn) 5 commands (named status capture + 4 conditions): mirror of the retry guard with cap>=3; keeping escalation and
602
606
  # retry as one test-chain pair makes the cap boundary auditable in the diff (0769).
603
607
  guard:
604
608
  kind: shell
605
609
  options:
606
- command: 'test "$profile" = auto && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" != PASS && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3'
610
+ command: >-
611
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
612
+ test "$profile" = auto && test "$ac_status" != PASS && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3
607
613
  # Interactive fallback: enter feature-check HITL gate
608
614
  - from: ac-generate
609
615
  to: feature-check
@@ -616,12 +622,14 @@ transitions:
616
622
  - from: feature-check
617
623
  to: system-design
618
624
  description: Feature check passed, design route — run system design.
619
- # (warn) 4 test segments: HITL answer + captured status + design route; same shape as the
625
+ # (warn) 5 commands (named status capture + 4 conditions): HITL answer + captured status + design route; same shape as the
620
626
  # ac-generate auto-skips, reading the same captured signal files (0769).
621
627
  guard:
622
628
  kind: shell
623
629
  options:
624
- command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false'
630
+ command: >-
631
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
632
+ test "$__hitlAnswer" = yes && test "$ac_status" = PASS && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false
625
633
  - from: feature-check
626
634
  to: decompose
627
635
  description: Feature check passed, skip-design route — go directly to decompose.
@@ -634,20 +642,24 @@ transitions:
634
642
  - from: feature-check
635
643
  to: ac-generate
636
644
  description: "Feature check failed — revise AC (retry cap: 3)."
637
- # (warn) 4 test segments: HITL reject/failed-status OR-pair + captured retry count; the
645
+ # (warn) 5 commands (named status capture + 4 conditions): HITL reject/failed-status OR-pair + captured retry count; the
638
646
  # interactive retry mirror of the ac-generate cap<3 guard (0769).
639
647
  guard:
640
648
  kind: shell
641
649
  options:
642
- command: '(test "$__hitlAnswer" = no || test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3'
650
+ command: >-
651
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
652
+ (test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3
643
653
  - from: feature-check
644
654
  to: failed
645
655
  description: Feature check failed after 3 retries — escalate to failed.
646
- # (warn) 4 test segments: mirror of the revise guard with cap>=3 (0769).
656
+ # (warn) 5 commands (named status capture + 4 conditions): mirror of the revise guard with cap>=3 (0769).
647
657
  guard:
648
658
  kind: shell
649
659
  options:
650
- command: '(test "$__hitlAnswer" = no || test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3'
660
+ command: >-
661
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
662
+ (test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3
651
663
  - from: feature-check
652
664
  to: cancelled
653
665
  description: Operator cancelled the feature-check gate.
@@ -603,10 +603,14 @@ transitions:
603
603
  - from: precheck
604
604
  to: implement
605
605
  description: Deterministic size, evidence, and task checks are green — begin implementation.
606
+ # (warn) 5 commands: named size/evidence status reads + task check (legibility, 0874).
606
607
  guard:
607
608
  kind: shell
608
609
  options:
609
- command: 'test "$(cat .spur/run/$wbs-precheck-size.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$wbs-precheck-evidence.status 2>/dev/null)" = PASS && $spurBin task check $wbs'
610
+ command: >-
611
+ size_status="$(cat .spur/run/$wbs-precheck-size.status 2>/dev/null)";
612
+ evidence_status="$(cat .spur/run/$wbs-precheck-evidence.status 2>/dev/null)";
613
+ test "$size_status" = PASS && test "$evidence_status" = PASS && $spurBin task check $wbs
610
614
  - from: precheck
611
615
  to: failed
612
616
  description: Size and/or task check failed — stop before implement.
@@ -626,14 +630,18 @@ transitions:
626
630
  guard:
627
631
  kind: shell
628
632
  options:
629
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" = fast'
633
+ command: >-
634
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
635
+ test "$gate_status" = PASS && test "$mode" = fast
630
636
  - from: test
631
637
  to: review
632
638
  description: Quality gate already green and safety mode — proceed to review.
633
639
  guard:
634
640
  kind: shell
635
641
  options:
636
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" != fast'
642
+ command: >-
643
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
644
+ test "$gate_status" = PASS && test "$mode" != fast
637
645
  - from: test
638
646
  to: test-fix
639
647
  description: Quality gate red — start bounded fixall loop.
@@ -659,28 +667,40 @@ transitions:
659
667
  guard:
660
668
  kind: shell
661
669
  options:
662
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" = fast'
670
+ command: >-
671
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
672
+ test "$gate_status" = PASS && test "$mode" = fast
663
673
  - from: test-recheck
664
674
  to: review
665
675
  description: Quality gate green after fixall and safety mode — proceed to review.
666
676
  guard:
667
677
  kind: shell
668
678
  options:
669
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" != fast'
679
+ command: >-
680
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
681
+ test "$gate_status" = PASS && test "$mode" != fast
670
682
  - from: test-recheck
671
683
  to: test-fix
672
684
  description: Still red and under qualityGateMaxFixAttempts — another fixall hop.
685
+ # (warn) 5 commands: named gate status + fix attempts (legibility, 0874).
673
686
  guard:
674
687
  kind: shell
675
688
  options:
676
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL && test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -lt "$qualityGateMaxFixAttempts"'
689
+ command: >-
690
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
691
+ fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
692
+ test "$gate_status" = FAIL && test "$fix_attempts" -lt "$qualityGateMaxFixAttempts"
677
693
  - from: test-recheck
678
694
  to: failed
679
695
  description: Still red after max fixall attempts — stop at failed (not silent abort).
696
+ # (warn) 5 commands: named gate status + fix attempts (legibility, 0874).
680
697
  guard:
681
698
  kind: shell
682
699
  options:
683
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL && test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -ge "$qualityGateMaxFixAttempts"'
700
+ command: >-
701
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
702
+ fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
703
+ test "$gate_status" = FAIL && test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
684
704
  # Defense: corrupt recheck status — failed, not review.
685
705
  - from: test-recheck
686
706
  to: failed
@@ -748,7 +768,16 @@ transitions:
748
768
  kind: shell
749
769
  options:
750
770
  command: >-
751
- jq -e --arg d "$proofDigest" --arg r "$__runId" --arg dd "$__definitionDigest" '.verdict == "PASS" and (.proof.digest // "") == $d and (.proof.stages.qualityGate.digest // "") == $d and (.proof.stages.review.digest // "") == $d and (.proof.stages.review.status // "") == "completed" and (.proof.stages.verification.digest // "") == $d and (.proof.runId // "") == $r and (.proof.definitionDigest // "") == $dd' ".spur/run/$wbs-verdict.json" >/dev/null 2>&1
771
+ jq -e --arg d "$proofDigest" --arg r "$__runId" --arg dd "$__definitionDigest" '
772
+ .verdict == "PASS"
773
+ and (.proof.digest // "") == $d
774
+ and (.proof.stages.qualityGate.digest // "") == $d
775
+ and (.proof.stages.review.digest // "") == $d
776
+ and (.proof.stages.review.status // "") == "completed"
777
+ and (.proof.stages.verification.digest // "") == $d
778
+ and (.proof.runId // "") == $r
779
+ and (.proof.definitionDigest // "") == $dd
780
+ ' ".spur/run/$wbs-verdict.json" >/dev/null 2>&1
752
781
  - from: verify
753
782
  to: test-fix
754
783
  description: >-
@@ -760,8 +789,8 @@ transitions:
760
789
  kind: shell
761
790
  options:
762
791
  command: >-
763
- V="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)";
764
- test -n "$V" && test "$V" != PASS &&
792
+ verdict="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)";
793
+ test -n "$verdict" && test "$verdict" != PASS &&
765
794
  test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -lt "$qualityGateMaxFixAttempts"
766
795
  - from: verify
767
796
  to: failed
@@ -778,6 +807,7 @@ transitions:
778
807
  - from: record
779
808
  to: done
780
809
  description: Task check passed and the verdict proof block still names the captured digest — certify done.
810
+ # (warn) 5 commands: named verdict + proof digest reads after the task check (legibility, 0874).
781
811
  guard:
782
812
  kind: shell
783
813
  options:
@@ -788,8 +818,9 @@ transitions:
788
818
  # one shell line.
789
819
  command: >-
790
820
  $spurBin task check $wbs --as done &&
791
- test "$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" = PASS &&
792
- test "$(jq -r '.proof.digest // ""' .spur/run/$wbs-verdict.json 2>/dev/null)" = "$proofDigest"
821
+ verdict="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" &&
822
+ proof_digest="$(jq -r '.proof.digest // ""' .spur/run/$wbs-verdict.json 2>/dev/null)" &&
823
+ test "$verdict" = PASS && test "$proof_digest" = "$proofDigest"
793
824
  - from: record
794
825
  to: failed
795
826
  description: Task check failed or proof evidence missing/malformed/mismatched — block before done.