@gobing-ai/spur 0.3.74 → 0.3.76
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 +6 -0
- package/config/rules/README.md +1 -1
- package/config/rules/strict/runtime-boundaries.yaml +1 -0
- package/config/rules/typescript/no-eslint-suppressions.yaml +30 -0
- 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 +9 -9
- package/plugins/sp/agents/expert-spur.md +7 -4
- package/plugins/sp/commands/dev-idea.md +1 -1
- package/plugins/sp/commands/dev-refineall.md +2 -2
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/dogfood-testing/SKILL.md +7 -4
- package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +9 -3
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +5 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +3 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +3 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +23 -8
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +3 -3
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +4 -1
- package/schemas/spur-config.schema.json +4 -0
- package/spur.js +3675 -838
- package/web/_astro/{BoardApp.C60RJZRj.js → BoardApp.CHQ1lycZ.js} +92 -92
- package/web/_astro/BoardApp.DV9kx0wo.js +1 -0
- package/web/_astro/{TaskDetail.Guk8VuNu.js → TaskDetail.GKfQJ60c.js} +1 -1
- package/web/_astro/{arc.CAZtlWJM.js → arc.DWEtA3Tx.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.9-XbF_Tz.js → architectureDiagram-3BPJPVTR.DB42oWmP.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DC4WLx3L.js → blockDiagram-GPEHLZMM.rhv-zNQV.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.JgjUQMgG.js → c4Diagram-AAUBKEIU.Ci4-4VvY.js} +1 -1
- package/web/_astro/channel.BAI6xLeV.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.D_JFiXa-.js → chunk-2J33WTMH.Cc9veUgf.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.Bk_RxoeT.js → chunk-4BX2VUAB.Bec9c4eI.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.a_Rj2VxL.js → chunk-55IACEB6.DoV8S1iB.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.C2NoS1U6.js → chunk-727SXJPM.DwR-Qlyj.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.D1Xn6CNa.js → chunk-AQP2D5EJ.ND_a81WY.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.BhJrSBsX.js → chunk-FMBD7UC4.Wv_jwG48.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.ByVGduYQ.js → chunk-ND2GUHAM.CXKXCMmp.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.B47Paunq.js → chunk-QZHKN3VN.nkaoNYQq.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.BERMSD5C.js → classDiagram-4FO5ZUOK.cMQcVlQu.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.BERMSD5C.js → classDiagram-v2-Q7XG4LA2.cMQcVlQu.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.O_rHGlhU.js → cose-bilkent-S5V4N54A.OaDJ7Mr2.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.qCW2GdNp.js → cynefin-OW5HDTMX.Chi8IphF.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.C5Y4lH_u.js → dagre-BM42HDAG.CzK2t_Fp.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DxZmRXxF.js → diagram-2AECGRRQ.DRvxlVS7.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.qSxZeYSK.js → diagram-5GNKFQAL.CnYvNdwA.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.-7vl3bXs.js → diagram-KO2AKTUF.CpLpMw5R.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BHVV-UH3.js → diagram-LMA3HP47.JTb78qUA.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.DHAuW9sK.js → diagram-OG6HWLK6.Bk-1jDIb.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.DMRLQwPN.js → erDiagram-TEJ5UH35.D8hN9GZq.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.B3EpPp_8.js → flowDiagram-I6XJVG4X.-6zQr6m5.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.BgUrExXM.js → ganttDiagram-6RSMTGT7.DboLQ9ca.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CU-9yVN4.js → gitGraphDiagram-PVQCEYII.4tYvJKGR.js} +1 -1
- package/web/_astro/index.Dcr_8fiK.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.CvVTCRLe.js → infoDiagram-5YYISTIA.Bd9rXpsB.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BUzOfKPf.js → ishikawaDiagram-YF4QCWOH.CvMoaf67.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.BBTb7ziz.js → journeyDiagram-JHISSGLW.Ccy1CA7y.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.CTQQr70i.js → kanban-definition-UN3LZRKU.0MaMqHNS.js} +1 -1
- package/web/_astro/{linear.CLKlJPiS.js → linear.CHXgcIbN.js} +1 -1
- package/web/_astro/{mermaid.core.BA4wFhLP.js → mermaid.core.Ca-kcelG.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.t1vG2l5e.js → mindmap-definition-RKZ34NQL.BUIDlHa0.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.C8zJhyTu.js → pieDiagram-4H26LBE5.2dX3CU1s.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.DqYYvb7T.js → quadrantDiagram-W4KKPZXB.B3LBlRiv.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.lzfDZ9nI.js → requirementDiagram-4Y6WPE33.X12I2uNx.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.lm6N5ORM.js → sankeyDiagram-5OEKKPKP.BXohIHqx.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.E6_IV4Dj.js → sequenceDiagram-3UESZ5HK.C37ZIUzg.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.Bnv6Ok2p.js → stateDiagram-AJRCARHV.BRgz317z.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.mJHH_Ng_.js → stateDiagram-v2-BHNVJYJU.7VYSXN9-.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.BhRgqSLa.js → timeline-definition-PNZ67QCA.BVNz_HiN.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.pjb5DMC9.js → vennDiagram-CIIHVFJN.CHVDkPX4.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CW20KUng.js → wardleyDiagram-YWT4CUSO.EQQ_qT9v.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.vX9_yuMl.js → xychartDiagram-2RQKCTM6.DrAT9WoP.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.76",
|
|
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",
|
|
@@ -53,14 +53,14 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@commander-js/extra-typings": "^14.0.0",
|
|
56
|
-
"@gobing-ai/ts-db": "^0.4.
|
|
57
|
-
"@gobing-ai/ts-ai-runner": "^0.4.
|
|
58
|
-
"@gobing-ai/ts-dual-workflow-engine": "^0.4.
|
|
59
|
-
"@gobing-ai/ts-infra": "^0.4.
|
|
60
|
-
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.
|
|
61
|
-
"@gobing-ai/ts-rule-engine": "^0.4.
|
|
62
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
63
|
-
"@gobing-ai/ts-utils": "^0.4.
|
|
56
|
+
"@gobing-ai/ts-db": "^0.4.57",
|
|
57
|
+
"@gobing-ai/ts-ai-runner": "^0.4.57",
|
|
58
|
+
"@gobing-ai/ts-dual-workflow-engine": "^0.4.57",
|
|
59
|
+
"@gobing-ai/ts-infra": "^0.4.57",
|
|
60
|
+
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.57",
|
|
61
|
+
"@gobing-ai/ts-rule-engine": "^0.4.57",
|
|
62
|
+
"@gobing-ai/ts-runtime": "^0.4.57",
|
|
63
|
+
"@gobing-ai/ts-utils": "^0.4.57",
|
|
64
64
|
"@types/bun": "1.3.14",
|
|
65
65
|
"@types/figlet": "^1.7.0",
|
|
66
66
|
"@types/node-notifier": "8.0.5",
|
|
@@ -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.
|
|
@@ -23,7 +23,7 @@ operation, applied to a resolved set (typically every task under a feature). Pas
|
|
|
23
23
|
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing refinement. | omit |
|
|
24
24
|
| `--auto` | Skip objective HITL gates. | off |
|
|
25
25
|
| `--keep-going` | Continue past per-task failures. | off |
|
|
26
|
-
| `--status` `<s>` | Only refine tasks in
|
|
26
|
+
| `--status` `<s>` | Only refine tasks in these statuses (applied in-agent to the frozen set). | `backlog` + `todo` |
|
|
27
27
|
| `--json` | Emit structured JSON. | off |
|
|
28
28
|
| `--worktree` `[<name>]` | Run the batch in an isolated git worktree; FF-merge on success, retain on failure. Bare `--worktree` creates a fresh tree; `--worktree <name>` adopts an existing worktree by name/path/branch. | off |
|
|
29
29
|
|
|
@@ -39,7 +39,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
|
|
|
39
39
|
Flags: `--feature` (sugar for `feature:<id>`), `--tasks <selector>`, shared refine flags
|
|
40
40
|
(`--focus`, `--description`, `--depth`, `--agent`, `--auto`),
|
|
41
41
|
plus `--keep-going`,
|
|
42
|
-
`--status` (default `backlog
|
|
42
|
+
`--status` (default `backlog` + `todo`),
|
|
43
43
|
`--json`, `--worktree` `[<name>]` (run the batch in an isolated git worktree — FF-merge onto the base ref on
|
|
44
44
|
full success, retain intact on any failure/halt/non-FF; bare form creates a fresh tree, `<name>`
|
|
45
45
|
form adopts an existing worktree by name/path/branch; see `execution-batch.md` § Worktree
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.76",
|
|
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"]
|
|
@@ -75,8 +75,11 @@ The command forwards these via `$ARGUMENTS`:
|
|
|
75
75
|
> independent mutation sources is present:
|
|
76
76
|
>
|
|
77
77
|
> - **Pipeline-driving testees** — tokens
|
|
78
|
-
>
|
|
79
|
-
> `runall`, `wrapall`, `run`, `wrap`, `idea
|
|
78
|
+
> <!-- pipeline-tokens:start -->
|
|
79
|
+
> [`--next`, `dev-runall`, `dev-wrapall`, `dev-refineall`, `dev-verifyall`, `dev-run`, `dev-wrap`, `dev-idea`,
|
|
80
|
+
> `refineall`, `verifyall`, `runall`, `wrapall`, `run`, `wrap`, `idea`]
|
|
81
|
+
> <!-- pipeline-tokens:end -->
|
|
82
|
+
> matched as a **distinct hyphen-word**
|
|
80
83
|
> (machine-checked by
|
|
81
84
|
> [`detectPipelineDriving`](../../scripts/dogfood-testing/detect-pipeline-driving.ts);
|
|
82
85
|
> see [§Pipeline-driving word-boundary contract](#pipeline-driving-word-boundary-contract))
|
|
@@ -335,8 +338,8 @@ node "$(superskill script path sp dogfood-testing/detect-pipeline-driving.mjs)"
|
|
|
335
338
|
|
|
336
339
|
| Token shape | Examples | Matches | Rejects |
|
|
337
340
|
|-------------|----------|---------|---------|
|
|
338
|
-
| Flag / complete |
|
|
339
|
-
| Bare noun |
|
|
341
|
+
| Flag / complete | <!-- pipeline-tokens:start -->`--next`, `dev-run`, `dev-runall`, `dev-wrap`, `dev-wrapall`, `dev-idea`, `dev-refineall`, `dev-verifyall`<!-- pipeline-tokens:end --> | `/sp:dev-run 0125`, bare `--next` | `--next-gen`, `dev-runner` |
|
|
342
|
+
| Bare noun | <!-- pipeline-tokens:start -->`run`, `runall`, `wrap`, `wrapall`, `idea`, `refineall`, `verifyall`<!-- pipeline-tokens:end --> | `task run 0042` | `runaway`, `wrapper`, `idealist` |
|
|
340
343
|
|
|
341
344
|
`-` is a **word character** for boundaries: a token must be a distinct hyphen-word. Contract tests:
|
|
342
345
|
`plugins/sp/tests/dogfood-testing/pipeline-detect.test.ts`. Helpers:
|
|
@@ -9,8 +9,8 @@ see_also:
|
|
|
9
9
|
|
|
10
10
|
Task bodies are edited section-by-section through `spur task update --section <name> --from-file
|
|
11
11
|
<path>`. The write is **file-wins and crash-safe** (atomic write): the named section's body is
|
|
12
|
-
replaced wholesale from the file you point at
|
|
13
|
-
body in a file first.
|
|
12
|
+
replaced wholesale from the file you point at — with one exception, `Q&A`, which appends (see
|
|
13
|
+
below). There is no inline-body flag — always stage the new body in a file first.
|
|
14
14
|
|
|
15
15
|
For **pipeline output**, section authorship is one-writer-per-section (F92 0593 R1):
|
|
16
16
|
`Testing` comes from `spur task record` (deterministic, from a verify verdict artifact — the
|
|
@@ -23,6 +23,8 @@ for `Plan`, `Acceptance Criteria`, hand-authored `Solution`, and any narrative s
|
|
|
23
23
|
|
|
24
24
|
1. **Assemble the full section body** in a temp file. The body is everything *under* the `###`
|
|
25
25
|
heading — do not include the heading line itself; the CLI owns the heading.
|
|
26
|
+
Sub-headings inside the body MUST be `####` or deeper: a `###` in a body parses as a new
|
|
27
|
+
top-level section and trips `L2.disallowed-section` (task 0787 grew 8 phantom sections this way).
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
30
|
cat > /tmp/review.md <<'EOF'
|
|
@@ -42,7 +44,11 @@ for `Plan`, `Acceptance Criteria`, hand-authored `Solution`, and any narrative s
|
|
|
42
44
|
|
|
43
45
|
3. The whole `### Review` body is now that file's contents. To amend rather than overwrite, read
|
|
44
46
|
the current body (`spur task show 0040`), edit the temp file to the full desired state, and
|
|
45
|
-
replace again — there is no append mode.
|
|
47
|
+
replace again — there is no append mode for ordinary sections.
|
|
48
|
+
|
|
49
|
+
**`Q&A` is the exception.** `--section "Q&A"` APPENDS a timestamped `#### Q&A entry — <ISO>`
|
|
50
|
+
block rather than replacing the section. Start the body with `<!-- qa:replace -->` to replace it
|
|
51
|
+
wholesale.
|
|
46
52
|
|
|
47
53
|
`--section` **requires** `--from-file` (exit `2` otherwise). Section names match the DD-08 headings
|
|
48
54
|
exactly: `Background`, `Requirements`, `Acceptance Criteria`, `Q&A`, `Design`, `Plan`, `Solution`, `Root Cause`, `Testing`, `Review`, `References`, `History`, `Notes` (universal sections are `History`, `References`, `Notes`; `Root Cause` is carried by the `issue` template variant).
|
|
@@ -61,8 +61,10 @@ frontmatter scalar.
|
|
|
61
61
|
to avoid orphaned nested lifecycle runs). **It is not a guard bypass** — the `wip→testing` and
|
|
62
62
|
`testing→done` `check` gates above still run; the CLI evaluates them inline when the FSM guard
|
|
63
63
|
does not. `--force-done` waives the verify **verdict** only, never the section matrix.
|
|
64
|
-
- **Section** (`--section` **requires** `--from-file`):
|
|
65
|
-
|
|
64
|
+
- **Section** (`--section` **requires** `--from-file`): writes the named section body from the file.
|
|
65
|
+
Most sections replace wholesale. Exception: `--section "Q&A"` APPENDS a timestamped
|
|
66
|
+
`#### Q&A entry — <ISO>` block; start the body with `<!-- qa:replace -->` to replace the section
|
|
67
|
+
wholesale. No inline-body flag. Section names: `Background`, `Requirements`, `Acceptance Criteria`, `Q&A`, `Design`, `Plan`, `Solution`, `Testing`, `Review`, `References`, `History`, `Notes`.
|
|
66
68
|
- **Frontmatter** (`--feature <id>`, `--priority <p>`): sets the scalar frontmatter field on an
|
|
67
69
|
existing task — the only post-create path, allow-listed to `feature_id` / `parent_wbs` / `priority`.
|
|
68
70
|
|
|
@@ -85,7 +87,7 @@ Flags: `--folder <path>`, `--json`. Exit codes: `0` success, `1` error, `2` usag
|
|
|
85
87
|
|
|
86
88
|
## `sections <wbs> <op> [name]`
|
|
87
89
|
|
|
88
|
-
CLI-safe, matrix-enforced task section mutation. Section names are validated against canonical sections (`Background`, `Requirements`, `Acceptance Criteria`, `Q&A`, `Design`, `Plan`, `Solution`, `Root Cause`, `Testing`, `Review`, `References`, `History`, `Notes`). Universal sections (`History`, `References`, `Notes`) are always allowed; `Root Cause` is carried by the `issue` template variant.
|
|
90
|
+
CLI-safe, matrix-enforced task section mutation. Section names are validated against canonical sections (`Background`, `Requirements`, `Acceptance Criteria`, `Q&A`, `Design`, `Plan`, `Solution`, `Root Cause`, `Testing`, `Review`, `References`, `History`, `Notes`). Universal sections (`History`, `References`, `Notes`) are always allowed; `Root Cause` is carried by the `issue` template variant. `Q&A` on `update --section` appends rather than replacing (see the Section bullet above); `<!-- qa:replace -->` forces a wholesale replace.
|
|
89
91
|
|
|
90
92
|
| Op | Usage | Description |
|
|
91
93
|
| --- | --- | --- |
|