@gobing-ai/spur 0.4.14 → 0.4.15
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/pipeline-budgets.json +0 -14
- package/config/workflow-candidates.json +18 -0
- package/config/workflows/feature-lifecycle.yaml +9 -16
- package/config/workflows/idea-pipeline.yaml +158 -740
- package/config/workflows/wrapup-pipeline.yaml +91 -623
- package/package.json +9 -9
- package/plugins/sp/README.md +8 -4
- package/plugins/sp/commands/dev-idea.md +9 -5
- package/plugins/sp/commands/dev-plan.md +1 -1
- package/plugins/sp/commands/dev-pr-review.md +6 -6
- package/plugins/sp/commands/dev-wrap.md +1 -1
- package/plugins/sp/commands/dev-wrapall.md +4 -4
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/pr-reviewing/SKILL.md +36 -18
- package/plugins/sp/skills/spec-decomposition/references/decomposition.md +2 -1
- package/plugins/sp/skills/spur-cli/references/builder.md +2 -2
- package/plugins/sp/skills/spur-cli/references/features.md +7 -0
- package/plugins/sp/skills/spur-cli/references/tasks.md +1 -1
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +2 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +10 -10
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +9 -10
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +1 -2
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +4 -4
- package/plugins/sp/skills/spur-dev/references/planning-workflow.md +7 -6
- package/plugins/sp/skills/wayfinder/SKILL.md +11 -1
- package/spur.js +800 -799
- package/config/workflows/feature-verification.yaml +0 -98
- package/config/workflows/pr-review.yaml +0 -209
- package/config/workflows/wayfinder-resolution.yaml +0 -302
- package/plugins/sp/skills/wayfinder/references/pipeline-resolution.md +0 -30
|
@@ -1,116 +1,41 @@
|
|
|
1
|
-
# Wrap-up pipeline — post-execution wrap-up for one task or a batch (
|
|
2
|
-
# §
|
|
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
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
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
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
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: "
|
|
102
|
-
description: "Post-execution wrap-up: doc-sync (doc drift + learning capture), metrics,
|
|
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
|
-
|
|
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
|
-
#
|
|
122
|
-
#
|
|
123
|
-
#
|
|
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
|
-
#
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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/
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
505
|
-
- from:
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
- from:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
- from:
|
|
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}}
|