@gobing-ai/spur 0.3.86 → 0.3.88

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 (107) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +9 -0
  3. package/config/plugin-scripts.json +4 -0
  4. package/config/rules/strict/runtime-boundaries.yaml +1 -0
  5. package/config/templates/feature/default.md +2 -0
  6. package/config/templates/task/brainstorm.md +2 -2
  7. package/config/templates/task/feature-impl.md +2 -2
  8. package/config/templates/task/issue.md +2 -2
  9. package/config/templates/task/meta.md +2 -2
  10. package/config/templates/task/review.md +2 -2
  11. package/config/templates/task/standard.md +2 -2
  12. package/config/workflow-candidates.json +13 -35
  13. package/config/workflows/feature-lifecycle.yaml +6 -0
  14. package/config/workflows/feature-verification.yaml +6 -5
  15. package/config/workflows/idea-pipeline.yaml +89 -36
  16. package/config/workflows/task-pipeline.yaml +25 -0
  17. package/package.json +9 -9
  18. package/plugins/sp/README.md +9 -6
  19. package/plugins/sp/commands/dev-idea.md +9 -2
  20. package/plugins/sp/commands/dev-refactor.md +33 -0
  21. package/plugins/sp/commands/dev-refine.md +25 -7
  22. package/plugins/sp/commands/dev-refineall.md +9 -6
  23. package/plugins/sp/lib/idea-handoff.generated.mjs +160 -152
  24. package/plugins/sp/plugin.json +1 -1
  25. package/plugins/sp/references/roles.md +1 -1
  26. package/plugins/sp/scripts/idea-coverage-check.ts +168 -0
  27. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +114 -3
  28. package/plugins/sp/scripts/inline-run-setup.ts +28 -18
  29. package/plugins/sp/skills/brainstorm/SKILL.md +4 -0
  30. package/plugins/sp/skills/code-refactoring/SKILL.md +155 -0
  31. package/plugins/sp/skills/code-refactoring/references/finding-schema.md +74 -0
  32. package/plugins/sp/skills/code-refactoring/references/fix-ladder.md +52 -0
  33. package/plugins/sp/skills/code-refactoring/references/focus-detection.md +44 -0
  34. package/plugins/sp/skills/code-refactoring/references/refactor-finding.schema.json +95 -0
  35. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +23 -16
  36. package/plugins/sp/skills/spur-cli/references/agent.md +92 -9
  37. package/plugins/sp/skills/spur-cli/references/features/acceptance-criteria.md +4 -2
  38. package/plugins/sp/skills/spur-cli/references/features.md +6 -1
  39. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +2 -2
  40. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +3 -3
  41. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +1 -1
  42. package/plugins/sp/skills/spur-dev/SKILL.md +33 -33
  43. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +24 -0
  44. package/plugins/sp/skills/spur-dev/references/dev-operations.md +99 -26
  45. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +27 -5
  46. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +11 -1
  47. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +33 -1
  48. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +10 -6
  49. package/plugins/sp/skills/taste-refactoring-api/SKILL.md +44 -1
  50. package/plugins/sp/skills/taste-refactoring-api/references/protocol-modes.md +31 -0
  51. package/plugins/sp/skills/taste-refactoring-architect/SKILL.md +43 -0
  52. package/plugins/sp/skills/taste-refactoring-tests/SKILL.md +42 -0
  53. package/plugins/sp/skills/taste-refactoring-ui/SKILL.md +43 -0
  54. package/schemas/spur-config.schema.json +14 -2
  55. package/schemas/task-batch.schema.json +2 -2
  56. package/spur.js +2012 -487
  57. package/web/_astro/{BoardApp.yBBcFXWP.js → BoardApp.CDUcHlTJ.js} +67 -67
  58. package/web/_astro/BoardApp.CaCGU_uX.js +1 -0
  59. package/web/_astro/{TaskDetail.DqFJbRFc.js → TaskDetail.DwTmbQp5.js} +1 -1
  60. package/web/_astro/{arc.DL-BpHoi.js → arc.BzF71EFI.js} +1 -1
  61. package/web/_astro/{architectureDiagram-3BPJPVTR.9IdQYyDq.js → architectureDiagram-3BPJPVTR.jvdDahWM.js} +1 -1
  62. package/web/_astro/{blockDiagram-GPEHLZMM.BKsFCqTl.js → blockDiagram-GPEHLZMM.zSg4AmFD.js} +1 -1
  63. package/web/_astro/{c4Diagram-AAUBKEIU.DhwI0dh1.js → c4Diagram-AAUBKEIU.BkUIUQWH.js} +1 -1
  64. package/web/_astro/channel.SSVY0JPQ.js +1 -0
  65. package/web/_astro/{chunk-2J33WTMH.B3QVmQ9S.js → chunk-2J33WTMH.DvfQ_f50.js} +1 -1
  66. package/web/_astro/{chunk-4BX2VUAB.DBSuqs9F.js → chunk-4BX2VUAB.DuI4gQqX.js} +1 -1
  67. package/web/_astro/{chunk-55IACEB6.BSYTWAYD.js → chunk-55IACEB6.D3BWBOpF.js} +1 -1
  68. package/web/_astro/{chunk-727SXJPM.cWVuxXfS.js → chunk-727SXJPM.3QSi0a9M.js} +1 -1
  69. package/web/_astro/{chunk-AQP2D5EJ.DpU_Ob3d.js → chunk-AQP2D5EJ.xazCQrAF.js} +1 -1
  70. package/web/_astro/{chunk-FMBD7UC4.BykFkyji.js → chunk-FMBD7UC4.B2g6u4rA.js} +1 -1
  71. package/web/_astro/{chunk-ND2GUHAM.DwgHlMdY.js → chunk-ND2GUHAM.wWwWs99t.js} +1 -1
  72. package/web/_astro/{chunk-QZHKN3VN.CusXUGWM.js → chunk-QZHKN3VN.BD5g3qa9.js} +1 -1
  73. package/web/_astro/{classDiagram-4FO5ZUOK.fx0ObzkN.js → classDiagram-4FO5ZUOK.C7CzCdsX.js} +1 -1
  74. package/web/_astro/{classDiagram-v2-Q7XG4LA2.fx0ObzkN.js → classDiagram-v2-Q7XG4LA2.C7CzCdsX.js} +1 -1
  75. package/web/_astro/{cose-bilkent-S5V4N54A.Z4HgOlsd.js → cose-bilkent-S5V4N54A.Xyiau0gw.js} +1 -1
  76. package/web/_astro/{cynefin-OW5HDTMX.B5dIZHJu.js → cynefin-OW5HDTMX.BeC5MWas.js} +1 -1
  77. package/web/_astro/{dagre-BM42HDAG.DT70Q_Yw.js → dagre-BM42HDAG.yZbMN9vc.js} +1 -1
  78. package/web/_astro/{diagram-2AECGRRQ.DqHA3XBF.js → diagram-2AECGRRQ.Cmo2zQM-.js} +1 -1
  79. package/web/_astro/{diagram-5GNKFQAL.BmCem957.js → diagram-5GNKFQAL.D033eSVi.js} +1 -1
  80. package/web/_astro/{diagram-KO2AKTUF.sn0-hrE0.js → diagram-KO2AKTUF.CR6k3Y3G.js} +1 -1
  81. package/web/_astro/{diagram-LMA3HP47.BSHe9tVc.js → diagram-LMA3HP47.x7mwu8jz.js} +1 -1
  82. package/web/_astro/{diagram-OG6HWLK6.DHIc-86k.js → diagram-OG6HWLK6.D8aTTvUr.js} +1 -1
  83. package/web/_astro/{erDiagram-TEJ5UH35.Bxayrs7v.js → erDiagram-TEJ5UH35.BoBqcKXQ.js} +1 -1
  84. package/web/_astro/{flowDiagram-I6XJVG4X.BkzoE_5I.js → flowDiagram-I6XJVG4X.D3mTQdrU.js} +1 -1
  85. package/web/_astro/{ganttDiagram-6RSMTGT7.okT6CvTo.js → ganttDiagram-6RSMTGT7.H-cqgIh-.js} +1 -1
  86. package/web/_astro/{gitGraphDiagram-PVQCEYII.CJuYbhC7.js → gitGraphDiagram-PVQCEYII.B6s9zbfC.js} +1 -1
  87. package/web/_astro/{infoDiagram-5YYISTIA.RqLy7nBo.js → infoDiagram-5YYISTIA.BzgCoV6P.js} +1 -1
  88. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BwIcoagw.js → ishikawaDiagram-YF4QCWOH.BZzVhy1-.js} +1 -1
  89. package/web/_astro/{journeyDiagram-JHISSGLW.UB1VbWtH.js → journeyDiagram-JHISSGLW.BV3195Py.js} +1 -1
  90. package/web/_astro/{kanban-definition-UN3LZRKU.AaxMKpTk.js → kanban-definition-UN3LZRKU.BjRd2DWz.js} +1 -1
  91. package/web/_astro/{linear.Nv_xOUjP.js → linear.BILTgS5N.js} +1 -1
  92. package/web/_astro/{mermaid.core.Bc4LqQgX.js → mermaid.core.DBy_WKeW.js} +4 -4
  93. package/web/_astro/{mindmap-definition-RKZ34NQL.oKUvU_qi.js → mindmap-definition-RKZ34NQL.BiEjaI4-.js} +1 -1
  94. package/web/_astro/{pieDiagram-4H26LBE5.DQk0oo03.js → pieDiagram-4H26LBE5.i_8V5pIn.js} +1 -1
  95. package/web/_astro/{quadrantDiagram-W4KKPZXB.BdDjESDa.js → quadrantDiagram-W4KKPZXB.BWaW3MHn.js} +1 -1
  96. package/web/_astro/{requirementDiagram-4Y6WPE33.C2u9hUeH.js → requirementDiagram-4Y6WPE33.CzddBbtg.js} +1 -1
  97. package/web/_astro/{sankeyDiagram-5OEKKPKP.CDEoiJST.js → sankeyDiagram-5OEKKPKP.X2ww0e-D.js} +1 -1
  98. package/web/_astro/{sequenceDiagram-3UESZ5HK.D_hT_GAT.js → sequenceDiagram-3UESZ5HK.DSA4kTcc.js} +1 -1
  99. package/web/_astro/{stateDiagram-AJRCARHV.DI8RYG0b.js → stateDiagram-AJRCARHV.D0DtFSpR.js} +1 -1
  100. package/web/_astro/{stateDiagram-v2-BHNVJYJU.Bkxz4DnP.js → stateDiagram-v2-BHNVJYJU.BfQq0zQv.js} +1 -1
  101. package/web/_astro/{timeline-definition-PNZ67QCA.DSY-kH3-.js → timeline-definition-PNZ67QCA.Dmlrgi1m.js} +1 -1
  102. package/web/_astro/{vennDiagram-CIIHVFJN.CpaDtuGr.js → vennDiagram-CIIHVFJN.D5mpl00Z.js} +1 -1
  103. package/web/_astro/{wardleyDiagram-YWT4CUSO.DujQWvo8.js → wardleyDiagram-YWT4CUSO.Df4BdzO4.js} +1 -1
  104. package/web/_astro/{xychartDiagram-2RQKCTM6.DcM5Y4b9.js → xychartDiagram-2RQKCTM6.DiTRreKN.js} +1 -1
  105. package/web/index.html +1 -1
  106. package/web/_astro/BoardApp.CJiqp5pS.js +0 -1
  107. package/web/_astro/channel.CX5453qQ.js +0 -1
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.3.86",
10
+ "version": "0.3.88",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -133,9 +133,18 @@ agent:
133
133
  # A disabled profile stays visible to `spur agent doctor` but never routes
134
134
  # (no role, team, stage, or explicit selection). Flip back to enabled by
135
135
  # removing the line or setting disabled: false.
136
+ # Quota refresh is external: schedule `spur agent usage` (cron/launchd) to
137
+ # capture provider usage and record quota-owned availability observations.
138
+ # spur serve never runs it (B6 0892; docs/design/session-pinned-dispatch.md §3.4).
139
+ # A bare `true` is operator-owned (only humans write booleans). Automatic
140
+ # writers (quota/probe checks) emit the ownership object instead; an
141
+ # operator-owned disable is never auto-re-enabled (B6 0890).
136
142
  # - name: retired-profile
137
143
  # agent: omp
138
144
  # disabled: true
145
+ # - name: quota-limited
146
+ # agent: omp
147
+ # disabled: { owner: quota, since: 2026-02-14T09:30:00.000Z, reason: agent.quota.exhausted quota-limited }
139
148
  - name: pi-dsv4-flash-volc
140
149
  agent: pi
141
150
  # tier: standard
@@ -50,6 +50,10 @@
50
50
  "rel": "inline-run-setup.ts",
51
51
  "contract": "repo-only"
52
52
  },
53
+ {
54
+ "rel": "idea-coverage-check.ts",
55
+ "contract": "repo-only"
56
+ },
53
57
  {
54
58
  "rel": "inline-pipeline-parity-check.ts",
55
59
  "contract": "repo-only"
@@ -58,6 +58,7 @@ rules:
58
58
  - "packages/app/src/services/token-ledger-watcher.ts" # node:fs watch() live watcher
59
59
  - "packages/app/src/services/project-registry.ts" # atomic projects.json persistence
60
60
  - "packages/app/src/services/slash-commands-service.ts" # synchronous ~/.config/spur/slash_commands.json persistence (mirrors project-registry.ts)
61
+ - "packages/app/src/services/agent-usage-producer.ts" # atomic agent-usage snapshot write (tmp + rename, mirrors project-registry.ts; task 0892 R1)
61
62
  - "packages/app/src/services/history-service.ts" # versioned analyze artifact + bounded-errors sidecar + latest.json symlink pointer (task 0474); ts-runtime FileSystem seam has no symlink, so the pointer uses node:fs directly (mirrors project-registry.ts persistence exemption)
62
63
  - "packages/app/src/observability/workflow-run-log-sink.ts" # sync FD append for mid-run tail-able all-in-one run log (task 0426 / feature D2); append() is sync from the observability bus
63
64
  - "apps/cli/src/commands/workflow.ts" # FD byte-window tail of the mid-run run log for `workflow trace --follow` streaming (task 0428 / feature D2); readSync at offset over the observability sink's FDs
@@ -23,6 +23,8 @@ updated_at: "{{ CREATED_AT }}"
23
23
  ## Acceptance Criteria
24
24
 
25
25
  ```gherkin
26
+ # Keep the Feature: line (feature check L3.ac-bdd-error without it). Each Scenario: title is the
27
+ # identity key tasks reference verbatim ("- [ ] AC1 — <title>"); number R1, R2, …; never rename after tasks link.
26
28
  Feature: {{ NAME }}
27
29
 
28
30
  Scenario: Basic acceptance
@@ -23,11 +23,11 @@ updated_at: "{{ CREATED_AT }}"
23
23
 
24
24
  ### Requirements
25
25
 
26
- <!-- Constraints the eventual direction must satisfy, if known. -->
26
+ <!-- One R-item per line, exactly `- [ ] R1. <text>` (checkbox + `R<n>.`); `spur task check` flags any other form. Constraints the eventual direction must satisfy, if known. -->
27
27
 
28
28
  ### Acceptance Criteria
29
29
 
30
- <!-- Decision criteria or success checks for the brainstorm output. Keep empty if not applicable. -->
30
+ <!-- Number items AC1, AC2, … (never R<n> — that is the Requirements namespace): `- [ ] AC1 — <feature scenario title without its R-number>` bullets or `Scenario: AC1 — <title>` blocks; add `(req: R<n>)` to bind a task requirement; task-only checks go in prose below, or set `ac_altitude: task-local`. Keep empty if not applicable. -->
31
31
 
32
32
  ### Q&A
33
33
 
@@ -23,11 +23,11 @@ updated_at: "{{ CREATED_AT }}"
23
23
 
24
24
  ### Requirements
25
25
 
26
- <!-- R-numbered list derived from the linked feature or refined task scope. -->
26
+ <!-- One R-item per line, exactly `- [ ] R1. <text>` (checkbox + `R<n>.`); `spur task check` flags any other form. Derive from the linked feature or refined task scope. -->
27
27
 
28
28
  ### Acceptance Criteria
29
29
 
30
- <!-- Copy or derive real scenarios from the linked feature. Do not leave placeholder AC here. -->
30
+ <!-- Number items AC1, AC2, … (never R<n> — that is the Requirements namespace): `- [ ] AC1 — <feature scenario title without its R-number>` bullets or `Scenario: AC1 — <title>` blocks; add `(req: R<n>)` to bind a task requirement; task-only checks go in prose below, or set `ac_altitude: task-local`. Do not leave placeholder AC here. -->
31
31
 
32
32
  ### Q&A
33
33
 
@@ -23,11 +23,11 @@ updated_at: "{{ CREATED_AT }}"
23
23
 
24
24
  ### Requirements
25
25
 
26
- <!-- R-numbered expectations for the fix. Include repro/expected behavior if it helps traceability. -->
26
+ <!-- One R-item per line, exactly `- [ ] R1. <text>` (checkbox + `R<n>.`); `spur task check` flags any other form. Include repro/expected behavior if it helps traceability. -->
27
27
 
28
28
  ### Acceptance Criteria
29
29
 
30
- <!-- Given/When/Then regression scenario or checklist proving the bug is fixed. -->
30
+ <!-- Number items AC1, AC2, … (never R<n> — that is the Requirements namespace): `- [ ] AC1 — <feature scenario title without its R-number>` bullets or `Scenario: AC1 — <title>` blocks; add `(req: R<n>)` to bind a task requirement; task-only checks go in prose below, or set `ac_altitude: task-local`. Use a regression scenario proving the bug is fixed. -->
31
31
 
32
32
  ### Q&A
33
33
 
@@ -23,11 +23,11 @@ updated_at: "{{ CREATED_AT }}"
23
23
 
24
24
  ### Requirements
25
25
 
26
- <!-- R-numbered expectations for the process/docs/chore outcome. Keep empty if not applicable. -->
26
+ <!-- One R-item per line, exactly `- [ ] R1. <text>` (checkbox + `R<n>.`); `spur task check` flags any other form. Keep empty if not applicable. -->
27
27
 
28
28
  ### Acceptance Criteria
29
29
 
30
- <!-- Lightweight checklist or Given/When/Then if there is an observable completion condition. -->
30
+ <!-- Number items AC1, AC2, … (never R<n> — that is the Requirements namespace): `- [ ] AC1 — <feature scenario title without its R-number>` bullets or `Scenario: AC1 — <title>` blocks; add `(req: R<n>)` to bind a task requirement; task-only checks go in prose below, or set `ac_altitude: task-local`. Keep empty if not applicable. -->
31
31
 
32
32
  ### Q&A
33
33
 
@@ -33,11 +33,11 @@ in the reviewed PR/commit/diff). Fix in priority order (P1 → P2 → …); re-r
33
33
 
34
34
  ### Requirements
35
35
 
36
- <!-- R-numbered fix requirements derived from the findings. Fill after triage/refinement. -->
36
+ <!-- One R-item per line, exactly `- [ ] R1. <text>` (checkbox + `R<n>.`); `spur task check` flags any other form. Fill after triage/refinement from the findings. -->
37
37
 
38
38
  ### Acceptance Criteria
39
39
 
40
- <!-- Checks that prove the findings were addressed. Keep empty until the review task becomes executable work. -->
40
+ <!-- Number items AC1, AC2, … (never R<n> — that is the Requirements namespace): `- [ ] AC1 — <feature scenario title without its R-number>` bullets or `Scenario: AC1 — <title>` blocks; add `(req: R<n>)` to bind a task requirement; task-only checks go in prose below, or set `ac_altitude: task-local`. Keep empty until the review task becomes executable work. -->
41
41
 
42
42
  ### Q&A
43
43
 
@@ -23,11 +23,11 @@ updated_at: "{{ CREATED_AT }}"
23
23
 
24
24
  ### Requirements
25
25
 
26
- <!-- R-numbered list of what must be true when this task is complete. Keep empty until requirements are known. -->
26
+ <!-- One R-item per line, exactly `- [ ] R1. <text>` (checkbox + `R<n>.`); `spur task check` flags any other form. Keep empty until requirements are known. -->
27
27
 
28
28
  ### Acceptance Criteria
29
29
 
30
- <!-- Given/When/Then scenarios or a checklist derived from Requirements. Keep empty if this task has no objective AC yet. -->
30
+ <!-- Number items AC1, AC2, … (never R<n> — that is the Requirements namespace): `- [ ] AC1 — <feature scenario title without its R-number>` bullets or `Scenario: AC1 — <title>` blocks; add `(req: R<n>)` to bind a task requirement; task-only checks go in prose below, or set `ac_altitude: task-local`. Keep empty if this task has no objective AC yet. -->
31
31
 
32
32
  ### Q&A
33
33
 
@@ -1,41 +1,19 @@
1
1
  {
2
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": [
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": [],
5
+ "retirements": [
5
6
  {
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
- }
7
+ "name": "planning-pipeline",
8
+ "recordedBy": "0872 (ADR-072/075 composition-baseline pass 2dc86579a)",
9
+ "date": "2026-08-19",
10
+ "rationale": "Planning pipeline superseded by the task-pipeline planning stages; 3 real terminal runs retained in history."
11
+ },
12
+ {
13
+ "name": "task-pipeline2",
14
+ "recordedBy": "ADR-076 (commit 017ac7a30)",
15
+ "date": "2026-08-20",
16
+ "rationale": "Standing parallel definition deleted with the D5-N fixture promotion bar; 5 real terminal runs retained in history."
39
17
  }
40
18
  ]
41
19
  }
@@ -37,6 +37,12 @@ states:
37
37
  description: >
38
38
  Feature verification work (DD-13). Makes verification derivable —
39
39
  listable, event-triggerable, assignable.
40
+ onEnter:
41
+ # Caller wiring (task 0880, ADR-119): entering verification RUNS the
42
+ # feature-scoped pass instead of only passively reading its status file.
43
+ - kind: shell
44
+ options:
45
+ command: '$spurBin workflow run feature-verification.yaml --vars "{\"featureId\":\"$featureId\"}"'
40
46
  - id: blocked
41
47
  description: Impediment; work suspended.
42
48
  - id: done
@@ -3,10 +3,11 @@
3
3
  # Owns a lifecycle boundary no per-task graph owns: the repo-wide checks whose
4
4
  # invariant spans the repository rather than a single task's diff — corpus
5
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).
6
+ # surface, and the repo-wide test set. They run once per feature, deliberately
7
+ # the slow pass; `feature-lifecycle`'s verifying entry invokes this workflow (task
8
+ # 0880 caller wiring), and its verifying→done guard refuses to complete the
9
+ # feature until this pass records PASS (ADR-119 consequence: a feature is not
10
+ # done until it passes).
10
11
  #
11
12
  # Run: spur workflow run feature-verification.yaml --vars '{"featureId":"D62"}'
12
13
  #
@@ -22,7 +23,7 @@
22
23
  kind: state-machine
23
24
  name: feature-verification
24
25
  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
+ description: "Feature-scoped verification pass (ADR-119): runs the repo-wide check set once per feature and records PASS/FAIL. Invoked by feature-lifecycle's verifying entry."
26
27
  iterationBound: 2
27
28
  initialState: verify
28
29
  terminalStates:
@@ -94,10 +94,17 @@ states:
94
94
  agent: "${vars.agent}"
95
95
  role: planner
96
96
  resolvedAgentVar: planningAgent
97
+ # B7 R1 (0894): also pin the role once — __executor.planner feeds stage
98
+ # dispatch so no later idea stage re-walks the doctor ladder.
99
+ roles:
100
+ planner: "${vars.agent}"
97
101
  - kind: shell
98
102
  options:
99
103
  command: >-
100
- test -n "$idea" || printf 'FAIL\n' > ".spur/run/$__runId-idea-precheck-doctor.status"
104
+ mkdir -p .spur/run &&
105
+ printf '%s\n' "$idea" > ".spur/run/$__runId-idea-input.md" &&
106
+ awk 'NF' ".spur/run/$__runId-idea-input.md" | grep -q . ||
107
+ printf 'FAIL\n' > ".spur/run/$__runId-idea-precheck-doctor.status"
101
108
 
102
109
  - id: discovery
103
110
  description: >
@@ -109,14 +116,19 @@ states:
109
116
  brainstorm also emits the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md
110
117
  (template: the `sp:spur-dev` skill's `idea-evaluation` reference — named by skill, not by
111
118
  repo path, because `spur init` never scaffolds `plugins/sp/` into a seeded project).
119
+ The operator's verbatim idea argument is persisted at .spur/run/${vars.__runId}-idea-input.md
120
+ (written by start; the authoritative ask every model-bearing stage reads).
112
121
  expectFile fails a silent no-op discovery (no eval report).
113
122
  onEnter:
114
123
  - kind: agent.run
115
124
  options:
116
125
  agent: ${vars.planningAgent}
117
- input: "Run sp:brainstorm for the idea: ${vars.idea}. The skill owns the approach-generation, design summary, and `needs_design` signal criteria; emit .spur/run/${vars.__runId}-idea-needs-design.json ({\"needs_design\": true|false}) and the design summary per the skill's `Design Approval Gate` and `The needs_design signal` sections. Also emit the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md per the `sp:spur-dev` skill's `idea-evaluation` reference (urgency/necessity 0–5, premises, pros/cons, alternatives, enhanced idea, recommendation). At the END of the report, append a provenance footer block of the exact form: `---\\nrun_id: ${vars.__runId}\\ngenerated_at: <RFC3339 timestamp>\\n---` (omit the footer only if run_id is empty)."
126
+ input: "The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md — idea persisted verbatim at start; authoritative, read it first (${vars.idea} is a convenience echo). Run sp:brainstorm. The skill owns the approach-generation, design summary, and `needs_design` signal criteria; emit .spur/run/${vars.__runId}-idea-needs-design.json ({\"needs_design\": true|false}) plus the design summary per its `Design Approval Gate` / `The needs_design signal` sections. Also emit the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md per the `sp:spur-dev` skill's `idea-evaluation` reference (urgency/necessity 0–5, premises, pros/cons, alternatives, enhanced idea, recommendation, plus mandatory `## Requirement inventory`: numbered I<n> items quoting/paraphrasing idea-input lines; `[unclear: ...]` marks ambiguity, `[deferred: <reason>]` marks out-of-scope). End with this exact footer: `---\\nrun_id: ${vars.__runId}\\ngenerated_at: <RFC3339>\\n---` (omit if run_id is empty)."
118
127
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
119
128
  role: planner
129
+ # B7 R6 (0894): planner stages declare fresh — the role default is stated
130
+ # explicitly so the definition is self-documenting.
131
+ session: fresh
120
132
  expectFile: .spur/run/${vars.__runId}-idea-eval-report.md
121
133
  timeoutMs: ${vars.stepTimeoutMs}
122
134
 
@@ -143,14 +155,18 @@ states:
143
155
  checklists never enter Goal. Shell actions then persist both sections through
144
156
  `spur feature update --section Goal|Scope --from-file ...`; a missing/empty artifact
145
157
  stops the state. Prefer the enhanced idea from .spur/run/${vars.__runId}-idea-eval-report.md as
146
- context; do not overwrite vars.idea.
158
+ context; do not overwrite vars.idea. The operator's verbatim ask of record is
159
+ .spur/run/${vars.__runId}-idea-input.md (persisted at start; authoritative over any
160
+ paraphrase).
147
161
  onEnter:
148
162
  - kind: agent.run
149
163
  options:
150
164
  agent: ${vars.planningAgent}
151
- input: 'Create a feature for the idea: ${vars.idea}. Read .spur/run/${vars.__runId}-idea-eval-report.md if present for the enhanced idea and scores. Use ''spur feature create "<name>" --json'' to create it. Write the feature id to .spur/run/${vars.__runId}-idea-feature-id.txt. If an existing feature is appropriate, use its id instead. Also write two body-only intent artifacts: .spur/run/${vars.__runId}-idea-goal.md with concise Goal intent only (a short statement of what the feature achieves; never task breakdowns, checklists, or how-to steps), and .spur/run/${vars.__runId}-idea-scope.md with explicit in-scope and out-of-scope boundary bullets.'
165
+ input: 'Create a feature for the idea. The operator''s ask of record is .spur/run/${vars.__runId}-idea-input.md — the idea argument persisted verbatim at start; treat it as the authoritative ask. Read .spur/run/${vars.__runId}-idea-eval-report.md if present for the enhanced idea and scores. Use ''spur feature create "<name>" --json'' to create it; with --json the feature id is returned under the .ref.id envelope (n), not a top-level .id — read it from there. Write the feature id to .spur/run/${vars.__runId}-idea-feature-id.txt. If an existing feature is appropriate, use its id instead. Also write two body-only intent artifacts: .spur/run/${vars.__runId}-idea-goal.md with concise Goal intent only (a short statement of what the feature achieves; never task breakdowns, checklists, or how-to steps), and .spur/run/${vars.__runId}-idea-scope.md with explicit in-scope and out-of-scope boundary bullets.'
152
166
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
153
167
  role: planner
168
+ # B7 R6 (0894): declared fresh policy (planner default).
169
+ session: fresh
154
170
  expectFile: .spur/run/${vars.__runId}-idea-feature-id.txt
155
171
  timeoutMs: ${vars.stepTimeoutMs}
156
172
  - kind: file.read.into-var
@@ -182,6 +198,9 @@ states:
182
198
  check records FAIL and routes through the capped retry loop via guards that consume the
183
199
  recorded result — it never fails the run (the engine's default onError policy is `fail`,
184
200
  so an in-action check failure would kill the run before the retry edges are evaluated).
201
+ Requirement coverage is MEASURED alongside it by the soft idea-coverage-check shell
202
+ (0887 R4): the recorded coverage status conjuncts into the profile=auto ac-generate
203
+ guards below so uncovered inventory items route through the same capped retry loop.
185
204
  onEnter:
186
205
  - kind: shell
187
206
  options:
@@ -189,9 +208,11 @@ states:
189
208
  - kind: agent.run
190
209
  options:
191
210
  agent: ${vars.planningAgent}
192
- input: "Generate acceptance criteria for feature ${vars.featureId}. Read the feature file and author R-numbered BDD Gherkin scenarios per ac-style-guide.md. Output only the complete Acceptance Criteria section body, including the gherkin code fence, with no surrounding commentary and no section heading."
211
+ input: "Generate acceptance criteria for feature ${vars.featureId}. The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md — idea persisted verbatim at start; authoritative. Read the feature file and author R-numbered BDD Gherkin scenarios per ac-style-guide.md. Tie every scenario to the `## Requirement inventory` of .spur/run/${vars.__runId}-idea-eval-report.md: under each `Scenario:` heading add a comment `# covers: I1, I3` listing covered ids — every non-`[deferred: ...]` item needs at least one covering scenario. Two `spur task check` rules bind AC text: (1) AC bullets copy scenario titles verbatim — the title is the byte-identical identity key of its bullet; (2) gate-language words (HITL, approval/approved, merged/merge event, content-gate, GATED, capstone standalone) are forbidden in titles, bodies, and enum values (L4.gate-language) — rephrase around them. Output only the Acceptance Criteria section body (gherkin fence included), no commentary, no heading."
193
212
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
194
213
  role: planner
214
+ # B7 R6 (0894): declared fresh policy (planner default).
215
+ session: fresh
195
216
  answerFile: .spur/run/${vars.__runId}-idea-ac-content.md
196
217
  expectFile: .spur/run/${vars.__runId}-idea-ac-content.md
197
218
  timeoutMs: ${vars.stepTimeoutMs}
@@ -221,6 +242,24 @@ states:
221
242
  resultFile: .spur/run/${vars.__runId}-idea-ac-check.status
222
243
  softFail: true
223
244
  timeoutMs: 120000
245
+ # 0887 R4: requirement-inventory ↔ AC coverage, measured once at the same author/revise
246
+ # boundary. Soft shell (exit 0 always): the checker writes the PASS/FAIL status itself and
247
+ # the guards below consume the recorded result — never re-run the checker (0769 pattern).
248
+ # Repo-checkout path first, then the superskill-staged twin (handoff-finalize resolution
249
+ # shape); neither present fails closed to FAIL so readiness degrades visibly instead of
250
+ # silently skipping coverage.
251
+ - kind: shell
252
+ options:
253
+ command: >-
254
+ mkdir -p .spur/run &&
255
+ S=plugins/sp/scripts/idea-coverage-check.ts &&
256
+ if [ ! -f "$S" ]; then S="$(superskill script path sp idea-coverage-check.ts 2>/dev/null)"; fi &&
257
+ if [ -n "$S" ] && [ -f "$S" ]; then
258
+ bun "$S" --run-id "$__runId" --report ".spur/run/$__runId-idea-eval-report.md" --ac ".spur/run/$__runId-idea-ac-content.md" || printf 'FAIL run=%s checker exited nonzero (bun missing or checker crash)\n' "$__runId" > ".spur/run/$__runId-idea-coverage.reason";
259
+ else
260
+ printf 'FAIL run=%s checker not found — run superskill install sp\n' "$__runId" | tee ".spur/run/$__runId-idea-coverage.reason" >&2;
261
+ printf 'FAIL\n' > ".spur/run/$__runId-idea-coverage.status";
262
+ fi
224
263
 
225
264
  - id: feature-check
226
265
  description: >
@@ -235,11 +274,14 @@ states:
235
274
  the transition guards route directly from ac-generate to the appropriate next
236
275
  state, so this state is only entered in interactive mode. On failure, the retry
237
276
  cap routes back to ac-generate (≤3 retries) or escalates to failed.
277
+ Requirement coverage (.spur/run/${vars.__runId}-idea-coverage.status, 0887 R4) is part of
278
+ the recorded results this gate surfaces: a FAIL there means some inventory items have no
279
+ covering scenario — answer no to route back to ac-generate for revision if needed.
238
280
  pause: true
239
281
  onEnter:
240
282
  - kind: hitl.confirm
241
283
  options:
242
- prompt: "Feature check for ${vars.featureId}. Review the AC and confirm to proceed? (Failures route back to ac-generate for revision, capped at 3 retries.)"
284
+ 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.)"
243
285
 
244
286
  - id: system-design
245
287
  description: >
@@ -264,9 +306,11 @@ states:
264
306
  - kind: agent.run
265
307
  options:
266
308
  agent: ${vars.planningAgent}
267
- input: 'Run sp:sys-architecture for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and .spur/run/${vars.__runId}-idea-design-review.md. Produce ADR entries, architecture updates, and design satellites (docs/design/<slug>.md) following the constitution edit rules. Do not write task or feature corpus files directly. Design-review contract (.spur/run/${vars.__runId}-idea-design-review.md, fixed headings `## Proposed design`, `## Operator feedback`, `## Reconciliation`): on the first pass write the proposed design summary under `## Proposed design` and leave `## Operator feedback` empty; on retry with operator feedback present, revise the design/ADR artifacts, document the changes under `## Reconciliation`, and when the feedback invalidates an Acceptance Criteria scenario write the revised AC section body to a file and persist it via `$spurBin feature update "$featureId" --section "Acceptance Criteria" --from-file <file>` — never edit feature corpus files directly.'
309
+ input: 'The operator''s ask of record is .spur/run/${vars.__runId}-idea-input.md (idea persisted verbatim at start; authoritative). Run sp:sys-architecture for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and .spur/run/${vars.__runId}-idea-design-review.md. Produce ADR entries, architecture updates, and design satellites (docs/design/<slug>.md) per the constitution edit rules; never write task or feature corpus files directly. Design-review contract (fixed headings `## Proposed design`, `## Operator feedback`, `## Reconciliation`): first pass — write the proposed summary under `## Proposed design`, leave `## Operator feedback` empty; retry after operator feedback — revise the design/ADR artifacts, document changes under `## Reconciliation`, and when feedback invalidates an Acceptance Criteria scenario write the revised AC section body to a file, persist via `$spurBin feature update "$featureId" --section "Acceptance Criteria" --from-file <file>`.'
268
310
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
269
311
  role: planner
312
+ # B7 R6 (0894): declared fresh policy (planner default).
313
+ session: fresh
270
314
  expectFile: .spur/run/${vars.__runId}-idea-design-review.md
271
315
  timeoutMs: ${vars.stepTimeoutMs}
272
316
  # expectFile proves existence only, and the onEnter skeleton pre-creates the file — so an
@@ -331,9 +375,11 @@ states:
331
375
  - kind: agent.run
332
376
  options:
333
377
  agent: ${vars.planningAgent}
334
- input: "Run sp:spec-decomposition for feature ${vars.featureId} per skill references/decomposition.md § Idea-pipeline emission: sizing first, then the batch JSON at .spur/run/${vars.__runId}-idea-task-batch.json and the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json."
378
+ input: "The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md (the idea argument persisted verbatim at start; authoritative). Run sp:spec-decomposition for feature ${vars.featureId} per skill references/decomposition.md § Idea-pipeline emission: sizing first, then the batch JSON at .spur/run/${vars.__runId}-idea-task-batch.json and the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json."
335
379
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
336
380
  role: planner
381
+ # B7 R6 (0894): declared fresh policy (planner default).
382
+ session: fresh
337
383
  expectFile: .spur/run/${vars.__runId}-idea-task-batch.json
338
384
  timeoutMs: ${vars.stepTimeoutMs}
339
385
  # R1 (0518): the task-order sidecar is the ordering contract for handoff-finalize.
@@ -417,12 +463,16 @@ states:
417
463
  agent: ${vars.planningAgent}
418
464
  # Declared Layer-1 role (0538 R2), same planner executor as decompose.
419
465
  role: planner
466
+ # B7 R6 (0894): declared fresh policy (planner default).
467
+ session: fresh
420
468
  timeoutMs: ${vars.stepTimeoutMs}
421
469
  # (warn) non-slash pointer: the 0788 checklist is per-checkout (digest via the
422
470
  # project's own computePlanningDigest), so the bounded prompt pins the skill
423
471
  # reference instead of a command; artifacts are gated by answerFile/expectFile.
424
472
  input: >-
425
- Run the ready-prepare stage for feature ${vars.featureId} per sp:spur-dev
473
+ The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md (the idea
474
+ argument persisted verbatim at start; authoritative). Run the ready-prepare stage
475
+ for feature ${vars.featureId} per sp:spur-dev
426
476
  references/planning-workflow.md § Step 5.6 (Ready preparation): read
427
477
  .spur/run/${vars.__runId}-idea-batch-create-result.json and write
428
478
  .spur/run/${vars.__runId}-idea-ready.json.
@@ -559,57 +609,60 @@ transitions:
559
609
 
560
610
  # ── ac-generate: auto-skip (profile=auto) OR enter feature-check HITL gate (interactive) ──
561
611
  # Declaration order: auto-skip guards tried FIRST (same pattern as task-pipeline review→verify
562
- # and design-gen→handoff). Under profile=auto, the recorded `idea-ac-check` result routes
563
- # directly to the appropriate next state — guards never re-run the CLI (task 0769). Under
564
- # interactive, the always fallback enters the feature-check state whose onEnter hitl.confirm
565
- # pauses for operator confirmation.
612
+ # and design-gen→handoff). Under profile=auto, the recorded `idea-ac-check` result and the
613
+ # recorded requirement-coverage status (0887 R4) route directly to the appropriate next
614
+ # state — guards never re-run the CLI or the checker (task 0769). Under interactive, the
615
+ # always fallback enters the feature-check state whose onEnter hitl.confirm pauses for
616
+ # operator confirmation (coverage is surfaced in that prompt; the operator's answer governs).
566
617
  #
567
- # Auto-skip 1: pass + design route → system-design (design=auto + needs_design != false)
618
+ # Auto-skip 1: pass + coverage + design route → system-design (design=auto + needs_design != false)
568
619
  - from: ac-generate
569
620
  to: system-design
570
- description: "profile=auto, check passed, design route — run system design."
571
- # (warn) 5 commands (named status capture + 4 conditions): profile gate + captured status + route signal jointly own the
572
- # route; guards never re-run the CLI (0769), so there is nothing smaller to extract.
621
+ description: "profile=auto, check passed, requirements covered, design route — run system design."
622
+ # (warn) 4 commands: one persistent multi-assignment line captures both statuses; `test -a` folds the
623
+ # profile/ac and design/needs pairs (same operand semantics); guards never re-run the CLI (0769).
573
624
  guard:
574
625
  kind: shell
575
626
  options:
576
627
  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
579
- # Auto-skip 2: pass + skip-design route → decompose
628
+ 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)";
629
+ 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
630
+ # Auto-skip 2: pass + coverage + skip-design route → decompose
580
631
  - from: ac-generate
581
632
  to: decompose
582
- description: "profile=auto, check passed, skip-design route — go directly to decompose."
583
- # (warn) 5 test segments: same as auto-skip 1 plus the OR'd design=auto/needs_design=false
584
- # pair that keeps one captured signal file authoritative for both routes (0769).
633
+ description: "profile=auto, check passed, requirements covered, skip-design route — go directly to decompose."
634
+ # (warn) 4 test segments: profile + inlined ac/coverage reads + the OR'd design=skip/(design=auto AND
635
+ # needs_design=false) pair folded into one `test` (`-a` binds tighter than `-o`), keeping one captured
636
+ # signal file authoritative for both routes (0769).
585
637
  guard:
586
638
  kind: shell
587
639
  options:
588
- command: 'test "$profile" = auto && 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))'
589
- # Auto-skip 3: check failed, retry < 3 → loop back to ac-generate (self-loop)
640
+ 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'
641
+ # Auto-skip 3: check or coverage failed, retry < 3 → loop back to ac-generate (self-loop)
590
642
  - from: ac-generate
591
643
  to: ac-generate
592
- description: "profile=auto, check failed, retry cap not reached — re-run ac-generate."
593
- # (warn) 5 commands (named status capture + 4 conditions): profile gate + captured status + captured retry count; the
594
- # retry loop is the smallest honest formulation of cap<3 routing (0769).
644
+ description: "profile=auto, check or coverage failed, retry cap not reached — re-run ac-generate."
645
+ # (warn) 5 commands: one persistent multi-assignment line captures both statuses and the retry count
646
+ # (with its 0 fallback); the failed-check pair stays verbatim and the profile gate folds into the retry
647
+ # test via `test -a`; the retry loop is the smallest honest formulation of cap<3 routing (0769).
595
648
  guard:
596
649
  kind: shell
597
650
  options:
598
651
  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
601
- # Auto-skip 4: check failed, retry cap reached → escalate to failed
652
+ 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)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)";
653
+ { test "$ac_status" != PASS || test "$cov_status" != PASS; } && test "$profile" = auto -a "$retry" -lt 3
654
+ # Auto-skip 4: check or coverage failed, retry cap reached → escalate to failed
602
655
  - from: ac-generate
603
656
  to: failed
604
- description: "profile=auto, check failed after 3 retries — escalate to failed."
605
- # (warn) 5 commands (named status capture + 4 conditions): mirror of the retry guard with cap>=3; keeping escalation and
606
- # retry as one test-chain pair makes the cap boundary auditable in the diff (0769).
657
+ description: "profile=auto, check or coverage failed after 3 retries — escalate to failed."
658
+ # (warn) 5 commands: mirror of the retry guard with cap>=3; keeping escalation and retry as one
659
+ # test-chain pair makes the cap boundary auditable in the diff (0769).
607
660
  guard:
608
661
  kind: shell
609
662
  options:
610
663
  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
664
+ 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)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)";
665
+ { test "$ac_status" != PASS || test "$cov_status" != PASS; } && test "$profile" = auto -a "$retry" -ge 3
613
666
  # Interactive fallback: enter feature-check HITL gate
614
667
  - from: ac-generate
615
668
  to: feature-check
@@ -196,6 +196,18 @@ states:
196
196
  command: >-
197
197
  mkdir -p .spur/run .spur/memory; RUN_ID="$__runId"; [ -n "$RUN_ID" ] || RUN_ID="pipeline-$wbs"; case "$RUN_ID" in *'$'*|*'{'*|*'}'*|*vars.*|*/*|*'\'*|*..*) echo "route-reason: refusing unsafe run id: $RUN_ID" >&2; exit 1 ;; esac; REASON_FILE=".spur/run/$RUN_ID-route-reason.txt"; jq -rn --arg m "$mode" '{"fast":"fast:evidence complete+consistent","":"safety:standard verification","unknown":"safety:unknown evidence quality","conflict":"safety:conflicting evidence"}[$m] // "safety:unrecognized evidence (mode=\($m))"' > "$REASON_FILE"; printf '%s %s %s\n' "$RUN_ID" "$wbs" "$(cat "$REASON_FILE")" >> .spur/memory/task-pipeline-routes.log; exit 0
198
198
 
199
+ # (e) B7 R1 (0894): resolve every declared role ONCE at precheck — pins land
200
+ # in __executor.<role> run vars so stage dispatch performs no doctor call.
201
+ # Soft probe: failures mark the status file and stages degrade to their own
202
+ # resolution; nothing here aborts the run.
203
+ - kind: doctor.probe
204
+ options:
205
+ resultFile: ".spur/run/${vars.__runId}-precheck-roles.status"
206
+ spurBin: "${vars.spurBin}"
207
+ roles:
208
+ coder: "${vars.implementAgent}"
209
+ reviewer: "${vars.agent}"
210
+
199
211
  - id: implement
200
212
  description: >
201
213
  Run agent-driven implementation via /sp:dev-run --mode implement, THEN move the
@@ -216,6 +228,9 @@ states:
216
228
  # Pure slash command only (ADR-043). Anti-recursion / implement discipline
217
229
  # Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
218
230
  role: coder
231
+ # B7 R3/R6 (0894): coder stages reuse the role session — the test-fix hop
232
+ # resumes the implement session instead of re-reading the task cold.
233
+ session: reuse
219
234
  # lives in /sp:dev-run --mode implement → sp:code-implementation, not YAML prose.
220
235
  input: /sp:dev-run --mode implement ${vars.wbs} --auto
221
236
  timeoutMs: ${vars.implementTimeoutMs}
@@ -356,6 +371,9 @@ states:
356
371
  # R3 (0482): `--findings` names the failing anchors inline; `--gate-log` remains
357
372
  # Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
358
373
  role: coder
374
+ # B7 R3/R6 (0894): coder stages reuse the role session — the fix hop
375
+ # continues the implement session it is repairing.
376
+ session: reuse
359
377
  # the full-context escape hatch when the digest is not enough.
360
378
  input: /sp:dev-fixall "${vars.qualityGateCmd}" --gate-log .spur/run/${vars.wbs}-test-gate.log --findings "${vars.gateFindings}"
361
379
  timeoutMs: ${vars.stepTimeoutMs}
@@ -417,7 +435,11 @@ states:
417
435
  expectFile: .spur/run/${vars.__runId}-review-answer.txt
418
436
  # Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
419
437
  role: reviewer
438
+ # B7 R3/R6 (0894): declared fresh policy (reviewer default) — the action
439
+ # result records this as declared, not defaulted. `freshSession: true` is
440
+ # kept as the action-level hard guarantee (0710 R2 structural assertion).
420
441
  freshSession: true
442
+ session: fresh
421
443
  priority: ${vars.taskPriority}
422
444
  compareExecutorWith: implement
423
445
  timeoutMs: ${vars.stepTimeoutMs}
@@ -477,7 +499,10 @@ states:
477
499
  input: /sp:dev-verify ${vars.wbs} --auto --fix none --focus all
478
500
  # Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
479
501
  role: reviewer
502
+ # B7 R3/R6 (0894): declared fresh policy (reviewer default).
503
+ # `freshSession: true` kept as the action-level hard guarantee (0710 R2).
480
504
  freshSession: true
505
+ session: fresh
481
506
  priority: ${vars.taskPriority}
482
507
  compareExecutorWith: implement
483
508
  timeoutMs: ${vars.stepTimeoutMs}