@gobing-ai/spur 0.3.92 → 0.3.94

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 (119) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +0 -7
  3. package/config/plugin-scripts.json +10 -0
  4. package/config/rules/boundary/test-subpath-boundary.yaml +48 -0
  5. package/config/rules/strict/runtime-boundaries.yaml +2 -1
  6. package/config/rules/structure/protected-files.yaml +4 -0
  7. package/config/templates/AGENTS.md +4 -0
  8. package/config/templates/docs/02_ROADMAP.md +2 -0
  9. package/config/templates/docs/03_ARCHITECTURE.md +3 -1
  10. package/config/templates/docs/04_DESIGN.md +2 -0
  11. package/config/templates/docs/99_PROJECT_CONSTITUTION.md +10 -2
  12. package/config/workflow-candidates.json +72 -1
  13. package/config/workflows/feature-verification.yaml +1 -0
  14. package/config/workflows/history-anatomy.yaml +2 -0
  15. package/config/workflows/idea-pipeline.yaml +67 -18
  16. package/config/workflows/pr-review.yaml +8 -0
  17. package/config/workflows/task-pipeline.yaml +171 -39
  18. package/config/workflows/wayfinder-resolution.yaml +5 -0
  19. package/config/workflows/wrapup-pipeline.yaml +55 -14
  20. package/package.json +9 -9
  21. package/plugins/sp/README.md +8 -3
  22. package/plugins/sp/commands/dev-review-session.md +4 -4
  23. package/plugins/sp/commands/dev-review.md +16 -5
  24. package/plugins/sp/commands/dev-run.md +2 -2
  25. package/plugins/sp/commands/dev-runall.md +2 -2
  26. package/plugins/sp/hooks/pi/guard-extension.ts +17 -36
  27. package/plugins/sp/lib/idea-handoff.generated.mjs +8 -4
  28. package/plugins/sp/lib/inline-run.generated.d.mts +3 -0
  29. package/plugins/sp/lib/inline-run.generated.mjs +48 -19
  30. package/plugins/sp/plugin.json +1 -1
  31. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
  32. package/plugins/sp/scripts/inline-run-setup.mjs +117 -7
  33. package/plugins/sp/scripts/inline-run-setup.ts +211 -7
  34. package/plugins/sp/scripts/quality-gate.mjs +255 -6
  35. package/plugins/sp/scripts/quality-gate.ts +425 -9
  36. package/plugins/sp/scripts/residual-scan.mjs +13 -5
  37. package/plugins/sp/scripts/residual-scan.ts +34 -7
  38. package/plugins/sp/scripts/task-diffstat.mjs +156 -0
  39. package/plugins/sp/scripts/task-diffstat.ts +229 -0
  40. package/plugins/sp/scripts/wrapup-drift-probe.mjs +181 -0
  41. package/plugins/sp/scripts/wrapup-drift-probe.ts +258 -0
  42. package/plugins/sp/scripts/wrapup-steps.mjs +60 -1
  43. package/plugins/sp/scripts/wrapup-steps.ts +89 -4
  44. package/plugins/sp/skills/brainstorm/SKILL.md +2 -0
  45. package/plugins/sp/skills/brainstorm/references/workflows.md +17 -2
  46. package/plugins/sp/skills/code-verification/SKILL.md +2 -2
  47. package/plugins/sp/skills/code-verification/references/secu-review.md +3 -2
  48. package/plugins/sp/skills/session-review/SKILL.md +11 -9
  49. package/plugins/sp/skills/spur-check/SKILL.md +112 -0
  50. package/plugins/sp/skills/spur-dev/SKILL.md +2 -1
  51. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +14 -2
  52. package/plugins/sp/skills/spur-dev/references/dev-operations.md +8 -2
  53. package/plugins/sp/skills/spur-dev/references/document-authoring.md +85 -0
  54. package/plugins/sp/skills/spur-dev/references/execution-batch.md +46 -18
  55. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +24 -3
  56. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +3 -2
  57. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +34 -2
  58. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +5 -5
  59. package/plugins/sp/skills/spur-dev/templates/design.md +31 -0
  60. package/plugins/sp/skills/spur-dev/templates/plan.md +32 -0
  61. package/plugins/sp/skills/spur-doctor/SKILL.md +60 -14
  62. package/schemas/state-machine-workflow.schema.json +4 -0
  63. package/spur.js +19577 -19822
  64. package/web/_astro/BoardApp.C02hAHPO.js +1 -0
  65. package/web/_astro/{BoardApp.CerSBgis.js → BoardApp.FTEs3-N8.js} +107 -105
  66. package/web/_astro/{TaskDetail.DCqiC-OZ.js → TaskDetail.C-GdsS-t.js} +1 -1
  67. package/web/_astro/arc.uuAf51IT.js +1 -0
  68. package/web/_astro/{architectureDiagram-3BPJPVTR.DM_vp_hO.js → architectureDiagram-3BPJPVTR.CGe629A1.js} +1 -1
  69. package/web/_astro/{blockDiagram-GPEHLZMM.DXVIiv0p.js → blockDiagram-GPEHLZMM.D1mGCq3p.js} +1 -1
  70. package/web/_astro/{c4Diagram-AAUBKEIU.BbF_zCxW.js → c4Diagram-AAUBKEIU.CMsolcde.js} +1 -1
  71. package/web/_astro/channel.fsgl7o5j.js +1 -0
  72. package/web/_astro/{chunk-2J33WTMH.CAgQHpPC.js → chunk-2J33WTMH.CrGA3fik.js} +1 -1
  73. package/web/_astro/{chunk-4BX2VUAB.BN-5tpw4.js → chunk-4BX2VUAB.DsLVVla0.js} +1 -1
  74. package/web/_astro/{chunk-55IACEB6.CnPkEEr0.js → chunk-55IACEB6.Dxc59Tfi.js} +1 -1
  75. package/web/_astro/{chunk-727SXJPM.BQzQeMVm.js → chunk-727SXJPM.CaEVE2Wy.js} +4 -4
  76. package/web/_astro/{chunk-AQP2D5EJ.B6xNyDnL.js → chunk-AQP2D5EJ.BiJ4HXeI.js} +1 -1
  77. package/web/_astro/{chunk-FMBD7UC4.C7f9Ih78.js → chunk-FMBD7UC4.Mxf1fru5.js} +1 -1
  78. package/web/_astro/{chunk-ND2GUHAM.CNV1dFXT.js → chunk-ND2GUHAM.pyOWQixH.js} +1 -1
  79. package/web/_astro/{chunk-QZHKN3VN.Cudn2TkJ.js → chunk-QZHKN3VN.BmEsg4vr.js} +1 -1
  80. package/web/_astro/{classDiagram-4FO5ZUOK.D1NwP50q.js → classDiagram-4FO5ZUOK.BHhhMFTO.js} +1 -1
  81. package/web/_astro/{classDiagram-v2-Q7XG4LA2.D1NwP50q.js → classDiagram-v2-Q7XG4LA2.BHhhMFTO.js} +1 -1
  82. package/web/_astro/{cose-bilkent-S5V4N54A.B1wSL-Xb.js → cose-bilkent-S5V4N54A.2fH4YOlp.js} +1 -1
  83. package/web/_astro/{cynefin-OW5HDTMX.BmK52w8G.js → cynefin-OW5HDTMX.C2j1_lKL.js} +1 -1
  84. package/web/_astro/{dagre-BM42HDAG.Bfy5CTDT.js → dagre-BM42HDAG.hZ2NCTdT.js} +2 -2
  85. package/web/_astro/diagram-2AECGRRQ.Bfw5_EzK.js +43 -0
  86. package/web/_astro/diagram-5GNKFQAL.Bt1V_Tmk.js +10 -0
  87. package/web/_astro/{diagram-KO2AKTUF.CW_vMJ4z.js → diagram-KO2AKTUF.DEk-YFwp.js} +3 -3
  88. package/web/_astro/{diagram-LMA3HP47.B_8ZGF67.js → diagram-LMA3HP47.CDjndtQm.js} +1 -1
  89. package/web/_astro/{diagram-OG6HWLK6.BppnHsdS.js → diagram-OG6HWLK6.cFnUHScG.js} +1 -1
  90. package/web/_astro/{erDiagram-TEJ5UH35.BEuHXcjJ.js → erDiagram-TEJ5UH35.DlhYp7NV.js} +5 -5
  91. package/web/_astro/{flowDiagram-I6XJVG4X.CH-UlnGr.js → flowDiagram-I6XJVG4X.DNTpsxfx.js} +4 -4
  92. package/web/_astro/{ganttDiagram-6RSMTGT7.BO81S85v.js → ganttDiagram-6RSMTGT7.DC_p36PI.js} +1 -1
  93. package/web/_astro/{gitGraphDiagram-PVQCEYII.XnPxPPZN.js → gitGraphDiagram-PVQCEYII.DkEqNI0P.js} +1 -1
  94. package/web/_astro/{infoDiagram-5YYISTIA.JyjYRu_T.js → infoDiagram-5YYISTIA.CrjioCTG.js} +1 -1
  95. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BBRBF-Fo.js → ishikawaDiagram-YF4QCWOH.BcK0CN8n.js} +5 -5
  96. package/web/_astro/{journeyDiagram-JHISSGLW.C_iymSyp.js → journeyDiagram-JHISSGLW.p0CbBDX1.js} +1 -1
  97. package/web/_astro/{kanban-definition-UN3LZRKU.DdfW-Oqt.js → kanban-definition-UN3LZRKU.puHIFt6J.js} +7 -7
  98. package/web/_astro/{linear.C2_IkbZT.js → linear.Bb-3a1d7.js} +1 -1
  99. package/web/_astro/mermaid.core.CJDgXOJs.js +301 -0
  100. package/web/_astro/{mindmap-definition-RKZ34NQL.DAZIxQSK.js → mindmap-definition-RKZ34NQL.BoAZEI9t.js} +2 -2
  101. package/web/_astro/{pieDiagram-4H26LBE5.CN8sIhKM.js → pieDiagram-4H26LBE5.CJNSdqY7.js} +3 -3
  102. package/web/_astro/{quadrantDiagram-W4KKPZXB.3dGcX5GP.js → quadrantDiagram-W4KKPZXB.DtTy7_0Z.js} +1 -1
  103. package/web/_astro/{requirementDiagram-4Y6WPE33.BV2y4dd6.js → requirementDiagram-4Y6WPE33.BNYV98Xg.js} +3 -3
  104. package/web/_astro/{sankeyDiagram-5OEKKPKP.Cqo15Tvo.js → sankeyDiagram-5OEKKPKP.DRq9DGHp.js} +4 -4
  105. package/web/_astro/{sequenceDiagram-3UESZ5HK.CROCPMJB.js → sequenceDiagram-3UESZ5HK.B7KdBFCm.js} +1 -1
  106. package/web/_astro/{stateDiagram-AJRCARHV.RfXZrkFE.js → stateDiagram-AJRCARHV.CD62ZJ2H.js} +1 -1
  107. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CPXmbBs9.js → stateDiagram-v2-BHNVJYJU.CeVk1TAj.js} +1 -1
  108. package/web/_astro/{timeline-definition-PNZ67QCA.DdgKTiO8.js → timeline-definition-PNZ67QCA.C_ZPA5tT.js} +3 -3
  109. package/web/_astro/{vennDiagram-CIIHVFJN.CPNVSHF1.js → vennDiagram-CIIHVFJN.CrThO_FQ.js} +5 -5
  110. package/web/_astro/{wardleyDiagram-YWT4CUSO.CQhA0Jyr.js → wardleyDiagram-YWT4CUSO.DSZCA5nl.js} +3 -3
  111. package/web/_astro/{xychartDiagram-2RQKCTM6.n61BWyy4.js → xychartDiagram-2RQKCTM6.KI7baTuk.js} +1 -1
  112. package/web/index.html +1 -1
  113. package/config/workflows/decision-routing-example.yaml +0 -134
  114. package/web/_astro/BoardApp.eoTz0pZs.js +0 -1
  115. package/web/_astro/arc.CPwg6Rw0.js +0 -1
  116. package/web/_astro/channel.MYZLKNwy.js +0 -1
  117. package/web/_astro/diagram-2AECGRRQ.DhNnvUvX.js +0 -43
  118. package/web/_astro/diagram-5GNKFQAL.lTX5KwnS.js +0 -10
  119. package/web/_astro/mermaid.core.GAOYeSR0.js +0 -303
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.3.92",
10
+ "version": "0.3.94",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -48,13 +48,6 @@
48
48
  "tokenCostUsd": null,
49
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.",
50
50
  "decision": null
51
- },
52
- "decision-routing-example": {
53
- "modelQueries": 0,
54
- "wallClockMs": null,
55
- "tokenCostUsd": null,
56
- "source": "Coverage entry (0911): authoring example (config/workflows/decision-routing-example.yaml) enumerating `decision:` modes. Measured 0 model queries — the producer is a shell action, so the example runs with no provider (live count via loadQueryCounts, 2026-09-20). Not a pipeline humans run; wall-clock and cost unenforced, never 0.",
57
- "decision": null
58
51
  }
59
52
  }
60
53
  }
@@ -36,6 +36,11 @@
36
36
  "contract": "standard",
37
37
  "twin": "idea-handoff.mjs"
38
38
  },
39
+ {
40
+ "rel": "wrapup-drift-probe.ts",
41
+ "contract": "standard",
42
+ "twin": "wrapup-drift-probe.mjs"
43
+ },
39
44
  {
40
45
  "rel": "wrapup-steps.ts",
41
46
  "contract": "standard",
@@ -96,6 +101,11 @@
96
101
  "rel": "surface-drift-inventory.ts",
97
102
  "contract": "repo-only"
98
103
  },
104
+ {
105
+ "rel": "task-diffstat.ts",
106
+ "contract": "standard",
107
+ "twin": "task-diffstat.mjs"
108
+ },
99
109
  {
100
110
  "rel": "task-evidence-precheck.ts",
101
111
  "contract": "repo-only"
@@ -0,0 +1,48 @@
1
+ $schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
2
+ # Test-only subpath boundary — @gobing-ai/spur-app/testing and the app's testing/
3
+ # module are for tests and the mock itself, never production sources (task 0975 R3).
4
+ #
5
+ # WHY: `@gobing-ai/spur-app/testing` (packages/app/src/testing/history-board-mock.ts)
6
+ # is a test-only mock subpath (E82). A production import couples shipped code to a
7
+ # fake, and it crept toward the server transport before 0961 severed the seam. The
8
+ # evaluator's glob matcher is segment-aware with `*`/`**` only (no brace groups), so
9
+ # the ts/tsx globs would need separate entries — the repo carries .ts sources only,
10
+ # matching the dao-boundary precedent.
11
+ #
12
+ # Scope covers src trees only: `<workspace>/tests/**` directories stay outside, and
13
+ # `packages/app/src/testing/**` (the mock's own home) is exempted explicitly.
14
+ rules:
15
+ - id: no-testing-subpath-import
16
+ description: >
17
+ Production sources must not import the @gobing-ai/spur-app/testing subpath
18
+ (history-board mock, E82/0961). Tests under <workspace>/tests/** and the
19
+ packages/app/src/testing/** module itself are exempt.
20
+ severity: error
21
+ evaluator:
22
+ type: forbidden-import
23
+ config:
24
+ forbidden:
25
+ - specifier: "@gobing-ai/spur-app/testing"
26
+ scope:
27
+ include:
28
+ - "apps/**/src/**/*.ts"
29
+ - "packages/**/src/**/*.ts"
30
+ exclude:
31
+ - "packages/app/src/testing/**"
32
+
33
+ - id: no-relative-testing-module-import
34
+ description: >
35
+ packages/app production sources must not reach the testing/ module through
36
+ relative imports (from './testing/…' or '../…/testing…'). The testing/**
37
+ module itself and workspace tests/ directories are exempt.
38
+ severity: error
39
+ evaluator:
40
+ type: forbidden-import
41
+ config:
42
+ forbidden:
43
+ - pattern: "(?:from\\s+|import\\(\\s*)['\"](\\.\\.?/)+(?:[^'\"]*/)?testing(?:/|['\"])"
44
+ scope:
45
+ include:
46
+ - "packages/app/src/**/*.ts"
47
+ exclude:
48
+ - "packages/app/src/testing/**"
@@ -73,6 +73,7 @@ rules:
73
73
 
74
74
  # Synchronous template & workflow resolvers:
75
75
  - "apps/cli/src/commands/task.ts" # sync loadTemplateContent/Bodies callbacks
76
- - "packages/app/src/services/workflow-service.ts" # composition advisory source (0775: facts extracted from the live definition inside the synchronous validate path; the baseline snapshot sync read was retired)
76
+ - "packages/app/src/services/workflow-service.ts" # run-log/checkpoint reclamation realpath confinement (:988, :1014)
77
+ - "packages/app/src/workflow/run-record.ts" # confined sync run-record reads: realpath + fstat byte-window (moved from workflow-service.ts, 0962)
77
78
  - "apps/cli/src/release-ops.ts" # sync manifest/workspace reads + Bun.write rewrites inside the git release flow (task 0617); mirrors task.ts sync-template exemption
78
79
  - "apps/cli/src/version-carriers.ts" # sync manifest reads + Bun.write rewrites for registered release carriers (task 0854); mirrors release-ops.ts sync exemption
@@ -37,6 +37,10 @@ rules:
37
37
  # this rule's include scope — scanning generated copies only adds noise
38
38
  # (e.g. `id: task-implement-transition` trips the sk- token shape).
39
39
  - "apps/cli/config/"
40
+ # `apps/cli/plugins/` is the same kind of gitignored build:bundle output — a staged
41
+ # npm copy of `plugins/`, itself outside the include scope. Minified generated libs
42
+ # there trip the api-key shape (0976: HEAD's inline-run.generated.mjs fires too).
43
+ - "apps/cli/plugins/"
40
44
 
41
45
  - id: no-unsafe-git-commands
42
46
  description: "Unsafe git commands (force-push, hard reset) can overwrite history or cause data loss (AGENTS.md CRITICAL safety)."
@@ -95,6 +95,10 @@ It owns document responsibilities and maintenance; `00` owns architectural choic
95
95
  `01` product scope, and root `DESIGN.md` UI/UX. Use **`sp:doc-evolve`** for drift,
96
96
  sync and contract checks. Follow host/operator precedence.
97
97
 
98
+ For new `docs/plans/` working records and `docs/design/` non-UI satellites, use the
99
+ `sp:spur-dev` plan/design templates and document-authoring reference. Preserve legacy files' history
100
+ and headings when revising them; `sp:spur-doctor` can propose safe upgrades.
101
+
98
102
  ### Doc map
99
103
 
100
104
  | File | Owns | Update when |
@@ -16,6 +16,8 @@ sync: [T5, T6]
16
16
 
17
17
  Phase goals, order, dependencies and exits only. Detailed feature state belongs in the
18
18
  feature tool's index; task progress belongs in task records (constitution §6.3).
19
+ Detailed proposals and investigations may live in dated `docs/plans/` working records;
20
+ link accepted phase changes here without copying the plan.
19
21
 
20
22
  ## Phase 0 — Foundation
21
23
 
@@ -31,4 +31,6 @@ _(Describe the primary data path: input → processing → output. Name the seam
31
31
  <!--
32
32
  Describes the CURRENT architecture only. Future/accepted designs go in sections explicitly
33
33
  titled "(accepted design — ADR-NNN; not yet built)". Keep command/schema detail in 04 satellites and task delivery receipts in task records.
34
- -->
34
+ Use the `sp:spur-dev` design template for a new non-UI satellite; link it instead of copying its
35
+ contract here.
36
+ -->
@@ -16,6 +16,8 @@ sync: [T3, T9]
16
16
 
17
17
  Non-UI contract index (constitution §6.5). Keep signatures, schemas, defaults, errors
18
18
  and boundary behavior in the owning `docs/design/<slug>.md` satellite.
19
+ Start a new satellite from the `sp:spur-dev` design template; distinguish proposed from current
20
+ behavior and preserve existing headings when revising one.
19
21
 
20
22
  ## UI/UX boundary & DESIGN.md
21
23
 
@@ -3,7 +3,7 @@ name: Project Constitution
3
3
  doc: 99_PROJECT_CONSTITUTION
4
4
  owns: PROCESS — how the key files are maintained
5
5
  authority: authoritative-on-process
6
- version: 1.7.0
6
+ version: 1.8.0
7
7
  created_at: {{init-date}}
8
8
  updated_at: {{init-date}}
9
9
  edit_rules: 99 §6.8
@@ -70,11 +70,15 @@ size, approval, task count, or completion alone does not qualify it for an ADR (
70
70
  ### 4.2 Working layers (outside the authority chain)
71
71
 
72
72
  - Dated `docs/plans/` and `docs/reports/` files hold proposals, investigations, audits and evidence.
73
- They record work; accepted conclusions take effect only in their proper owner.
73
+ They record work; accepted conclusions take effect only in their proper owner. New plan files use
74
+ ordinary Markdown with concise frontmatter and purpose, evidence, recommendation, and follow-up
75
+ as applicable; the `sp:spur-dev` plan template is a starting point, not a rigid schema.
74
76
  - Configured task folders hold requirements, implementation plans, results and verification,
75
77
  maintained through the task tool. Resolve their paths through that tool.
76
78
  - Existing project context or learning storage holds reusable lessons (§8).
77
79
  - `docs/design/` and `docs/features/` are governed detail layers (§4.5), not scratch storage.
80
+ New non-UI design satellites use the `sp:spur-dev` design template; revisions preserve existing
81
+ meaning and references.
78
82
 
79
83
  Preserve historical evidence and its dates. It may be linked for provenance, but current
80
84
  contracts must be understandable without treating an old plan or report as governing policy.
@@ -90,6 +94,8 @@ Use `authoritative` for `00`, `authoritative-on-scope` for `01`,
90
94
  `owns` must match §4.1 in meaning. Bump the minor version for substantive edits and refresh
91
95
  `updated_at` when content changes. Do not touch unrelated documents just to update a date.
92
96
  Keep one metadata block. Root entry/UI files may use their established native format.
97
+ Plans and design satellites use lighter frontmatter for identity, truthful status and dates;
98
+ legacy files without it remain valid and readable. Do not infer historical status from a template.
93
99
 
94
100
  ### 4.4 AGENTS.md synchronization
95
101
 
@@ -194,6 +200,8 @@ Keep the index short. Satellites own non-UI signatures, schemas, defaults, error
194
200
  and boundary behavior. Verify against registrations/contracts; prefer generated artifacts to
195
201
  manual copies. UI rules belong in root `DESIGN.md`; architectural rationale belongs in `00`/`03`.
196
202
  Separate proposed from current behavior. A completed task adds no delivery receipt here.
203
+ Use the design template to structure new satellites around boundaries, contracts, invariants and
204
+ compatibility. Preserve stable headings and historical claims when revising old ones.
197
205
 
198
206
  ### 6.6 `docs/05_FEATURES.md` + `docs/features/<feature-id>_<slug>.md`
199
207
 
@@ -1,7 +1,72 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
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, any unreferenced parallel definition in config/workflows/, and (0877 R8) any retired definition with real (non-dry) terminal runs lacking a recorded decision in `retirements[]`.",
4
- "candidates": [],
4
+ "candidates": [
5
+ {
6
+ "id": "task-pipeline-triage-lanes",
7
+ "canonical": "task-pipeline",
8
+ "deadline": "2026-11-24",
9
+ "createdAt": "2026-09-25",
10
+ "rationale": "0943 (feature D64): task-pipeline work routes by triage lane and failure class instead of static graphs. Between a green gate and the review/verify fork a deterministic `triage` state writes the diffstat (plugins/sp/scripts/task-diffstat.ts, numstat + untracked scan against the precheck-anchored run base), honors a caller-set mode verbatim (never overridden), pins the safety lane on sensitive paths (drizzle/, packages/config/, server auth basenames, *secret*, .github/, plugins/sp/hooks/, *.sql) or >400 changed lines with NO model call, otherwise consults `decide task-triage` (low → fast lane skips review; standard/high → review), and appends the route reason to .spur/memory/task-pipeline-routes.log (R5). On a red gate, `decide failure-class` routes retryable → test-recheck (attempt counted, qualityGateMaxFixAttempts still bounds), fix → test-fix, stop → failed(failed-check); missing/corrupt decision fails closed. Projected basis: the 0938 baseline (docs/reports/2026-09-workflow-cost-baseline.md) measured task-pipeline wall median 7983s over 1 real terminal run with agent.run median 0; the fast lane saves the review agent.run + wall on triage-low diffs, and retryable classification saves fixall hops (and their gate re-runs) on transient failures. Measurement gap honestly named: with workflow.decideDecisionMaker off (R4) triage degrades to standard lanes (diffstat-guard-sensitive/large still forced safety — pure shell, no model), so benefit only materializes with the flag on; per-lane route counts are observable post-merge in .spur/memory/task-pipeline-routes.log. Measured-benefit status: projected from 0938, NOT shadow-run-confirmed — plan step 5 (recorded-input shadow runs) is DEFERRED out of the 0943 session and tracked by this candidate's deadline; `promotion evaluate` runs post-merge. If it does not beat the baseline, it is reverted (ADR-076).",
11
+ "measurement": {
12
+ "workflow": "task-pipeline"
13
+ },
14
+ "delta": {
15
+ "agentRunCount": 0,
16
+ "baselineAgentRunCount": 0,
17
+ "note": "Structural graph agent.run count unchanged (no new model state); fast-lane runs save the existing review agent.run (1 fewer on triage-low diffs), retryable classification can save fixall fix hops. baselineAgentRunCount = 0938 task-pipeline agentRunCountMedian."
18
+ },
19
+ "verdict": null
20
+ },
21
+ {
22
+ "id": "check-dedup-task-pipeline",
23
+ "canonical": "task-pipeline",
24
+ "deadline": "2026-11-24",
25
+ "createdAt": "2026-09-25",
26
+ "rationale": "0940 (feature D64): the comprehensive check runs once at the quality boundary — no-progress rechecks skip the full gate to the probe path (check.skipped-no-progress) and review confirms the check receipt (check.reused) instead of re-running tests/lint. Projected basis: the 0938 baseline (docs/reports/2026-09-workflow-cost-baseline.md) measured task-pipeline wall median 7983s over 1 real terminal run with agent.run median 0; per-state rows cover only `record` (visits=0) — no test-recheck/review state-level wall deltas exist yet, so the benefit is projected as wall time (full-gate vs probe cost on no-progress loops, plus removed review-stage re-runs), not agent.run count. Measured-benefit status: projected from 0938, NOT shadow-run-confirmed; `promotion evaluate` runs post-merge against recorded runs. If it does not beat the baseline, it is reverted (ADR-076).",
27
+ "measurement": {
28
+ "workflow": "task-pipeline"
29
+ },
30
+ "delta": {
31
+ "agentRunCount": 0,
32
+ "baselineAgentRunCount": 0,
33
+ "note": "No agent.run delta by design: check dedup removes full-gate wall time on no-progress rechecks and model-stage re-runs, not model hops. baselineAgentRunCount = 0938 task-pipeline agentRunCountMedian."
34
+ },
35
+ "verdict": null
36
+ },
37
+ {
38
+ "id": "wrapup-drift-probe",
39
+ "canonical": "wrapup-pipeline",
40
+ "deadline": "2026-11-24",
41
+ "createdAt": "2026-09-25",
42
+ "rationale": "0944 (feature D64): a deterministic probe (plugins/sp/scripts/wrapup-drift-probe.ts) runs only when the caller leaves wrapup mode empty and projects mode=fast when no changed path matches a doc-owned surface (AGENTS.md doc map + docs/99_PROJECT_CONSTITUTION.md ownership table) and every task's Solution change map parsed — clean wraps then skip doc-sync via the existing fast edge (fast:drift-probe-clean), any doubt stays on the safety path. Projected basis: doc-sync costs exactly 1 agent.run per wrapup (wrapup-pipeline declares one model query; 0938 baseline docs/reports/2026-09-workflow-cost-baseline.json has 0 recorded wrapup runs — agentRunCountMedian null — so the 1 is structural, never a measured median). Measurement gap honestly named: skipping doc-sync also skips learnings capture (0607 R2 merge), so clean wraps lose per-task learnings rows; measured post-promotion by comparing .spur/memory/wrapup-routes.log fast:drift-probe-clean counts against captured <runId>-wrapup-learnings.md files. For 0946 (measurement-only confirmation): the 0938 snapshot carries per-state rows in states[] but history-anatomy has zero of them (0 recorded runs — only task-pipeline/record exists), and cache hit/miss disposition lives in the history-anatomy provenance stamp (0659/0660, history-anatomy-cache.ts), not in real-run-cost.ts; terminalReasonMix exists per workflow but history-anatomy's is empty. Measured-benefit status: projected from structure + 0938, NOT shadow-run-confirmed; `promotion evaluate` runs post-merge. If it does not beat the baseline, it is reverted (ADR-076).",
43
+ "measurement": {
44
+ "workflow": "wrapup-pipeline"
45
+ },
46
+ "delta": {
47
+ "agentRunCount": 0,
48
+ "baselineAgentRunCount": 1,
49
+ "note": "Clean wrap skips the single doc-sync agent.run (1 -> 0 for those wraps). baselineAgentRunCount = 1 structural doc-sync agent.run per wrapup run (wrapup-pipeline declares one model query — config/pipeline-budgets.json); the 0938 snapshot records 0 wrapup runs (agentRunCountMedian null), so no measured median exists yet."
50
+ },
51
+ "verdict": null
52
+ },
53
+ {
54
+ "id": "idea-pipeline-guard-legibility",
55
+ "canonical": "idea-pipeline",
56
+ "deadline": "2026-11-24",
57
+ "createdAt": "2026-09-25",
58
+ "rationale": "0945 (feature D64): the four idea-pipeline route guards (ac-generate/feature-check → system-design|decompose) read derived fact files (.spur/run/<runId>-idea-design-route.txt, <runId>-idea-ac-ready.status) written by one deterministic shell action at the end of both ac-generate and feature-check onEnter, instead of re-deriving design×needs_design and dual status reads inline — guards drop from 4-5 logical commands with (warn) markers to 3 predicates with no warn (ADR-115), and routing is proven identical by the executed truth-table test (idea-pipeline-routing.test.ts: the frozen pre-refactor guards are the oracle across profile × __hitlAnswer × design × needs_design × ac × coverage, 3456 cells, plus the guard-parity fixture refreshed per the 0874→0887 protocol). Projected basis: 0938 terminal-reason coverage for idea-pipeline is 100% classified for new runs (all nine failure-state edges declare terminalReason under 0937), so route outcomes stay measurable per terminalReason; this is projected from structure, NOT shadow-run-confirmed — promotion evaluate fills the verdict post-merge, and a losing candidate reverts (ADR-076).",
59
+ "measurement": {
60
+ "workflow": "idea-pipeline"
61
+ },
62
+ "verdict": null,
63
+ "delta": {
64
+ "agentRunCount": 6,
65
+ "baselineAgentRunCount": 6,
66
+ "note": "No agent.run delta by design: legibility derives the route facts in shell, model hops are unchanged (composition baseline modelQueries=6 in config/pipeline-budgets.json holds). baselineAgentRunCount = 0938 idea-pipeline agentRunCountMedian."
67
+ }
68
+ }
69
+ ],
5
70
  "retirements": [
6
71
  {
7
72
  "name": "planning-pipeline",
@@ -14,6 +79,12 @@
14
79
  "recordedBy": "ADR-076 (commit 017ac7a30)",
15
80
  "date": "2026-08-20",
16
81
  "rationale": "Standing parallel definition deleted with the D5-N fixture promotion bar; 5 real terminal runs retained in history."
82
+ },
83
+ {
84
+ "name": "decision-routing-example",
85
+ "recordedBy": "0946 (D64)",
86
+ "date": "2026-09-25",
87
+ "rationale": "Example-only authoring sample (0911 D7, retained-as-example 0921) retired per the mechanical catalogue rule (D64 design §10): zero real runs in the 0938 window (2026-06-01, run-record plane) and no live caller — only the plugin README catalogue row, a 0-query budget coverage entry and design/help doc mentions. YAML deleted; README row, budget entry and doc mentions removed; the decision-policy regression fixture moved to packages/app/tests/services/fixtures/decision-routing-example.yaml (workflow-service.test.ts decision-policy block). Its exit also dropped `hitl.select` from the live action union, so the inline-pipeline-parity-check DOCUMENTED set and driver mirror dropped it (parity contract). No run history deleted; 0 real terminal runs retained in history."
17
88
  }
18
89
  ]
19
90
  }
@@ -74,6 +74,7 @@ transitions:
74
74
  command: 'test "$(cat .spur/run/$__runId-feature-verification.status 2>/dev/null)" = PASS'
75
75
  - from: verify
76
76
  to: failed
77
+ terminalReason: failed-check
77
78
  description: Repo-wide checks failed (or the evidence is missing/corrupt) — stop at failed.
78
79
  guard:
79
80
  kind: always
@@ -391,6 +391,7 @@ transitions:
391
391
  command: 'grep -q "^PASS$" .spur/run/$__runId-structure-gate.txt 2>/dev/null'
392
392
  - from: structure-gate
393
393
  to: failed
394
+ terminalReason: retry-exhausted
394
395
  description: Structure gate FAIL with the correction cap exhausted — never publish a malformed candidate.
395
396
  guard:
396
397
  kind: shell
@@ -418,6 +419,7 @@ transitions:
418
419
  command: '! tail -n 1 .spur/run/$__runId-validation.txt 2>/dev/null | grep -qx "Verdict: PASS" && test "$(cat .spur/run/$__runId-correction-count 2>/dev/null || echo 0)" -lt 2'
419
420
  - from: validate
420
421
  to: failed
422
+ terminalReason: retry-exhausted
421
423
  description: Validation FAIL with the correction cap exhausted — terminate without publishing.
422
424
  guard:
423
425
  kind: always
@@ -262,6 +262,23 @@ states:
262
262
  printf 'FAIL run=%s checker not found — run superskill install sp\n' "$__runId" | tee ".spur/run/$__runId-idea-coverage.reason" >&2;
263
263
  printf 'FAIL\n' > ".spur/run/$__runId-idea-coverage.status";
264
264
  fi
265
+ # 0945 R2: derive the route facts ONCE at this boundary (0769 pattern) — guards read these
266
+ # named files instead of re-deriving design×needs_design inline. The same command is
267
+ # duplicated in feature-check's onEnter (YAML has no include); byte-equality is
268
+ # test-pinned (idea-pipeline-routing.test.ts). Missing/corrupt needs-design JSON fails
269
+ # safe to `design`; readiness is PASS only when both recorded checks are PASS.
270
+ # (warn) 7 commands: two three-way `test && printf || printf` derivations over captured
271
+ # signal files (ADR-115); no model hop, no CLI re-run.
272
+ - kind: shell
273
+ options:
274
+ command: >-
275
+ mkdir -p .spur/run &&
276
+ { test "$design" = skip -o "$design" = auto -a "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" = false &&
277
+ printf 'skip\n' > .spur/run/$__runId-idea-design-route.txt ||
278
+ printf 'design\n' > .spur/run/$__runId-idea-design-route.txt; } &&
279
+ { test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS -a "$(cat .spur/run/$__runId-idea-coverage.status 2>/dev/null)" = PASS &&
280
+ printf 'PASS\n' > .spur/run/$__runId-idea-ac-ready.status ||
281
+ printf 'FAIL\n' > .spur/run/$__runId-idea-ac-ready.status; }
265
282
 
266
283
  - id: feature-check
267
284
  description: >
@@ -286,6 +303,19 @@ states:
286
303
  prompt: "Feature check for ${vars.featureId}. Review the AC and confirm to proceed? Requirement coverage status: $(cat .spur/run/${vars.__runId}-idea-coverage.status 2>/dev/null || echo unknown) — reason: $(cat .spur/run/${vars.__runId}-idea-coverage.reason 2>/dev/null || echo n/a) (.spur/run/${vars.__runId}-idea-coverage.status[.reason]). (Failures route back to ac-generate for revision, capped at 3 retries.)"
287
304
  decision:
288
305
  mode: never
306
+ # 0945 R2: same writer as ac-generate's onEnter (byte-equal, test-pinned) — refreshes the
307
+ # derived route facts after the interactive gate so both entries into this boundary read
308
+ # consistent files; hitl.confirm only sets $__hitlAnswer.
309
+ - kind: shell
310
+ options:
311
+ command: >-
312
+ mkdir -p .spur/run &&
313
+ { test "$design" = skip -o "$design" = auto -a "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" = false &&
314
+ printf 'skip\n' > .spur/run/$__runId-idea-design-route.txt ||
315
+ printf 'design\n' > .spur/run/$__runId-idea-design-route.txt; } &&
316
+ { test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS -a "$(cat .spur/run/$__runId-idea-coverage.status 2>/dev/null)" = PASS &&
317
+ printf 'PASS\n' > .spur/run/$__runId-idea-ac-ready.status ||
318
+ printf 'FAIL\n' > .spur/run/$__runId-idea-ac-ready.status; }
289
319
 
290
320
  - id: system-design
291
321
  description: >
@@ -567,6 +597,7 @@ transitions:
567
597
  command: 'test "$(cat .spur/run/$__runId-idea-precheck-doctor.status 2>/dev/null)" = PASS'
568
598
  - from: start
569
599
  to: failed
600
+ terminalReason: failed-check
570
601
  description: Agent doctor FAIL — stop before discovery.
571
602
  guard:
572
603
  kind: always
@@ -606,6 +637,7 @@ transitions:
606
637
  command: 'test "$__hitlAnswer" = yes'
607
638
  - from: idea-eval
608
639
  to: cancelled
640
+ terminalReason: cancelled
609
641
  description: Idea evaluation rejected or cancelled — no feature created.
610
642
  guard:
611
643
  kind: shell
@@ -621,9 +653,11 @@ transitions:
621
653
 
622
654
  # ── ac-generate: auto-skip (profile=auto) OR enter feature-check HITL gate (interactive) ──
623
655
  # Declaration order: auto-skip guards tried FIRST (same pattern as task-pipeline review→verify
624
- # and design-gen→handoff). Under profile=auto, the recorded `idea-ac-check` result and the
625
- # recorded requirement-coverage status (0887 R4) route directly to the appropriate next
626
- # state — guards never re-run the CLI or the checker (task 0769). Under interactive, the
656
+ # and design-gen→handoff). Under profile=auto, the derived route facts — the ready file (both
657
+ # recorded checks PASS, 0887 R4) and the design route file (0945 R2 writer at the end of both
658
+ # ac-generate and feature-check onEnter) — route directly to the appropriate next state:
659
+ # guards never re-run the CLI or the checker and never re-derive the signals (task 0769).
660
+ # Under interactive, the
627
661
  # always fallback enters the feature-check state whose onEnter hitl.confirm pauses for
628
662
  # operator confirmation (coverage is surfaced in that prompt; the operator's answer governs).
629
663
  #
@@ -631,25 +665,29 @@ transitions:
631
665
  - from: ac-generate
632
666
  to: system-design
633
667
  description: "profile=auto, check passed, requirements covered, design route — run system design."
634
- # (warn) 4 commands: one persistent multi-assignment line captures both statuses; `test -a` folds the
635
- # profile/ac and design/needs pairs (same operand semantics); guards never re-run the CLI (0769).
668
+ # 0945 R3: three predicates over derived facts — the ready file folds both recorded check
669
+ # statuses (PASS only when ac-check AND coverage are PASS), the route file folds
670
+ # design×needs_design (fail-safe design); nothing re-runs, nothing re-derives (0769).
636
671
  guard:
637
672
  kind: shell
638
673
  options:
639
674
  command: >-
640
- ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" cov_status="$(cat .spur/run/$__runId-idea-coverage.status 2>/dev/null)";
641
- test "$profile" = auto -a "$ac_status" = PASS && test "$cov_status" = PASS && test "$design" = auto -a "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false
675
+ test "$profile" = auto &&
676
+ test "$(cat .spur/run/$__runId-idea-ac-ready.status 2>/dev/null)" = PASS &&
677
+ test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = design
642
678
  # Auto-skip 2: pass + coverage + skip-design route → decompose
643
679
  - from: ac-generate
644
680
  to: decompose
645
681
  description: "profile=auto, check passed, requirements covered, skip-design route — go directly to decompose."
646
- # (warn) 4 test segments: profile + inlined ac/coverage reads + the OR'd design=skip/(design=auto AND
647
- # needs_design=false) pair folded into one `test` (`-a` binds tighter than `-o`), keeping one captured
648
- # signal file authoritative for both routes (0769).
682
+ # 0945 R3: mirror of the design-route guard over the same derived facts with route=skip;
683
+ # one captured signal file per fact keeps both routes symmetric (0769).
649
684
  guard:
650
685
  kind: shell
651
686
  options:
652
- command: 'test "$profile" = auto && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$__runId-idea-coverage.status 2>/dev/null)" = PASS && test "$design" = skip -o "$design" = auto -a "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" = false'
687
+ command: >-
688
+ test "$profile" = auto &&
689
+ test "$(cat .spur/run/$__runId-idea-ac-ready.status 2>/dev/null)" = PASS &&
690
+ test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = skip
653
691
  # Auto-skip 3: check or coverage failed, retry < 3 → loop back to ac-generate (self-loop)
654
692
  - from: ac-generate
655
693
  to: ac-generate
@@ -666,6 +704,7 @@ transitions:
666
704
  # Auto-skip 4: check or coverage failed, retry cap reached → escalate to failed
667
705
  - from: ac-generate
668
706
  to: failed
707
+ terminalReason: retry-exhausted
669
708
  description: "profile=auto, check or coverage failed after 3 retries — escalate to failed."
670
709
  # (warn) 5 commands: mirror of the retry guard with cap>=3; keeping escalation and retry as one
671
710
  # test-chain pair makes the cap boundary auditable in the diff (0769).
@@ -687,23 +726,27 @@ transitions:
687
726
  - from: feature-check
688
727
  to: system-design
689
728
  description: Feature check passed, design route — run system design.
690
- # (warn) 5 commands (named status capture + 4 conditions): HITL answer + captured status + design route; same shape as the
691
- # ac-generate auto-skips, reading the same captured signal files (0769).
729
+ # 0945 R3: three predicates over derived facts — the recorded ac-check status stays the
730
+ # conjunct here (parity: the operator's answer governs the interactive gate, 0887), the
731
+ # route file folds design×needs_design (fail-safe design); nothing re-runs (0769).
692
732
  guard:
693
733
  kind: shell
694
734
  options:
695
735
  command: >-
696
- ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
697
- 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
736
+ test "$__hitlAnswer" = yes &&
737
+ test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS &&
738
+ test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = design
698
739
  - from: feature-check
699
740
  to: decompose
700
741
  description: Feature check passed, skip-design route — go directly to decompose.
701
- # (warn) 5 test segments: same as the feature-check design route plus the OR'd
702
- # design=auto/needs_design=false pair (0769).
742
+ # 0945 R3: mirror of the design-route guard over the same derived facts with route=skip.
703
743
  guard:
704
744
  kind: shell
705
745
  options:
706
- command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS && (test "$design" = skip || (test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" = false))'
746
+ command: >-
747
+ test "$__hitlAnswer" = yes &&
748
+ test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS &&
749
+ test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = skip
707
750
  - from: feature-check
708
751
  to: ac-generate
709
752
  description: "Feature check failed — revise AC (retry cap: 3)."
@@ -717,6 +760,7 @@ transitions:
717
760
  (test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3
718
761
  - from: feature-check
719
762
  to: failed
763
+ terminalReason: retry-exhausted
720
764
  description: Feature check failed after 3 retries — escalate to failed.
721
765
  # (warn) 5 commands (named status capture + 4 conditions): mirror of the revise guard with cap>=3 (0769).
722
766
  guard:
@@ -727,6 +771,7 @@ transitions:
727
771
  (test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3
728
772
  - from: feature-check
729
773
  to: cancelled
774
+ terminalReason: cancelled
730
775
  description: Operator cancelled the feature-check gate.
731
776
  guard:
732
777
  kind: shell
@@ -772,6 +817,7 @@ transitions:
772
817
  command: 'test "$__hitlAnswer" = no && test "$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0)" -le 1'
773
818
  - from: design-approval
774
819
  to: failed
820
+ terminalReason: retry-exhausted
775
821
  description: "Design rejected after 1 revise - design-approval gate exhausted (R7 0433)."
776
822
  guard:
777
823
  kind: shell
@@ -779,6 +825,7 @@ transitions:
779
825
  command: 'test "$__hitlAnswer" = no && test "$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0)" -gt 1'
780
826
  - from: design-approval
781
827
  to: cancelled
828
+ terminalReason: cancelled
782
829
  description: Operator cancelled design approval.
783
830
  guard:
784
831
  kind: shell
@@ -816,6 +863,7 @@ transitions:
816
863
  command: 'test "$__hitlAnswer" = no'
817
864
  - from: batch-create
818
865
  to: cancelled
866
+ terminalReason: cancelled
819
867
  description: Operator cancelled batch creation.
820
868
  guard:
821
869
  kind: shell
@@ -845,6 +893,7 @@ transitions:
845
893
  command: 'test -f .spur/run/$__runId-idea-batch-create.failed && test "$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0)" -lt 3'
846
894
  - from: batch-create-run
847
895
  to: failed
896
+ terminalReason: retry-exhausted
848
897
  description: Batch-create failed after 3 retries — escalate to failed.
849
898
  guard:
850
899
  kind: shell
@@ -223,6 +223,7 @@ transitions:
223
223
  command: 'test "$(cat .spur/run/$__runId-pr-preflight.status 2>/dev/null)" = PASS'
224
224
  - from: preflight
225
225
  to: failed
226
+ terminalReason: failed-check
226
227
  description: Preflight red (detached HEAD, dirty tree, gh auth, no GitHub remote, or the current branch being the base branch) — stop before any publishing
227
228
  guard:
228
229
  kind: always
@@ -236,6 +237,7 @@ transitions:
236
237
  command: 'grep -qE "^(PASS|WARN)$" .spur/run/$__runId-pr-hygiene.status 2>/dev/null'
237
238
  - from: hygiene
238
239
  to: failed
240
+ terminalReason: failed-check
239
241
  description: BLOCK (secrets/.env/conflict markers) or probe failure — never submit a tainted diff
240
242
  guard:
241
243
  kind: always
@@ -249,6 +251,7 @@ transitions:
249
251
  command: 'grep -qE "^(PASS|SKIP)$" .spur/run/$__runId-pr-precheck.status 2>/dev/null'
250
252
  - from: precheck
251
253
  to: failed
254
+ terminalReason: failed-check
252
255
  description: Project checks red — do not spend a review request on code that fails its own gate
253
256
  guard:
254
257
  kind: always
@@ -262,6 +265,7 @@ transitions:
262
265
  command: 'grep -qE "^(PUSHED|UP_TO_DATE)$" .spur/run/$__runId-pr-push.status 2>/dev/null'
263
266
  - from: push
264
267
  to: failed
268
+ terminalReason: failed-check
265
269
  description: Push rejected (e.g. non-fast-forward) — report; never force-push as a workaround
266
270
  guard:
267
271
  kind: always
@@ -275,6 +279,7 @@ transitions:
275
279
  command: 'grep -qE "^(FOUND|CREATED)$" .spur/run/$__runId-pr-ensure.status 2>/dev/null'
276
280
  - from: ensure-pr
277
281
  to: failed
282
+ terminalReason: failed-check
278
283
  description: No reviewable delta or PR creation failed — report the actual git/GitHub state
279
284
  guard:
280
285
  kind: always
@@ -306,6 +311,7 @@ transitions:
306
311
  command: 'test "$(cat .spur/run/$__runId-pr-request.status 2>/dev/null)" = REQUESTED'
307
312
  - from: request
308
313
  to: failed
314
+ terminalReason: failed-check
309
315
  description: Request failed (no PR, gh error) — report the preserved error
310
316
  guard:
311
317
  kind: always
@@ -326,6 +332,7 @@ transitions:
326
332
  command: 'test "$(cat .spur/run/$__runId-pr-wait.status 2>/dev/null)" = TIMEOUT'
327
333
  - from: wait
328
334
  to: failed
335
+ terminalReason: failed-check
329
336
  description: Poll probe itself failed
330
337
  guard:
331
338
  kind: always
@@ -346,6 +353,7 @@ transitions:
346
353
  command: 'test "$(cat .spur/run/$__runId-pr-collect.status 2>/dev/null)" = PENDING'
347
354
  - from: collect
348
355
  to: failed
356
+ terminalReason: failed-check
349
357
  description: Collect probe failed
350
358
  guard:
351
359
  kind: always