@gobing-ai/spur 0.3.73 → 0.3.75

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 (93) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +15 -4
  3. package/config/pipeline-budgets.json +4 -4
  4. package/config/rules/strict/runtime-boundaries.yaml +1 -1
  5. package/config/templates/AGENTS.md +5 -0
  6. package/config/templates/docs/99_PROJECT_CONSTITUTION.md +2 -0
  7. package/config/workflows/basic.yaml +5 -2
  8. package/config/workflows/docs-pipeline.yaml +117 -55
  9. package/config/workflows/feature-dev.yaml +185 -121
  10. package/config/workflows/feature-lifecycle.yaml +1 -0
  11. package/config/workflows/history-anatomy.yaml +8 -4
  12. package/config/workflows/idea-pipeline.yaml +123 -43
  13. package/config/workflows/pr-review.yaml +31 -8
  14. package/config/workflows/task-lifecycle.yaml +1 -0
  15. package/config/workflows/task-pipeline.yaml +965 -905
  16. package/config/workflows/wayfinder-resolution.yaml +124 -90
  17. package/config/workflows/wrapup-pipeline.yaml +304 -107
  18. package/package.json +1 -1
  19. package/plugins/sp/agents/expert-spur.md +7 -4
  20. package/plugins/sp/commands/dev-idea.md +1 -1
  21. package/plugins/sp/plugin.json +1 -1
  22. package/plugins/sp/scripts/history-anatomy-cache.mjs +7 -1
  23. package/plugins/sp/scripts/history-anatomy-cache.ts +11 -2
  24. package/plugins/sp/scripts/pr-reviewing.mjs +5 -1
  25. package/plugins/sp/scripts/pr-reviewing.ts +7 -1
  26. package/plugins/sp/scripts/transition-shim-check.ts +6 -6
  27. package/plugins/sp/skills/pr-reviewing/SKILL.md +4 -1
  28. package/plugins/sp/skills/spur-cli/references/self.md +17 -3
  29. package/plugins/sp/skills/spur-cli/references/tasks.md +4 -2
  30. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +5 -5
  31. package/plugins/sp/skills/spur-cli/references/workflows.md +4 -4
  32. package/plugins/sp/skills/spur-dev/SKILL.md +17 -0
  33. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +23 -8
  34. package/plugins/sp/skills/spur-dev/references/dev-operations.md +1 -1
  35. package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
  36. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +5 -3
  37. package/plugins/sp/skills/wayfinder/SKILL.md +1 -1
  38. package/spur.js +2999 -1010
  39. package/web/_astro/{BoardApp.BYCNkMOn.js → BoardApp.BOW1815F.js} +101 -100
  40. package/web/_astro/BoardApp.C4zimv1U.js +1 -0
  41. package/web/_astro/{TaskDetail.CgUreSP2.js → TaskDetail.CX6C7dSJ.js} +1 -1
  42. package/web/_astro/{arc.BySSh34M.js → arc.CwSvH1ji.js} +1 -1
  43. package/web/_astro/{architectureDiagram-3BPJPVTR.DM46TS_h.js → architectureDiagram-3BPJPVTR.Dxne1RvP.js} +1 -1
  44. package/web/_astro/{blockDiagram-GPEHLZMM.tZhvNUHA.js → blockDiagram-GPEHLZMM.D9Fb14rC.js} +1 -1
  45. package/web/_astro/{c4Diagram-AAUBKEIU.PT4Or4Nf.js → c4Diagram-AAUBKEIU.DmvWSFvv.js} +1 -1
  46. package/web/_astro/channel.CQHdDVp9.js +1 -0
  47. package/web/_astro/{chunk-2J33WTMH.J9r0_Bbe.js → chunk-2J33WTMH.BCCktIrE.js} +1 -1
  48. package/web/_astro/{chunk-4BX2VUAB.hzyeIvhR.js → chunk-4BX2VUAB.BzCk9q27.js} +1 -1
  49. package/web/_astro/{chunk-55IACEB6.B0rO7qVh.js → chunk-55IACEB6.CZXVgTk4.js} +1 -1
  50. package/web/_astro/{chunk-727SXJPM.wE_Uk5D4.js → chunk-727SXJPM.CJL8UIpX.js} +1 -1
  51. package/web/_astro/{chunk-AQP2D5EJ.DqEEjQw7.js → chunk-AQP2D5EJ.Dz8MDEdw.js} +1 -1
  52. package/web/_astro/{chunk-FMBD7UC4.CDoD9sBX.js → chunk-FMBD7UC4.CkQeYYUw.js} +1 -1
  53. package/web/_astro/{chunk-ND2GUHAM.CtX5nF9P.js → chunk-ND2GUHAM.KiC1QgzH.js} +1 -1
  54. package/web/_astro/{chunk-QZHKN3VN.CK_EwfaT.js → chunk-QZHKN3VN.AJ08mw2e.js} +1 -1
  55. package/web/_astro/{classDiagram-4FO5ZUOK.DLt5a8Lh.js → classDiagram-4FO5ZUOK.DRWRDKzK.js} +1 -1
  56. package/web/_astro/{classDiagram-v2-Q7XG4LA2.DLt5a8Lh.js → classDiagram-v2-Q7XG4LA2.DRWRDKzK.js} +1 -1
  57. package/web/_astro/{cose-bilkent-S5V4N54A.CMCWP49h.js → cose-bilkent-S5V4N54A.CavydfLP.js} +1 -1
  58. package/web/_astro/{cynefin-OW5HDTMX.HyXw_vdS.js → cynefin-OW5HDTMX.D_7o_a0B.js} +1 -1
  59. package/web/_astro/{dagre-BM42HDAG.BTuAzh01.js → dagre-BM42HDAG.LWk2dKgg.js} +1 -1
  60. package/web/_astro/{diagram-2AECGRRQ.D9dr9wfT.js → diagram-2AECGRRQ.82lbq6aB.js} +1 -1
  61. package/web/_astro/{diagram-5GNKFQAL.C4Rot0hj.js → diagram-5GNKFQAL.hyNL1pwY.js} +1 -1
  62. package/web/_astro/{diagram-KO2AKTUF.B_TK5uWC.js → diagram-KO2AKTUF.BaLqVf-b.js} +1 -1
  63. package/web/_astro/{diagram-LMA3HP47.JkXKK7CO.js → diagram-LMA3HP47.DQJxj0oz.js} +1 -1
  64. package/web/_astro/{diagram-OG6HWLK6.BzMN8Bd6.js → diagram-OG6HWLK6.DhlL8cG2.js} +1 -1
  65. package/web/_astro/{erDiagram-TEJ5UH35.DVZaWGUd.js → erDiagram-TEJ5UH35.HoTnXwkF.js} +1 -1
  66. package/web/_astro/{flowDiagram-I6XJVG4X.rjEiWUfR.js → flowDiagram-I6XJVG4X.DkFKlZIZ.js} +1 -1
  67. package/web/_astro/{ganttDiagram-6RSMTGT7.C_EgAarK.js → ganttDiagram-6RSMTGT7.9qPfOTDb.js} +1 -1
  68. package/web/_astro/{gitGraphDiagram-PVQCEYII.B-QQSDsK.js → gitGraphDiagram-PVQCEYII.B4G18Dwc.js} +1 -1
  69. package/web/_astro/index.Dcr_8fiK.css +1 -0
  70. package/web/_astro/{infoDiagram-5YYISTIA.DlWesz7T.js → infoDiagram-5YYISTIA.e3KJkXAM.js} +1 -1
  71. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BUMZOawi.js → ishikawaDiagram-YF4QCWOH.CY3yddhD.js} +1 -1
  72. package/web/_astro/{journeyDiagram-JHISSGLW.CWfkxfjY.js → journeyDiagram-JHISSGLW.DaAKIO1t.js} +1 -1
  73. package/web/_astro/{kanban-definition-UN3LZRKU.B-YpMwXf.js → kanban-definition-UN3LZRKU.DGkDddtc.js} +1 -1
  74. package/web/_astro/{linear.D7uqzENp.js → linear.DnDPcpd1.js} +1 -1
  75. package/web/_astro/{mermaid.core.CxrNppBD.js → mermaid.core.1uBmxa9t.js} +4 -4
  76. package/web/_astro/{mindmap-definition-RKZ34NQL.B4Qe7cM2.js → mindmap-definition-RKZ34NQL.XFJVayxw.js} +1 -1
  77. package/web/_astro/{pieDiagram-4H26LBE5.Ds-5j2ro.js → pieDiagram-4H26LBE5.BXm2OgHh.js} +1 -1
  78. package/web/_astro/{quadrantDiagram-W4KKPZXB.tBd38uNC.js → quadrantDiagram-W4KKPZXB.DgB2p5fc.js} +1 -1
  79. package/web/_astro/{requirementDiagram-4Y6WPE33.sFENkWl3.js → requirementDiagram-4Y6WPE33.jBR39bnS.js} +1 -1
  80. package/web/_astro/{sankeyDiagram-5OEKKPKP.BeB-Hk7C.js → sankeyDiagram-5OEKKPKP.N3u9OhE3.js} +1 -1
  81. package/web/_astro/{sequenceDiagram-3UESZ5HK.DnTeaSpx.js → sequenceDiagram-3UESZ5HK.CZhqZXNL.js} +1 -1
  82. package/web/_astro/{stateDiagram-AJRCARHV.B-8Jt5EJ.js → stateDiagram-AJRCARHV.DlNu1VEa.js} +1 -1
  83. package/web/_astro/{stateDiagram-v2-BHNVJYJU.Br7xoqMW.js → stateDiagram-v2-BHNVJYJU.BEqi8NQV.js} +1 -1
  84. package/web/_astro/{timeline-definition-PNZ67QCA.C-3WdOyi.js → timeline-definition-PNZ67QCA.BPcexclc.js} +1 -1
  85. package/web/_astro/{vennDiagram-CIIHVFJN.DCIs7Lc6.js → vennDiagram-CIIHVFJN.BZalxKGQ.js} +1 -1
  86. package/web/_astro/{wardleyDiagram-YWT4CUSO.rGAL-bbz.js → wardleyDiagram-YWT4CUSO.BqOljod9.js} +1 -1
  87. package/web/_astro/{xychartDiagram-2RQKCTM6.hkfQKiRl.js → xychartDiagram-2RQKCTM6.tkO4ppN5.js} +1 -1
  88. package/web/index.html +2 -2
  89. package/config/corpus-baseline.json +0 -1805
  90. package/config/workflow-composition-baseline.json +0 -615
  91. package/web/_astro/BoardApp.E12MFjOS.js +0 -1
  92. package/web/_astro/channel.5cYKr5cs.js +0 -1
  93. package/web/_astro/index.B5MTfe7k.css +0 -1
@@ -1,36 +1,57 @@
1
- # Feature development loop — brainstorm plan → execute-tasks → feature-verify → done.
1
+ # Feature development loop — existing-feature reuse: precheck → execute-tasks →
2
+ # feature-verify → integration-review → done.
3
+ #
4
+ # 0782 (feature D6): the caller supplies a featureId whose AC are already accepted and whose
5
+ # task roster already exists. This definition REUSES them — the brainstorm/plan states and
6
+ # their edges are deleted, so no model hop ever re-plans or re-decomposes an existing feature
7
+ # (explicit planning stays at /sp:dev-plan and /sp:dev-idea, outside this consumer).
2
8
  #
3
9
  # Reliability (aligned with task-pipeline / ADR-043):
4
- # - Soft precheck (featureId non-empty + agent doctor) → failed via transitions
10
+ # - Soft precheck (feature identity + roster structural contract) → failed via transitions,
11
+ # never a raw lifecycle abort. The doctor probe is gone: the precheck now performs the
12
+ # essential feature/roster validation itself; task execution owns executor availability.
5
13
  # - Every agent.run input is a pure slash command (no free-form pipeline essays)
6
14
  # - featureId must be supplied by the caller (workflow vars are not agent-mutated)
7
- # - feature-verify is fail-closed on `spur feature check --strict`
15
+ # - feature-verify runs `feature check --as done --json` EXACTLY ONCE (0782 R3): the shell
16
+ # captures the exit code and JSON result, writes the decision to a .status sibling, and
17
+ # the guards only read that status — no sibling guard re-runs the check.
18
+ # - A nonempty all-terminal roster routes straight to feature-verify with zero execution
19
+ # model calls (0782 R2).
8
20
 
9
21
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
10
22
  kind: state-machine
11
23
  name: feature-dev
24
+ version: "3"
12
25
  description: >
13
- End-to-end FEATURE development loop: precheck → brainstormplan (decompose)
14
- execute-tasks feature-verify done. Drives a whole feature (one description a
15
- feature with BDD AC many tasks all tasks done → feature verified), NOT a single
16
- task for one deliverable use task-pipeline.yaml directly.
26
+ Existing-feature execution loop: precheck → execute-tasksfeature-verify done.
27
+ Drives a WHOLE feature whose accepted AC and task roster already exist the caller
28
+ supplies featureId, this loop reuses the roster and runs it to verified completion.
29
+ For planning a NEW feature use /sp:dev-plan / /sp:dev-idea; for one deliverable use
30
+ task-pipeline.yaml directly.
17
31
 
18
- The engine has no native for-each over a dynamic task list, so the execute-tasks step
19
- DELEGATES the fan-out: a single agent.run instructs the agent to run every pending task
20
- under the feature through task-pipeline.yaml, enumerated via `spur task list --feature`.
21
- This mirrors how /sp:dev-run --mode full delegates its inner work loop (ADR-022: orchestration
22
- is configuration zero new engine code).
32
+ The engine has no native for-each over a dynamic task list, so execution DELEGATES the
33
+ fan-out: the precheck resolves the linked roster via `spur feature show` + `spur task
34
+ list --feature` (captured once under .spur/run/<runId>-feature-dev-{feature,roster}.json),
35
+ validates the essential structural contract (identity, roster shape, duplicate/mismatched
36
+ identities, refinement-blocking statuses), and freezes the sorted todo WBS list into
37
+ .spur/run/<runId>-feature-dev-tasks.txt. The execute-tasks step reads that frozen list
38
+ into a var (file.read.into-var) and dispatches ONE pure-slash /sp:dev-runall --tasks run
39
+ with it — the child never re-enumerates membership (0782 R2).
23
40
 
24
- Shape: precheck → brainstorm → plan → execute-tasks[-auto](delegated loop) → feature-verify → done
25
- (precheck/plan failure or a non-PASS feature check short-circuits to `failed`).
41
+ Shape: precheck → execute-tasks[-auto](delegated frozen batch) → feature-verify →
42
+ integration-review done
43
+ (precheck failure, a non-PASS completion check, or a blocking review short-circuits
44
+ to `failed`; a nonempty all-terminal roster skips execution entirely).
26
45
 
27
46
  Vars (passed as a JSON object via `--vars`):
28
- featureId — the feature ID this loop drives (required), e.g. `--vars '{"featureId":"B3"}'`
47
+ featureId — the existing feature ID this loop drives (required), e.g. `--vars '{"featureId":"B3"}'`
29
48
  agent — agent the agent.run steps invoke; override `--vars '{"agent":"claude"}'`
30
- profile — `auto` selects execute-tasks-auto (`/sp:dev-runall … --auto`); otherwise
49
+ profile — `auto` selects execute-tasks-auto (`/sp:dev-runall --tasks … --auto`); otherwise
31
50
  interactive execute-tasks (no --auto, per-task HITL remains)
51
+ featureTaskIds — frozen todo WBS list projected from the roster file at run time; declared
52
+ empty so template resolution cannot throw before the read lands (0782 design)
32
53
  spurBin — PATH-independent spur invocation (CLI overrides at run start; literal fallback)
33
- stepTimeoutMs — agent.run budget (ms) for brainstorm/plan/runall
54
+ stepTimeoutMs — agent.run budget (ms) for the runall dispatch
34
55
 
35
56
  Reliability (ADR-043): every agent.run input is a pure slash command. featureId must be
36
57
  supplied by the caller (workflow vars cannot be mutated by the agent). profile is wired
@@ -46,6 +67,7 @@ vars:
46
67
  featureId: ""
47
68
  agent: "auto"
48
69
  profile: "standard"
70
+ featureTaskIds: ""
49
71
  spurBin: "spur"
50
72
  stepTimeoutMs: "1800000"
51
73
  # Integration review (D5-P, feature R11). Advisory by default: a pending, timed-out, or
@@ -59,102 +81,135 @@ vars:
59
81
  states:
60
82
  - id: precheck
61
83
  description: >
62
- Soft precheck: non-empty featureId + agent doctor. Always exit 0 so failures
63
- route to `failed` via transitions (not a raw lifecycle abort).
84
+ Soft precheck (0782 R1): resolve the supplied feature and its linked task roster via
85
+ existing CLI reads, then validate the essential structural contract — non-empty
86
+ featureId/runId, a feature-show JSON whose identity matches, a nonempty array roster
87
+ with unique non-empty WBS identities and known statuses, no backlog/wip/testing/blocked
88
+ member (planning/refine/resume handoff), and at least one linked task. Rejects fail
89
+ closed with an actionable handoff BEFORE any model dispatch; nothing is auto-created
90
+ or re-planned. Always exit 0 so failures route to `failed` via transitions.
64
91
  onEnter:
65
92
  - kind: shell
66
93
  options:
67
94
  command: >-
68
- mkdir -p .spur/run &&
69
- set +e &&
70
- test -n "$featureId"; id_rc=$?;
71
- $spurBin agent doctor $agent; doctor_rc=$?;
72
- set -e &&
73
- if [ "$id_rc" -eq 0 ] && [ "$doctor_rc" -eq 0 ]; then
74
- printf 'PASS\n' > ".spur/run/$__runId-feature-dev-precheck.status";
95
+ mkdir -p .spur/run;
96
+ FEATURE_JSON=".spur/run/$__runId-feature-dev-feature.json";
97
+ ROSTER_JSON=".spur/run/$__runId-feature-dev-roster.json";
98
+ TASKS_TXT=".spur/run/$__runId-feature-dev-tasks.txt";
99
+ PRECHECK_STATUS=".spur/run/$__runId-feature-dev-precheck.status";
100
+ rm -f "$PRECHECK_STATUS";
101
+ set +e;
102
+ test -n "$featureId" && test -n "$__runId";
103
+ id_rc=$?;
104
+ if [ "$id_rc" -eq 0 ]; then $spurBin feature show "$featureId" --json > "$FEATURE_JSON" 2>&1; show_rc=$?; else show_rc=1; fi;
105
+ if [ "$id_rc" -eq 0 ] && [ "$show_rc" -eq 0 ] && jq -e --arg id "$featureId" 'type == "object" and .id == $id' "$FEATURE_JSON" > /dev/null 2>&1; then $spurBin task list --feature "$featureId" --json > "$ROSTER_JSON" 2>&1; list_rc=$?; else list_rc=1; fi;
106
+ if [ "$id_rc" -ne 0 ] || [ "$show_rc" -ne 0 ] || [ "$list_rc" -ne 0 ]; then
107
+ echo "feature-dev precheck: missing featureId/runId, unknown feature '$featureId', or unreadable roster (rc $id_rc/$show_rc/$list_rc) — supply an existing planned feature via /sp:dev-plan or /sp:dev-idea; nothing was auto-created or re-planned" >&2;
108
+ printf 'FAIL\n' > "$PRECHECK_STATUS";
109
+ elif ! jq -e 'type == "array" and length > 0' "$ROSTER_JSON" > /dev/null 2>&1; then
110
+ echo "feature-dev precheck: roster at $ROSTER_JSON is malformed, not an array, or empty — plan the feature first via /sp:dev-plan; refusing to replan or run an empty batch" >&2;
111
+ printf 'FAIL\n' > "$PRECHECK_STATUS";
112
+ elif ! jq -e 'all(.[]; (.wbs | type == "string" and length > 0)) and ([.[].wbs] | length == (unique | length)) and all(.[]; .status == "todo" or .status == "done" or .status == "cancelled" or .status == "backlog" or .status == "wip" or .status == "testing" or .status == "blocked")' "$ROSTER_JSON" > /dev/null 2>&1; then
113
+ echo "feature-dev precheck: roster has empty/duplicate/mismatched WBS identities or unknown statuses at $ROSTER_JSON — repair the task corpus; refusing to batch a broken roster" >&2;
114
+ printf 'FAIL\n' > "$PRECHECK_STATUS";
115
+ elif [ "$(jq '[.[] | select(.status == "backlog" or .status == "wip" or .status == "testing" or .status == "blocked")] | length' "$ROSTER_JSON")" -gt 0 ]; then
116
+ echo "feature-dev precheck: linked task(s) are backlog/wip/testing/blocked — refine or resume them through their own task pipelines before batching; refusing to launch overlapping work" >&2;
117
+ printf 'FAIL\n' > "$PRECHECK_STATUS";
75
118
  else
76
- printf 'FAIL\n' > ".spur/run/$__runId-feature-dev-precheck.status";
77
- fi &&
119
+ printf '%s' "$(jq -r '[.[] | select(.status == "todo") | .wbs] | sort | join(",")' "$ROSTER_JSON")" > "$TASKS_TXT";
120
+ printf 'PASS\n' > "$PRECHECK_STATUS";
121
+ fi;
78
122
  exit 0
79
123
  - kind: note
80
124
  options:
81
- message: "Feature-dev start for feature ${vars.featureId}."
82
-
83
- - id: brainstorm
84
- description: >
85
- Validate / expand the feature with BDD AC via pure slash /sp:dev-brainstorm.
86
- Caller must pass vars.featureId (required for plan/runall).
87
- onEnter:
88
- - kind: agent.run
89
- options:
90
- agent: ${vars.agent}
91
- input: /sp:dev-brainstorm --feature ${vars.featureId}
92
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
93
- role: planner
94
- timeoutMs: ${vars.stepTimeoutMs}
95
-
96
- - id: plan
97
- description: >
98
- Decompose the validated feature into a CLI-validated task batch via pure slash
99
- /sp:dev-plan. The batch-create gate rejects a malformed decomposition.
100
- onEnter:
101
- - kind: agent.run
102
- options:
103
- agent: ${vars.agent}
104
- input: /sp:dev-plan --feature ${vars.featureId}
105
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
106
- role: planner
107
- timeoutMs: ${vars.stepTimeoutMs}
125
+ message: "Feature-dev start for existing feature ${vars.featureId} (0782 reuse path): roster resolved under .spur/run/${vars.__runId}-feature-dev-roster.json; frozen todo list at .spur/run/${vars.__runId}-feature-dev-tasks.txt."
108
126
 
109
127
  # Two pure-slash execute hops so vars.profile actually controls --auto (ADR-043).
110
128
  # Engine templates cannot branch a single input string on profile; declaration-order
111
- # transitions pick the correct hop (auto first, then interactive fallback).
129
+ # transitions pick the correct hop (auto first, then interactive fallback). Each hop first
130
+ # reads the FROZEN todo list produced by precheck into featureTaskIds, then dispatches the
131
+ # explicit list — the runall child never re-enumerates membership (0782 R2).
112
132
  - id: execute-tasks-auto
113
133
  description: >
114
- profile=auto — drive every pending task via pure slash /sp:dev-runall --auto
115
- (skips per-task HITL inside task-pipeline).
134
+ profile=auto — dispatch the frozen todo WBS list via pure slash
135
+ /sp:dev-runall --tasks <list> --auto (skips per-task HITL inside task-pipeline).
116
136
  onEnter:
137
+ - kind: file.read.into-var
138
+ options:
139
+ path: .spur/run/${vars.__runId}-feature-dev-tasks.txt
140
+ var: featureTaskIds
141
+ trim: true
117
142
  - kind: agent.run
118
143
  options:
119
144
  agent: ${vars.agent}
120
- input: /sp:dev-runall --feature ${vars.featureId} --auto
145
+ input: /sp:dev-runall --tasks ${vars.featureTaskIds} --auto
121
146
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
122
147
  role: planner
123
148
  timeoutMs: ${vars.stepTimeoutMs}
124
149
 
125
150
  - id: execute-tasks
126
151
  description: >
127
- Interactive — drive every pending task via pure slash /sp:dev-runall without
128
- --auto (per-task HITL approve gates remain available).
152
+ Interactive — dispatch the frozen todo WBS list via pure slash
153
+ /sp:dev-runall --tasks <list> without --auto (per-task HITL approve gates remain
154
+ available).
129
155
  onEnter:
156
+ - kind: file.read.into-var
157
+ options:
158
+ path: .spur/run/${vars.__runId}-feature-dev-tasks.txt
159
+ var: featureTaskIds
160
+ trim: true
130
161
  - kind: agent.run
131
162
  options:
132
163
  agent: ${vars.agent}
133
- input: /sp:dev-runall --feature ${vars.featureId}
164
+ input: /sp:dev-runall --tasks ${vars.featureTaskIds}
134
165
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
135
166
  role: planner
136
167
  timeoutMs: ${vars.stepTimeoutMs}
137
168
 
138
169
  - id: feature-verify
139
170
  description: >
140
- Certify the feature is complete: `spur feature check ${featureId} --strict` validates the
141
- AC, traceability, and that all linked tasks are done. A clean strict check is the only
142
- proof the feature is shippable.
171
+ Certify the feature is complete (0782 R3): run `spur feature check --as done --json`
172
+ EXACTLY ONCE, capture the exit code and JSON result at
173
+ .spur/run/<runId>-feature-dev-verify.json, and decide PASS/FAIL into the .status
174
+ sibling. PASS requires exit 0, a nonempty result array, and every returned scope
175
+ pass === true. The guards below only read the captured status — no sibling guard
176
+ re-runs the check. No --strict elevation, no whole-corpus scan.
177
+ onEnter:
178
+ - kind: shell
179
+ options:
180
+ command: >-
181
+ mkdir -p .spur/run;
182
+ VERIFY_JSON=".spur/run/$__runId-feature-dev-verify.json";
183
+ VERIFY_STATUS=".spur/run/$__runId-feature-dev-verify.status";
184
+ VERIFY_TMP=".spur/run/$__runId-feature-dev-verify.status.tmp";
185
+ rm -f "$VERIFY_STATUS";
186
+ set +e;
187
+ $spurBin feature check "$featureId" --as done --json > "$VERIFY_JSON" 2>&1;
188
+ check_rc=$?;
189
+ set -e;
190
+ if [ "$check_rc" -eq 0 ] && jq -e 'type == "array" and length > 0 and all(.[]; .pass == true)' "$VERIFY_JSON" > /dev/null 2>&1; then printf 'PASS\n' > "$VERIFY_TMP" && mv -f "$VERIFY_TMP" "$VERIFY_STATUS"; else printf 'FAIL\n' > "$VERIFY_TMP" && mv -f "$VERIFY_TMP" "$VERIFY_STATUS"; fi
191
+ - kind: note
192
+ options:
193
+ message: "Feature verification for ${vars.featureId}: decision at .spur/run/${vars.__runId}-feature-dev-verify.status (evidence at .spur/run/${vars.__runId}-feature-dev-verify.json) — exactly one `feature check --as done --json` invocation per run (0782 R3)."
143
194
 
144
195
  - id: integration-review
145
196
  description: >
146
- Advisory integration review (D5-P, feature R11). Spends one Codex review
147
- on the current HEAD after the local gates and before wrap merge/cleanup.
197
+ Advisory integration review (D5-P, feature R11), upgraded by 0770 to a
198
+ truthful collect: ONE Codex review request per HEAD, then exactly ONE
199
+ `pr-reviewing.ts collect --head <captured-head>` against that same HEAD —
200
+ no wait loop, no polling. The request verdict (`REQUESTED` /
201
+ `ALREADY_REQUESTED` / `ALREADY_REVIEWED`) describes request state only;
202
+ it is NEVER clean evidence. The ONLY clean evidence is the collected
203
+ status (CLEAN / FINDINGS / PENDING) persisted run-scoped at
204
+ .spur/run/<runId>-integration-review-collect.status. A missing head SHA,
205
+ an invalid collect result, or head drift records collect FAIL.
148
206
 
149
- D8 decision D1 (0753 R3): the nested `spur workflow run pr-review.yaml`
150
- was refused by the SPUR_WORKFLOW_RUN_ACTIVE child guard and the refusal
151
- was masked by `softFail: true`. The non-spawning replacement invokes
152
- `pr-reviewing.ts request` directly — the same per-HEAD dedupe-or-request
153
- logic `config/workflows/pr-review.yaml` uses internally and writes its
154
- own PASS/FAIL status. `ALREADY_REVIEWED` / `ALREADY_REQUESTED` / `REQUESTED`
155
- all count as PASS (review is initiated for this HEAD); only a request
156
- error counts as FAIL. `softFail` is gone: a real failure now records FAIL
157
- loudly. `requireCleanReview=true` still turns that FAIL into a blocking edge.
207
+ D8 decision D1 (0753 R3): invoking `pr-reviewing.ts request` directly —
208
+ the same per-HEAD dedupe-or-request logic `config/workflows/pr-review.yaml`
209
+ uses internally. Request FAIL records FAIL in
210
+ .spur/run/<runId>-integration-review.status, loudly (no softFail).
211
+ `requireCleanReview=true` turns a non-clean COLLECTED verdict (including
212
+ collect FAIL) into the blocking edge declared first below.
158
213
  onEnter:
159
214
  - kind: shell
160
215
  options:
@@ -162,63 +217,69 @@ states:
162
217
  mkdir -p .spur/run &&
163
218
  STATUS_FILE=".spur/run/$__runId-integration-review.status" &&
164
219
  REQUEST_JSON=".spur/run/$__runId-integration-review.json" &&
220
+ COLLECT_JSON=".spur/run/$__runId-integration-review-collect.json" &&
221
+ COLLECT_STATUS=".spur/run/$__runId-integration-review-collect.status" &&
165
222
  PR_REQUEST_STATUS=".spur/run/$__runId-integration-review-pr-request.status" &&
166
223
  set +e &&
167
224
  bun "$(superskill script path sp pr-reviewing.ts)" request --base "$baseBranch" --json
168
225
  --status-file "$PR_REQUEST_STATUS" > "$REQUEST_JSON" 2>&1;
169
- rc=$?; set -e &&
170
- if [ "$rc" -eq 0 ]; then
226
+ request_rc=$?; set -e &&
227
+ if [ "$request_rc" -eq 0 ]; then
171
228
  printf 'PASS\n' > "$STATUS_FILE";
172
229
  else
173
230
  printf 'FAIL\n' > "$STATUS_FILE";
174
231
  fi &&
232
+ HEAD_SHA=$(jq -r '.head // empty' "$REQUEST_JSON" 2>/dev/null || true) &&
233
+ if [ -z "$HEAD_SHA" ]; then
234
+ echo "integration-review: request result carried no head SHA — collect not run, recorded FAIL" >&2;
235
+ printf 'FAIL\n' > "$COLLECT_STATUS";
236
+ else
237
+ set +e &&
238
+ bun "$(superskill script path sp pr-reviewing.ts)" collect --head "$HEAD_SHA" --json
239
+ --status-file "$COLLECT_STATUS" > "$COLLECT_JSON" 2>&1;
240
+ collect_rc=$?; set -e &&
241
+ echo "integration-review: collect rc=$collect_rc status=$(cat "$COLLECT_STATUS" 2>/dev/null || echo missing) — verdict captured to $COLLECT_JSON" >&2;
242
+ fi;
175
243
  exit 0
176
244
  - kind: note
177
245
  options:
178
- message: "Integration review for feature ${vars.featureId} recorded at .spur/run/${vars.__runId}-integration-review.status (advisory unless requireCleanReview=true)."
246
+ message: "Integration review for feature ${vars.featureId}: request verdict at .spur/run/${vars.__runId}-integration-review.status (request state only — REQUESTED/ALREADY_* are NEVER clean evidence); collected verdict at .spur/run/${vars.__runId}-integration-review-collect.status (CLEAN/FINDINGS/PENDING — the only clean evidence). Advisory unless requireCleanReview=true."
179
247
 
180
248
  - id: done
181
- description: Terminal — feature planned, all tasks executed, and the feature verified.
182
- # Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
183
- onEnter:
184
- - kind: shell
185
- options:
186
- command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: feature-dev done featureId=$featureId ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/$featureId-checkpoint.md'
249
+ description: Terminal — the existing feature's roster executed and the feature verified.
187
250
 
188
251
  - id: failed
189
- description: Terminal — plan rejected, a task failed verification, or the feature check did not pass.
252
+ description: Terminal — the roster contract was rejected at precheck, a task failed verification, or the feature check did not pass.
190
253
 
191
254
  transitions:
255
+ # ── precheck routing (0782 R1/R2) — declaration order matters: the all-terminal
256
+ # direct-verify edge is tried FIRST so a fully-terminal roster reaches completion
257
+ # verification with zero execution model calls; then the profile hops; then the
258
+ # failure fallback. ──
192
259
  - from: precheck
193
- to: brainstorm
194
- description: Precheck PASS — begin brainstorm.
260
+ to: feature-verify
261
+ description: Roster PASS and every linked task is already terminal verify the feature directly, zero execution model calls.
195
262
  guard:
196
263
  kind: shell
197
264
  options:
198
- command: 'test "$(cat .spur/run/$__runId-feature-dev-precheck.status 2>/dev/null)" = PASS'
265
+ command: 'test "$(cat ".spur/run/$__runId-feature-dev-precheck.status" 2>/dev/null)" = PASS && test -f ".spur/run/$__runId-feature-dev-feature.json" && test ! -s ".spur/run/$__runId-feature-dev-tasks.txt"'
199
266
  - from: precheck
200
- to: failed
201
- description: Precheck FAIL (missing featureId and/or doctor red) — stop.
202
- guard:
203
- kind: always
204
-
205
- # Linear prefix: brainstorm → plan → execute-tasks
206
- - from: brainstorm
207
- to: plan
208
- description: Feature validated; decompose it into tasks
209
- guard:
210
- kind: always
211
-
212
- - from: plan
213
267
  to: execute-tasks-auto
214
- description: Tasks created; profile=auto — batch with --auto (no per-task HITL).
268
+ description: Roster PASS with todo work; profile=auto — batch the frozen list with --auto (no per-task HITL).
215
269
  guard:
216
270
  kind: shell
217
271
  options:
218
- command: 'test "$profile" = auto'
219
- - from: plan
272
+ command: 'test "$(cat ".spur/run/$__runId-feature-dev-precheck.status" 2>/dev/null)" = PASS && test -s ".spur/run/$__runId-feature-dev-tasks.txt" && test "$profile" = auto'
273
+ - from: precheck
220
274
  to: execute-tasks
221
- description: Tasks created; interactive — batch without --auto.
275
+ description: Roster PASS with todo work; interactive — batch the frozen list without --auto.
276
+ guard:
277
+ kind: shell
278
+ options:
279
+ command: 'test "$(cat ".spur/run/$__runId-feature-dev-precheck.status" 2>/dev/null)" = PASS && test -s ".spur/run/$__runId-feature-dev-tasks.txt"'
280
+ - from: precheck
281
+ to: failed
282
+ description: Precheck FAIL (missing identity, malformed/empty roster, duplicate identities, or refinement-blocking statuses) — stop before any model dispatch.
222
283
  guard:
223
284
  kind: always
224
285
 
@@ -233,37 +294,40 @@ transitions:
233
294
  guard:
234
295
  kind: always
235
296
 
236
- # ── feature-verify gate: strict check certifies done, else fail ──
237
- # Declaration order: the pass guard is tried FIRST.
297
+ # ── feature-verify gate: the captured one-shot check decision certifies review, else
298
+ # fail. Declaration order: the pass guard is tried FIRST. The check itself is NOT
299
+ # re-run here (0782 R3) — the shell decision file is the only evidence. ──
238
300
  - from: feature-verify
239
301
  to: integration-review
240
- description: Strict feature check passed — spend one integration review on this stable HEAD.
302
+ description: Captured completion check is PASS — spend one integration review on this stable HEAD.
241
303
  guard:
242
304
  kind: shell
243
305
  options:
244
- command: "$spurBin feature check $featureId --strict"
245
-
306
+ command: 'test "$(cat ".spur/run/$__runId-feature-dev-verify.status" 2>/dev/null)" = PASS'
246
307
  - from: integration-review
247
308
  to: failed
248
309
  description: >
249
- Review came back FAIL and this branch opted into a hard gate
250
- (requireCleanReview=true). Ordered before the advisory edge so the strict
251
- policy wins when both would match.
310
+ Collected verdict is not CLEAN (FINDINGS / PENDING / collect FAIL or
311
+ missing) and this branch opted into a hard gate (requireCleanReview=true).
312
+ Exactly one collect ran against the captured request HEAD — no wait loop —
313
+ so pending or findings stop the feature here with the actual reason.
314
+ Ordered before the advisory edge so the strict policy wins when both
315
+ would match.
252
316
  guard:
253
317
  kind: shell
254
318
  options:
255
- command: 'test "$requireCleanReview" = true && test "$(cat .spur/run/$__runId-integration-review.status 2>/dev/null)" = FAIL'
319
+ command: 'test "$requireCleanReview" = true && test "$(cat .spur/run/$__runId-integration-review-collect.status 2>/dev/null)" != CLEAN'
256
320
  - from: integration-review
257
321
  to: done
258
322
  description: >
259
- Review clean, pending, unavailable, or advisory the feature is complete. A
260
- non-clean advisory review never blocks a locally-verified feature (feature R11).
323
+ Collected CLEAN, or advisory mode (requireCleanReview != true) the
324
+ feature is complete. A non-clean advisory collect never blocks a
325
+ locally-verified feature (feature R11); the note above preserves the
326
+ collected non-clean state in the run record instead of hiding it.
261
327
  guard:
262
328
  kind: always
263
329
  - from: feature-verify
264
330
  to: failed
265
- description: Strict feature check failed — block before done; investigate incomplete tasks or AC.
331
+ description: Completion check missing, malformed, or non-PASS (or its evidence failed to persist) — block BEFORE integration review; no review is requested (0782 R3).
266
332
  guard:
267
- kind: shell
268
- options:
269
- command: '! $spurBin feature check $featureId --strict'
333
+ kind: always
@@ -9,6 +9,7 @@
9
9
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
10
10
  kind: state-machine
11
11
  name: feature-lifecycle
12
+ version: "1"
12
13
  description: >
13
14
  Feature lifecycle FSM (design §2.3, §5.1, DD-13). States are the canonical
14
15
  FeatureStatus vocabulary; transitions encode the §2.3 graph including the
@@ -48,6 +48,7 @@
48
48
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
49
49
  kind: state-machine
50
50
  name: history-anatomy
51
+ version: "1"
51
52
  description: "Daily/ad-hoc history-anatomy report: cache branch, deterministic analyze/render, skill enrichment, deterministic structure gate, independent evidence validation, bounded correction, atomic publication"
52
53
  iterationBound: 20
53
54
  initialState: start
@@ -153,7 +154,7 @@ states:
153
154
  --baseline .spur/run/$__runId-history-anatomy-baseline.json --target "$HA_TARGET"
154
155
  --mode "$mode" --date "$HA_DATE" --recompute "$recompute" --executor "$agent"
155
156
  --skill-dir "$HA_SKILL" --contract "$HA_SKILL/references/report-contract.md"
156
- --workflow "$workflowFile" --contract-version "$contractVersion" --run-id "$__runId"
157
+ --workflow "$workflowFile" --helper "$HA_HELPER" --contract-version "$contractVersion" --run-id "$__runId"
157
158
  --out .spur/run/$__runId-provenance.json
158
159
  > .spur/run/$__runId-cache-disposition.txt
159
160
 
@@ -208,6 +209,9 @@ states:
208
209
  description: >
209
210
  Independent evidence validation via the skill operation sp:history-anatomy validate.
210
211
  Publication is reachable only from a PASS here. Same undeclared-write assertion as enrich (0676 R3).
212
+ The publish guard reads the ANCHORED FINAL LINE (0771): only a validation artifact whose last
213
+ line is exactly `Verdict: PASS` publishes — a leading PASS under a later FAIL or a
214
+ `not Verdict: PASS` line can never satisfy it.
211
215
  onEnter:
212
216
  - kind: shell
213
217
  options:
@@ -390,18 +394,18 @@ transitions:
390
394
 
391
395
  - from: validate
392
396
  to: stamp
393
- description: Independent validation PASS — stamp provenance, then publish atomically.
397
+ description: Independent validation PASS (anchored final line, 0771) — stamp provenance, then publish atomically.
394
398
  guard:
395
399
  kind: shell
396
400
  options:
397
- command: 'grep -q "Verdict: PASS" .spur/run/$__runId-validation.txt 2>/dev/null'
401
+ command: 'tail -n 1 .spur/run/$__runId-validation.txt 2>/dev/null | grep -qx "Verdict: PASS"'
398
402
  - from: validate
399
403
  to: correct
400
404
  description: Validation FAIL under the shared two-pass correction cap.
401
405
  guard:
402
406
  kind: shell
403
407
  options:
404
- command: '! grep -q "Verdict: PASS" .spur/run/$__runId-validation.txt 2>/dev/null && test "$(cat .spur/run/$__runId-correction-count 2>/dev/null || echo 0)" -lt 2'
408
+ 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'
405
409
  - from: validate
406
410
  to: failed
407
411
  description: Validation FAIL with the correction cap exhausted — terminate without publishing.