@gobing-ai/spur 0.3.69 → 0.3.71
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/corpus-baseline.json +0 -18
- package/config/plugin-scripts.json +8 -0
- package/config/workflow-composition-baseline.json +123 -377
- package/config/workflows/idea-pipeline.yaml +4 -3
- package/config/workflows/task-pipeline.yaml +58 -9
- package/config/workflows/wrapup-pipeline.yaml +10 -4
- package/package.json +9 -9
- package/plugins/sp/commands/dev-fixall.md +4 -3
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/task-evidence-precheck.ts +181 -0
- package/plugins/sp/scripts/verify-answer-lint.ts +395 -0
- package/plugins/sp/skills/code-implementation/SKILL.md +1 -1
- package/plugins/sp/skills/code-verification/SKILL.md +11 -10
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +10 -0
- package/plugins/sp/skills/spec-decomposition/SKILL.md +1 -1
- package/plugins/sp/skills/spur-cli/references/history.md +8 -6
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -2
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +8 -1
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +17 -1
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +25 -0
- package/spur.js +1385 -291
- package/web/_astro/BoardApp.BnjsI80-.js +178 -0
- package/web/_astro/BoardApp.SJcrHBZp.js +1 -0
- package/web/_astro/{TaskDetail.Dl2Eaj1w.js → TaskDetail.BvkKvo57.js} +1 -1
- package/web/_astro/{arc.uG14rp8A.js → arc.DuEIzPMi.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.Dye6uD_x.js → architectureDiagram-3BPJPVTR.D0dUxCjA.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.B9Pkh7Hb.js → blockDiagram-GPEHLZMM.Cv60iOpM.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.C2x7SC_X.js → c4Diagram-AAUBKEIU.Ddx7WGhb.js} +1 -1
- package/web/_astro/channel.tWfETQvX.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.D2p4-nWk.js → chunk-2J33WTMH.BGKzU_1t.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.S-6jf33o.js → chunk-4BX2VUAB.FlApjIIH.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DVXj4Fdh.js → chunk-55IACEB6.CDfmvDeW.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.Dz689FMN.js → chunk-727SXJPM.T9bc-xir.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.KxYj5TnI.js → chunk-AQP2D5EJ.BiTc-MQo.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.itTQyHQB.js → chunk-FMBD7UC4.he4KrHni.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.euSrbJf5.js → chunk-ND2GUHAM.D05XuUuJ.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.OWASJRQy.js → chunk-QZHKN3VN.CA2NThIE.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.BLvrlpNO.js → classDiagram-4FO5ZUOK.Couj-zYZ.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.BLvrlpNO.js → classDiagram-v2-Q7XG4LA2.Couj-zYZ.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.XBF-rmyD.js → cose-bilkent-S5V4N54A.B8YYW7NG.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.DlCx762Z.js → cynefin-OW5HDTMX.BExFdiin.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.D17Rshxv.js → dagre-BM42HDAG.BybKbz3q.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.AhBIVJC8.js → diagram-2AECGRRQ.UyRTSl9n.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.C9ximjyC.js → diagram-5GNKFQAL.BrxCucBf.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CZb7Ru_9.js → diagram-KO2AKTUF.CdE5oy5J.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BW7LwqoS.js → diagram-LMA3HP47.BJLgdosK.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.XC025W0V.js → diagram-OG6HWLK6.CUynieTU.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.CpMXmBDP.js → erDiagram-TEJ5UH35.C0vS6DJv.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.D2ednJWg.js → flowDiagram-I6XJVG4X.T3QLi_en.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.BjL9FGKO.js → ganttDiagram-6RSMTGT7.BNsk3w9Z.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.B90g1VGk.js → gitGraphDiagram-PVQCEYII.Mwe2I4V6.js} +1 -1
- package/web/_astro/index.9npdrEIr.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.RqgycKtQ.js → infoDiagram-5YYISTIA.BlcjLmtc.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.Ctn-zt6a.js → ishikawaDiagram-YF4QCWOH.js8qeS0h.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.DJhT8Ctp.js → journeyDiagram-JHISSGLW.CM6UK0a4.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BY1QdejI.js → kanban-definition-UN3LZRKU.a6ihOzMd.js} +1 -1
- package/web/_astro/{linear.Di7YObSt.js → linear.CsIB2jFu.js} +1 -1
- package/web/_astro/{mermaid.core.CbxtJS3Q.js → mermaid.core.Br2Fo22q.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.CxvR4g_J.js → mindmap-definition-RKZ34NQL.29inC1Mk.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.jNWqnBHH.js → pieDiagram-4H26LBE5.C9CxG_Kf.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.BCp12MbA.js → quadrantDiagram-W4KKPZXB.6qo9MOJM.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.Dxhm4TyR.js → requirementDiagram-4Y6WPE33.DN07zrP5.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.BtQXp4J9.js → sankeyDiagram-5OEKKPKP.BSw5o173.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.BWEM1R_Q.js → sequenceDiagram-3UESZ5HK.LJPzySKw.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.BG3wUkWB.js → stateDiagram-AJRCARHV.ClNjEiKV.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.BLtMeFVP.js → stateDiagram-v2-BHNVJYJU.c6Z-_WfX.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.D5fHo0az.js → timeline-definition-PNZ67QCA.CIMR-87j.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.0DcuMluU.js → vennDiagram-CIIHVFJN.BnRSRI9I.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.BZ-dxgHm.js → wardleyDiagram-YWT4CUSO.uN08C3gv.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.Bg-XWF7z.js → xychartDiagram-2RQKCTM6.BjbEvfuq.js} +1 -1
- package/web/index.html +2 -2
- package/web/_astro/BoardApp.BQFbkeqq.js +0 -178
- package/web/_astro/BoardApp.CTkqrhWd.js +0 -1
- package/web/_astro/channel.Dsvulp7W.js +0 -1
- package/web/_astro/index.BVXdIsZV.css +0 -1
|
@@ -99,13 +99,14 @@ states:
|
|
|
99
99
|
boolean signal written to .spur/run/${vars.__runId}-idea-needs-design.json — this determines
|
|
100
100
|
whether the system-design state runs. As its terminal artifact for the idea path,
|
|
101
101
|
brainstorm also emits the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md
|
|
102
|
-
(template:
|
|
102
|
+
(template: the `sp:spur-dev` skill's `idea-evaluation` reference — named by skill, not by
|
|
103
|
+
repo path, because `spur init` never scaffolds `plugins/sp/` into a seeded project).
|
|
103
104
|
expectFile fails a silent no-op discovery (no eval report).
|
|
104
105
|
onEnter:
|
|
105
106
|
- kind: agent.run
|
|
106
107
|
options:
|
|
107
108
|
agent: ${vars.planningAgent}
|
|
108
|
-
input: "Run sp:brainstorm for the idea: ${vars.idea}. The skill owns the approach-generation, design summary, and `needs_design` signal criteria; emit .spur/run/${vars.__runId}-idea-needs-design.json ({\"needs_design\": true|false}) and the design summary per the skill's `Design Approval Gate` and `The needs_design signal` sections. Also emit the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md per
|
|
109
|
+
input: "Run sp:brainstorm for the idea: ${vars.idea}. The skill owns the approach-generation, design summary, and `needs_design` signal criteria; emit .spur/run/${vars.__runId}-idea-needs-design.json ({\"needs_design\": true|false}) and the design summary per the skill's `Design Approval Gate` and `The needs_design signal` sections. Also emit the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md per the `sp:spur-dev` skill's `idea-evaluation` reference (urgency/necessity 0–5, premises, pros/cons, alternatives, enhanced idea, recommendation). At the END of the report, append a provenance footer block of the exact form: `---\\nrun_id: ${vars.__runId}\\ngenerated_at: <RFC3339 timestamp>\\n---` (omit the footer only if run_id is empty)."
|
|
109
110
|
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
110
111
|
role: planner
|
|
111
112
|
expectFile: .spur/run/${vars.__runId}-idea-eval-report.md
|
|
@@ -298,7 +299,7 @@ states:
|
|
|
298
299
|
- kind: agent.run
|
|
299
300
|
options:
|
|
300
301
|
agent: ${vars.planningAgent}
|
|
301
|
-
input: "Run sp:spec-decomposition for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and design doc. SIZING FIRST, before any JSON: apply the skill's `Default to NOT decomposing` rubric to the whole unit of work — if it scores 0-2 the correct output is a ONE-entry batch, not many. Scenario count is not task count: merge scenarios that one task delivers (same file surface, same subsystem, or unreadable apart in review), and list every scenario a task covers in its background. Merging never costs AC coverage — one task may carry several scenarios. Do not emit one entry per scenario or per requirement by reflex. Then produce a task-batch JSON array at .spur/run/${vars.__runId}-idea-task-batch.json, validated against
|
|
302
|
+
input: "Run sp:spec-decomposition for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and design doc. SIZING FIRST, before any JSON: apply the skill's `Default to NOT decomposing` rubric to the whole unit of work — if it scores 0-2 the correct output is a ONE-entry batch, not many. Scenario count is not task count: merge scenarios that one task delivers (same file surface, same subsystem, or unreadable apart in review), and list every scenario a task covers in its background. Merging never costs AC coverage — one task may carry several scenarios. Do not emit one entry per scenario or per requirement by reflex. Then produce a task-batch JSON array at .spur/run/${vars.__runId}-idea-task-batch.json, validated against task-batch.schema.json. Schema-permitted fields per entry: `name`, `background`, `requirements`, `feature_id`, `parent_wbs`, `priority`, `tags`, `template` — schema validation rejects anything else. Acceptance Criteria, Design, and Plan sections are filled in by the per-task refine step after batch-create, NOT at decompose time. Validate locally against the schema before emitting. Also emit the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json: a JSON array (one entry per batch item) of `{ name: <exact batch item name>, depends_on_names: [<batch item names>] }` declaring ordering/dependencies between the batch items; use `[]` when no ordering exists. Every `name` and every dependency must match exactly one batch item `name` — it is private workflow data, not part of task-batch.schema.json."
|
|
302
303
|
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
303
304
|
role: planner
|
|
304
305
|
expectFile: .spur/run/${vars.__runId}-idea-task-batch.json
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# agent / spurBin — executor + spur binary (CLI overrides spurBin)
|
|
20
20
|
# stepTimeoutMs — agent.run budget for review/verify/test-fix (ms)
|
|
21
21
|
# implementTimeoutMs — implement agent.run budget (ms)
|
|
22
|
-
# qualityGateCmd — project gate (default: bun run
|
|
22
|
+
# qualityGateCmd — project gate (default: bun run spur-check)
|
|
23
23
|
# qualityGateMaxFixAttempts — max /sp:dev-fixall hops after a red gate (default: 2)
|
|
24
24
|
#
|
|
25
25
|
# Seeded by `spur init`. agent.run inputs are pure slash commands (ADR-043).
|
|
@@ -104,7 +104,12 @@ vars:
|
|
|
104
104
|
# and the fixall slash input all use this same var so the command stays single-sourced.
|
|
105
105
|
# TRUSTED CONFIG ONLY — this string is executed via `sh -c` (see test/test-recheck). Never
|
|
106
106
|
# interpolate untrusted operator/LLM input into qualityGateCmd (task 0436 SECUA residual).
|
|
107
|
-
|
|
107
|
+
# NO `format` PREFIX: `implement` already ran `$formatCmd` on its way out, and `test`
|
|
108
|
+
# captures `proofDigest` (onEnter[3]) BEFORE this command runs (onEnter[4]) — a formatter
|
|
109
|
+
# inside the gate rewrites the very tree the digest just fingerprinted (ADR-071 proof
|
|
110
|
+
# window). It was a no-op only because the implement-stage format got there first; that is
|
|
111
|
+
# an accident, not an invariant. The gate observes, it does not mutate.
|
|
112
|
+
qualityGateCmd: "bun run spur-check"
|
|
108
113
|
# Cheap red-detector run before the full gate on **recheck only**; empty ⇒ no probe
|
|
109
114
|
# (full gate every recheck — the pre-0587 behavior). A project overriding qualityGateCmd
|
|
110
115
|
# should override this too. TRUSTED CONFIG ONLY — executed via `sh -c` (same surface as
|
|
@@ -208,16 +213,41 @@ states:
|
|
|
208
213
|
command: >-
|
|
209
214
|
SIZE_FILE=".spur/run/$wbs-precheck-size.status" &&
|
|
210
215
|
mkdir -p .spur/run &&
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
SIZE_SCRIPT="plugins/sp/scripts/task-size-precheck.ts";
|
|
217
|
+
[ -f "$SIZE_SCRIPT" ] ||
|
|
218
|
+
SIZE_SCRIPT="$(superskill script path sp task-size-precheck.ts 2>/dev/null)";
|
|
219
|
+
if [ -n "$SIZE_SCRIPT" ] && [ -f "$SIZE_SCRIPT" ]; then
|
|
220
|
+
bun "$SIZE_SCRIPT" "$wbs"
|
|
213
221
|
--spur-bin "$spurBin" --max-reqs "$maxImplementReqs"
|
|
214
222
|
--max-plan-items "$maxImplementPlanItems";
|
|
215
223
|
else
|
|
216
|
-
echo "task-size-precheck failed closed — checker
|
|
217
|
-
echo "plugins/sp/scripts/
|
|
224
|
+
echo "task-size-precheck failed closed — checker not found in" >&2 &&
|
|
225
|
+
echo "plugins/sp/scripts/ nor staged; run 'superskill install sp'." >&2 &&
|
|
218
226
|
echo "FAIL" > "$SIZE_FILE";
|
|
219
227
|
fi &&
|
|
220
228
|
exit 0
|
|
229
|
+
# 0726 R2: task evidence precheck — deterministic live-data
|
|
230
|
+
# evidence-channel proof before implement dispatch. Writes PASS/FAIL to
|
|
231
|
+
# .spur/run/<wbs>-precheck-evidence.status. Always exit 0 (soft action);
|
|
232
|
+
# the precheck→implement guard reads the file, so a missing checker
|
|
233
|
+
# fails closed (writes FAIL, never PASS).
|
|
234
|
+
- kind: shell
|
|
235
|
+
options:
|
|
236
|
+
command: >-
|
|
237
|
+
EVID_FILE=".spur/run/$wbs-precheck-evidence.status" &&
|
|
238
|
+
mkdir -p .spur/run &&
|
|
239
|
+
EVID_SCRIPT="plugins/sp/scripts/task-evidence-precheck.ts";
|
|
240
|
+
[ -f "$EVID_SCRIPT" ] ||
|
|
241
|
+
EVID_SCRIPT="$(superskill script path sp task-evidence-precheck.ts 2>/dev/null)";
|
|
242
|
+
if [ -n "$EVID_SCRIPT" ] && [ -f "$EVID_SCRIPT" ]; then
|
|
243
|
+
bun "$EVID_SCRIPT" "$wbs"
|
|
244
|
+
--spur-bin "$spurBin";
|
|
245
|
+
else
|
|
246
|
+
echo "task-evidence-precheck failed closed — checker not found" >&2 &&
|
|
247
|
+
echo "in plugins/sp/scripts/ nor staged; run 'superskill install sp'." >&2 &&
|
|
248
|
+
echo "FAIL" > "$EVID_FILE";
|
|
249
|
+
fi &&
|
|
250
|
+
exit 0
|
|
221
251
|
|
|
222
252
|
- id: implement
|
|
223
253
|
description: >
|
|
@@ -536,7 +566,24 @@ states:
|
|
|
536
566
|
priority: ${vars.taskPriority}
|
|
537
567
|
compareExecutorWith: implement
|
|
538
568
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
539
|
-
|
|
569
|
+
expectFile: .spur/run/${vars.wbs}-verify-answer.txt
|
|
570
|
+
# 0726 R3: hard lint gate over the verifier-owned answer — shape and
|
|
571
|
+
# evidence-row identity, before the verdict derivation reads it.
|
|
572
|
+
# Hard action: a malformed answer halts the sequence here instead of
|
|
573
|
+
# poisoning the verdict parse downstream.
|
|
574
|
+
- kind: shell
|
|
575
|
+
options:
|
|
576
|
+
command: >-
|
|
577
|
+
LINT_SCRIPT="plugins/sp/scripts/verify-answer-lint.ts";
|
|
578
|
+
[ -f "$LINT_SCRIPT" ] ||
|
|
579
|
+
LINT_SCRIPT="$(superskill script path sp verify-answer-lint.ts 2>/dev/null)";
|
|
580
|
+
if [ -z "$LINT_SCRIPT" ] || [ ! -f "$LINT_SCRIPT" ]; then
|
|
581
|
+
echo "verify-answer-lint: checker not found in plugins/sp/scripts/ nor staged — run 'superskill install sp'" >&2;
|
|
582
|
+
exit 1;
|
|
583
|
+
fi;
|
|
584
|
+
bun "$LINT_SCRIPT" "$wbs"
|
|
585
|
+
--answer ".spur/run/$wbs-verify-answer.txt"
|
|
586
|
+
--spur-bin "$spurBin"
|
|
540
587
|
- kind: shell
|
|
541
588
|
options:
|
|
542
589
|
command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
|
|
@@ -614,6 +661,8 @@ states:
|
|
|
614
661
|
if [ -n "$FID" ]; then
|
|
615
662
|
if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then
|
|
616
663
|
bun plugins/sp/scripts/feature-sync-bounded.ts "$FID" --spur-bin "$spurBin" --json;
|
|
664
|
+
elif SYNC_MJS="$(superskill script path sp feature-sync-bounded.mjs 2>/dev/null)" && [ -f "$SYNC_MJS" ]; then
|
|
665
|
+
node "$SYNC_MJS" "$FID" --spur-bin "$spurBin" --json;
|
|
617
666
|
else
|
|
618
667
|
$spurBin feature sync "$FID" --json;
|
|
619
668
|
fi;
|
|
@@ -679,11 +728,11 @@ transitions:
|
|
|
679
728
|
# ── precheck: size PASS + task check → implement; else → failed ──
|
|
680
729
|
- from: precheck
|
|
681
730
|
to: implement
|
|
682
|
-
description: Deterministic size and task checks are green — begin implementation.
|
|
731
|
+
description: Deterministic size, evidence, and task checks are green — begin implementation.
|
|
683
732
|
guard:
|
|
684
733
|
kind: shell
|
|
685
734
|
options:
|
|
686
|
-
command: 'test "$(cat .spur/run/$wbs-precheck-size.status 2>/dev/null)" = PASS && $spurBin task check $wbs'
|
|
735
|
+
command: 'test "$(cat .spur/run/$wbs-precheck-size.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$wbs-precheck-evidence.status 2>/dev/null)" = PASS && $spurBin task check $wbs'
|
|
687
736
|
- from: precheck
|
|
688
737
|
to: failed
|
|
689
738
|
description: Size and/or task check failed — stop before implement.
|
|
@@ -54,12 +54,16 @@ vars:
|
|
|
54
54
|
# Corpus-aware quality gate for the feature-transition hop (R1, task 0625).
|
|
55
55
|
# The per-task gate (`spur-check`) deliberately excludes corpus-check — the only
|
|
56
56
|
# sweep that observes feature-level findings — so a transition that arms one
|
|
57
|
-
# would go green.
|
|
58
|
-
#
|
|
59
|
-
# and
|
|
57
|
+
# would go green. This gate is therefore the CORPUS SWEEP ALONE (~29 s), not
|
|
58
|
+
# `spur-check-new`: every task in the feature already paid a full `spur-check`
|
|
59
|
+
# in its own pipeline, and the states that run before this hop (`doc-sync`,
|
|
60
|
+
# `metrics-record`) write only markdown and task sections, which Biome skips
|
|
61
|
+
# (`ignoreUnknown: true`). Re-running the ~105 s per-task gate here re-verifies
|
|
62
|
+
# unchanged code and measures nothing new. Soft by design: the gate reports
|
|
63
|
+
# PASS/FAIL and lets the operator decide; it never hard-fails the wrap-up shell.
|
|
60
64
|
# TRUSTED CONFIG ONLY — this string is executed via `sh -c` (same surface as
|
|
61
65
|
# task-pipeline's qualityGateCmd). Never interpolate untrusted input into it.
|
|
62
|
-
featureGateCmd: "bun run
|
|
66
|
+
featureGateCmd: "bun run corpus-check"
|
|
63
67
|
__hitlAnswer: ""
|
|
64
68
|
|
|
65
69
|
states:
|
|
@@ -178,6 +182,8 @@ states:
|
|
|
178
182
|
fi;
|
|
179
183
|
SYNC_OUTPUT=$(if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then
|
|
180
184
|
bun plugins/sp/scripts/feature-sync-bounded.ts "$feature" --spur-bin "$spurBin" --json;
|
|
185
|
+
elif SYNC_MJS="$(superskill script path sp feature-sync-bounded.mjs 2>/dev/null)" && [ -f "$SYNC_MJS" ]; then
|
|
186
|
+
node "$SYNC_MJS" "$feature" --spur-bin "$spurBin" --json;
|
|
181
187
|
else
|
|
182
188
|
$spurBin feature sync "$feature" --json;
|
|
183
189
|
fi);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.71",
|
|
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.50",
|
|
57
|
+
"@gobing-ai/ts-ai-runner": "^0.4.50",
|
|
58
|
+
"@gobing-ai/ts-dual-workflow-engine": "^0.4.50",
|
|
59
|
+
"@gobing-ai/ts-infra": "^0.4.50",
|
|
60
|
+
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.50",
|
|
61
|
+
"@gobing-ai/ts-rule-engine": "^0.4.50",
|
|
62
|
+
"@gobing-ai/ts-runtime": "^0.4.50",
|
|
63
|
+
"@gobing-ai/ts-utils": "^0.4.50",
|
|
64
64
|
"@types/bun": "1.3.14",
|
|
65
65
|
"@types/figlet": "^1.7.0",
|
|
66
66
|
"@types/node-notifier": "8.0.5",
|
|
@@ -28,7 +28,8 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
|
|
|
28
28
|
## Implementation
|
|
29
29
|
|
|
30
30
|
Under the pipeline, the `test-recheck` state runs the full gate immediately after this hop — that is
|
|
31
|
-
the deciding run.
|
|
32
|
-
probes (`bun test <file> --test-name-pattern <test>`) during fix loops,
|
|
33
|
-
|
|
31
|
+
the deciding run. When `--gate-log` is set (the pipeline signal), fixall runs **no full gate at
|
|
32
|
+
all**: targeted probes (`bun test <file> --test-name-pattern <test>`) during fix loops, then one
|
|
33
|
+
`bun run lint` before returning. Invoked standalone, it keeps the single confirming run (R4, task
|
|
34
|
+
0483). `qualityGateCmd` itself is unchanged so `test-recheck` still runs the full gate.
|
|
34
35
|
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.71",
|
|
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"]
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* task-evidence-precheck — deterministic evidence-channel precheck (R2, task 0726).
|
|
4
|
+
*
|
|
5
|
+
* Parses the task content for an exact `evidence-channel:` declaration and proves the
|
|
6
|
+
* declared live-data channel exists in the local spur database before implementation
|
|
7
|
+
* begins. Currently exactly one channel is allowlisted:
|
|
8
|
+
*
|
|
9
|
+
* evidence-channel: history_tool_call.args_raw[pi]
|
|
10
|
+
*
|
|
11
|
+
* …satisfied only when the fixed query
|
|
12
|
+
*
|
|
13
|
+
* SELECT COUNT(*) FROM history_tool_call WHERE args_raw IS NOT NULL AND source = 'pi'
|
|
14
|
+
*
|
|
15
|
+
* returns a positive count on `<cwd>/.spur/spur.db` — i.e. a live non-dry-run pi import
|
|
16
|
+
* has already preserved tool-call `args_raw` (0722 R1). Unknown declarations, a missing
|
|
17
|
+
* database, a missing table, and a zero count all fail closed.
|
|
18
|
+
*
|
|
19
|
+
* A task without any `evidence-channel:` declaration passes without opening SQLite —
|
|
20
|
+
* the check only gates tasks that declare a live-data evidence channel.
|
|
21
|
+
*
|
|
22
|
+
* Always exits 0 (soft action). Both precheck→implement guards in task-pipeline.yaml
|
|
23
|
+
* read the status file; a missing or failing checker writes FAIL, so readiness fails
|
|
24
|
+
* closed.
|
|
25
|
+
*
|
|
26
|
+
* Ships with the plugin to arbitrary projects; node-builtin + bun:sqlite only —
|
|
27
|
+
* no workspace imports.
|
|
28
|
+
*
|
|
29
|
+
* Usage:
|
|
30
|
+
* bun plugins/sp/scripts/task-evidence-precheck.ts <wbs> [--spur-bin <path>]
|
|
31
|
+
*
|
|
32
|
+
* Env: SPUR_BIN
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { Database } from 'bun:sqlite';
|
|
36
|
+
import { execFileSync } from 'node:child_process';
|
|
37
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
38
|
+
import { join } from 'node:path';
|
|
39
|
+
import { fileURLToPath } from 'node:url';
|
|
40
|
+
|
|
41
|
+
/** Exact task-content declaration that activates the live-evidence gate (0726 R2). */
|
|
42
|
+
const DECLARATION_PREFIX = 'evidence-channel:';
|
|
43
|
+
|
|
44
|
+
/** The only allowlisted live-data channel (0726 R2). */
|
|
45
|
+
const EVIDENCE_CHANNEL = 'history_tool_call.args_raw[pi]';
|
|
46
|
+
|
|
47
|
+
/** Declaration text as it must appear in the task body. */
|
|
48
|
+
const DECLARATION = `${DECLARATION_PREFIX} ${EVIDENCE_CHANNEL}`;
|
|
49
|
+
|
|
50
|
+
/** The only live-data query this precheck is allowed to run — fixed, never task-authored. */
|
|
51
|
+
const EVIDENCE_QUERY = "SELECT COUNT(*) AS n FROM history_tool_call WHERE args_raw IS NOT NULL AND source = 'pi'";
|
|
52
|
+
|
|
53
|
+
// ─── CLI (same spur-bin chain as task-size-precheck.ts) ─────────────────────
|
|
54
|
+
|
|
55
|
+
function usage(): never {
|
|
56
|
+
console.error('Usage: bun plugins/sp/scripts/task-evidence-precheck.ts <wbs> [--spur-bin <path>]');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function defaultSpurBin(): string {
|
|
61
|
+
if (process.env.SPUR_BIN) return process.env.SPUR_BIN;
|
|
62
|
+
const local = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
|
|
63
|
+
if (existsSync(local)) return `bun ${local}`;
|
|
64
|
+
return 'spur';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseArgs(argv: string[]): { wbs: string; spurBin: string } {
|
|
68
|
+
let spurBin = defaultSpurBin();
|
|
69
|
+
let wbs = '';
|
|
70
|
+
let i = 0;
|
|
71
|
+
while (i < argv.length) {
|
|
72
|
+
const arg = argv[i];
|
|
73
|
+
if (arg === '--spur-bin') {
|
|
74
|
+
spurBin = argv[i + 1] ?? defaultSpurBin();
|
|
75
|
+
i += 2;
|
|
76
|
+
} else if (!arg.startsWith('--')) {
|
|
77
|
+
wbs = arg;
|
|
78
|
+
i++;
|
|
79
|
+
} else {
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!wbs) usage();
|
|
84
|
+
return { wbs, spurBin };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Split a multi-token `spurBin` (`<runtime> <mainModule>`) the same way
|
|
89
|
+
* `runSpurJson` does in feature-sync-bounded.ts — execFileSync's first arg is
|
|
90
|
+
* one executable path, not a shell command line.
|
|
91
|
+
*/
|
|
92
|
+
function runSpur(spurBin: string, args: string[]): string {
|
|
93
|
+
const [file = 'spur', ...lead] = spurBin.split(/\s+/).filter(Boolean);
|
|
94
|
+
return execFileSync(file, [...lead, ...args], {
|
|
95
|
+
encoding: 'utf-8',
|
|
96
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function writeStatus(wbs: string, status: 'PASS' | 'FAIL'): void {
|
|
101
|
+
const statusDir = join(process.cwd(), '.spur', 'run');
|
|
102
|
+
if (!existsSync(statusDir)) mkdirSync(statusDir, { recursive: true });
|
|
103
|
+
writeFileSync(join(statusDir, `${wbs}-precheck-evidence.status`), `${status}\n`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function fail(wbs: string, reasons: string[]): void {
|
|
107
|
+
writeStatus(wbs, 'FAIL');
|
|
108
|
+
console.error(`task-evidence-precheck: FAIL`);
|
|
109
|
+
for (const r of reasons) {
|
|
110
|
+
console.error(` ${r}`);
|
|
111
|
+
}
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function main(): void {
|
|
116
|
+
const { wbs, spurBin } = parseArgs(process.argv.slice(2));
|
|
117
|
+
|
|
118
|
+
let taskContent: string;
|
|
119
|
+
try {
|
|
120
|
+
const result = runSpur(spurBin, ['task', 'show', wbs, '--json']);
|
|
121
|
+
const task = JSON.parse(result);
|
|
122
|
+
taskContent = task.content ?? task.body ?? '';
|
|
123
|
+
} catch {
|
|
124
|
+
fail(wbs, [`could not fetch task ${wbs} via ${spurBin} — evidence channel unverifiable`]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Collect every declaration token. A repeated exact declaration still gates the
|
|
128
|
+
// single fixed query; any non-allowlisted token is an unknown declaration.
|
|
129
|
+
const declarations: string[] = [];
|
|
130
|
+
for (const match of taskContent.matchAll(/evidence-channel:\s*(\S+)/g)) {
|
|
131
|
+
declarations.push(match[1] ?? '');
|
|
132
|
+
}
|
|
133
|
+
const unknown = declarations.filter((d) => d !== EVIDENCE_CHANNEL);
|
|
134
|
+
if (unknown.length > 0) {
|
|
135
|
+
fail(wbs, [
|
|
136
|
+
`unknown evidence-channel declaration(s): ${unknown.join(', ')}`,
|
|
137
|
+
`allowlisted declaration: ${DECLARATION}`,
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
if (declarations.length === 0) {
|
|
141
|
+
writeStatus(wbs, 'PASS');
|
|
142
|
+
console.error(`task-evidence-precheck: PASS — no evidence-channel declaration; live-data gate not active`);
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const dbPath = join(process.cwd(), '.spur', 'spur.db');
|
|
147
|
+
if (!existsSync(dbPath)) {
|
|
148
|
+
fail(wbs, [`spur database not found at ${dbPath} — run a real history import first`]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let count: number;
|
|
152
|
+
try {
|
|
153
|
+
const db = new Database(dbPath, { readonly: true });
|
|
154
|
+
try {
|
|
155
|
+
const row = db.query(EVIDENCE_QUERY).get() as { n: number } | undefined;
|
|
156
|
+
count = row?.n ?? 0;
|
|
157
|
+
} finally {
|
|
158
|
+
db.close();
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
fail(wbs, [
|
|
162
|
+
`evidence query failed on ${dbPath}: ${e instanceof Error ? e.message : String(e)}`,
|
|
163
|
+
'history_tool_call table missing or unreadable — run a real history import first',
|
|
164
|
+
]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!(count > 0)) {
|
|
168
|
+
fail(wbs, [
|
|
169
|
+
`0 live pi rows with args_raw (query: ${EVIDENCE_QUERY})`,
|
|
170
|
+
'run a non-dry-run pi history import with a safe importer before implementing',
|
|
171
|
+
]);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
writeStatus(wbs, 'PASS');
|
|
175
|
+
console.error(
|
|
176
|
+
`task-evidence-precheck: PASS — ${count} live pi history_tool_call row(s) with args_raw (declaration: ${DECLARATION})`,
|
|
177
|
+
);
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
main();
|