@gobing-ai/spur 0.4.14 → 0.4.16

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 (121) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +21 -8
  3. package/config/decisions/executor.yaml +53 -0
  4. package/config/decisions/task-pipeline.yaml +44 -28
  5. package/config/pipeline-budgets.json +0 -14
  6. package/config/workflow-candidates.json +18 -0
  7. package/config/workflows/feature-lifecycle.yaml +9 -16
  8. package/config/workflows/idea-pipeline.yaml +158 -740
  9. package/config/workflows/task-pipeline.yaml +66 -54
  10. package/config/workflows/wrapup-pipeline.yaml +91 -623
  11. package/package.json +11 -10
  12. package/plugins/sp/README.md +9 -5
  13. package/plugins/sp/agents/expert-spur.md +3 -0
  14. package/plugins/sp/agents/super-planner.md +5 -1
  15. package/plugins/sp/agents/super-reviewer.md +43 -12
  16. package/plugins/sp/commands/dev-fixall.md +7 -2
  17. package/plugins/sp/commands/dev-idea.md +15 -6
  18. package/plugins/sp/commands/dev-plan.md +1 -1
  19. package/plugins/sp/commands/dev-pr-review.md +6 -6
  20. package/plugins/sp/commands/dev-run.md +3 -0
  21. package/plugins/sp/commands/dev-runall.md +6 -3
  22. package/plugins/sp/commands/dev-verify.md +1 -1
  23. package/plugins/sp/commands/dev-wrap.md +1 -1
  24. package/plugins/sp/commands/dev-wrapall.md +4 -4
  25. package/plugins/sp/plugin.json +1 -1
  26. package/plugins/sp/skills/code-implementation/SKILL.md +8 -8
  27. package/plugins/sp/skills/code-improvement/SKILL.md +3 -3
  28. package/plugins/sp/skills/code-review/references/verification-before-completion.md +5 -4
  29. package/plugins/sp/skills/code-verification/SKILL.md +4 -4
  30. package/plugins/sp/skills/code-verification/references/verdict-schema.md +27 -11
  31. package/plugins/sp/skills/functional-review/SKILL.md +1 -1
  32. package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
  33. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  34. package/plugins/sp/skills/pr-reviewing/SKILL.md +36 -18
  35. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +2 -1
  36. package/plugins/sp/skills/spur-check/SKILL.md +1 -1
  37. package/plugins/sp/skills/spur-cli/SKILL.md +23 -2
  38. package/plugins/sp/skills/spur-cli/references/agent.md +12 -0
  39. package/plugins/sp/skills/spur-cli/references/builder.md +2 -2
  40. package/plugins/sp/skills/spur-cli/references/decision/lifecycle.md +166 -0
  41. package/plugins/sp/skills/spur-cli/references/decision.md +146 -16
  42. package/plugins/sp/skills/spur-cli/references/features.md +19 -0
  43. package/plugins/sp/skills/spur-cli/references/rules.md +2 -0
  44. package/plugins/sp/skills/spur-cli/references/tasks.md +14 -1
  45. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +1 -1
  46. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +3 -2
  47. package/plugins/sp/skills/spur-cli/references/workflows.md +9 -0
  48. package/plugins/sp/skills/spur-composer/SKILL.md +20 -1
  49. package/plugins/sp/skills/spur-dev/SKILL.md +16 -16
  50. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +2 -1
  51. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +2 -2
  52. package/plugins/sp/skills/spur-dev/references/decision-points.md +97 -0
  53. package/plugins/sp/skills/spur-dev/references/dev-operations.md +18 -18
  54. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +1 -1
  55. package/plugins/sp/skills/spur-dev/references/execution-batch.md +19 -10
  56. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +4 -4
  57. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +41 -29
  58. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +34 -2
  59. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +4 -4
  60. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +3 -3
  61. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +3 -2
  62. package/plugins/sp/skills/spur-dev/references/glossary.md +12 -3
  63. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +7 -6
  64. package/plugins/sp/skills/spur-doctor/SKILL.md +23 -0
  65. package/plugins/sp/skills/wayfinder/SKILL.md +11 -1
  66. package/spur.js +25617 -24290
  67. package/web/_astro/{BoardApp.Bjdp3XWV.js → BoardApp.CC7Mwswv.js} +65 -65
  68. package/web/_astro/{BoardApp.DCDZxLlQ.js → BoardApp.mWyrWCAR.js} +1 -1
  69. package/web/_astro/{TaskDetail.B3vBxynO.js → TaskDetail.CtAXuiv8.js} +1 -1
  70. package/web/_astro/{arc.DcuHTB_T.js → arc.Duf4u60i.js} +1 -1
  71. package/web/_astro/{architectureDiagram-3BPJPVTR.ClOFWBON.js → architectureDiagram-3BPJPVTR.JoymtqxN.js} +1 -1
  72. package/web/_astro/{blockDiagram-GPEHLZMM.otYyr7Zb.js → blockDiagram-GPEHLZMM.BvmhcioC.js} +1 -1
  73. package/web/_astro/{c4Diagram-AAUBKEIU.BCjpc9LP.js → c4Diagram-AAUBKEIU.CfaWkgoQ.js} +1 -1
  74. package/web/_astro/channel.DtEPXXcH.js +1 -0
  75. package/web/_astro/{chunk-2J33WTMH.D5Wpnpt_.js → chunk-2J33WTMH.B7uR02cD.js} +1 -1
  76. package/web/_astro/{chunk-4BX2VUAB.DkQGtWNz.js → chunk-4BX2VUAB.SwiEauYR.js} +1 -1
  77. package/web/_astro/{chunk-55IACEB6.B7dqjAip.js → chunk-55IACEB6.6ZRCK8PO.js} +1 -1
  78. package/web/_astro/{chunk-727SXJPM.BDQvnKE2.js → chunk-727SXJPM.Cxra_G3N.js} +1 -1
  79. package/web/_astro/{chunk-AQP2D5EJ.D92doBX5.js → chunk-AQP2D5EJ.Cxd7gZeX.js} +1 -1
  80. package/web/_astro/{chunk-FMBD7UC4.C968oI5G.js → chunk-FMBD7UC4.DCLhFkVc.js} +1 -1
  81. package/web/_astro/{chunk-ND2GUHAM.AkkYC1bY.js → chunk-ND2GUHAM.B9AmKcv6.js} +1 -1
  82. package/web/_astro/{chunk-QZHKN3VN.sCc-rKKo.js → chunk-QZHKN3VN.DOjCvl6S.js} +1 -1
  83. package/web/_astro/{classDiagram-4FO5ZUOK.pOQ9u0mY.js → classDiagram-4FO5ZUOK.BMiT_mNf.js} +1 -1
  84. package/web/_astro/{classDiagram-v2-Q7XG4LA2.pOQ9u0mY.js → classDiagram-v2-Q7XG4LA2.BMiT_mNf.js} +1 -1
  85. package/web/_astro/{cose-bilkent-S5V4N54A.CRaeD6FY.js → cose-bilkent-S5V4N54A.mLBCEGjI.js} +1 -1
  86. package/web/_astro/{cynefin-OW5HDTMX.BiCPBu54.js → cynefin-OW5HDTMX.CjDAY0qn.js} +1 -1
  87. package/web/_astro/{dagre-BM42HDAG.C1-q3vkT.js → dagre-BM42HDAG._G_sP8fP.js} +1 -1
  88. package/web/_astro/{diagram-2AECGRRQ.BSVOv30O.js → diagram-2AECGRRQ.39OcfSuz.js} +1 -1
  89. package/web/_astro/{diagram-5GNKFQAL.CHMkjJ-V.js → diagram-5GNKFQAL.D7v_eNC4.js} +1 -1
  90. package/web/_astro/{diagram-KO2AKTUF.CKY9M564.js → diagram-KO2AKTUF.Da1x-lhg.js} +1 -1
  91. package/web/_astro/{diagram-LMA3HP47.B9yNJiTP.js → diagram-LMA3HP47.C8EnzbEb.js} +1 -1
  92. package/web/_astro/{diagram-OG6HWLK6.Dk8AUGsk.js → diagram-OG6HWLK6.DPnBj69F.js} +1 -1
  93. package/web/_astro/{erDiagram-TEJ5UH35.BQHR89TZ.js → erDiagram-TEJ5UH35.CXMu2TIh.js} +1 -1
  94. package/web/_astro/{flowDiagram-I6XJVG4X.oNkWVgoK.js → flowDiagram-I6XJVG4X.ZklePVtQ.js} +1 -1
  95. package/web/_astro/{ganttDiagram-6RSMTGT7.Kz2U2nu9.js → ganttDiagram-6RSMTGT7.BhYOkdqk.js} +1 -1
  96. package/web/_astro/{gitGraphDiagram-PVQCEYII.DHIZ_wPq.js → gitGraphDiagram-PVQCEYII.CADyX8a1.js} +1 -1
  97. package/web/_astro/{infoDiagram-5YYISTIA.D0NJLcv9.js → infoDiagram-5YYISTIA.BsdmwjHt.js} +1 -1
  98. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CFEfTWRG.js → ishikawaDiagram-YF4QCWOH.CzjZ3Crd.js} +1 -1
  99. package/web/_astro/{journeyDiagram-JHISSGLW.Dc7IE7I4.js → journeyDiagram-JHISSGLW.Bxj-ZH_t.js} +1 -1
  100. package/web/_astro/{kanban-definition-UN3LZRKU.D5v0xO9k.js → kanban-definition-UN3LZRKU.Cbg3wVvX.js} +1 -1
  101. package/web/_astro/{linear.wR5KzXnE.js → linear.DJNUrBI9.js} +1 -1
  102. package/web/_astro/{mermaid.core.BDQ2FoPd.js → mermaid.core.DVHQV3SF.js} +4 -4
  103. package/web/_astro/{mindmap-definition-RKZ34NQL.W6_INsia.js → mindmap-definition-RKZ34NQL.DyomosRU.js} +1 -1
  104. package/web/_astro/{pieDiagram-4H26LBE5.61ihbgkK.js → pieDiagram-4H26LBE5.DajtFknN.js} +1 -1
  105. package/web/_astro/{quadrantDiagram-W4KKPZXB.B5lEpFLo.js → quadrantDiagram-W4KKPZXB.Bjq6SjGe.js} +1 -1
  106. package/web/_astro/{requirementDiagram-4Y6WPE33.CmhVBVX0.js → requirementDiagram-4Y6WPE33.ubBVqGLP.js} +1 -1
  107. package/web/_astro/{sankeyDiagram-5OEKKPKP.D7Vvj9Wf.js → sankeyDiagram-5OEKKPKP.I4q46xd-.js} +1 -1
  108. package/web/_astro/{sequenceDiagram-3UESZ5HK.B1vPLixM.js → sequenceDiagram-3UESZ5HK.DsCgZIa8.js} +1 -1
  109. package/web/_astro/{stateDiagram-AJRCARHV.BnACmrGi.js → stateDiagram-AJRCARHV.DcYjYSqT.js} +1 -1
  110. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CW4pnmc3.js → stateDiagram-v2-BHNVJYJU.Dt8yPqiS.js} +1 -1
  111. package/web/_astro/{timeline-definition-PNZ67QCA.CkzMCPnC.js → timeline-definition-PNZ67QCA.0YTkoa5F.js} +1 -1
  112. package/web/_astro/{vennDiagram-CIIHVFJN.CY4aNdAR.js → vennDiagram-CIIHVFJN.BNl0l1hs.js} +1 -1
  113. package/web/_astro/{wardleyDiagram-YWT4CUSO.BtoU9UFk.js → wardleyDiagram-YWT4CUSO.BYSX6cfn.js} +1 -1
  114. package/web/_astro/{xychartDiagram-2RQKCTM6._9ijUUAs.js → xychartDiagram-2RQKCTM6.DzxxpixK.js} +1 -1
  115. package/web/index.html +1 -1
  116. package/worker/laya_worker.py +219 -0
  117. package/config/workflows/feature-verification.yaml +0 -98
  118. package/config/workflows/pr-review.yaml +0 -209
  119. package/config/workflows/wayfinder-resolution.yaml +0 -302
  120. package/plugins/sp/skills/wayfinder/references/pipeline-resolution.md +0 -30
  121. package/web/_astro/channel.qgOLXjI_.js +0 -1
@@ -1,116 +1,41 @@
1
- # Wrap-up pipeline — post-execution wrap-up for one task or a batch (design
2
- # §wrapup-pipeline).
1
+ # Wrap-up pipeline — post-execution wrap-up for one task or a batch (ADR-142,
2
+ # docs/design/workflow-consolidation.md §3.3). YAML over the existing dual-workflow engine; zero new
3
+ # engine code. The pipeline NEVER mutates task or feature status: it consumes completed tasks and
4
+ # produces learning/metrics/doc artifacts only (feature state belongs to feature-lifecycle).
3
5
  #
4
- # Orchestration is configuration (ADR-022 / §3.2): this is YAML over the
5
- # existing
6
- # dual-workflow engine — zero new engine code. The pipeline NEVER mutates task
7
- # status —
8
- # wrap-up consumes completed tasks and produces learning/metrics/doc artifacts
9
- # only.
10
- # Feature transitions go through `spur feature update` so the feature-lifecycle
11
- # guards
12
- # apply identically. Branch cleanup is an irreversible HITL gate — it always
13
- # pauses,
14
- # even under --auto (Iron Law #6: irreversible action -> surface to human).
6
+ # Shape: start -> task-resolve -> doc-sync -> learnings-validate -> learnings-append ->
7
+ # doc-tripwire (1037: repo-wide tripwire over the still-uncommitted wrap diff; repair also
8
+ # enters it; the task-resolve fast path does not) -> metrics-record ->
9
+ # branch-cleanup (conditional: merge=true) -> done.
15
10
  #
16
- # Shape: start -> task-resolve -> doc-sync -> learnings-validate -> learnings-append
17
- # -> doc-tripwire (1037: repo-wide tripwire over the still-uncommitted wrap
18
- # diff; repair also enters it; the task-resolve fast path does not)
19
- # -> metrics-record
20
- # -> feature-transition (conditional: if vars.feature set)
21
- # -> feature-verify (post-transition feature check gate, 0915 F1)
22
- # -> branch-cleanup (conditional: if vars.merge=true)
23
- # -> done
24
- # doc-sync routes a contract violation to `repair` (cheap, no
25
- # re-dispatch — ADR-118 pilot) and an executor failure to `failed`;
26
- # 1147 adds two deterministic post-doc-sync checks that fail the step
27
- # BEFORE the tripwire backstop and name doc-sync. Those edges are
28
- # declared before the contract-violation edge, so a doc-sync that both
29
- # misses its learnings post-condition and trips a deterministic check
30
- # fails the step instead of taking repair. The checks are: the write-scope check
31
- # (the entry snapshot diffed against the working listing; allowed paths
32
- # are docs/** minus docs/tasks*/docs/features* plus the declared learnings
33
- # capture — a violation writes scope-violation: <paths> and is reported,
34
- # never auto-reverted) and the supersession-pin re-run
35
- # (repo-wide-tests/adr-supersession.test.ts), so a resurrected delinked
36
- # index row is attributable to doc-sync; doc-tripwire stays the full
37
- # backstop and is never weakened;
38
- # learnings-validate (0986) rejects a nonempty narration-only capture to
39
- # the same `repair` edge with a distinct invalid-learnings-shape status —
40
- # expectFile proves existence, never shape — while empty/missing captures
41
- # keep the soft skip by passing through to learnings-append;
42
- # learnings-append and repair converge on doc-tripwire (1037), which runs
43
- # docTripwireCmd — the repo-wide tripwires over the still-uncommitted wrap
44
- # diff — before metrics-record; the task-resolve fast path never enters it;
45
- # task-resolve with a VALIDATED empty list short-circuits to `skipped`;
46
- # invalid wrap input, an unresolved/non-completed task, a failed metrics
47
- # capture, a failed required sync, a failed repo-wide tripwire (1037), or
48
- # a failed feature gate routes to `failed` (0770) instead of being
49
- # absorbed by an always-defensive skip.
11
+ # task-resolve validates with ONE typed gate (`command.gate` on `spur task wrapup resolve`, 1249 R2):
12
+ # 0 = PASS, 1 = FAIL (malformed input, an unresolved or non-completed task, an empty __runId), anything
13
+ # else = BLOCKED (crash/timeout). FAIL/BLOCKED -> failed; a validated [] -> skipped; length>0 &&
14
+ # mode=fast -> metrics-record; else doc-sync. Only canonical four-digit WBS strings pass (whitespace
15
+ # rejected, not trimmed) and every member must resolve to done/cancelled; every consumer reads the
16
+ # run-scoped `.spur/run/<runId>-wrapup-tasks.json`, never raw vars.tasks (0770/0783), and a
17
+ # missing/corrupted capture or status refuses progression. The drift probe (0944) projects mode=fast
18
+ # only when no changed path is doc-owned and every Solution change map parsed.
50
19
  #
51
- # Vars (passed as a JSON object via `--vars`):
52
- # tasks — JSON array of WBS strings, passed as a JSON-encoded STRING value
53
- # (the CLI
54
- # rejects non-string --vars values), e.g. --vars '{"tasks":"[\"0167\"]"}'
55
- # feature — feature id to advance through legal lifecycle edges (optional)
56
- # profile — set --vars '{"profile":"auto"}' to skip objective confirmations
57
- # merge — set --vars '{"merge":"true"}' to run branch cleanup (irreversible
58
- # HITL)
59
- # spurBin — PATH-independent spur invocation (overridden by CLI at run start)
60
- # agent — agent for agent.run steps (default: auto → `agent.default` in config)
61
- #
62
- # Reliability (aligned with task-pipeline / ADR-043):
63
- # - Prefer pure slash commands when a command exists; free-form inputs remain
64
- # only
65
- # where capture artifacts (answerFile) have no dedicated slash surface yet.
66
- # - doc-sync / metrics-record use expectFile + soft append (empty capture
67
- # does not abort wrap-up after prior steps already landed). The doc-sync hop is
68
- # a
69
- # single model query that both repairs doc drift (sp:doc-evolve) and captures
70
- # working learnings (task 0607 R2 — one query answers both; count 2 -> 1).
71
- # - feature-transition is a soft shell (always exit 0); a failed required sync
72
- # or feature gate records FAIL and routes to `failed` instead of claiming
73
- # success (0770).
74
- # - 0770 truthful outcomes: vars.tasks is parsed and validated ONCE at
75
- # task-resolve (JSON array of non-empty WBS strings, every member resolved
76
- # via `task show` to a completed status); siblings consume the normalized
77
- # run-scoped artifact and never re-parse raw input (malformed input can no
78
- # longer be silently re-interpreted as an empty list). All captures are
79
- # run-scoped under .spur/run/<runId>-wrapup-*; a lookup failure is
80
- # recorded, never silently omitted as success.
81
- # - 0783 validated consumers (audit 0781 F-04): validation accepts only
82
- # canonical four-digit WBS strings (whitespace is rejected, not trimmed);
83
- # the route writer, every guard, model prompt and operator note after
84
- # resolution read the run-scoped capture, never raw vars.tasks, and a
85
- # missing/corrupted capture or status refuses progression. Metrics
86
- # revalidate the capture, require well-shaped task lookups, serialize rows
87
- # with jq (never interpolated printf), and write PASS only after every
88
- # required append succeeds. Required feature sync succeeds only for a
89
- # valid matching proposal with no gateBlocked/requiresConfirm condition
90
- # whose target status is freshly observed; applied:false is a successful
91
- # no-op only when from == to and that status is observed — nonzero,
92
- # malformed, partial, blocked or unreadable outcomes fail explicitly, and
93
- # an affected-feature check can never convert a failed sync into success.
94
- # - branch-cleanup HITL is exhaustive (yes/no/cancel → done; missing answer
95
- # defense) and consent-only: it records the decision, performs no git op.
96
- # - Wrap-up never mutates task status (consumes completed work only).
20
+ # doc-sync is one model query that repairs doc drift (sp:doc-evolve) and captures working learnings
21
+ # (0607 R2); expectFile + soft append means an empty capture does not abort wrap-up. A contract
22
+ # violation routes to the cheap `repair` edge, never a full re-dispatch (ADR-118 pilot). 1147's
23
+ # post-doc-sync checks (write scope: docs/** minus the corpus plus the declared learnings capture,
24
+ # reported never reverted; supersession pin) precede the contract-violation edge, so a compound miss
25
+ # fails the step; 0986 rejects a nonempty narration-only capture to `repair`. Vars: tasks (a
26
+ # JSON-encoded STRING array), profile, merge (branch-cleanup HITL, consent-only), spurBin, agent.
97
27
 
98
28
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
99
29
  kind: state-machine
100
30
  name: wrapup-pipeline
101
- version: "11"
102
- description: "Post-execution wrap-up: doc-sync (doc drift + learning capture), metrics, feature-transition, branch-cleanup"
31
+ version: "12"
32
+ description: "Post-execution wrap-up: doc-sync (doc drift + learning capture), metrics, branch-cleanup"
103
33
  iterationBound: 10
104
34
  initialState: start
105
- terminalStates:
106
- - done
107
- - skipped
108
- - failed
109
- failureStates:
110
- - failed
35
+ terminalStates: [done, skipped, failed]
36
+ failureStates: [failed]
111
37
  vars:
112
38
  tasks: "[]"
113
- feature: ""
114
39
  profile: "standard"
115
40
  merge: "false"
116
41
  spurBin: "spur"
@@ -118,177 +43,83 @@ vars:
118
43
  __runId: ""
119
44
  mode: ""
120
45
  stepTimeoutMs: "1800000"
121
- # Recheck the affected feature after sync (D61 / ADR-108), not the corpus
122
- # or full code suite. Explicit caller overrides remain supported.
123
- # TRUSTED CONFIG ONLY — this string is executed via `sh -c` (same surface as
124
- # task-pipeline's qualityGateCmd). Never interpolate untrusted input into it.
125
- featureGateCmd: '$spurBin feature check "$feature"'
126
- # Repo-wide tripwire over the wrap diff (1037): runs `$spurBin self exec test-repo-wide`
127
- # only when that action is bound (a project declares it as a custom action), so other
128
- # projects no-op — loudly: the skip prints one stderr note naming the missing binding, so a
129
- # silently-dead tripwire cannot masquerade as a green wrap (task 1229). Callers
130
- # can override it with --vars; an empty string disables the check (the hop
131
- # still writes PASS).
132
- # TRUSTED CONFIG ONLY — this string is executed via `sh -c` (same surface as
133
- # featureGateCmd). Never interpolate untrusted input into it.
46
+ # Runs `$spurBin self exec test-repo-wide` only when that action is bound, so other projects
47
+ # no-op loudly (1229). TRUSTED CONFIG ONLY — executed via `sh -c`; never interpolate untrusted
48
+ # input. An empty string disables the check (the hop still writes PASS).
134
49
  docTripwireCmd: 'if $spurBin self exec --list --json 2>/dev/null | jq -e ''.[] | select(.name == "test-repo-wide" and .source != "unbound")'' >/dev/null 2>&1; then $spurBin self exec test-repo-wide; else echo "wrapup: doc tripwire skipped — the test-repo-wide action is unbound; declare builder.actions.test-repo-wide to enable it" >&2; fi'
135
50
  __hitlAnswer: ""
136
- # Accepted/deferred status the branch-cleanup gate's decision binding writes (1236 R2).
137
51
  branchCleanupStatus: ""
138
52
 
139
53
  states:
140
54
  - id: start
141
- description: >
142
- Pipeline start. Task statuses are NOT mutated by wrap-up — wrap-up consumes
143
- completed tasks and produces learning/metrics/doc artifacts only.
55
+ description: "Pipeline start. Task and feature statuses are NOT mutated by wrap-up."
144
56
  onEnter:
145
57
  - kind: note
146
58
  options:
147
- message: "Wrap-up pipeline start for tasks: ${vars.tasks}. Task statuses are NOT mutated."
59
+ message: "Wrap-up pipeline start for tasks: ${vars.tasks}. Task and feature statuses are NOT mutated."
148
60
 
149
61
  - id: task-resolve
150
- description: >
151
- Validate the task list ONCE and evaluate closed proportional routing (0758
152
- R1-R4). When vars.feature is set, resolve also runs the feature pre-flight
153
- (1033 R2). Validation (0770 + 0783): vars.tasks must be a JSON array of
154
- canonical four-digit WBS strings — non-arrays, non-strings, whitespace or
155
- other shapes are rejected, not trimmed — deduplicated in first-seen
156
- order, with __runId present, and every member must resolve via `spur task
157
- show` to a completed status (done/cancelled); failed or malformed
158
- lookups fail. Invalid input or an unresolved/non-completed task records
159
- FAIL (reason into the run-scoped reason file) and routes to `failed`;
160
- the route writer and every guard, prompt and note after resolution read
161
- the normalized run-scoped artifact
162
- .spur/run/<runId>-wrapup-tasks.json and never re-parse raw input — a
163
- missing or corrupted capture refuses progression. A validated empty list
164
- -> skipped; length > 0 && mode == fast -> fast-path (metrics-record,
165
- bypassing doc-sync); missing/unknown/conflict -> doc-sync (safety-path).
166
- Writes bounded machine-readable reason to reasonFile. When the caller leaves
167
- mode empty, a deterministic drift probe (0944) projects mode=fast only when
168
- no changed path matches a doc-owned surface and every task's Solution
169
- change map parsed — any doubt stays on the safety path.
62
+ description: "Validate vars.tasks ONCE (canonical four-digit WBS, deduplicated, each resolved to done/cancelled) with one typed gate, then project the proportional route (0758 R1-R4, 0770, 1249 R2)."
170
63
  onEnter:
171
64
  - kind: note
172
65
  options:
173
66
  message: "Resolving task list: ${vars.tasks} (proportional mode: ${vars.mode})"
174
- # (d) `spur task wrapup resolve` owns task capture and status resolution (0824;
175
- # script-absorption PR-5 retired the wrapup-steps plugin script). A nonzero exit
176
- # fails closed, writing FAIL for the failed defense edge — never PASS.
67
+ # softFail keeps the run alive so the declared guards route the typed outcome.
68
+ - kind: command.gate
69
+ options:
70
+ id: task-resolve-gate
71
+ executable: "${vars.spurBin}"
72
+ args: ["task", "wrapup", "resolve", "--no-logo"]
73
+ resultFile: .spur/run/${vars.__runId}-wrapup-resolve.status
74
+ softFail: true
75
+ # Runs ONLY when the caller left mode empty (a caller-set mode is projected verbatim).
177
76
  - kind: shell
178
77
  options:
179
78
  command: >-
180
- mkdir -p .spur/run &&
181
- $spurBin task wrapup resolve --no-logo || { rc=$?; echo "task wrapup resolve failed closed (rc=$rc)" >&2; printf 'FAIL\n' > ".spur/run/$__runId-wrapup-resolve.status"; }
182
- # (0944 R1-R3) deterministic drift probe — runs ONLY when the caller left mode
183
- # empty (a caller-set mode is projected verbatim and the probe never runs). It
184
- # writes <runId>-drift-probe.json {clean,reasons,paths} and projects the wrapup
185
- # mode by writing <runId>-mode.txt: caller mode verbatim, `fast` when the probe
186
- # is clean, empty when dirty (`spur task wrapup drift-probe` fails safe on any lookup
187
- # or parse problem; a nonzero exit writes a dirty probe + empty mode here). The next
188
- # action reads the mode file into vars.mode via file.read.into-var.
189
- - kind: shell
190
- options:
191
- command: >-
192
- if [ -n "$mode" ]; then
193
- printf '%s\n' "$mode" > ".spur/run/$__runId-mode.txt";
194
- else
195
- $spurBin task wrapup drift-probe --no-logo || {
196
- rc=$?;
197
- printf '{"clean":false,"reasons":["drift probe failed (rc=%s)"],"paths":[]}' "$rc" > ".spur/run/$__runId-drift-probe.json";
198
- printf '\n' > ".spur/run/$__runId-mode.txt";
199
- };
200
- fi
79
+ if [ -n "$mode" ]; then printf '%s\n' "$mode" > ".spur/run/$__runId-mode.txt"; else $spurBin task wrapup drift-probe --no-logo || { rc=$?; printf '{"clean":false,"reasons":["drift probe failed (rc=%s)"],"paths":[]}' "$rc" > ".spur/run/$__runId-drift-probe.json"; printf '\n' > ".spur/run/$__runId-mode.txt"; }; fi
201
80
  - kind: file.read.into-var
202
81
  options:
203
82
  path: .spur/run/${vars.__runId}-mode.txt
204
83
  var: mode
205
- # (e) route-reason writer: one table lookup over the projected mode var over
206
- # the validated capture and the drift probe verdict; workflow-local routing
207
- # glue (0824, moved behind `spur task wrapup` in 0944/PR-5 to stay inside the
208
- # ADR-115 shell caps). Runs AFTER the probe/projection so the reason reflects
209
- # the projected mode. Reads ONLY .spur/run/<runId>-wrapup-tasks.json and the
210
- # probe verdict — never raw vars.tasks (0783 R2/R5). A FAIL resolve exits
211
- # without writing: the failed reason written by validation stands, and the
212
- # skip reason can never mask it. A clean probe yields `fast:drift-probe-clean`
213
- # (0944); mode `safety` claims `safety:operator-forced doc-sync`; a missing or
214
- # corrupted capture fails the lookup (no skipped or clean claim can be
215
- # invented); the failed defense edge owns the run.
216
84
  - kind: shell
217
85
  options:
218
86
  command: >-
219
87
  mkdir -p .spur/run .spur/memory &&
220
- $spurBin task wrapup route-reason --no-logo || { rc=$?; echo "task wrapup route-reason failed closed (rc=$rc)" >&2; printf 'FAIL\n' > ".spur/run/$__runId-wrapup-resolve.status"; }
88
+ $spurBin task wrapup route-reason --no-logo || { rc=$?; echo "task wrapup route-reason failed closed (rc=$rc)" >&2; if [ "$(cat .spur/run/$__runId-wrapup-resolve.status 2>/dev/null)" = PASS ]; then printf 'FAIL\n' > ".spur/run/$__runId-wrapup-resolve.status"; fi; }
221
89
 
222
90
  - id: doc-sync
223
- description: >
224
- ONE model query that both repairs project-level doc drift for the batch
225
- (04_DESIGN, 03_ARCHITECTURE, 00_ADR, docs/design/*) and captures working
226
- learnings (task 0607 R2: two adjacent agent.run hops merged, count 2 -> 1;
227
- no operator-decision pause or verdict boundary sits between them). No dedicated
228
- slash command yet — free-form skill pointer until a /sp:doc-evolve command
229
- exists (ADR-043 residual). This is the expensive stage; a contract violation
230
- (a clean exit that missed its declared answerFile/expectFile post-condition)
231
- routes to the cheap `repair` edge instead of a full re-dispatch (ADR-118
232
- pilot, 0871) — `onError: continue` lets the transition guards read the
233
- agent's result instead of halting the run. 1147 R1/R2 wrap it with a
234
- deterministic write-scope guard (entry snapshot before the query, diff after
235
- it; a violation writes scope-violation: <paths> and routes to `failed`, the
236
- edits are reported and never auto-reverted) and a supersession-pin re-run, so
237
- an out-of-scope or delinked-row write is attributed here instead of surfacing
238
- late at doc-tripwire.
91
+ description: "Repair doc drift and capture working learnings in one model query, then run the deterministic write-scope guard and the supersession-pin re-run."
239
92
  onEnter:
240
- # (1147 R1) entry snapshot: the working listing is recorded BEFORE the model
241
- # query so the post-doc-sync check can attribute only this step's writes. Never
242
- # aborts; a git failure or a missing script leaves the snapshot absent and the
243
- # check fails closed rather than guessing.
244
93
  - kind: shell
245
94
  options:
246
95
  command: >-
247
96
  mkdir -p .spur/run &&
248
97
  $spurBin task wrapup doc-sync-snapshot --no-logo || echo "task wrapup doc-sync-snapshot failed (rc=$?) — the scope check fails closed on the missing snapshot" >&2
249
- # (warn) free-form prompt: no /sp:doc-evolve command surface yet (ADR-043
250
- # residual, 0607 R2 one-query merge); capture is bounded by expectFile.
251
98
  - kind: agent.run
252
99
  onError: continue
253
100
  options:
254
101
  agent: ${vars.agent}
255
102
  input: >-
256
- First run Skill(skill="sp:doc-evolve", args="wrapup") to repair drift in
257
- docs/00_ADR.md, docs/03_ARCHITECTURE.md, docs/04_DESIGN.md, docs/design/* per constitution edit
258
- rules; do not write task/feature corpus. Write ONLY inside docs/ (never docs/tasks* or
259
- docs/features*) — any other changed path fails the wrap step with a scope violation. A row
260
- absent from docs/04_DESIGN.md for a superseded satellite is intentional: do NOT re-add it.
261
- The batch task list is the normalized, validated
262
- capture at .spur/run/${vars.__runId}-wrapup-tasks.json — read it for the WBS ids; never
263
- re-parse raw input. THEN extract working learnings from those tasks —
264
- conventions, errors fixed, patterns, gotchas, grouped by date and task WBS — as raw markdown
265
- (no fences) and END your final message with that markdown (it is captured to the
266
- run-scoped .spur/run/${vars.__runId}-wrapup-learnings.md).
267
- # Declared Layer-1 role (0538 R2): routing reason beside the agent:
268
- # pin.
103
+ First run Skill(skill="sp:doc-evolve", args="wrapup") to repair drift in docs/00_ADR.md, docs/03_ARCHITECTURE.md,
104
+ docs/04_DESIGN.md, docs/design/* per constitution edit rules; do not write task/feature corpus. Write ONLY inside
105
+ docs/ (never docs/tasks* or docs/features*) — any other changed path fails the wrap step with a scope violation.
106
+ A row absent from docs/04_DESIGN.md for a superseded satellite is intentional: do NOT re-add it. The batch task
107
+ list is the normalized, validated capture at .spur/run/${vars.__runId}-wrapup-tasks.json — read it for the WBS
108
+ ids; never re-parse raw input. THEN extract working learnings from those tasks — conventions, errors fixed,
109
+ patterns, gotchas, grouped by date and task WBS — as raw markdown (no fences) and END your final message with that
110
+ markdown (it is captured to the run-scoped .spur/run/${vars.__runId}-wrapup-learnings.md).
269
111
  role: coder
270
112
  answerFile: .spur/run/${vars.__runId}-wrapup-learnings.md
271
113
  expectFile: .spur/run/${vars.__runId}-wrapup-learnings.md
272
- # The task-resolve state writes this list through `spur task wrapup resolve`,
273
- # so the path never appears in a YAML shell and the static scan cannot see the
274
- # production — declare it as the stage's input instead (1233 R2).
275
114
  inputs:
276
115
  - path: .spur/run/${vars.__runId}-wrapup-tasks.json
277
116
  kind: artifact
278
117
  timeoutMs: ${vars.stepTimeoutMs}
279
- # (1147 R1) post-doc-sync write-scope guard: the script diffs the working listing
280
- # against the entry snapshot and writes PASS or `scope-violation: <paths>` — the
281
- # action always exits 0 so status truth lives in the file (0783 R4) and the
282
- # declared guard routes. Violating edits are reported, never reverted.
283
118
  - kind: shell
284
119
  options:
285
120
  command: >-
286
121
  mkdir -p .spur/run &&
287
122
  $spurBin task wrapup doc-sync-scope --no-logo || { rc=$?; echo "task wrapup doc-sync-scope failed closed (rc=$rc)" >&2; printf 'scope-violation: task wrapup doc-sync-scope failed\n' > ".spur/run/$__runId-wrapup-doc-sync-scope.status"; }
288
- # (1147 R2) supersession pins re-run: a doc-sync that resurrects a deliberately
289
- # delinked index row (`repo-wide-tests/adr-supersession.test.ts`, g2) fails HERE,
290
- # named with the pin, instead of being attributed to the later doc-tripwire
291
- # backstop. Skips in a project without the pin; always exits 0 (0783 R4).
292
123
  - kind: shell
293
124
  options:
294
125
  command: >-
@@ -296,20 +127,8 @@ states:
296
127
  $spurBin task wrapup doc-supersession --no-logo || { rc=$?; echo "task wrapup doc-supersession failed closed (rc=$rc)" >&2; printf 'supersession-pin-failed: task wrapup doc-supersession failed\n' > ".spur/run/$__runId-wrapup-doc-supersession.status"; }
297
128
 
298
129
  - id: learnings-validate
299
- description: >
300
- Structural shape gate between doc-sync and the append (0986): expectFile
301
- asserts the capture EXISTS and nothing about its shape, so a
302
- stream-of-thought capture could be appended verbatim to the tracked
303
- memory file. This state classifies a NONEMPTY capture — a date, a
304
- four-digit task WBS, and at least one markdown bullet — and writes PASS
305
- or invalid-learnings-shape to a run-scoped status. An empty or missing
306
- capture keeps the existing soft skip (PASS) so learnings-append still
307
- performs its no-op; an unclassifiable status fails loud instead of
308
- appending it. Structural check only — the doc-sync prompt owns prose
309
- quality; no new engine-level content contract.
130
+ description: "Classify a nonempty learnings capture's structural shape (date + task WBS + bullet) before appending it; empty/missing keeps the soft skip (0986)."
310
131
  onEnter:
311
- # (warn) structural check only; the doc-sync prompt owns prose quality (0986 Design).
312
- # The WBS predicate excludes digit/dash neighbours so a date's year cannot satisfy it.
313
132
  - kind: shell
314
133
  options:
315
134
  command: >-
@@ -324,13 +143,8 @@ states:
324
143
  exit 0
325
144
 
326
145
  - id: learnings-append
327
- description: >
328
- Best-effort soft append of the captured learnings to .spur/memory/learnings.md
329
- (0607 R2): an empty capture skips without aborting wrap-up. Runs only after
330
- doc-sync's contract held; a contract violation never reaches this state.
146
+ description: "Soft-append the captured learnings to .spur/memory/learnings.md; an empty capture skips without aborting wrap-up (0607 R2)."
331
147
  onEnter:
332
- # (warn) learnings append is intentionally soft (empty capture skips
333
- # without aborting wrap-up, 0607 R2).
334
148
  - kind: shell
335
149
  options:
336
150
  command: >-
@@ -345,15 +159,7 @@ states:
345
159
  exit 0
346
160
 
347
161
  - id: repair
348
- description: >
349
- Contract-violation repair (ADR-118 pilot, 0871): doc-sync's agent.run missed
350
- its declared post-condition after a clean exit, so wrap-up does NOT re-dispatch
351
- the expensive agent on this attempt. Also the destination for a nonempty
352
- capture that fails learnings-validate's shape gate (0986). The miss is
353
- recorded to a run-scoped repair status — text distinguishing a narration-only
354
- shape failure from the missing-file contract violation — and wrap-up proceeds
355
- through the doc-tripwire hop (1037) to metrics-record; the run log and
356
- action trace already name the violated contract and the observed value.
162
+ description: "Record the doc-sync contract violation (or narration-only learnings shape) and proceed through doc-tripwire without re-dispatching the agent (ADR-118 pilot, 0871; 0986)."
357
163
  onEnter:
358
164
  - kind: shell
359
165
  options:
@@ -367,18 +173,7 @@ states:
367
173
  exit 0
368
174
 
369
175
  - id: doc-tripwire
370
- description: >
371
- Repo-wide tripwire over the wrap diff (1037): the doc-sync edits are still
372
- uncommitted when wrap-up reaches a terminal success, and the repo-wide
373
- tripwires (repo-wide-tests/) execute only inside spur-check-feature — once
374
- a wrap edit is committed on a clean tree, the working-diff tests (e.g.
375
- adr-supersession (e)) can no longer see it. Entered from learnings-append
376
- and repair, before metrics or feature state mutate, it runs the trusted
377
- `docTripwireCmd` and records PASS or FAIL to a run-scoped status file; the
378
- action always exits 0 so status truth lives in the file (0783 R4). Only a
379
- verified run writes PASS; an empty docTripwireCmd disables the check and
380
- still writes PASS. The no-doc-sync fast path (task-resolve →
381
- metrics-record) never enters this state.
176
+ description: "Run the trusted repo-wide tripwire over the still-uncommitted wrap diff and record PASS or FAIL to a run-scoped status file (1037; always exits 0)."
382
177
  onEnter:
383
178
  - kind: shell
384
179
  options:
@@ -388,86 +183,16 @@ states:
388
183
  printf 'FAIL\n' > ".spur/run/$__runId-wrapup-doc-tripwire.status"
389
184
 
390
185
  - id: metrics-record
391
- description: >
392
- Append one JSONL row per task to .spur/memory/wrapup-metrics.jsonl deterministically.
393
- Revalidates the normalized run-scoped capture from task-resolve (0770 + 0783):
394
- a missing, corrupted or non-canonical capture records FAIL; every member must
395
- produce a successful, well-shaped `task show` lookup (a missing metrics row is
396
- never silently absorbed as success); rows are serialized with jq — never
397
- interpolated printf JSON — so escaped fields stay parseable; and PASS is
398
- written only after every required append succeeds. A missing per-task verdict
399
- is UNKNOWN telemetry, never proof of completion; previously written valid rows
400
- survive a failure.
186
+ description: "Append one deterministic JSONL metrics row per captured task; a missing row is recorded as failure, never silently omitted as success (0770 + 0783)."
401
187
  onEnter:
402
- # (d) `spur task wrapup metrics` owns the JSONL row append (0824); a nonzero
403
- # exit fails closed — FAIL, never a silent skip.
404
188
  - kind: shell
405
189
  options:
406
190
  command: >-
407
191
  mkdir -p .spur/run &&
408
192
  $spurBin task wrapup metrics --no-logo || { rc=$?; echo "task wrapup metrics failed closed (rc=$rc)" >&2; printf 'FAIL\n' > ".spur/run/$__runId-wrapup-metrics.status"; }
409
193
 
410
- - id: feature-transition
411
- description: >
412
- If vars.feature is set, sync feature status via `spur feature sync` (repeated-BLOCKED
413
- suppression is built into the sync service, so no wrapper script is needed)
414
- (0411). The shell terminates `exit 0` so artifacts already landed are never
415
- discarded, but the outcome is truthful (0770 + 0783 R4): sync succeeds only
416
- with a valid proposal matching vars.feature, no gateBlocked/requiresConfirm
417
- condition, and a freshly observed feature status equal to proposal.to;
418
- applied:false is a successful explicit no-change only when from == to and
419
- that status is observed. A nonzero, malformed, partial, blocked or
420
- unreadable outcome records FAIL in the run-scoped sync status and routes
421
- to `failed` — an affected-feature check cannot convert a failed sync into
422
- success. Empty vars.feature is NOT a blocked sync but a mis-invocation
423
- (the engine only enters this state when vars.feature is set) and fails
424
- loud (dogfood 2026-08-15, feature I3).
425
- onEnter:
426
- # (d) `spur task wrapup feature-transition` owns the bounded sync, observation
427
- # and the gate (0824); a nonzero exit fails closed. The
428
- # step terminates exit 0 on every path so artifacts that already landed
429
- # are never discarded; soft TERMINATION is not soft TRUTH — only a verified
430
- # sync writes PASS (0783 R4). A gate PASS can never convert a failed sync
431
- # into success, and a gate FAIL fails the step (0770 failure routing).
432
- - kind: shell
433
- options:
434
- command: >-
435
- mkdir -p .spur/run &&
436
- $spurBin task wrapup feature-transition --no-logo || { rc=$?; echo "task wrapup feature-transition failed closed (rc=$rc)" >&2; printf 'FAIL\n' > ".spur/run/$__runId-wrapup-sync.status"; }
437
-
438
- - id: feature-verify
439
- description: >
440
- Post-feature-transition verification hop (D63 task 0915, review finding
441
- F1): wrapup's doc-sync and the feature transition mutate the corpus AFTER
442
- the feature-scoped verification pass ran. The proof digest deliberately
443
- excludes docs/tasks*/docs/features* (shared evidence, concurrent writers),
444
- so the bound receipt stays valid — but the feature's completion boundary
445
- must still observe the post-wrapup state. This hop re-runs the feature
446
- check gate (`featureGateCmd`). When the configured command is the done-
447
- boundary variant (`feature check --as done`), it validates the bound
448
- evidence receipt (feature identity, verifier definition digest, terminal
449
- recording run, artifact registration, checked-input digest) against the
450
- current feature contract; a plain gate command observes the post-wrapup
451
- corpus state but does not enforce the receipt (0915 re-review P3). A FAIL
452
- fails the wrapup loudly instead of letting a stale PASS ride; only a
453
- verified gate writes PASS (0783 R4).
454
- onEnter:
455
- - kind: shell
456
- options:
457
- command: >-
458
- sh -c "$featureGateCmd" &&
459
- printf 'PASS\n' > ".spur/run/$__runId-wrapup-feature-verify.status" ||
460
- printf 'FAIL\n' > ".spur/run/$__runId-wrapup-feature-verify.status"
461
-
462
194
  - id: branch-cleanup
463
- description: >
464
- Irreversible-operation HITL gate, consent-only (0770). If vars.merge=true,
465
- branch cleanup (merge or delete) is a candidate — but this gate records
466
- the operator decision and performs NO git operation itself; an operator
467
- or a separate approved surface executes any actual merge/delete. Interactive
468
- runs always ask the operator; profile=auto answers from the doc-sync stage's
469
- recorded evidence and still records the decision (1236 R1/R2). Nothing is
470
- auto-executed: both answers route to `done` (Auto-Decision Principle #6).
195
+ description: "Irreversible-operation HITL gate, consent-only: it records the operator decision and performs NO git operation itself (0770, 1236 R1/R2)."
471
196
  onEnter:
472
197
  - kind: hitl.confirm
473
198
  options:
@@ -479,302 +204,45 @@ states:
479
204
  - doc-sync
480
205
 
481
206
  - id: done
482
- description: >
483
- Terminal — wrap-up complete. Summary: tasks wrapped, learnings captured,
484
- metrics recorded, feature advanced or explicitly no-change (if applicable),
485
- branch-cleanup decision recorded (consent-only; no git operation performed).
207
+ description: "Terminal — wrap-up complete: tasks wrapped, learnings captured, metrics recorded, branch cleanup consent recorded only (no git operation performed by wrap-up)."
486
208
  onEnter:
487
209
  - kind: note
488
210
  options:
489
211
  message: "Wrap-up pipeline complete for the validated task list at .spur/run/${vars.__runId}-wrapup-tasks.json. Learnings at .spur/memory/learnings.md, metrics at .spur/memory/wrapup-metrics.jsonl. Branch cleanup: consent recorded only (merge=${vars.merge}); no git operation performed by wrap-up."
490
212
 
491
213
  - id: skipped
492
- description: Terminal — wrap-up skipped (empty task list or operator abort).
214
+ description: "Terminal — wrap-up skipped (empty task list or operator abort)."
493
215
 
494
216
  - id: failed
495
- description: >
496
- Terminal — wrap-up failed (0770): invalid wrap input, an unresolved or
497
- non-completed task, a failed metrics capture, a failed required sync, a
498
- failed repo-wide tripwire (1037), a failed feature gate, or one of the
499
- deterministic post-doc-sync checks (1147: write scope, supersession pins).
500
- Already-written artifacts (learnings, metrics rows, route reason) are
501
- preserved; nothing is rolled back.
217
+ description: "Terminal — wrap-up failed: invalid wrap input, an unresolved or non-completed task, a crash or timeout at the resolve gate (BLOCKED), a failed metrics capture, a failed repo-wide tripwire (1037), or a post-doc-sync check (1147). Already-written artifacts are preserved; nothing is rolled back."
502
218
 
219
+ # ── Routing table ──
220
+ # Declaration order is load-bearing: the engine takes the first passing guard. task-resolve's
221
+ # gate-outcome edges precede its route edges; doc-sync's two deterministic checks precede
222
+ # contract-violation, which precedes action-ok and the executor-failure defense; each later state's
223
+ # discriminating failure edge precedes its success edge.
503
224
  transitions:
504
- # ── start -> task-resolve ──
505
- - from: start
506
- to: task-resolve
507
- description: Begin wrap-up.
508
- guard:
509
- kind: always
510
-
511
- # ── task-resolve: validation fail + proportional route table (0758 R1-R4, 0770) ──
512
- - from: task-resolve
513
- to: failed
514
- terminalReason: failed-check
515
- description: >
516
- Validation FAIL (0770): malformed wrap input, a non-string/empty entry, an
517
- unresolved or non-completed task, or an empty __runId. Declared before the
518
- route edges so invalid input can never be absorbed by the empty-list skip.
519
- guard:
520
- kind: shell
521
- options:
522
- command: 'test "$(cat .spur/run/$__runId-wrapup-resolve.status 2>/dev/null)" = FAIL'
523
- # Route edges key on the validated run-scoped capture, never raw vars.tasks
524
- # (0783 R2). A missing or corrupted capture yields -1, which satisfies none
525
- # of the numeric edges, so the run falls through to the always-defense
526
- # `failed` edge instead of claiming a skip or a route.
527
- - from: task-resolve
528
- to: skipped
529
- description: Validated task list is empty — skip wrap-up (only a validated [] may skip).
530
- guard:
531
- kind: shell
532
- options:
533
- command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -eq 0'
534
- - from: task-resolve
535
- to: metrics-record
536
- description: Proportional fast path — complete and consistent evidence bypasses doc-sync.
537
- guard:
538
- kind: shell
539
- options:
540
- command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -gt 0 && test "$mode" = fast'
541
- - from: task-resolve
542
- to: doc-sync
543
- description: Proportional safety path — missing/unknown/conflicting evidence routes to full doc-sync.
544
- guard:
545
- kind: shell
546
- options:
547
- command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -gt 0 && test "$mode" != fast'
548
- - from: task-resolve
549
- to: failed
550
- terminalReason: failed-check
551
- description: >
552
- Defense (0770) — no PASS/FAIL resolve status (shell crashed or never ran):
553
- route to `failed` rather than claiming a skip.
554
- guard:
555
- kind: always
556
-
557
- # ── doc-sync: deterministic checks, then contract repair (ADR-118; 1147 R1/R2) ──
558
- # Declaration order matters. The engine takes the first passing guard. The two
559
- # deterministic post-doc-sync checks (write scope, supersession pins) are tried
560
- # first, so a doc-sync that both misses its learnings post-condition and writes
561
- # out of scope (or resurrects a delinked row) fails the step. A violation they
562
- # recorded can never be absorbed by repair or by the success edge. Contract
563
- # violation is next (repair only when neither deterministic check fired), then
564
- # success (action-ok), then the executor-failure defense (always). A failed
565
- # agent.run reaches these guards because doc-sync's agent.run declares onError: continue.
566
- - from: doc-sync
567
- to: failed
568
- terminalReason: failed-check
569
- description: >
570
- Write-scope violation (1147 R1): the post-doc-sync check found a changed path
571
- outside docs/** minus the task/feature corpus (and the declared learnings
572
- capture) — the status file names the paths and the failing step is doc-sync
573
- itself. The edits are reported, never auto-reverted; already-written artifacts
574
- stay on disk. Declared before contract-violation so a compound miss fails here.
575
- guard:
576
- kind: shell
577
- options:
578
- command: 'grep -q "^scope-violation:" .spur/run/$__runId-wrapup-doc-sync-scope.status 2>/dev/null'
579
- - from: doc-sync
580
- to: failed
581
- terminalReason: failed-check
582
- description: >
583
- Supersession drift (1147 R2): the post-doc-sync re-run of
584
- repo-wide-tests/adr-supersession.test.ts failed — doc-sync re-added a
585
- deliberately delinked index row. Attributed here (named with the pin) rather
586
- than at the later doc-tripwire backstop, which stays unchanged. Declared
587
- before contract-violation so a compound miss fails here.
588
- guard:
589
- kind: shell
590
- options:
591
- command: 'grep -q "^supersession-pin-failed:" .spur/run/$__runId-wrapup-doc-supersession.status 2>/dev/null'
592
- - from: doc-sync
593
- to: repair
594
- description: >
595
- Contract violation (ADR-118): the agent exited 0 but missed its declared
596
- post-condition, and neither deterministic check fired. Route to the cheap
597
- repair edge — never re-dispatch the full agent.run on this attempt.
598
- trigger: contract-violation
599
- guard:
600
- kind: contract-violation
601
- - from: doc-sync
602
- to: learnings-validate
603
- description: Doc drift repair + learning capture succeeded — validate the capture's shape before appending.
604
- guard:
605
- kind: action-ok
606
- - from: doc-sync
607
- to: failed
608
- terminalReason: failed-check
609
- description: >
610
- Executor failure (non-zero exit, signal, or dispatch error) — keep the
611
- existing fail semantics. A scope or supersession violation is declared
612
- earlier and fails the step; a contract violation never falls through here
613
- because its edge is declared before this defense.
614
- trigger: executor-failure
615
- guard:
616
- kind: always
617
-
618
- # ── learnings-validate: shape gate (0986) — invalid-shape to repair, PASS to append ──
619
- # Declaration order matters: the discriminating invalid-shape edge is tried
620
- # first, then PASS; an unreadable status fails loud rather than appending an
621
- # unclassified capture.
622
- - from: learnings-validate
623
- to: repair
624
- description: >
625
- Nonempty capture failed the structural shape gate (no date/WBS/bullet) —
626
- route to the cheap repair edge and never append narration (0986 R2).
627
- guard:
628
- kind: shell
629
- options:
630
- command: 'test "$(cat .spur/run/$__runId-wrapup-learnings.status 2>/dev/null)" = invalid-learnings-shape'
631
- - from: learnings-validate
632
- to: learnings-append
633
- description: Capture is shape-valid (or empty/missing, keeping the soft skip) — append.
634
- guard:
635
- kind: shell
636
- options:
637
- command: 'test "$(cat .spur/run/$__runId-wrapup-learnings.status 2>/dev/null)" = PASS'
638
- - from: learnings-validate
639
- to: failed
640
- terminalReason: failed-check
641
- description: >
642
- Validation status missing or unreadable — fail loud instead of appending
643
- a capture whose shape was never classified.
644
- guard:
645
- kind: always
646
-
647
- # ── doc-sync exits converge on the doc-tripwire hop (1037) before metrics ──
648
- - from: learnings-append
649
- to: doc-tripwire
650
- description: Learning capture appended (or soft-skipped) — run the repo-wide tripwire over the wrap diff.
651
- guard:
652
- kind: always
653
-
654
- - from: repair
655
- to: doc-tripwire
656
- description: Contract-violation repair recorded — run the tripwire before metrics without re-dispatch.
657
- guard:
658
- kind: always
659
-
660
- # ── doc-tripwire: repo-wide tripwire gate (1037) ──
661
- # Declaration order matters: the FAIL edge is tried first so a failed tripwire
662
- # can never be absorbed by the PASS edge; a missing status matches neither
663
- # edge and follows the engine's no-match behavior for status-file states.
664
- - from: doc-tripwire
665
- to: failed
666
- terminalReason: failed-check
667
- description: >
668
- Repo-wide tripwire FAILED on the still-uncommitted wrap diff (1037): an
669
- edit the doc-sync stage made (e.g. an in-place historical ADR change)
670
- violates a working-diff test. Learnings and docs already on disk stay
671
- put; nothing is rolled back.
672
- guard:
673
- kind: shell
674
- options:
675
- command: 'test "$(cat .spur/run/$__runId-wrapup-doc-tripwire.status 2>/dev/null)" = FAIL'
676
- - from: doc-tripwire
677
- to: metrics-record
678
- description: Repo-wide tripwire PASS (or the check disabled by an empty docTripwireCmd) — record metrics.
679
- guard:
680
- kind: shell
681
- options:
682
- command: 'test "$(cat .spur/run/$__runId-wrapup-doc-tripwire.status 2>/dev/null)" = PASS'
683
-
684
- # ── metrics-record: fail edge, then conditional routing (feature → merge → done) ──
685
- # Declaration order matters: the FAIL edge is tried first (0770), then
686
- # feature-transition (if feature is set), then branch-cleanup (if merge=true
687
- # but no feature), then done (neither). Routing edges additionally require
688
- # the metrics status PASS so a missing status can never claim success.
689
- - from: metrics-record
690
- to: failed
691
- terminalReason: failed-check
692
- description: >
693
- Metrics capture failed for at least one task — a missing metrics row is
694
- recorded as failure, never silently omitted as success (0770).
695
- guard:
696
- kind: shell
697
- options:
698
- command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = FAIL'
699
- - from: metrics-record
700
- to: feature-transition
701
- description: Metrics PASS and feature id is set — advance feature through legal lifecycle edges.
702
- guard:
703
- kind: shell
704
- options:
705
- command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = PASS && test -n "$feature"'
706
- - from: metrics-record
707
- to: branch-cleanup
708
- description: Metrics PASS, no feature id, but merge is requested — go to branch cleanup.
709
- guard:
710
- kind: shell
711
- options:
712
- command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = PASS && test -z "$feature" && test "$merge" = true'
713
- - from: metrics-record
714
- to: done
715
- description: Metrics PASS, no feature id and no merge — wrap-up is done.
716
- guard:
717
- kind: shell
718
- options:
719
- command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = PASS && test -z "$feature" && test "$merge" != true'
720
-
721
- # ── feature-transition: fail edge, then branch-cleanup (if merge) or done ──
722
- - from: feature-transition
723
- to: failed
724
- terminalReason: failed-check
725
- description: >
726
- Required synchronization failed (0770): non-zero sync exit, an invalid
727
- sync result, or a failed feature gate. The failed status carries the
728
- actual reason; already-written learnings/metrics stay on disk.
729
- guard:
730
- kind: shell
731
- options:
732
- command: 'test "$(cat .spur/run/$__runId-wrapup-sync.status 2>/dev/null)" = FAIL'
733
- - from: feature-transition
734
- to: feature-verify
735
- description: Sync PASS — re-run the feature check gate against the post-wrapup state.
736
- guard:
737
- kind: shell
738
- options:
739
- command: 'test "$(cat .spur/run/$__runId-wrapup-sync.status 2>/dev/null)" = PASS'
740
- - from: feature-verify
741
- to: failed
742
- terminalReason: failed-check
743
- description: >
744
- Post-wrapup feature check failed (0915 F1): the bound receipt no longer
745
- validates against the current feature contract. Already-written
746
- learnings/metrics stay on disk.
747
- guard:
748
- kind: shell
749
- options:
750
- command: 'test "$(cat .spur/run/$__runId-wrapup-feature-verify.status 2>/dev/null)" = FAIL'
751
- - from: feature-verify
752
- to: branch-cleanup
753
- description: Post-wrapup feature check PASS — merge is requested.
754
- guard:
755
- kind: shell
756
- options:
757
- command: 'test "$(cat .spur/run/$__runId-wrapup-feature-verify.status 2>/dev/null)" = PASS && test "$merge" = true'
758
- - from: feature-verify
759
- to: done
760
- description: Post-wrapup feature check PASS — no merge requested.
761
- guard:
762
- kind: shell
763
- options:
764
- command: 'test "$(cat .spur/run/$__runId-wrapup-feature-verify.status 2>/dev/null)" = PASS && test "$merge" != true'
765
-
766
- # ── branch-cleanup: HITL exhaustive routing (yes / no / cancel → done) ──
767
- # No irreversible git op is wired yet; confirmation is recorded, then wrap
768
- # completes.
769
- - from: branch-cleanup
770
- to: done
771
- description: Branch cleanup HITL answered — wrap-up complete.
772
- guard:
773
- kind: shell
774
- options:
775
- command: 'test "$__hitlAnswer" = yes || test "$__hitlAnswer" = no || test "$__hitlAnswer" = cancel'
776
- - from: branch-cleanup
777
- to: done
778
- description: Defense — missing HITL answer still completes wrap (cleanup is optional).
779
- guard:
780
- kind: always
225
+ - {from: start, to: task-resolve, guard: {kind: always}}
226
+ - {from: task-resolve, to: failed, terminalReason: failed-check, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-wrapup-resolve.status 2>/dev/null)" = FAIL'}}}
227
+ - {from: task-resolve, to: failed, terminalReason: failed-check, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-wrapup-resolve.status 2>/dev/null)" = BLOCKED'}}}
228
+ - {from: task-resolve, to: skipped, guard: {kind: shell, options: {command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -eq 0'}}}
229
+ - {from: task-resolve, to: metrics-record, guard: {kind: shell, options: {command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -gt 0 && test "$mode" = fast'}}}
230
+ - {from: task-resolve, to: doc-sync, guard: {kind: shell, options: {command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -gt 0 && test "$mode" != fast'}}}
231
+ - {from: task-resolve, to: failed, terminalReason: failed-check, guard: {kind: always}}
232
+ - {from: doc-sync, to: failed, terminalReason: failed-check, guard: {kind: shell, options: {command: 'grep -q "^scope-violation:" .spur/run/$__runId-wrapup-doc-sync-scope.status 2>/dev/null'}}}
233
+ - {from: doc-sync, to: failed, terminalReason: failed-check, guard: {kind: shell, options: {command: 'grep -q "^supersession-pin-failed:" .spur/run/$__runId-wrapup-doc-supersession.status 2>/dev/null'}}}
234
+ - {from: doc-sync, to: repair, trigger: contract-violation, guard: {kind: contract-violation}}
235
+ - {from: doc-sync, to: learnings-validate, guard: {kind: action-ok}}
236
+ - {from: doc-sync, to: failed, terminalReason: failed-check, trigger: executor-failure, guard: {kind: always}}
237
+ - {from: learnings-validate, to: repair, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-wrapup-learnings.status 2>/dev/null)" = invalid-learnings-shape'}}}
238
+ - {from: learnings-validate, to: learnings-append, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-wrapup-learnings.status 2>/dev/null)" = PASS'}}}
239
+ - {from: learnings-validate, to: failed, terminalReason: failed-check, guard: {kind: always}}
240
+ - {from: learnings-append, to: doc-tripwire, guard: {kind: always}}
241
+ - {from: repair, to: doc-tripwire, guard: {kind: always}}
242
+ - {from: doc-tripwire, to: failed, terminalReason: failed-check, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-wrapup-doc-tripwire.status 2>/dev/null)" = FAIL'}}}
243
+ - {from: doc-tripwire, to: metrics-record, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-wrapup-doc-tripwire.status 2>/dev/null)" = PASS'}}}
244
+ - {from: metrics-record, to: failed, terminalReason: failed-check, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = FAIL'}}}
245
+ - {from: metrics-record, to: branch-cleanup, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = PASS && test "$merge" = true'}}}
246
+ - {from: metrics-record, to: done, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = PASS && test "$merge" != true'}}}
247
+ - {from: branch-cleanup, to: done, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = yes || test "$__hitlAnswer" = no || test "$__hitlAnswer" = cancel'}}}
248
+ - {from: branch-cleanup, to: done, guard: {kind: always}}