@gobing-ai/spur 0.3.74 → 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/workflows/docs-pipeline.yaml +26 -1
- package/config/workflows/feature-dev.yaml +143 -102
- package/config/workflows/idea-pipeline.yaml +58 -11
- package/config/workflows/task-pipeline.yaml +76 -40
- package/config/workflows/wrapup-pipeline.yaml +183 -111
- 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/skills/spur-cli/references/tasks.md +3 -1
- 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/gate-checklists.md +4 -1
- package/spur.js +2010 -362
- package/web/_astro/{BoardApp.C60RJZRj.js → BoardApp.BOW1815F.js} +97 -97
- package/web/_astro/BoardApp.C4zimv1U.js +1 -0
- package/web/_astro/{TaskDetail.Guk8VuNu.js → TaskDetail.CX6C7dSJ.js} +1 -1
- package/web/_astro/{arc.CAZtlWJM.js → arc.CwSvH1ji.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.9-XbF_Tz.js → architectureDiagram-3BPJPVTR.Dxne1RvP.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DC4WLx3L.js → blockDiagram-GPEHLZMM.D9Fb14rC.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.JgjUQMgG.js → c4Diagram-AAUBKEIU.DmvWSFvv.js} +1 -1
- package/web/_astro/channel.CQHdDVp9.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.D_JFiXa-.js → chunk-2J33WTMH.BCCktIrE.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.Bk_RxoeT.js → chunk-4BX2VUAB.BzCk9q27.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.a_Rj2VxL.js → chunk-55IACEB6.CZXVgTk4.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.C2NoS1U6.js → chunk-727SXJPM.CJL8UIpX.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.D1Xn6CNa.js → chunk-AQP2D5EJ.Dz8MDEdw.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.BhJrSBsX.js → chunk-FMBD7UC4.CkQeYYUw.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.ByVGduYQ.js → chunk-ND2GUHAM.KiC1QgzH.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.B47Paunq.js → chunk-QZHKN3VN.AJ08mw2e.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.BERMSD5C.js → classDiagram-4FO5ZUOK.DRWRDKzK.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.BERMSD5C.js → classDiagram-v2-Q7XG4LA2.DRWRDKzK.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.O_rHGlhU.js → cose-bilkent-S5V4N54A.CavydfLP.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.qCW2GdNp.js → cynefin-OW5HDTMX.D_7o_a0B.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.C5Y4lH_u.js → dagre-BM42HDAG.LWk2dKgg.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DxZmRXxF.js → diagram-2AECGRRQ.82lbq6aB.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.qSxZeYSK.js → diagram-5GNKFQAL.hyNL1pwY.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.-7vl3bXs.js → diagram-KO2AKTUF.BaLqVf-b.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BHVV-UH3.js → diagram-LMA3HP47.DQJxj0oz.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.DHAuW9sK.js → diagram-OG6HWLK6.DhlL8cG2.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.DMRLQwPN.js → erDiagram-TEJ5UH35.HoTnXwkF.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.B3EpPp_8.js → flowDiagram-I6XJVG4X.DkFKlZIZ.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.BgUrExXM.js → ganttDiagram-6RSMTGT7.9qPfOTDb.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CU-9yVN4.js → gitGraphDiagram-PVQCEYII.B4G18Dwc.js} +1 -1
- package/web/_astro/index.Dcr_8fiK.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.CvVTCRLe.js → infoDiagram-5YYISTIA.e3KJkXAM.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BUzOfKPf.js → ishikawaDiagram-YF4QCWOH.CY3yddhD.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.BBTb7ziz.js → journeyDiagram-JHISSGLW.DaAKIO1t.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.CTQQr70i.js → kanban-definition-UN3LZRKU.DGkDddtc.js} +1 -1
- package/web/_astro/{linear.CLKlJPiS.js → linear.DnDPcpd1.js} +1 -1
- package/web/_astro/{mermaid.core.BA4wFhLP.js → mermaid.core.1uBmxa9t.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.t1vG2l5e.js → mindmap-definition-RKZ34NQL.XFJVayxw.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.C8zJhyTu.js → pieDiagram-4H26LBE5.BXm2OgHh.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.DqYYvb7T.js → quadrantDiagram-W4KKPZXB.DgB2p5fc.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.lzfDZ9nI.js → requirementDiagram-4Y6WPE33.jBR39bnS.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.lm6N5ORM.js → sankeyDiagram-5OEKKPKP.N3u9OhE3.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.E6_IV4Dj.js → sequenceDiagram-3UESZ5HK.CZhqZXNL.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.Bnv6Ok2p.js → stateDiagram-AJRCARHV.DlNu1VEa.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.mJHH_Ng_.js → stateDiagram-v2-BHNVJYJU.BEqi8NQV.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.BhRgqSLa.js → timeline-definition-PNZ67QCA.BPcexclc.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.pjb5DMC9.js → vennDiagram-CIIHVFJN.BZalxKGQ.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CW20KUng.js → wardleyDiagram-YWT4CUSO.BqOljod9.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.vX9_yuMl.js → xychartDiagram-2RQKCTM6.tkO4ppN5.js} +1 -1
- package/web/index.html +2 -2
- package/web/_astro/BoardApp.ymfj1EHA.js +0 -1
- package/web/_astro/channel.Bxxe2Byc.js +0 -1
- package/web/_astro/index.BhvM3djt.css +0 -1
|
@@ -52,6 +52,19 @@
|
|
|
52
52
|
# longer be silently re-interpreted as an empty list). All captures are
|
|
53
53
|
# run-scoped under .spur/run/<runId>-wrapup-*; a lookup failure is
|
|
54
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.
|
|
55
68
|
# - branch-cleanup HITL is exhaustive (yes/no/cancel → done; missing answer
|
|
56
69
|
# defense) and consent-only: it records the decision, performs no git op.
|
|
57
70
|
# - Wrap-up never mutates task status (consumes completed work only).
|
|
@@ -59,7 +72,7 @@
|
|
|
59
72
|
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
60
73
|
kind: state-machine
|
|
61
74
|
name: wrapup-pipeline
|
|
62
|
-
version: "
|
|
75
|
+
version: "3"
|
|
63
76
|
description: "Post-execution wrap-up: doc-sync (doc drift + learning capture), metrics, feature-transition, branch-cleanup"
|
|
64
77
|
iterationBound: 10
|
|
65
78
|
initialState: start
|
|
@@ -99,54 +112,33 @@ states:
|
|
|
99
112
|
- id: task-resolve
|
|
100
113
|
description: >
|
|
101
114
|
Validate the task list ONCE and evaluate closed proportional routing (0758
|
|
102
|
-
R1-R4). Validation (0770): vars.tasks must be a JSON array of
|
|
103
|
-
WBS strings
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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,
|
|
127
|
+
bypassing doc-sync); missing/unknown/conflict -> doc-sync (safety-path).
|
|
128
|
+
Writes bounded machine-readable reason to reasonFile.
|
|
112
129
|
onEnter:
|
|
113
130
|
- kind: note
|
|
114
131
|
options:
|
|
115
132
|
message: "Resolving task list: ${vars.tasks} (proportional mode: ${vars.mode})"
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
RUN_ID="$__runId" &&
|
|
126
|
-
if [ -z "$RUN_ID" ]; then RUN_ID="wrapup"; fi &&
|
|
127
|
-
REASON_FILE=".spur/run/$RUN_ID-route-reason.txt" &&
|
|
128
|
-
if [ "$(echo "$tasks" | jq length 2>/dev/null || echo 0)" -eq 0 ]; then
|
|
129
|
-
echo "skipped:empty task list" > "$REASON_FILE";
|
|
130
|
-
elif [ "$mode" = "fast" ]; then
|
|
131
|
-
echo "fast:evidence complete+consistent" > "$REASON_FILE";
|
|
132
|
-
elif [ -z "$mode" ]; then
|
|
133
|
-
echo "safety:missing evidence (mode empty)" > "$REASON_FILE";
|
|
134
|
-
elif [ "$mode" = "unknown" ]; then
|
|
135
|
-
echo "safety:unknown evidence quality" > "$REASON_FILE";
|
|
136
|
-
elif [ "$mode" = "conflict" ]; then
|
|
137
|
-
echo "safety:conflicting evidence" > "$REASON_FILE";
|
|
138
|
-
else
|
|
139
|
-
echo "safety:unrecognized evidence (mode=$mode)" > "$REASON_FILE";
|
|
140
|
-
fi &&
|
|
141
|
-
printf '%s %s\n' "$RUN_ID" "$(cat "$REASON_FILE")"
|
|
142
|
-
>> .spur/memory/wrapup-routes.log &&
|
|
143
|
-
exit 0
|
|
144
|
-
# 0770 truthful input: parse and validate vars.tasks exactly once, here.
|
|
145
|
-
# Malformed JSON, a non-array, a non-string or empty entry, an unresolved
|
|
146
|
-
# task, or a task not at a completed status records FAIL (routed to
|
|
147
|
-
# `failed` by the first declared edge) and is never re-interpreted as an
|
|
148
|
-
# empty list by a later sibling guard. The normalized, deduplicated list
|
|
149
|
-
# is persisted run-scoped; metrics/doc siblings only ever read it.
|
|
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.
|
|
150
142
|
- kind: shell
|
|
151
143
|
options:
|
|
152
144
|
command: >-
|
|
@@ -159,9 +151,9 @@ states:
|
|
|
159
151
|
REASON_FILE=".spur/run/$RUN_ID-route-reason.txt" &&
|
|
160
152
|
STATUS_FILE=".spur/run/$RUN_ID-wrapup-resolve.status" &&
|
|
161
153
|
TASKS_FILE=".spur/run/$RUN_ID-wrapup-tasks.json" &&
|
|
162
|
-
if ! printf '%s' "$tasks" | jq -e 'type == "array" and all(.[]; type == "string" and
|
|
163
|
-
echo "task-resolve: tasks must be a JSON array of
|
|
164
|
-
echo "failed:tasks is not a JSON array of
|
|
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";
|
|
165
157
|
printf 'FAIL\n' > "$STATUS_FILE";
|
|
166
158
|
else
|
|
167
159
|
printf '%s' "$tasks" | jq -c 'reduce .[] as $w ([]; if any(.[]; . == $w) then . else . + [$w] end)' > "$TASKS_FILE";
|
|
@@ -184,6 +176,45 @@ states:
|
|
|
184
176
|
fi;
|
|
185
177
|
fi;
|
|
186
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.
|
|
186
|
+
- kind: shell
|
|
187
|
+
options:
|
|
188
|
+
command: >-
|
|
189
|
+
mkdir -p .spur/run .spur/memory &&
|
|
190
|
+
RUN_ID="$__runId" &&
|
|
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 &&
|
|
195
|
+
REASON_FILE=".spur/run/$RUN_ID-route-reason.txt" &&
|
|
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
|
|
203
|
+
echo "skipped:empty task list" > "$REASON_FILE";
|
|
204
|
+
elif [ "$mode" = "fast" ]; then
|
|
205
|
+
echo "fast:evidence complete+consistent" > "$REASON_FILE";
|
|
206
|
+
elif [ -z "$mode" ]; then
|
|
207
|
+
echo "safety:missing evidence (mode empty)" > "$REASON_FILE";
|
|
208
|
+
elif [ "$mode" = "unknown" ]; then
|
|
209
|
+
echo "safety:unknown evidence quality" > "$REASON_FILE";
|
|
210
|
+
elif [ "$mode" = "conflict" ]; then
|
|
211
|
+
echo "safety:conflicting evidence" > "$REASON_FILE";
|
|
212
|
+
else
|
|
213
|
+
echo "safety:unrecognized evidence (mode=$mode)" > "$REASON_FILE";
|
|
214
|
+
fi &&
|
|
215
|
+
printf '%s %s\n' "$RUN_ID" "$(cat "$REASON_FILE")"
|
|
216
|
+
>> .spur/memory/wrapup-routes.log &&
|
|
217
|
+
exit 0
|
|
187
218
|
|
|
188
219
|
- id: doc-sync
|
|
189
220
|
description: >
|
|
@@ -200,9 +231,11 @@ states:
|
|
|
200
231
|
options:
|
|
201
232
|
agent: ${vars.agent}
|
|
202
233
|
input: >-
|
|
203
|
-
First run Skill(skill="sp:doc-evolve", args="wrapup
|
|
234
|
+
First run Skill(skill="sp:doc-evolve", args="wrapup") to repair drift in
|
|
204
235
|
docs/00_ADR.md, docs/03_ARCHITECTURE.md, docs/04_DESIGN.md, docs/design/* per constitution edit
|
|
205
|
-
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 —
|
|
206
239
|
conventions, errors fixed, patterns, gotchas, grouped by date and task WBS — as raw markdown
|
|
207
240
|
(no fences) and END your final message with that markdown (it is captured to the
|
|
208
241
|
run-scoped .spur/run/${vars.__runId}-wrapup-learnings.md).
|
|
@@ -228,9 +261,14 @@ states:
|
|
|
228
261
|
- id: metrics-record
|
|
229
262
|
description: >
|
|
230
263
|
Append one JSONL row per task to .spur/memory/wrapup-metrics.jsonl deterministically.
|
|
231
|
-
|
|
232
|
-
a
|
|
233
|
-
|
|
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.
|
|
234
272
|
onEnter:
|
|
235
273
|
- kind: shell
|
|
236
274
|
options:
|
|
@@ -239,63 +277,69 @@ states:
|
|
|
239
277
|
STATUS_FILE=".spur/run/$__runId-wrapup-metrics.status" &&
|
|
240
278
|
TASKS_FILE=".spur/run/$__runId-wrapup-tasks.json" &&
|
|
241
279
|
METRICS_RC=0 &&
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if
|
|
249
|
-
|
|
250
|
-
|
|
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
|
|
285
|
+
task_json=$($spurBin task show "$wbs" --json 2>/dev/null || true);
|
|
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"');
|
|
289
|
+
verdict="UNKNOWN";
|
|
290
|
+
if [ -f ".spur/run/$wbs-verdict.json" ]; then
|
|
291
|
+
v=$(jq -r '.verdict // "UNKNOWN"' ".spur/run/$wbs-verdict.json" 2>/dev/null || true);
|
|
292
|
+
if [ -n "$v" ]; then verdict="$v"; fi;
|
|
293
|
+
fi;
|
|
294
|
+
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ");
|
|
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;
|
|
251
302
|
fi;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
echo "metrics-record: task $wbs did not resolve — recording FAIL instead of silently omitting its metrics row" >&2;
|
|
256
|
-
METRICS_RC=1;
|
|
257
|
-
fi;
|
|
258
|
-
done;
|
|
259
|
-
if [ "$METRICS_RC" -eq 0 ]; then printf 'PASS\n' > "$STATUS_FILE"; else printf 'FAIL\n' > "$STATUS_FILE"; fi;
|
|
303
|
+
done;
|
|
304
|
+
if [ "$METRICS_RC" -eq 0 ]; then printf 'PASS\n' > "$STATUS_FILE"; else printf 'FAIL\n' > "$STATUS_FILE"; fi;
|
|
305
|
+
fi;
|
|
260
306
|
exit 0
|
|
261
307
|
|
|
262
308
|
- id: feature-transition
|
|
263
309
|
description: >
|
|
264
310
|
If vars.feature is set, sync feature status via bounded feature-sync-bounded
|
|
265
|
-
(0411).
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
|
270
320
|
(the engine only enters this state when vars.feature is set) and fails
|
|
271
|
-
loud (dogfood 2026-08-15, feature I3
|
|
272
|
-
complete with no transition).
|
|
321
|
+
loud (dogfood 2026-08-15, feature I3).
|
|
273
322
|
onEnter:
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
# state
|
|
282
|
-
# description promises against). `feature-sync-bounded.ts` is a
|
|
283
|
-
# Spur-monorepo
|
|
284
|
-
# path (`spur init` does not scaffold `plugins/sp/`), so seeded projects
|
|
285
|
-
# fall
|
|
286
|
-
# back to the plain `spur feature sync` verb.
|
|
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.
|
|
287
330
|
# R1 (0625): a feature transition that changed state may have left
|
|
288
|
-
# cross-task fallout the per-task fast gates never see.
|
|
289
|
-
# affected-feature gate (featureGateCmd) after an
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
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).
|
|
293
336
|
# NOTE: do not add
|
|
294
337
|
# `#` comments INSIDE the folded shell string — YAML `>-` folds them onto
|
|
295
338
|
# the following statement line and comments it out.
|
|
296
339
|
- kind: shell
|
|
297
340
|
options:
|
|
298
341
|
command: >-
|
|
342
|
+
mkdir -p .spur/run &&
|
|
299
343
|
if [ -z "$feature" ]; then
|
|
300
344
|
echo "feature-transition: vars.feature is empty — refusing no-op feature sync (mis-invocation, not a blocked sync)" >&2;
|
|
301
345
|
exit 1;
|
|
@@ -309,10 +353,32 @@ states:
|
|
|
309
353
|
fi);
|
|
310
354
|
SYNC_RC=$?;
|
|
311
355
|
printf '%s\n' "$SYNC_OUTPUT";
|
|
312
|
-
|
|
313
|
-
|
|
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";
|
|
314
365
|
else
|
|
315
|
-
|
|
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;
|
|
316
382
|
fi;
|
|
317
383
|
GATE="skipped";
|
|
318
384
|
if [ "$APPLIED" = "true" ] || [ "$SYNC_RC" -ne 0 ]; then
|
|
@@ -325,10 +391,17 @@ states:
|
|
|
325
391
|
echo "feature-transition: feature gate FAIL for feature $feature — inspect findings before reporting the transition complete" >&2;
|
|
326
392
|
fi;
|
|
327
393
|
else
|
|
328
|
-
echo "feature-transition: sync did not apply a transition (rc=$SYNC_RC, applied=$APPLIED) — feature gate skipped
|
|
394
|
+
echo "feature-transition: sync did not apply a transition (rc=$SYNC_RC, applied=$APPLIED) — feature gate skipped";
|
|
329
395
|
fi;
|
|
330
396
|
SYNC_STATUS="PASS";
|
|
331
|
-
if [ "$
|
|
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;
|
|
332
405
|
if [ "$SYNC_STATUS" = "PASS" ]; then
|
|
333
406
|
printf 'PASS\n' > ".spur/run/$__runId-wrapup-sync.status";
|
|
334
407
|
else
|
|
@@ -348,7 +421,7 @@ states:
|
|
|
348
421
|
onEnter:
|
|
349
422
|
- kind: hitl.confirm
|
|
350
423
|
options:
|
|
351
|
-
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?"
|
|
352
425
|
|
|
353
426
|
- id: done
|
|
354
427
|
description: >
|
|
@@ -358,12 +431,7 @@ states:
|
|
|
358
431
|
onEnter:
|
|
359
432
|
- kind: note
|
|
360
433
|
options:
|
|
361
|
-
message: "Wrap-up pipeline complete for
|
|
362
|
-
# Checkpoint write: record session state for resume (Phase 4, task 0171
|
|
363
|
-
# R3)
|
|
364
|
-
- kind: shell
|
|
365
|
-
options:
|
|
366
|
-
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."
|
|
367
435
|
|
|
368
436
|
- id: skipped
|
|
369
437
|
description: Terminal — wrap-up skipped (empty task list or operator abort).
|
|
@@ -394,27 +462,31 @@ transitions:
|
|
|
394
462
|
kind: shell
|
|
395
463
|
options:
|
|
396
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.
|
|
397
469
|
- from: task-resolve
|
|
398
470
|
to: skipped
|
|
399
|
-
description:
|
|
471
|
+
description: Validated task list is empty — skip wrap-up (only a validated [] may skip).
|
|
400
472
|
guard:
|
|
401
473
|
kind: shell
|
|
402
474
|
options:
|
|
403
|
-
command: 'test "$(
|
|
475
|
+
command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -eq 0'
|
|
404
476
|
- from: task-resolve
|
|
405
477
|
to: metrics-record
|
|
406
478
|
description: Proportional fast path — complete and consistent evidence bypasses doc-sync.
|
|
407
479
|
guard:
|
|
408
480
|
kind: shell
|
|
409
481
|
options:
|
|
410
|
-
command: 'test "$(
|
|
482
|
+
command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -gt 0 && test "$mode" = fast'
|
|
411
483
|
- from: task-resolve
|
|
412
484
|
to: doc-sync
|
|
413
485
|
description: Proportional safety path — missing/unknown/conflicting evidence routes to full doc-sync.
|
|
414
486
|
guard:
|
|
415
487
|
kind: shell
|
|
416
488
|
options:
|
|
417
|
-
command: 'test "$(
|
|
489
|
+
command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -gt 0 && test "$mode" != fast'
|
|
418
490
|
- from: task-resolve
|
|
419
491
|
to: failed
|
|
420
492
|
description: >
|
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",
|
|
@@ -46,8 +46,10 @@ CLI nouns for direct use, but they are not this corpus specialist's scope.
|
|
|
46
46
|
3. Run the noun's read/check/validate path before mutation where available.
|
|
47
47
|
4. Mutate only through `spur`; parse `--json` output when the verb advertises it.
|
|
48
48
|
5. Inspect each result before the next dependent operation; stop on structural or validation failure.
|
|
49
|
-
6. Run
|
|
50
|
-
`spur task check
|
|
49
|
+
6. Run affected-input checks after mutation (constitution T11): after task/feature batch writes,
|
|
50
|
+
run `spur task check <wbs>` / `spur feature check <id>` for each changed document and its
|
|
51
|
+
required linked evidence — not a corpus sweep. The explicit unsuppressed audit
|
|
52
|
+
(`spur task check --corpus --json`) is reserved for checker-policy changes (T10).
|
|
51
53
|
|
|
52
54
|
Workflow fit, mode selection, simplicity budgets, authoring, and tuning live in the workflow
|
|
53
55
|
references under `plugins/sp/skills/spur-cli/references/workflows/`; load them rather than copying
|
|
@@ -59,7 +61,7 @@ their runbook here.
|
|
|
59
61
|
|
|
60
62
|
- Use the source-local CLI when working in the Spur repository.
|
|
61
63
|
- Use `spur task update --section --from-file` for task section writes.
|
|
62
|
-
- Keep check-before/write/check-after evidence and the final scoped
|
|
64
|
+
- Keep check-before/write/check-after evidence and the final scoped validation result.
|
|
63
65
|
- Preserve declaration order and currently executing runs when changing workflows.
|
|
64
66
|
|
|
65
67
|
### Never
|
|
@@ -87,7 +89,8 @@ their runbook here.
|
|
|
87
89
|
### Gates
|
|
88
90
|
- pre-check: <result>
|
|
89
91
|
- post-check/validate: <result>
|
|
90
|
-
-
|
|
92
|
+
- scoped validation: <affected task/feature checks + linked evidence, or n/a; explicit T10
|
|
93
|
+
corpus audit only when checker policy changed>
|
|
91
94
|
```
|
|
92
95
|
|
|
93
96
|
## Platform Notes
|
|
@@ -46,4 +46,4 @@ vars as subsets of `--approve-taste` (`idea_approved` / `design_approved`). Pref
|
|
|
46
46
|
- `auto`/name: launch `spur workflow run idea-pipeline.yaml --async`, observe with one `workflow trace --follow`, and only report cancellation as stopped when `workflow cancel --json` returns `killed: true`.
|
|
47
47
|
- `Skill(skill="sp:spur-dev", args="idea $ARGUMENTS")`
|
|
48
48
|
- Stage contract (discovery → idea-eval → feature-create → AC → feature-check → system-design →
|
|
49
|
-
decompose → batch-create → handoff): `plugins/sp/skills/spur-dev/references/dev-operations.md` § idea.
|
|
49
|
+
decompose → batch-create → ready-prepare → handoff): `plugins/sp/skills/spur-dev/references/dev-operations.md` § idea.
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.75",
|
|
4
4
|
"description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
|
|
5
5
|
"extensions": {
|
|
6
6
|
"pi": ["./hooks/pi/guard-extension.ts"]
|
|
@@ -187,7 +187,9 @@ spur task record 0040 --verdict-file .spur/run/0040-verdict.json --solution-from
|
|
|
187
187
|
- **`--solution-from-diff`** backfills `Solution` from `git diff -U0` hunk headers **only when the
|
|
188
188
|
Solution section is still bare** — a safety net, not an overwrite.
|
|
189
189
|
- **`--transition <status>`** optionally advances the lifecycle after writing (e.g. `testing`).
|
|
190
|
-
`
|
|
190
|
+
`--transition done` is a **guarded** completion path: it requires a PASS verdict, auto-walks
|
|
191
|
+
`wip → testing → done`, and auto-creates the pipeline run-link (0436 R4); a non-PASS verdict to
|
|
192
|
+
`done` errors instead of transitioning, and the normal done gates still apply (0108).
|
|
191
193
|
|
|
192
194
|
The verdict shape (`wbs`, `verdict`, `requirements[]`, `checks[]`) and the rendered tables are
|
|
193
195
|
documented in [tasks/verbs.md](tasks/verbs.md).
|
|
@@ -652,21 +652,35 @@ Field semantics (enforced by `parseCheckpointMetadata` / `checkpointStaleness`):
|
|
|
652
652
|
up once retention expires.
|
|
653
653
|
- `source_commit` pins repository HEAD at write time; drift staleness is reported, not hidden.
|
|
654
654
|
- `artifacts` lists referenced run files; a missing artifact makes the checkpoint stale.
|
|
655
|
-
|
|
656
|
-
|
|
655
|
+
|
|
656
|
+
**Writer cadence (0784 R4).** There is exactly one canonical writer: the `task-pipeline` done
|
|
657
|
+
state's terminal checkpoint (`status: done`, real HEAD, run id from `$__runId`, and `$wbs`-expanded
|
|
658
|
+
artifact paths). It is a plain `shell` step — checkpoints are working memory, not CLI-gated corpus.
|
|
659
|
+
The `feature-dev`, `wrapup-pipeline`, and `idea-pipeline` pipelines used to echo pseudo-checkpoints
|
|
660
|
+
("checkpoint: <workflow> done ...") that violated the canonical schema; those writers were removed
|
|
661
|
+
in 0784 — the persisted run row is the authoritative terminal record, and a non-canonical echo
|
|
662
|
+
cannot be resumed, routed, or reclaimed safely.
|
|
663
|
+
|
|
664
|
+
**Engine state vs checkpoint status (0784 R3).** The persisted run row's status is authoritative;
|
|
665
|
+
a checkpoint's `status` is an advisory projection written by consumers. On resume
|
|
666
|
+
(`workflow continue`), a checkpoint associated with the paused run must project a nonterminal
|
|
667
|
+
engine state — `pending`, `running`, or `approved` are accepted; `done`/`failed`/`cancelled`/
|
|
668
|
+
`skipped`, a missing status, or any unknown value refuse the resume with a named reason. The
|
|
669
|
+
engine owns no persisted `paused` checkpoint status.
|
|
657
670
|
|
|
658
671
|
**Write checkpoints after:**
|
|
659
672
|
|
|
660
|
-
-
|
|
661
|
-
-
|
|
662
|
-
|
|
663
|
-
- Every terminal state (`done`, `failed`, `cancelled`, `skipped`).
|
|
673
|
+
- Terminal task completion in `task-pipeline` (the canonical writer above).
|
|
674
|
+
- Any additional consumer-specific checkpoints you genuinely need for local resume; keep the
|
|
675
|
+
canonical schema — non-canonical files are ignored by routing and cleanup.
|
|
664
676
|
|
|
665
677
|
**Read checkpoints when:**
|
|
666
678
|
|
|
667
679
|
- `/sp:dev-run --continue` or `/sp:dev-runall --continue` is used.
|
|
668
680
|
- The operator asks to resume a task or feature.
|
|
669
|
-
- A workflow run is paused and later continued (`spur workflow continue <run-id>`).
|
|
681
|
+
- A workflow run is paused and later continued (`spur workflow continue <run-id>`). Resume
|
|
682
|
+
validates freshness in the run's recorded launch workdir (0784 R1/R3): artifacts resolve relative
|
|
683
|
+
to that workdir, and git HEAD is probed there — never in the ambient process cwd.
|
|
670
684
|
|
|
671
685
|
**Rules:**
|
|
672
686
|
|
|
@@ -678,7 +692,8 @@ Field semantics (enforced by `parseCheckpointMetadata` / `checkpointStaleness`):
|
|
|
678
692
|
- **Retention.** Terminal checkpoints older than `workflowLogRetentionDays` with no active run are
|
|
679
693
|
reclaimed by `spur workflow clean` (task 0711 R5–R8); non-terminal checkpoints are always kept.
|
|
680
694
|
- **Not a validated corpus.** Checkpoints are working memory. They are overwritten when a session
|
|
681
|
-
resumes and re-checkpoints. They are NOT authoritative task state — the task file
|
|
695
|
+
resumes and re-checkpoints. They are NOT authoritative task state — the task file and the
|
|
696
|
+
persisted run row are.
|
|
682
697
|
- **One file per session.** The `session_id` is `<date>-<wbs-or-feature>`. A resumed session
|
|
683
698
|
overwrites the same file.
|
|
684
699
|
- **Operator-readable.** The YAML frontmatter is machine-parseable; the body is free-form markdown
|
|
@@ -341,7 +341,7 @@ must not be changed without updating the backing skill.
|
|
|
341
341
|
- `--approve-taste` — with `--auto`, skip **all** remaining taste pauses this run (idea-eval + design-approval). Sets `idea_approved=true` and `design_approved=true`.
|
|
342
342
|
Aliases (prefer `--approve-taste`): `--idea-approved` → `idea_approved`; `--design-approved` → `design_approved`. There is **no** `--design` force flag.
|
|
343
343
|
- **Backing:** `idea-pipeline.yaml` through the inline driver for omitted/`inline`, or `spur workflow run idea-pipeline.yaml --async` for `auto`/name.
|
|
344
|
-
- **Behavior:** Builds vars from the table above and drives the idea pipeline. Flow: discovery → **idea-eval** (taste; reject → cancelled) → feature-create → ac-generate → feature-check → system-design (conditional) → design-approval (taste) → decompose → batch-create → handoff. STOPS at handoff — no task execution, no pipeline nesting. Headless runs use one `trace --follow`; cancellation is reported stopped only when `workflow cancel --json` returns `killed: true`.
|
|
344
|
+
- **Behavior:** Builds vars from the table above and drives the idea pipeline. Flow: discovery → **idea-eval** (taste; reject → cancelled) → feature-create → ac-generate → feature-check → system-design (conditional) → design-approval (taste) → decompose → batch-create (`--skip-ready`) → ready-prepare (ready checklist per created task + ready-evidence sidecar, 0788) → handoff. STOPS at handoff — no task execution, no pipeline nesting. Headless runs use one `trace --follow`; cancellation is reported stopped only when `workflow cancel --json` returns `killed: true`.
|
|
345
345
|
- **Delegation:** Host-session inline driver by default; explicit executor selection uses the async workflow worker.
|
|
346
346
|
- **Idea-evaluation gate:** After discovery, operator reviews `.spur/run/idea-eval-report.md` ([idea-evaluation.md](idea-evaluation.md)). Approve continues; reject/cancel → no feature. Under `--auto`, still pauses unless taste pre-cleared (`--approve-taste` / alias). Enhanced idea is a sidecar — `vars.idea` is not overwritten.
|
|
347
347
|
- **Design package (`--skip-design` only):**
|
|
@@ -149,7 +149,10 @@ passed, so provenance denied first and Review L3 denied on the retry.
|
|
|
149
149
|
|
|
150
150
|
| # | Gate layer | Triggers denial when | Remediation |
|
|
151
151
|
|---|------------|----------------------|-------------|
|
|
152
|
-
| 1 | **Strict-core + verdict artifact** (`spur task check <wbs> --strict-core` + `done-transition-guard.ts`) | The strict-core check fails, or `.spur/run/<wbs>-verdict.json` is **missing** or has a non-PASS aggregate. **Missing artifact is a deny** (not a silent allow — closes the 0349 "done without verdict" class). The aggregate is recomputed from requirement/AC rows; the harsher of stored and computed wins. | Re-run `/sp:dev-verify <wbs>` until PASS (writes the artifact), or explicitly override with `spur task update <wbs> done --force-done --reason "<why>"`. Docs-only pipelines
|
|
152
|
+
| 1 | **Strict-core + verdict artifact** (`spur task check <wbs> --strict-core` + `done-transition-guard.ts`) | The strict-core check fails, or `.spur/run/<wbs>-verdict.json` is **missing** or has a non-PASS aggregate. **Missing artifact is a deny** (not a silent allow — closes the 0349 "done without verdict" class). The aggregate is recomputed from requirement/AC rows; the harsher of stored and computed wins. | Re-run `/sp:dev-verify <wbs>` until PASS (writes the artifact), or explicitly override with `spur task update <wbs> done --force-done --reason "<why>"`. Docs-only pipelines (`docs-pipeline.yaml`) meet the same layer: read-only measured verification
|
|
153
|
+
(answer file + `spur task verdict`) writes the standard `.spur/run/<wbs>-verdict.json` artifact
|
|
154
|
+
under proof-input digest bracketing; missing or non-PASS evidence is a refusal, never a synthetic
|
|
155
|
+
PASS stub. |
|
|
153
156
|
| 2 | **Provenance guard** (`lifecycle-adapter.ts`) | No pipeline-kind run link exists for `<wbs>`. | Run `/sp:dev-run <wbs>` through the full pipeline, use `/sp:dev-run <wbs> --mode implement --auto --next` for the explicit step chain, or record the audited bypass with `SPUR_PROVENANCE_OVERRIDE=1`. |
|
|
154
157
|
| 3 | **Review L3** (`task-check.ts`) | `### Review` is empty, placeholder-only, or lacks a populated P1–P4 findings table. | Run `/sp:dev-review <wbs>`; verify cannot write Review because of the Step 10 prohibition above. |
|
|
155
158
|
|