@gobing-ai/spur 0.3.73 → 0.3.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/config.example.yaml +15 -4
- package/config/pipeline-budgets.json +4 -4
- package/config/rules/strict/runtime-boundaries.yaml +1 -1
- package/config/templates/AGENTS.md +5 -0
- package/config/templates/docs/99_PROJECT_CONSTITUTION.md +2 -0
- package/config/workflows/basic.yaml +5 -2
- package/config/workflows/docs-pipeline.yaml +117 -55
- package/config/workflows/feature-dev.yaml +185 -121
- package/config/workflows/feature-lifecycle.yaml +1 -0
- package/config/workflows/history-anatomy.yaml +8 -4
- package/config/workflows/idea-pipeline.yaml +123 -43
- package/config/workflows/pr-review.yaml +31 -8
- package/config/workflows/task-lifecycle.yaml +1 -0
- package/config/workflows/task-pipeline.yaml +965 -905
- package/config/workflows/wayfinder-resolution.yaml +124 -90
- package/config/workflows/wrapup-pipeline.yaml +304 -107
- package/package.json +1 -1
- package/plugins/sp/agents/expert-spur.md +7 -4
- package/plugins/sp/commands/dev-idea.md +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/history-anatomy-cache.mjs +7 -1
- package/plugins/sp/scripts/history-anatomy-cache.ts +11 -2
- package/plugins/sp/scripts/pr-reviewing.mjs +5 -1
- package/plugins/sp/scripts/pr-reviewing.ts +7 -1
- package/plugins/sp/scripts/transition-shim-check.ts +6 -6
- package/plugins/sp/skills/pr-reviewing/SKILL.md +4 -1
- package/plugins/sp/skills/spur-cli/references/self.md +17 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +4 -2
- package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +5 -5
- package/plugins/sp/skills/spur-cli/references/workflows.md +4 -4
- package/plugins/sp/skills/spur-dev/SKILL.md +17 -0
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +23 -8
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +1 -1
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +5 -3
- package/plugins/sp/skills/wayfinder/SKILL.md +1 -1
- package/spur.js +2999 -1010
- package/web/_astro/{BoardApp.BYCNkMOn.js → BoardApp.BOW1815F.js} +101 -100
- package/web/_astro/BoardApp.C4zimv1U.js +1 -0
- package/web/_astro/{TaskDetail.CgUreSP2.js → TaskDetail.CX6C7dSJ.js} +1 -1
- package/web/_astro/{arc.BySSh34M.js → arc.CwSvH1ji.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.DM46TS_h.js → architectureDiagram-3BPJPVTR.Dxne1RvP.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.tZhvNUHA.js → blockDiagram-GPEHLZMM.D9Fb14rC.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.PT4Or4Nf.js → c4Diagram-AAUBKEIU.DmvWSFvv.js} +1 -1
- package/web/_astro/channel.CQHdDVp9.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.J9r0_Bbe.js → chunk-2J33WTMH.BCCktIrE.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.hzyeIvhR.js → chunk-4BX2VUAB.BzCk9q27.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.B0rO7qVh.js → chunk-55IACEB6.CZXVgTk4.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.wE_Uk5D4.js → chunk-727SXJPM.CJL8UIpX.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.DqEEjQw7.js → chunk-AQP2D5EJ.Dz8MDEdw.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.CDoD9sBX.js → chunk-FMBD7UC4.CkQeYYUw.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.CtX5nF9P.js → chunk-ND2GUHAM.KiC1QgzH.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.CK_EwfaT.js → chunk-QZHKN3VN.AJ08mw2e.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.DLt5a8Lh.js → classDiagram-4FO5ZUOK.DRWRDKzK.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.DLt5a8Lh.js → classDiagram-v2-Q7XG4LA2.DRWRDKzK.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.CMCWP49h.js → cose-bilkent-S5V4N54A.CavydfLP.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.HyXw_vdS.js → cynefin-OW5HDTMX.D_7o_a0B.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.BTuAzh01.js → dagre-BM42HDAG.LWk2dKgg.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.D9dr9wfT.js → diagram-2AECGRRQ.82lbq6aB.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.C4Rot0hj.js → diagram-5GNKFQAL.hyNL1pwY.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.B_TK5uWC.js → diagram-KO2AKTUF.BaLqVf-b.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.JkXKK7CO.js → diagram-LMA3HP47.DQJxj0oz.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.BzMN8Bd6.js → diagram-OG6HWLK6.DhlL8cG2.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.DVZaWGUd.js → erDiagram-TEJ5UH35.HoTnXwkF.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.rjEiWUfR.js → flowDiagram-I6XJVG4X.DkFKlZIZ.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.C_EgAarK.js → ganttDiagram-6RSMTGT7.9qPfOTDb.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.B-QQSDsK.js → gitGraphDiagram-PVQCEYII.B4G18Dwc.js} +1 -1
- package/web/_astro/index.Dcr_8fiK.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.DlWesz7T.js → infoDiagram-5YYISTIA.e3KJkXAM.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BUMZOawi.js → ishikawaDiagram-YF4QCWOH.CY3yddhD.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.CWfkxfjY.js → journeyDiagram-JHISSGLW.DaAKIO1t.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.B-YpMwXf.js → kanban-definition-UN3LZRKU.DGkDddtc.js} +1 -1
- package/web/_astro/{linear.D7uqzENp.js → linear.DnDPcpd1.js} +1 -1
- package/web/_astro/{mermaid.core.CxrNppBD.js → mermaid.core.1uBmxa9t.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.B4Qe7cM2.js → mindmap-definition-RKZ34NQL.XFJVayxw.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.Ds-5j2ro.js → pieDiagram-4H26LBE5.BXm2OgHh.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.tBd38uNC.js → quadrantDiagram-W4KKPZXB.DgB2p5fc.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.sFENkWl3.js → requirementDiagram-4Y6WPE33.jBR39bnS.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.BeB-Hk7C.js → sankeyDiagram-5OEKKPKP.N3u9OhE3.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DnTeaSpx.js → sequenceDiagram-3UESZ5HK.CZhqZXNL.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.B-8Jt5EJ.js → stateDiagram-AJRCARHV.DlNu1VEa.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.Br7xoqMW.js → stateDiagram-v2-BHNVJYJU.BEqi8NQV.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.C-3WdOyi.js → timeline-definition-PNZ67QCA.BPcexclc.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DCIs7Lc6.js → vennDiagram-CIIHVFJN.BZalxKGQ.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.rGAL-bbz.js → wardleyDiagram-YWT4CUSO.BqOljod9.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.hkfQKiRl.js → xychartDiagram-2RQKCTM6.tkO4ppN5.js} +1 -1
- package/web/index.html +2 -2
- package/config/corpus-baseline.json +0 -1805
- package/config/workflow-composition-baseline.json +0 -615
- package/web/_astro/BoardApp.E12MFjOS.js +0 -1
- package/web/_astro/channel.5cYKr5cs.js +0 -1
- package/web/_astro/index.B5MTfe7k.css +0 -1
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
# -> feature-transition (conditional: if vars.feature set)
|
|
18
18
|
# -> branch-cleanup (conditional: if vars.merge=true)
|
|
19
19
|
# -> done
|
|
20
|
-
#
|
|
20
|
+
# task-resolve with a VALIDATED empty list short-circuits to `skipped`;
|
|
21
|
+
# invalid wrap input, an unresolved/non-completed task, a failed metrics
|
|
22
|
+
# capture, a failed required sync, or a failed feature gate routes to
|
|
23
|
+
# `failed` (0770) instead of being absorbed by an always-defensive skip.
|
|
21
24
|
#
|
|
22
25
|
# Vars (passed as a JSON object via `--vars`):
|
|
23
26
|
# tasks — JSON array of WBS strings, passed as a JSON-encoded STRING value
|
|
@@ -39,21 +42,46 @@
|
|
|
39
42
|
# a
|
|
40
43
|
# single model query that both repairs doc drift (sp:doc-evolve) and captures
|
|
41
44
|
# working learnings (task 0607 R2 — one query answers both; count 2 -> 1).
|
|
42
|
-
# - feature-transition is a soft shell (always exit 0)
|
|
43
|
-
#
|
|
45
|
+
# - feature-transition is a soft shell (always exit 0); a failed required sync
|
|
46
|
+
# or feature gate records FAIL and routes to `failed` instead of claiming
|
|
47
|
+
# success (0770).
|
|
48
|
+
# - 0770 truthful outcomes: vars.tasks is parsed and validated ONCE at
|
|
49
|
+
# task-resolve (JSON array of non-empty WBS strings, every member resolved
|
|
50
|
+
# via `task show` to a completed status); siblings consume the normalized
|
|
51
|
+
# run-scoped artifact and never re-parse raw input (malformed input can no
|
|
52
|
+
# longer be silently re-interpreted as an empty list). All captures are
|
|
53
|
+
# run-scoped under .spur/run/<runId>-wrapup-*; a lookup failure is
|
|
54
|
+
# recorded, never silently omitted as success.
|
|
55
|
+
# - 0783 validated consumers (audit 0781 F-04): validation accepts only
|
|
56
|
+
# canonical four-digit WBS strings (whitespace is rejected, not trimmed);
|
|
57
|
+
# the route writer, every guard, model prompt and operator note after
|
|
58
|
+
# resolution read the run-scoped capture, never raw vars.tasks, and a
|
|
59
|
+
# missing/corrupted capture or status refuses progression. Metrics
|
|
60
|
+
# revalidate the capture, require well-shaped task lookups, serialize rows
|
|
61
|
+
# with jq (never interpolated printf), and write PASS only after every
|
|
62
|
+
# required append succeeds. Required feature sync succeeds only for a
|
|
63
|
+
# valid matching proposal with no gateBlocked/requiresConfirm condition
|
|
64
|
+
# whose target status is freshly observed; applied:false is a successful
|
|
65
|
+
# no-op only when from == to and that status is observed — nonzero,
|
|
66
|
+
# malformed, partial, blocked or unreadable outcomes fail explicitly, and
|
|
67
|
+
# an affected-feature check can never convert a failed sync into success.
|
|
44
68
|
# - branch-cleanup HITL is exhaustive (yes/no/cancel → done; missing answer
|
|
45
|
-
# defense).
|
|
69
|
+
# defense) and consent-only: it records the decision, performs no git op.
|
|
46
70
|
# - Wrap-up never mutates task status (consumes completed work only).
|
|
47
71
|
|
|
48
72
|
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
49
73
|
kind: state-machine
|
|
50
74
|
name: wrapup-pipeline
|
|
75
|
+
version: "3"
|
|
51
76
|
description: "Post-execution wrap-up: doc-sync (doc drift + learning capture), metrics, feature-transition, branch-cleanup"
|
|
52
77
|
iterationBound: 10
|
|
53
78
|
initialState: start
|
|
54
79
|
terminalStates:
|
|
55
80
|
- done
|
|
56
81
|
- skipped
|
|
82
|
+
- failed
|
|
83
|
+
failureStates:
|
|
84
|
+
- failed
|
|
57
85
|
vars:
|
|
58
86
|
tasks: "[]"
|
|
59
87
|
feature: ""
|
|
@@ -64,22 +92,11 @@ vars:
|
|
|
64
92
|
__runId: ""
|
|
65
93
|
mode: ""
|
|
66
94
|
stepTimeoutMs: "1800000"
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
# only
|
|
70
|
-
# sweep that observes feature-level findings — so a transition that arms one
|
|
71
|
-
# would go green. This gate is therefore the CORPUS SWEEP ALONE (~29 s), not
|
|
72
|
-
# `spur-check-new`: every task in the feature already paid a full `spur-check`
|
|
73
|
-
# in its own pipeline, and the states that run before this hop (`doc-sync`,
|
|
74
|
-
# `metrics-record`) write only markdown and task sections, which Biome skips
|
|
75
|
-
# (`ignoreUnknown: true`). Re-running the ~105 s per-task gate here
|
|
76
|
-
# re-verifies
|
|
77
|
-
# unchanged code and measures nothing new. Soft by design: the gate reports
|
|
78
|
-
# PASS/FAIL and lets the operator decide; it never hard-fails the wrap-up
|
|
79
|
-
# shell.
|
|
95
|
+
# Recheck the affected feature after sync (D61 / ADR-108), not the corpus
|
|
96
|
+
# or full code suite. Explicit caller overrides remain supported.
|
|
80
97
|
# TRUSTED CONFIG ONLY — this string is executed via `sh -c` (same surface as
|
|
81
98
|
# task-pipeline's qualityGateCmd). Never interpolate untrusted input into it.
|
|
82
|
-
featureGateCmd:
|
|
99
|
+
featureGateCmd: '$spurBin feature check "$feature"'
|
|
83
100
|
__hitlAnswer: ""
|
|
84
101
|
|
|
85
102
|
states:
|
|
@@ -94,27 +111,95 @@ states:
|
|
|
94
111
|
|
|
95
112
|
- id: task-resolve
|
|
96
113
|
description: >
|
|
97
|
-
Validate the task list and evaluate closed proportional routing (0758
|
|
98
|
-
|
|
114
|
+
Validate the task list ONCE and evaluate closed proportional routing (0758
|
|
115
|
+
R1-R4). Validation (0770 + 0783): vars.tasks must be a JSON array of
|
|
116
|
+
canonical four-digit WBS strings — non-arrays, non-strings, whitespace or
|
|
117
|
+
other shapes are rejected, not trimmed — deduplicated in first-seen
|
|
118
|
+
order, with __runId present, and every member must resolve via `spur task
|
|
119
|
+
show` to a completed status (done/cancelled); failed or malformed
|
|
120
|
+
lookups fail. Invalid input or an unresolved/non-completed task records
|
|
121
|
+
FAIL (reason into the run-scoped reason file) and routes to `failed`;
|
|
122
|
+
the route writer and every guard, prompt and note after resolution read
|
|
123
|
+
the normalized run-scoped artifact
|
|
124
|
+
.spur/run/<runId>-wrapup-tasks.json and never re-parse raw input — a
|
|
125
|
+
missing or corrupted capture refuses progression. A validated empty list
|
|
126
|
+
-> skipped; length > 0 && mode == fast -> fast-path (metrics-record,
|
|
99
127
|
bypassing doc-sync); missing/unknown/conflict -> doc-sync (safety-path).
|
|
100
128
|
Writes bounded machine-readable reason to reasonFile.
|
|
101
129
|
onEnter:
|
|
102
130
|
- kind: note
|
|
103
131
|
options:
|
|
104
132
|
message: "Resolving task list: ${vars.tasks} (proportional mode: ${vars.mode})"
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
133
|
+
# 0770/0783 truthful input: parse and validate vars.tasks exactly once,
|
|
134
|
+
# here and only here. Malformed JSON, a non-array, a non-string entry,
|
|
135
|
+
# whitespace or a non-canonical WBS (anything but four digits), a missing
|
|
136
|
+
# __runId, an unresolved task, a failed or malformed lookup, or a task
|
|
137
|
+
# not at a completed status records FAIL (routed to `failed` by the first
|
|
138
|
+
# declared edge) and is never re-interpreted as an empty list by a later
|
|
139
|
+
# sibling guard. The normalized, deduplicated list is persisted
|
|
140
|
+
# run-scoped; the route writer, metrics, prompts, notes and guards only
|
|
141
|
+
# ever read that capture.
|
|
142
|
+
- kind: shell
|
|
143
|
+
options:
|
|
144
|
+
command: >-
|
|
145
|
+
mkdir -p .spur/run &&
|
|
146
|
+
RUN_ID="$__runId" &&
|
|
147
|
+
if [ -z "$RUN_ID" ]; then
|
|
148
|
+
echo "task-resolve: __runId is empty — refusing the legacy fixed-path fallback" >&2;
|
|
149
|
+
exit 1;
|
|
150
|
+
fi &&
|
|
151
|
+
REASON_FILE=".spur/run/$RUN_ID-route-reason.txt" &&
|
|
152
|
+
STATUS_FILE=".spur/run/$RUN_ID-wrapup-resolve.status" &&
|
|
153
|
+
TASKS_FILE=".spur/run/$RUN_ID-wrapup-tasks.json" &&
|
|
154
|
+
if ! printf '%s' "$tasks" | jq -e 'type == "array" and all(.[]; type == "string" and test("^[0-9]{4}$"))' > /dev/null 2>&1; then
|
|
155
|
+
echo "task-resolve: tasks must be a JSON array of canonical four-digit WBS strings (whitespace is rejected, not trimmed)" >&2;
|
|
156
|
+
echo "failed:tasks is not a JSON array of canonical four-digit WBS strings" > "$REASON_FILE";
|
|
157
|
+
printf 'FAIL\n' > "$STATUS_FILE";
|
|
158
|
+
else
|
|
159
|
+
printf '%s' "$tasks" | jq -c 'reduce .[] as $w ([]; if any(.[]; . == $w) then . else . + [$w] end)' > "$TASKS_FILE";
|
|
160
|
+
RESOLVE_RC=0;
|
|
161
|
+
for wbs in $(jq -r '.[]' "$TASKS_FILE"); do
|
|
162
|
+
TASK_JSON=$($spurBin task show "$wbs" --json 2>/dev/null || true);
|
|
163
|
+
TASK_STATUS=$(printf '%s' "$TASK_JSON" | jq -r '.frontmatter.status // .status // empty' 2>/dev/null || true);
|
|
164
|
+
if [ "$TASK_STATUS" != "done" ] && [ "$TASK_STATUS" != "cancelled" ]; then
|
|
165
|
+
STATUS_DISPLAY="$TASK_STATUS";
|
|
166
|
+
if [ -z "$STATUS_DISPLAY" ]; then STATUS_DISPLAY=unresolved; fi;
|
|
167
|
+
echo "task-resolve: task $wbs did not resolve to a completed status (status=$STATUS_DISPLAY)" >&2;
|
|
168
|
+
RESOLVE_RC=1;
|
|
169
|
+
fi;
|
|
170
|
+
done;
|
|
171
|
+
if [ "$RESOLVE_RC" -eq 0 ]; then
|
|
172
|
+
printf 'PASS\n' > "$STATUS_FILE";
|
|
173
|
+
else
|
|
174
|
+
echo "failed:unresolved or non-completed task (see $TASKS_FILE)" > "$REASON_FILE";
|
|
175
|
+
printf 'FAIL\n' > "$STATUS_FILE";
|
|
176
|
+
fi;
|
|
177
|
+
fi;
|
|
178
|
+
exit 0
|
|
179
|
+
# Route reason writer (0758 R3/R4 run-attribution pins intact). Runs AFTER
|
|
180
|
+
# validation and reads ONLY the validated run-scoped capture — never raw
|
|
181
|
+
# vars.tasks (0783 R2/R5); the fixed-path fallback is gone because
|
|
182
|
+
# validation refuses an empty __runId above. A FAIL resolve exits without
|
|
183
|
+
# writing: the failed reason written by validation stands, and the skip
|
|
184
|
+
# reason can never mask it. A missing/corrupted capture yields N=-1 and
|
|
185
|
+
# no skipped claim; the failed defense edge owns the run.
|
|
110
186
|
- kind: shell
|
|
111
187
|
options:
|
|
112
188
|
command: >-
|
|
113
189
|
mkdir -p .spur/run .spur/memory &&
|
|
114
190
|
RUN_ID="$__runId" &&
|
|
115
|
-
if [ -z "$RUN_ID" ]; then
|
|
191
|
+
if [ -z "$RUN_ID" ]; then
|
|
192
|
+
echo "task-resolve: __runId is empty — refusing to write a route reason" >&2;
|
|
193
|
+
exit 1;
|
|
194
|
+
fi &&
|
|
116
195
|
REASON_FILE=".spur/run/$RUN_ID-route-reason.txt" &&
|
|
117
|
-
|
|
196
|
+
STATUS_FILE=".spur/run/$RUN_ID-wrapup-resolve.status" &&
|
|
197
|
+
TASKS_FILE=".spur/run/$RUN_ID-wrapup-tasks.json" &&
|
|
198
|
+
if [ "$(cat "$STATUS_FILE" 2>/dev/null)" = FAIL ]; then
|
|
199
|
+
exit 0;
|
|
200
|
+
fi &&
|
|
201
|
+
N=$(jq length "$TASKS_FILE" 2>/dev/null || echo -1) &&
|
|
202
|
+
if [ "$N" -eq 0 ]; then
|
|
118
203
|
echo "skipped:empty task list" > "$REASON_FILE";
|
|
119
204
|
elif [ "$mode" = "fast" ]; then
|
|
120
205
|
echo "fast:evidence complete+consistent" > "$REASON_FILE";
|
|
@@ -146,88 +231,115 @@ states:
|
|
|
146
231
|
options:
|
|
147
232
|
agent: ${vars.agent}
|
|
148
233
|
input: >-
|
|
149
|
-
First run Skill(skill="sp:doc-evolve", args="wrapup
|
|
234
|
+
First run Skill(skill="sp:doc-evolve", args="wrapup") to repair drift in
|
|
150
235
|
docs/00_ADR.md, docs/03_ARCHITECTURE.md, docs/04_DESIGN.md, docs/design/* per constitution edit
|
|
151
|
-
rules; do not write task/feature corpus.
|
|
236
|
+
rules; do not write task/feature corpus. The batch task list is the normalized, validated
|
|
237
|
+
capture at .spur/run/${vars.__runId}-wrapup-tasks.json — read it for the WBS ids; never
|
|
238
|
+
re-parse raw input. THEN extract working learnings from those tasks —
|
|
152
239
|
conventions, errors fixed, patterns, gotchas, grouped by date and task WBS — as raw markdown
|
|
153
|
-
(no fences) and END your final message with that markdown (it is captured to
|
|
240
|
+
(no fences) and END your final message with that markdown (it is captured to the
|
|
241
|
+
run-scoped .spur/run/${vars.__runId}-wrapup-learnings.md).
|
|
154
242
|
# Declared Layer-1 role (0538 R2): routing reason beside the agent:
|
|
155
243
|
# pin.
|
|
156
244
|
role: coder
|
|
157
|
-
answerFile: .spur/run
|
|
158
|
-
expectFile: .spur/run
|
|
245
|
+
answerFile: .spur/run/${vars.__runId}-wrapup-learnings.md
|
|
246
|
+
expectFile: .spur/run/${vars.__runId}-wrapup-learnings.md
|
|
159
247
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
160
248
|
- kind: shell
|
|
161
249
|
options:
|
|
162
250
|
command: >-
|
|
163
|
-
mkdir -p .spur/memory &&
|
|
164
|
-
|
|
165
|
-
|
|
251
|
+
mkdir -p .spur/run .spur/memory &&
|
|
252
|
+
LEARNINGS_FILE=".spur/run/$__runId-wrapup-learnings.md" &&
|
|
253
|
+
if test -s "$LEARNINGS_FILE"; then
|
|
254
|
+
cat "$LEARNINGS_FILE" >> .spur/memory/learnings.md &&
|
|
166
255
|
printf '\n' >> .spur/memory/learnings.md;
|
|
167
256
|
else
|
|
168
|
-
echo "wrapup: learning capture missing or empty at $PWD
|
|
257
|
+
echo "wrapup: learning capture missing or empty at $PWD/$LEARNINGS_FILE - append skipped" >&2;
|
|
169
258
|
fi &&
|
|
170
259
|
exit 0
|
|
171
260
|
|
|
172
261
|
- id: metrics-record
|
|
173
262
|
description: >
|
|
174
263
|
Append one JSONL row per task to .spur/memory/wrapup-metrics.jsonl deterministically.
|
|
264
|
+
Revalidates the normalized run-scoped capture from task-resolve (0770 + 0783):
|
|
265
|
+
a missing, corrupted or non-canonical capture records FAIL; every member must
|
|
266
|
+
produce a successful, well-shaped `task show` lookup (a missing metrics row is
|
|
267
|
+
never silently absorbed as success); rows are serialized with jq — never
|
|
268
|
+
interpolated printf JSON — so escaped fields stay parseable; and PASS is
|
|
269
|
+
written only after every required append succeeds. A missing per-task verdict
|
|
270
|
+
is UNKNOWN telemetry, never proof of completion; previously written valid rows
|
|
271
|
+
survive a failure.
|
|
175
272
|
onEnter:
|
|
176
273
|
- kind: shell
|
|
177
274
|
options:
|
|
178
275
|
command: >-
|
|
179
|
-
mkdir -p .spur/memory &&
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
276
|
+
mkdir -p .spur/run .spur/memory &&
|
|
277
|
+
STATUS_FILE=".spur/run/$__runId-wrapup-metrics.status" &&
|
|
278
|
+
TASKS_FILE=".spur/run/$__runId-wrapup-tasks.json" &&
|
|
279
|
+
METRICS_RC=0 &&
|
|
280
|
+
if ! jq -e 'type == "array" and all(.[]; type == "string" and test("^[0-9]{4}$"))' "$TASKS_FILE" > /dev/null 2>&1; then
|
|
281
|
+
echo "metrics-record: run-scoped task capture missing, corrupted or non-canonical — refusing to record metrics" >&2;
|
|
282
|
+
printf 'FAIL\n' > "$STATUS_FILE";
|
|
283
|
+
else
|
|
284
|
+
for wbs in $(jq -r '.[]' "$TASKS_FILE"); do
|
|
183
285
|
task_json=$($spurBin task show "$wbs" --json 2>/dev/null || true);
|
|
184
|
-
if
|
|
185
|
-
feat=$(printf '%s' "$task_json" | jq -r '.frontmatter.feature_id // .feature_id //
|
|
186
|
-
stat=$(printf '%s' "$task_json" | jq -r '.frontmatter.status // .status // "unknown"'
|
|
286
|
+
if printf '%s' "$task_json" | jq -e '(.frontmatter.status // .status) != null' > /dev/null 2>&1; then
|
|
287
|
+
feat=$(printf '%s' "$task_json" | jq -r '.frontmatter.feature_id // .feature_id // ""');
|
|
288
|
+
stat=$(printf '%s' "$task_json" | jq -r '.frontmatter.status // .status // "unknown"');
|
|
187
289
|
verdict="UNKNOWN";
|
|
188
290
|
if [ -f ".spur/run/$wbs-verdict.json" ]; then
|
|
189
291
|
v=$(jq -r '.verdict // "UNKNOWN"' ".spur/run/$wbs-verdict.json" 2>/dev/null || true);
|
|
190
292
|
if [ -n "$v" ]; then verdict="$v"; fi;
|
|
191
293
|
fi;
|
|
192
294
|
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ");
|
|
193
|
-
|
|
295
|
+
if ! jq -cn --arg wbs "$wbs" --arg feature_id "$feat" --arg status "$stat" --arg verdict "$verdict" --arg timestamp "$ts" '{wbs:$wbs, feature_id:$feature_id, status:$status, verdict:$verdict, timestamp:$timestamp}' >> .spur/memory/wrapup-metrics.jsonl; then
|
|
296
|
+
echo "metrics-record: metrics append failed for task $wbs — recording FAIL instead of claiming the row landed" >&2;
|
|
297
|
+
METRICS_RC=1;
|
|
298
|
+
fi;
|
|
299
|
+
else
|
|
300
|
+
echo "metrics-record: task $wbs lookup failed or was malformed — recording FAIL instead of silently omitting its metrics row" >&2;
|
|
301
|
+
METRICS_RC=1;
|
|
194
302
|
fi;
|
|
195
|
-
|
|
196
|
-
|
|
303
|
+
done;
|
|
304
|
+
if [ "$METRICS_RC" -eq 0 ]; then printf 'PASS\n' > "$STATUS_FILE"; else printf 'FAIL\n' > "$STATUS_FILE"; fi;
|
|
305
|
+
fi;
|
|
197
306
|
exit 0
|
|
198
307
|
|
|
199
308
|
- id: feature-transition
|
|
200
309
|
description: >
|
|
201
310
|
If vars.feature is set, sync feature status via bounded feature-sync-bounded
|
|
202
|
-
(0411).
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
311
|
+
(0411). The shell terminates `exit 0` so artifacts already landed are never
|
|
312
|
+
discarded, but the outcome is truthful (0770 + 0783 R4): sync succeeds only
|
|
313
|
+
with a valid proposal matching vars.feature, no gateBlocked/requiresConfirm
|
|
314
|
+
condition, and a freshly observed feature status equal to proposal.to;
|
|
315
|
+
applied:false is a successful explicit no-change only when from == to and
|
|
316
|
+
that status is observed. A nonzero, malformed, partial, blocked or
|
|
317
|
+
unreadable outcome records FAIL in the run-scoped sync status and routes
|
|
318
|
+
to `failed` — an affected-feature check cannot convert a failed sync into
|
|
319
|
+
success. Empty vars.feature is NOT a blocked sync but a mis-invocation
|
|
320
|
+
(the engine only enters this state when vars.feature is set) and fails
|
|
321
|
+
loud (dogfood 2026-08-15, feature I3).
|
|
206
322
|
onEnter:
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
# state
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
# R1 (0625): a feature transition that changed state may have armed a
|
|
221
|
-
# feature-level finding the per-task fast gate (spur-check) never sees.
|
|
222
|
-
# Run the corpus-aware gate after an applied sync OR a non-zero exit that
|
|
223
|
-
# may follow a partial multi-hop transition, then report PASS/FAIL. Soft
|
|
224
|
-
# by design: the operator decides, and wrap-up never hard-fails here.
|
|
323
|
+
# The terminating `exit 0` must be reached on every branch — the sync is
|
|
324
|
+
# chained with `;`, because `&&` would abort the state, discarding the
|
|
325
|
+
# learnings/metrics that already landed. Soft TERMINATION is not soft
|
|
326
|
+
# TRUTH: the result is classified per 0783 R4 and only a verified sync
|
|
327
|
+
# writes PASS. `feature-sync-bounded.ts` is a Spur-monorepo path (`spur
|
|
328
|
+
# init` does not scaffold `plugins/sp/`), so seeded projects fall back to
|
|
329
|
+
# the plain `spur feature sync` verb.
|
|
330
|
+
# R1 (0625): a feature transition that changed state may have left
|
|
331
|
+
# cross-task fallout the per-task fast gates never see. The
|
|
332
|
+
# affected-feature gate (featureGateCmd) runs after an applied sync OR a
|
|
333
|
+
# non-zero exit that may follow a partial multi-hop transition. It is a
|
|
334
|
+
# diagnostic only: a gate PASS can never convert a failed sync into
|
|
335
|
+
# success, and a gate FAIL fails the step (0770 failure routing).
|
|
225
336
|
# NOTE: do not add
|
|
226
337
|
# `#` comments INSIDE the folded shell string — YAML `>-` folds them onto
|
|
227
338
|
# the following statement line and comments it out.
|
|
228
339
|
- kind: shell
|
|
229
340
|
options:
|
|
230
341
|
command: >-
|
|
342
|
+
mkdir -p .spur/run &&
|
|
231
343
|
if [ -z "$feature" ]; then
|
|
232
344
|
echo "feature-transition: vars.feature is empty — refusing no-op feature sync (mis-invocation, not a blocked sync)" >&2;
|
|
233
345
|
exit 1;
|
|
@@ -241,47 +353,96 @@ states:
|
|
|
241
353
|
fi);
|
|
242
354
|
SYNC_RC=$?;
|
|
243
355
|
printf '%s\n' "$SYNC_OUTPUT";
|
|
244
|
-
|
|
356
|
+
OBSERVED=$($spurBin feature show "$feature" --json 2>/dev/null | jq -r '.status // .frontmatter.status // empty' 2>/dev/null || true);
|
|
357
|
+
if [ -z "$OBSERVED" ]; then OBSERVED=unreadable; fi;
|
|
358
|
+
APPLIED=unreadable;
|
|
359
|
+
SYNC_OK=0;
|
|
360
|
+
REASON="";
|
|
361
|
+
if [ "$SYNC_RC" -ne 0 ]; then
|
|
362
|
+
REASON="sync exited nonzero (rc=$SYNC_RC)";
|
|
363
|
+
elif ! printf '%s' "$SYNC_OUTPUT" | jq -e 'type == "object" and (.proposal | type == "object") and (.proposal.featureId | type == "string") and (.proposal.from | type == "string") and (.proposal.to | type == "string") and (.applied | type == "boolean")' > /dev/null 2>&1; then
|
|
364
|
+
REASON="malformed or unreadable sync result";
|
|
365
|
+
else
|
|
366
|
+
P_FROM=$(printf '%s' "$SYNC_OUTPUT" | jq -r '.proposal.from // ""');
|
|
367
|
+
P_TO=$(printf '%s' "$SYNC_OUTPUT" | jq -r '.proposal.to // ""');
|
|
368
|
+
APPLIED=$(printf '%s' "$SYNC_OUTPUT" | jq -r '.applied');
|
|
369
|
+
if [ "$(printf '%s' "$SYNC_OUTPUT" | jq -r '.proposal.featureId // ""')" != "$feature" ]; then
|
|
370
|
+
REASON="sync proposal does not match feature $feature";
|
|
371
|
+
elif [ "$(printf '%s' "$SYNC_OUTPUT" | jq -r '.proposal.gateBlocked // false')" = "true" ]; then
|
|
372
|
+
REASON="sync proposal is gate-blocked — a blocked sync is not a no-change success";
|
|
373
|
+
elif [ "$(printf '%s' "$SYNC_OUTPUT" | jq -r '.proposal.requiresConfirm // false')" = "true" ]; then
|
|
374
|
+
REASON="sync proposal requires operator confirmation";
|
|
375
|
+
elif [ "$APPLIED" = "true" ] && [ "$OBSERVED" != "$P_TO" ]; then
|
|
376
|
+
REASON="applied sync did not land on the proposal target (observed=$OBSERVED, to=$P_TO)";
|
|
377
|
+
elif [ "$APPLIED" = "false" ] && { [ "$P_FROM" != "$P_TO" ] || [ "$OBSERVED" != "$P_TO" ]; }; then
|
|
378
|
+
REASON="sync applied nothing without a from==to observed no-op (from=$P_FROM, to=$P_TO, observed=$OBSERVED)";
|
|
379
|
+
else
|
|
380
|
+
SYNC_OK=1;
|
|
381
|
+
fi;
|
|
382
|
+
fi;
|
|
383
|
+
GATE="skipped";
|
|
245
384
|
if [ "$APPLIED" = "true" ] || [ "$SYNC_RC" -ne 0 ]; then
|
|
246
|
-
echo "feature-transition: sync applied or failed after a possible partial transition for $feature — running
|
|
385
|
+
echo "feature-transition: sync applied or failed after a possible partial transition for $feature — running feature gate: $featureGateCmd";
|
|
247
386
|
if sh -c "$featureGateCmd"; then
|
|
248
|
-
|
|
387
|
+
GATE="PASS";
|
|
388
|
+
echo "feature-transition: feature gate PASS for feature $feature";
|
|
249
389
|
else
|
|
250
|
-
|
|
390
|
+
GATE="FAIL";
|
|
391
|
+
echo "feature-transition: feature gate FAIL for feature $feature — inspect findings before reporting the transition complete" >&2;
|
|
251
392
|
fi;
|
|
252
393
|
else
|
|
253
|
-
echo "feature-transition: sync did not apply a transition (rc=$SYNC_RC, applied=$APPLIED) —
|
|
394
|
+
echo "feature-transition: sync did not apply a transition (rc=$SYNC_RC, applied=$APPLIED) — feature gate skipped";
|
|
395
|
+
fi;
|
|
396
|
+
SYNC_STATUS="PASS";
|
|
397
|
+
if [ "$SYNC_OK" -ne 1 ] || [ "$GATE" = "FAIL" ]; then
|
|
398
|
+
SYNC_STATUS="FAIL";
|
|
399
|
+
echo "feature-transition: required synchronization failed for $feature — $REASON; gate=$GATE" >&2;
|
|
400
|
+
elif [ "$APPLIED" = "false" ]; then
|
|
401
|
+
echo "feature-transition: feature sync verified for $feature (from==to observed at $OBSERVED, gate=$GATE) — explicit no-change";
|
|
402
|
+
else
|
|
403
|
+
echo "feature-transition: feature sync verified for $feature (applied, observed=$OBSERVED, gate=$GATE)";
|
|
404
|
+
fi;
|
|
405
|
+
if [ "$SYNC_STATUS" = "PASS" ]; then
|
|
406
|
+
printf 'PASS\n' > ".spur/run/$__runId-wrapup-sync.status";
|
|
407
|
+
else
|
|
408
|
+
printf 'FAIL\n' > ".spur/run/$__runId-wrapup-sync.status";
|
|
254
409
|
fi;
|
|
255
410
|
exit 0
|
|
256
411
|
|
|
257
412
|
- id: branch-cleanup
|
|
258
413
|
description: >
|
|
259
|
-
Irreversible HITL gate. If vars.merge=true,
|
|
260
|
-
|
|
261
|
-
|
|
414
|
+
Irreversible-operation HITL gate, consent-only (0770). If vars.merge=true,
|
|
415
|
+
branch cleanup (merge or delete) is a candidate — but this gate records
|
|
416
|
+
the operator decision and performs NO git operation itself; an operator
|
|
417
|
+
or a separate approved surface executes any actual merge/delete. This
|
|
418
|
+
gate ALWAYS pauses — even under --auto — because branch operations are
|
|
419
|
+
irreversible (Auto-Decision Principle #6).
|
|
262
420
|
pause: true
|
|
263
421
|
onEnter:
|
|
264
422
|
- kind: hitl.confirm
|
|
265
423
|
options:
|
|
266
|
-
prompt: "Branch cleanup for
|
|
424
|
+
prompt: "Branch cleanup for the validated task list (.spur/run/${vars.__runId}-wrapup-tasks.json). This is IRREVERSIBLE (merge or delete). Confirm to proceed?"
|
|
267
425
|
|
|
268
426
|
- id: done
|
|
269
427
|
description: >
|
|
270
|
-
Terminal — wrap-up complete.
|
|
271
|
-
metrics recorded, feature advanced
|
|
428
|
+
Terminal — wrap-up complete. Summary: tasks wrapped, learnings captured,
|
|
429
|
+
metrics recorded, feature advanced or explicitly no-change (if applicable),
|
|
430
|
+
branch-cleanup decision recorded (consent-only; no git operation performed).
|
|
272
431
|
onEnter:
|
|
273
432
|
- kind: note
|
|
274
433
|
options:
|
|
275
|
-
message: "Wrap-up pipeline complete for
|
|
276
|
-
# Checkpoint write: record session state for resume (Phase 4, task 0171
|
|
277
|
-
# R3)
|
|
278
|
-
- kind: shell
|
|
279
|
-
options:
|
|
280
|
-
command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: wrapup-pipeline done tasks=$tasks ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/wrapup-checkpoint.md'
|
|
434
|
+
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."
|
|
281
435
|
|
|
282
436
|
- id: skipped
|
|
283
437
|
description: Terminal — wrap-up skipped (empty task list or operator abort).
|
|
284
438
|
|
|
439
|
+
- id: failed
|
|
440
|
+
description: >
|
|
441
|
+
Terminal — wrap-up failed (0770): invalid wrap input, an unresolved or
|
|
442
|
+
non-completed task, a failed metrics capture, a failed required sync, or
|
|
443
|
+
a failed feature gate. Already-written artifacts (learnings, metrics
|
|
444
|
+
rows, route reason) are preserved; nothing is rolled back.
|
|
445
|
+
|
|
285
446
|
transitions:
|
|
286
447
|
# ── start -> task-resolve ──
|
|
287
448
|
- from: start
|
|
@@ -290,31 +451,47 @@ transitions:
|
|
|
290
451
|
guard:
|
|
291
452
|
kind: always
|
|
292
453
|
|
|
293
|
-
# ── task-resolve: proportional route table (
|
|
454
|
+
# ── task-resolve: validation fail + proportional route table (0758 R1-R4, 0770) ──
|
|
455
|
+
- from: task-resolve
|
|
456
|
+
to: failed
|
|
457
|
+
description: >
|
|
458
|
+
Validation FAIL (0770): malformed wrap input, a non-string/empty entry, an
|
|
459
|
+
unresolved or non-completed task, or an empty __runId. Declared before the
|
|
460
|
+
route edges so invalid input can never be absorbed by the empty-list skip.
|
|
461
|
+
guard:
|
|
462
|
+
kind: shell
|
|
463
|
+
options:
|
|
464
|
+
command: 'test "$(cat .spur/run/$__runId-wrapup-resolve.status 2>/dev/null)" = FAIL'
|
|
465
|
+
# Route edges key on the validated run-scoped capture, never raw vars.tasks
|
|
466
|
+
# (0783 R2). A missing or corrupted capture yields -1, which satisfies none
|
|
467
|
+
# of the numeric edges, so the run falls through to the always-defense
|
|
468
|
+
# `failed` edge instead of claiming a skip or a route.
|
|
294
469
|
- from: task-resolve
|
|
295
470
|
to: skipped
|
|
296
|
-
description:
|
|
471
|
+
description: Validated task list is empty — skip wrap-up (only a validated [] may skip).
|
|
297
472
|
guard:
|
|
298
473
|
kind: shell
|
|
299
474
|
options:
|
|
300
|
-
command: 'test "$(
|
|
475
|
+
command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -eq 0'
|
|
301
476
|
- from: task-resolve
|
|
302
477
|
to: metrics-record
|
|
303
478
|
description: Proportional fast path — complete and consistent evidence bypasses doc-sync.
|
|
304
479
|
guard:
|
|
305
480
|
kind: shell
|
|
306
481
|
options:
|
|
307
|
-
command: 'test "$(
|
|
482
|
+
command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -gt 0 && test "$mode" = fast'
|
|
308
483
|
- from: task-resolve
|
|
309
484
|
to: doc-sync
|
|
310
485
|
description: Proportional safety path — missing/unknown/conflicting evidence routes to full doc-sync.
|
|
311
486
|
guard:
|
|
312
487
|
kind: shell
|
|
313
488
|
options:
|
|
314
|
-
command: 'test "$(
|
|
489
|
+
command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -gt 0 && test "$mode" != fast'
|
|
315
490
|
- from: task-resolve
|
|
316
|
-
to:
|
|
317
|
-
description:
|
|
491
|
+
to: failed
|
|
492
|
+
description: >
|
|
493
|
+
Defense (0770) — no PASS/FAIL resolve status (shell crashed or never ran):
|
|
494
|
+
route to `failed` rather than claiming a skip.
|
|
318
495
|
guard:
|
|
319
496
|
kind: always
|
|
320
497
|
|
|
@@ -325,47 +502,67 @@ transitions:
|
|
|
325
502
|
guard:
|
|
326
503
|
kind: always
|
|
327
504
|
|
|
328
|
-
# ── metrics-record: conditional routing
|
|
329
|
-
# Declaration order matters:
|
|
330
|
-
# set),
|
|
331
|
-
#
|
|
505
|
+
# ── metrics-record: fail edge, then conditional routing (feature → merge → done) ──
|
|
506
|
+
# Declaration order matters: the FAIL edge is tried first (0770), then
|
|
507
|
+
# feature-transition (if feature is set), then branch-cleanup (if merge=true
|
|
508
|
+
# but no feature), then done (neither). Routing edges additionally require
|
|
509
|
+
# the metrics status PASS so a missing status can never claim success.
|
|
510
|
+
- from: metrics-record
|
|
511
|
+
to: failed
|
|
512
|
+
description: >
|
|
513
|
+
Metrics capture failed for at least one task — a missing metrics row is
|
|
514
|
+
recorded as failure, never silently omitted as success (0770).
|
|
515
|
+
guard:
|
|
516
|
+
kind: shell
|
|
517
|
+
options:
|
|
518
|
+
command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = FAIL'
|
|
332
519
|
- from: metrics-record
|
|
333
520
|
to: feature-transition
|
|
334
|
-
description:
|
|
521
|
+
description: Metrics PASS and feature id is set — advance feature through legal lifecycle edges.
|
|
335
522
|
guard:
|
|
336
523
|
kind: shell
|
|
337
524
|
options:
|
|
338
|
-
command: 'test -n "$feature"'
|
|
525
|
+
command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = PASS && test -n "$feature"'
|
|
339
526
|
- from: metrics-record
|
|
340
527
|
to: branch-cleanup
|
|
341
|
-
description:
|
|
528
|
+
description: Metrics PASS, no feature id, but merge is requested — go to branch cleanup.
|
|
342
529
|
guard:
|
|
343
530
|
kind: shell
|
|
344
531
|
options:
|
|
345
|
-
command: 'test -z "$feature" && test "$merge" = true'
|
|
532
|
+
command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = PASS && test -z "$feature" && test "$merge" = true'
|
|
346
533
|
- from: metrics-record
|
|
347
534
|
to: done
|
|
348
|
-
description:
|
|
535
|
+
description: Metrics PASS, no feature id and no merge — wrap-up is done.
|
|
349
536
|
guard:
|
|
350
537
|
kind: shell
|
|
351
538
|
options:
|
|
352
|
-
command: 'test -z "$feature" && test "$merge" != true'
|
|
539
|
+
command: 'test "$(cat .spur/run/$__runId-wrapup-metrics.status 2>/dev/null)" = PASS && test -z "$feature" && test "$merge" != true'
|
|
353
540
|
|
|
354
|
-
# ── feature-transition:
|
|
541
|
+
# ── feature-transition: fail edge, then branch-cleanup (if merge) or done ──
|
|
542
|
+
- from: feature-transition
|
|
543
|
+
to: failed
|
|
544
|
+
description: >
|
|
545
|
+
Required synchronization failed (0770): non-zero sync exit, an invalid
|
|
546
|
+
sync result, or a failed feature gate. The failed status carries the
|
|
547
|
+
actual reason; already-written learnings/metrics stay on disk.
|
|
548
|
+
guard:
|
|
549
|
+
kind: shell
|
|
550
|
+
options:
|
|
551
|
+
command: 'test "$(cat .spur/run/$__runId-wrapup-sync.status 2>/dev/null)" = FAIL'
|
|
355
552
|
- from: feature-transition
|
|
356
553
|
to: branch-cleanup
|
|
357
|
-
description:
|
|
554
|
+
description: Sync PASS — merge is requested.
|
|
358
555
|
guard:
|
|
359
556
|
kind: shell
|
|
360
557
|
options:
|
|
361
|
-
command: 'test "$merge" = true'
|
|
558
|
+
command: 'test "$(cat .spur/run/$__runId-wrapup-sync.status 2>/dev/null)" = PASS && test "$merge" = true'
|
|
362
559
|
- from: feature-transition
|
|
363
560
|
to: done
|
|
364
|
-
description:
|
|
561
|
+
description: Sync PASS — no merge requested.
|
|
365
562
|
guard:
|
|
366
563
|
kind: shell
|
|
367
564
|
options:
|
|
368
|
-
command: 'test "$merge" != true'
|
|
565
|
+
command: 'test "$(cat .spur/run/$__runId-wrapup-sync.status 2>/dev/null)" = PASS && test "$merge" != true'
|
|
369
566
|
|
|
370
567
|
# ── branch-cleanup: HITL exhaustive routing (yes / no / cancel → done) ──
|
|
371
568
|
# No irreversible git op is wired yet; confirmation is recorded, then wrap
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.75",
|
|
4
4
|
"description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spur",
|