@gobing-ai/spur 0.3.92 → 0.3.93

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.
Files changed (56) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +0 -7
  3. package/config/plugin-scripts.json +10 -0
  4. package/config/templates/AGENTS.md +4 -0
  5. package/config/templates/docs/02_ROADMAP.md +2 -0
  6. package/config/templates/docs/03_ARCHITECTURE.md +3 -1
  7. package/config/templates/docs/04_DESIGN.md +2 -0
  8. package/config/templates/docs/99_PROJECT_CONSTITUTION.md +10 -2
  9. package/config/workflow-candidates.json +72 -1
  10. package/config/workflows/feature-verification.yaml +1 -0
  11. package/config/workflows/history-anatomy.yaml +2 -0
  12. package/config/workflows/idea-pipeline.yaml +67 -18
  13. package/config/workflows/pr-review.yaml +8 -0
  14. package/config/workflows/task-pipeline.yaml +180 -39
  15. package/config/workflows/wayfinder-resolution.yaml +5 -0
  16. package/config/workflows/wrapup-pipeline.yaml +55 -14
  17. package/package.json +9 -9
  18. package/plugins/sp/README.md +7 -2
  19. package/plugins/sp/commands/dev-run.md +2 -2
  20. package/plugins/sp/commands/dev-runall.md +2 -2
  21. package/plugins/sp/lib/idea-handoff.generated.mjs +8 -4
  22. package/plugins/sp/lib/inline-run.generated.d.mts +1 -0
  23. package/plugins/sp/lib/inline-run.generated.mjs +24 -16
  24. package/plugins/sp/plugin.json +1 -1
  25. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
  26. package/plugins/sp/scripts/inline-run-setup.mjs +75 -3
  27. package/plugins/sp/scripts/inline-run-setup.ts +133 -3
  28. package/plugins/sp/scripts/quality-gate.mjs +248 -6
  29. package/plugins/sp/scripts/quality-gate.ts +410 -9
  30. package/plugins/sp/scripts/residual-scan.mjs +12 -4
  31. package/plugins/sp/scripts/residual-scan.ts +32 -6
  32. package/plugins/sp/scripts/task-diffstat.mjs +156 -0
  33. package/plugins/sp/scripts/task-diffstat.ts +229 -0
  34. package/plugins/sp/scripts/wrapup-drift-probe.mjs +181 -0
  35. package/plugins/sp/scripts/wrapup-drift-probe.ts +258 -0
  36. package/plugins/sp/scripts/wrapup-steps.mjs +60 -1
  37. package/plugins/sp/scripts/wrapup-steps.ts +89 -4
  38. package/plugins/sp/skills/brainstorm/SKILL.md +2 -0
  39. package/plugins/sp/skills/brainstorm/references/workflows.md +17 -2
  40. package/plugins/sp/skills/code-verification/SKILL.md +2 -2
  41. package/plugins/sp/skills/code-verification/references/secu-review.md +3 -2
  42. package/plugins/sp/skills/spur-check/SKILL.md +112 -0
  43. package/plugins/sp/skills/spur-dev/SKILL.md +2 -1
  44. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +14 -2
  45. package/plugins/sp/skills/spur-dev/references/document-authoring.md +85 -0
  46. package/plugins/sp/skills/spur-dev/references/execution-batch.md +1 -1
  47. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +8 -0
  48. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +3 -2
  49. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +19 -1
  50. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +5 -5
  51. package/plugins/sp/skills/spur-dev/templates/design.md +31 -0
  52. package/plugins/sp/skills/spur-dev/templates/plan.md +32 -0
  53. package/plugins/sp/skills/spur-doctor/SKILL.md +60 -14
  54. package/schemas/state-machine-workflow.schema.json +4 -0
  55. package/spur.js +1117 -472
  56. package/config/workflows/decision-routing-example.yaml +0 -134
@@ -486,8 +486,8 @@ states:
486
486
  - id: test-recheck
487
487
  description: >
488
488
  Soft recheck after fixall with bounded SQLite-lock retry. Writes PASS|FAIL (always exit 0). Transitions
489
- branch to review (PASS), another test-fix (FAIL and under max attempts),
490
- or the pipeline `failed` state (FAIL and attempts exhausted) — never a
489
+ branch to `triage` (PASS — the 0943 lane router), `test-fail-triage` (FAIL — the 0943
490
+ failure-class router), or the pipeline `failed` state (corrupt status) — never a
491
491
  raw lifecycle abort that skips the terminal `failed` state.
492
492
  onEnter:
493
493
  # R4 (0703, ADR-071): bounded remediation mutated the tree by design, so the fresh evidence
@@ -510,6 +510,99 @@ states:
510
510
  command: >-
511
511
  mkdir -p .spur/run; if [ -f plugins/sp/scripts/quality-gate.ts ]; then bun plugins/sp/scripts/quality-gate.ts recheck; elif Q="$(superskill script path sp quality-gate.mjs 2>/dev/null)" && [ -f "$Q" ]; then node "$Q" recheck; else echo "quality gate failed closed — quality-gate script not found — run 'superskill install sp'" >&2; printf 'FAIL\n' > ".spur/run/$wbs-test-gate.status"; fi; exit 0
512
512
 
513
+ - id: triage
514
+ description: >-
515
+ Deterministic lane router between a green gate and the review/verify fork (0943 R1).
516
+ Writes the diffstat evidence file, honors a caller-set `mode` (never overridden), pins
517
+ the safety lane on sensitive paths or >400 changed lines without a model call (R2c),
518
+ otherwise consults `decide task-triage` (low → fast lane; anything else stays empty →
519
+ standard review path), and appends the triage route reason to
520
+ .spur/memory/task-pipeline-routes.log (R5). The `decide` action itself runs
521
+ unconditionally (the engine has no onEnter conditionals); its result is consulted only
522
+ when neither a caller mode nor the deterministic guard resolved the lane. The resolved
523
+ lane lands in the `mode` var for the triage → verify/review guards.
524
+ onEnter:
525
+ # (a) diffstat producer: task-diffstat.ts owns git reading and sensitive-path
526
+ # classification; shell only resolves it and fails closed (writes sensitive:true,
527
+ # which the pre-guard below maps to the safety lane).
528
+ - kind: shell
529
+ options:
530
+ command: >-
531
+ mkdir -p .spur/run; if [ -f plugins/sp/scripts/task-diffstat.ts ]; then bun plugins/sp/scripts/task-diffstat.ts --spur-bin "$spurBin"; elif D="$(superskill script path sp task-diffstat.mjs 2>/dev/null)" && [ -f "$D" ]; then node "$D"; else echo "task-diffstat failed closed — task-diffstat script not found — run 'superskill install sp'" >&2; printf '{"files":0,"insertions":0,"deletions":0,"paths":[],"sensitive":true}\n' > ".spur/run/$wbs-diffstat.json"; fi; exit 0
532
+ # (b)+(c) lane pre-guard BEFORE decide: a caller-set mode is projected verbatim (R2b);
533
+ # a sensitive path or >400 changed lines pins safety (R2c) and the decide result below
534
+ # is never consulted. A missing/malformed diffstat row (producer crashed or never ran,
535
+ # disk error) pins safety HERE: an empty mode file would let the low decide row project
536
+ # the fast lane downstream, so the "never to fast" fail-safe is enforced at the guard,
537
+ # not by the projection (the full-suite gate run exposed this fail-open path).
538
+ - kind: shell
539
+ options:
540
+ command: >-
541
+ mkdir -p .spur/run; if [ -n "$mode" ]; then printf '%s\n' "$mode" > ".spur/run/$wbs-mode.txt"; exit 0; fi; DF=".spur/run/$wbs-diffstat.json"; if ! jq -e 'type == "object"' "$DF" >/dev/null 2>&1; then printf 'safety\n' > ".spur/run/$wbs-mode.txt"; exit 0; fi; jq -r 'if .sensitive == true or ((.insertions // 0) + (.deletions // 0)) > 400 then "safety" else empty end' "$DF" 2>/dev/null > ".spur/run/$wbs-mode.txt"; exit 0
542
+ # (d) lane decision — degrades to the standard default whenever workflow.decideDecisionMaker
543
+ # is off (R4), so with the flag off this state is pure shell: no model call is made.
544
+ - kind: decide
545
+ options:
546
+ id: task-triage
547
+ method: choice
548
+ question: >-
549
+ The quality gate is green. Which verification lane does this diff deserve —
550
+ low (small, contained diff — fast lane), standard, or high caution?
551
+ choices: [low, standard, high]
552
+ default: standard
553
+ evidence:
554
+ - .spur/run/${vars.wbs}-diffstat.json
555
+ - ${vars.taskSpecPath}
556
+ resultFile: .spur/run/${vars.wbs}-triage.decision
557
+ # (d) project the decision: low → fast lane; anything else (standard/high, degraded
558
+ # default, missing row) leaves the mode empty → standard review path. A non-empty mode
559
+ # file (caller-set or deterministic-high) is never overridden.
560
+ - kind: shell
561
+ options:
562
+ command: >-
563
+ MF=".spur/run/$wbs-mode.txt"; [ -s "$MF" ] && exit 0; jq -r 'if .value == "low" then "fast" else empty end' ".spur/run/$wbs-triage.decision" 2>/dev/null > "$MF"; exit 0
564
+ # R5: append the triage route reason in the shared routes-log format.
565
+ - kind: shell
566
+ options:
567
+ command: >-
568
+ m="$(cat ".spur/run/$wbs-mode.txt")"; RUN_ID="$__runId"; [ -n "$RUN_ID" ] || RUN_ID="pipeline-$wbs"; mkdir -p .spur/memory; printf '%s %s %s\n' "$RUN_ID" "$wbs" "$(jq -rn --arg m "$m" --arg c "$mode" 'if $c != "" then ($c + ":caller-set") else {"fast":"fast:triage low lane","safety":"safety:triage deterministic-high","":"safety:triage standard lane"}[$m] // "safety:triage unrecognized mode" end')" >> .spur/memory/task-pipeline-routes.log
569
+ # (d) the resolved lane becomes the `mode` var for the triage → verify/review guards.
570
+ - kind: file.read.into-var
571
+ options:
572
+ path: .spur/run/${vars.wbs}-mode.txt
573
+ var: mode
574
+
575
+ - id: test-fail-triage
576
+ description: >-
577
+ Deterministic failure-class router on a red gate (0943 R3). The engine has no onEnter
578
+ conditionals, so the FAIL branch from `test`/`test-recheck` lands here as its own
579
+ deterministic state (the sanctioned alternative), while the green branch goes to
580
+ `triage`. `decide failure-class` reads the bounded gate findings and routes: retryable
581
+ → test-recheck (the attempt is counted on entry, so qualityGateMaxFixAttempts still
582
+ bounds the loop), fix → test-fix (the repair hop, as before), stop → failed with
583
+ terminalReason failed-check. A missing/corrupt decision fails closed to failed-check.
584
+ onEnter:
585
+ - kind: decide
586
+ options:
587
+ id: failure-class
588
+ method: choice
589
+ question: >-
590
+ The quality gate failed. Is the failure retryable (transient or environmental
591
+ — just re-run the gate), fixable by the repair agent, or should the run stop
592
+ here?
593
+ choices: [retryable, fix, stop]
594
+ default: fix
595
+ evidence:
596
+ - .spur/run/${vars.wbs}-test-gate.findings
597
+ resultFile: .spur/run/${vars.wbs}-failure-class.decision
598
+ # A retryable classification still counts an attempt so the existing
599
+ # qualityGateMaxFixAttempts cap bounds the recheck loop (0943 R3; the cap edge reads
600
+ # the same counter the fixall path uses).
601
+ - kind: shell
602
+ options:
603
+ command: >-
604
+ A=".spur/run/$wbs-test-fix-attempt"; [ "$(jq -r '.value // ""' ".spur/run/$wbs-failure-class.decision" 2>/dev/null)" = retryable ] && { n="$(cat "$A" 2>/dev/null || echo 0)"; printf '%s\n' "$((n + 1))" > "$A"; }; exit 0
605
+
513
606
  - id: review
514
607
  description: Three-dimensional code review via /sp:dev-review (functional requirements traceability + SECUA framework (Security, Efficiency, Correctness, Usability, Architecture) + architecture depth), findings written to `## Review`.
515
608
  onEnter:
@@ -779,6 +872,7 @@ transitions:
779
872
  test "$size_status" = PASS && test "$evidence_status" = PASS && $spurBin task check $wbs
780
873
  - from: precheck
781
874
  to: failed
875
+ terminalReason: failed-check
782
876
  description: Size and/or task check failed — stop before implement.
783
877
  guard:
784
878
  kind: always
@@ -791,6 +885,7 @@ transitions:
791
885
  # fresh question file so a COMPLETED implement after max asks still routes to test.
792
886
  - from: implement
793
887
  to: failed
888
+ terminalReason: retry-exhausted
794
889
  description: Escalation bound exhausted — the agent paused again after maxEscalations operator answers.
795
890
  # 0933 R30 (guard-lines compression): thin predicate; the report note is written
796
891
  # by the failed state's onEnter when it observes a bound-exhausted pause.
@@ -826,38 +921,36 @@ transitions:
826
921
  command: 'test -n "$__hitlInput"'
827
922
  - from: escalate
828
923
  to: failed
924
+ terminalReason: cancelled
829
925
  description: Empty operator answer — give-up routes to failed (question + transcript preserved as evidence).
830
926
  guard:
831
927
  kind: shell
832
928
  options:
833
929
  command: 'test -z "$__hitlInput"'
834
- # Soft probe branching (declaration order: PASS first, then FAIL, then defense).
930
+ # Soft probe branching (declaration order: PASS → triage, FAIL → test-fail-triage, defense).
931
+ # 0943 R1/R3: the four former gate-PASS edges (test/test-recheck → verify/review, split on
932
+ # `$mode`) are replaced by the deterministic `triage` lane producer; a red gate lands in
933
+ # `test-fail-triage` where `decide failure-class` routes the failure instead of a static fixall.
835
934
  - from: test
836
- to: verify
837
- description: Quality gate already green and mode is fast — proportional fast path bypasses review.
935
+ to: triage
936
+ description: Quality gate green — route the lane deterministically (triage, 0943 R1).
838
937
  guard:
839
938
  kind: shell
840
939
  options:
841
940
  command: >-
842
941
  gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
843
- test "$gate_status" = PASS && test "$mode" = fast
942
+ test "$gate_status" = PASS
844
943
  - from: test
845
- to: review
846
- description: Quality gate already green and safety mode — proceed to review.
847
- guard:
848
- kind: shell
849
- options:
850
- command: >-
851
- gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
852
- test "$gate_status" = PASS && test "$mode" != fast
853
- - from: test
854
- to: test-fix
855
- description: Quality gate red — start bounded fixall loop.
944
+ to: test-fail-triage
945
+ description: Quality gate red — classify the failure before the repair hop (0943 R3).
856
946
  guard:
857
947
  kind: shell
858
948
  options:
859
949
  command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL'
860
- # Defense: missing/corrupt status — treat as FAIL path, not silent PASS.
950
+ # 0943 review P3#2: the former `test → test-fix` FAIL edge is removed — it was dead (the
951
+ # identical guard on test → test-fail-triage fires first) and a silent spec-bypass trap:
952
+ # restoring it would skip failure-class on every red gate. The always-defense below keeps
953
+ # pre-0943 corrupt-status behavior (fixall then recheck).
861
954
  - from: test
862
955
  to: test-fix
863
956
  description: Probe status missing/corrupt — attempt fixall then recheck.
@@ -868,51 +961,95 @@ transitions:
868
961
  description: Fixall finished — soft recheck the same quality gate.
869
962
  guard:
870
963
  kind: always
871
- # Recheck branching (PASS first; under-max FAIL → fixall again; exhausted → failed).
964
+ # Recheck branching (PASS → triage; red → test-fail-triage; defense). The two former
965
+ # static FAIL edges (under-max → test-fix, exhausted → failed retry-exhausted) moved into
966
+ # test-fail-triage's decision routing (0943 R3).
872
967
  - from: test-recheck
873
- to: verify
874
- description: Quality gate green after fixall and mode is fast — proportional fast path bypasses review.
968
+ to: triage
969
+ description: Quality gate green after fixall — route the lane deterministically (triage, 0943 R1).
875
970
  guard:
876
971
  kind: shell
877
972
  options:
878
973
  command: >-
879
974
  gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
880
- test "$gate_status" = PASS && test "$mode" = fast
975
+ test "$gate_status" = PASS
881
976
  - from: test-recheck
882
- to: review
883
- description: Quality gate green after fixall and safety mode — proceed to review.
977
+ to: test-fail-triage
978
+ description: Still red after fixall — classify the failure (0943 R3).
884
979
  guard:
885
980
  kind: shell
886
981
  options:
887
982
  command: >-
888
983
  gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
889
- test "$gate_status" = PASS && test "$mode" != fast
984
+ test "$gate_status" = FAIL
985
+ # Defense: corrupt recheck status — failed, not review.
890
986
  - from: test-recheck
891
- to: test-fix
892
- description: Still red and under qualityGateMaxFixAttempts — another fixall hop.
893
- # (warn) 5 commands: named gate status + fix attempts (legibility, 0874).
987
+ to: failed
988
+ terminalReason: failed-check
989
+ description: Recheck status missing/corrupt — stop at failed.
990
+ guard:
991
+ kind: always
992
+ # ── triage routing (0943 R1/R2): the lane producer replaced the four gate-PASS edges.
993
+ # mode=fast → verify; anything else (standard/high, deterministic-high, degraded default,
994
+ # caller-set non-fast) → review. Together the two guards are exhaustive, so triage never hangs.
995
+ - from: triage
996
+ to: verify
997
+ description: Triage resolved the fast lane (caller-set fast or task-triage low) — skip review.
998
+ guard:
999
+ kind: shell
1000
+ options:
1001
+ command: 'test "$mode" = fast'
1002
+ - from: triage
1003
+ to: review
1004
+ description: Triage resolved a review lane (standard/high, deterministic-high, or caller-set) — review.
1005
+ guard:
1006
+ kind: shell
1007
+ options:
1008
+ command: 'test "$mode" != fast'
1009
+ # ── failure-class routing (0943 R3): stop first (never mislabeled by the cap), then the
1010
+ # attempt cap (bounds BOTH lanes — retryable counted on entry, fix as before), then the
1011
+ # decision lanes, then the fail-closed defense. Declaration order matters.
1012
+ - from: test-fail-triage
1013
+ to: failed
1014
+ terminalReason: failed-check
1015
+ description: Failure class stop — the run terminates at failed, never bypassed.
894
1016
  guard:
895
1017
  kind: shell
896
1018
  options:
897
1019
  command: >-
898
- gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
899
- fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
900
- test "$gate_status" = FAIL && test "$fix_attempts" -lt "$qualityGateMaxFixAttempts"
901
- - from: test-recheck
1020
+ jq -e '.value == "stop"' ".spur/run/$wbs-failure-class.decision" >/dev/null 2>&1
1021
+ - from: test-fail-triage
902
1022
  to: failed
903
- description: Still red after max fixall attempts — stop at failed (not silent abort).
904
- # (warn) 5 commands: named gate status + fix attempts (legibility, 0874).
1023
+ terminalReason: retry-exhausted
1024
+ description: Attempt cap reached — qualityGateMaxFixAttempts bounds the retryable recheck and fix lanes alike.
1025
+ # (warn) 5 commands: named fix attempts (legibility, 0874).
905
1026
  guard:
906
1027
  kind: shell
907
1028
  options:
908
1029
  command: >-
909
- gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
910
1030
  fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
911
- test "$gate_status" = FAIL && test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
912
- # Defense: corrupt recheck status — failed, not review.
913
- - from: test-recheck
1031
+ test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
1032
+ - from: test-fail-triage
1033
+ to: test-recheck
1034
+ description: Failure class retryable and under the cap — re-run the gate without fixall; the attempt was counted on entry.
1035
+ guard:
1036
+ kind: shell
1037
+ options:
1038
+ command: >-
1039
+ jq -e '.value == "retryable"' ".spur/run/$wbs-failure-class.decision" >/dev/null 2>&1
1040
+ - from: test-fail-triage
1041
+ to: test-fix
1042
+ description: Failure class fix and under the cap — the bounded repair hop, as before.
1043
+ guard:
1044
+ kind: shell
1045
+ options:
1046
+ command: >-
1047
+ jq -e '.value == "fix"' ".spur/run/$wbs-failure-class.decision" >/dev/null 2>&1
1048
+ # Defense: missing/corrupt failure-class decision — fail closed instead of silently repairing.
1049
+ - from: test-fail-triage
914
1050
  to: failed
915
- description: Recheck status missing/corrupt — stop at failed.
1051
+ terminalReason: failed-check
1052
+ description: Failure-class decision missing/corrupt — stop at failed.
916
1053
  guard:
917
1054
  kind: always
918
1055
  # ── review → approve, OR skip the HITL gate entirely when profile=auto (R4) ──
@@ -947,6 +1084,7 @@ transitions:
947
1084
  command: 'test "$__hitlAnswer" = yes'
948
1085
  - from: approve
949
1086
  to: failed
1087
+ terminalReason: cancelled
950
1088
  description: Operator rejected at the approval gate — report and stop.
951
1089
  guard:
952
1090
  kind: shell
@@ -954,6 +1092,7 @@ transitions:
954
1092
  command: 'test "$__hitlAnswer" = no'
955
1093
  - from: approve
956
1094
  to: cancelled
1095
+ terminalReason: cancelled
957
1096
  description: Operator cancelled at the approval gate.
958
1097
  guard:
959
1098
  kind: shell
@@ -1002,6 +1141,7 @@ transitions:
1002
1141
  test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -lt "$qualityGateMaxFixAttempts"
1003
1142
  - from: verify
1004
1143
  to: failed
1144
+ terminalReason: retry-exhausted
1005
1145
  description: >-
1006
1146
  Non-PASS with the fix budget exhausted, or a PASS/missing/malformed verdict whose proof
1007
1147
  block is absent or mismatched (task 0703 R5) — block before done; defense catch-all so the
@@ -1031,6 +1171,7 @@ transitions:
1031
1171
  test "$verdict" = PASS && test "$proof_digest" = "$proofDigest"
1032
1172
  - from: record
1033
1173
  to: failed
1174
+ terminalReason: failed-check
1034
1175
  description: Task check failed or proof evidence missing/malformed/mismatched — block before done.
1035
1176
  guard:
1036
1177
  kind: always
@@ -209,6 +209,7 @@ transitions:
209
209
  command: 'test "$(cat .spur/run/$__runId-wayfinder-precheck.status 2>/dev/null)" = PASS'
210
210
  - from: precheck
211
211
  to: failed
212
+ terminalReason: failed-check
212
213
  description: "Precheck FAIL — stop before delegation."
213
214
  guard:
214
215
  kind: always
@@ -221,6 +222,7 @@ transitions:
221
222
  command: 'test -s .spur/run/$__runId-wayfinder-answer.md'
222
223
  - from: investigate
223
224
  to: failed
225
+ terminalReason: failed-agent
224
226
  description: "Empty answer capture — investigate produced nothing to verify; stop rather than certify hollow evidence."
225
227
  guard:
226
228
  kind: shell
@@ -254,6 +256,7 @@ transitions:
254
256
  command: 'test "$__hitlAnswer" = yes'
255
257
  - from: approve
256
258
  to: cancelled
259
+ terminalReason: cancelled
257
260
  description: "Operator cancelled the research resolution."
258
261
  guard:
259
262
  kind: shell
@@ -261,6 +264,7 @@ transitions:
261
264
  command: 'test "$__hitlAnswer" = cancel'
262
265
  - from: approve
263
266
  to: failed
267
+ terminalReason: cancelled
264
268
  description: "Operator rejected the research resolution."
265
269
  guard:
266
270
  kind: shell
@@ -275,6 +279,7 @@ transitions:
275
279
  command: 'test "$(cat .spur/run/$__runId-wayfinder-final.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$__runId-wayfinder-status.txt 2>/dev/null)" = done'
276
280
  - from: record
277
281
  to: failed
282
+ terminalReason: failed-check
278
283
  description: "Recording failed, was denied, or the task never reached done — stop without claiming completion."
279
284
  guard:
280
285
  kind: always
@@ -75,7 +75,7 @@
75
75
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
76
76
  kind: state-machine
77
77
  name: wrapup-pipeline
78
- version: "4"
78
+ version: "5"
79
79
  description: "Post-execution wrap-up: doc-sync (doc drift + learning capture), metrics, feature-transition, branch-cleanup"
80
80
  iterationBound: 10
81
81
  initialState: start
@@ -128,7 +128,10 @@ states:
128
128
  missing or corrupted capture refuses progression. A validated empty list
129
129
  -> skipped; length > 0 && mode == fast -> fast-path (metrics-record,
130
130
  bypassing doc-sync); missing/unknown/conflict -> doc-sync (safety-path).
131
- Writes bounded machine-readable reason to reasonFile.
131
+ Writes bounded machine-readable reason to reasonFile. When the caller leaves
132
+ mode empty, a deterministic drift probe (0944) projects mode=fast only when
133
+ no changed path matches a doc-owned surface and every task's Solution
134
+ change map parsed — any doubt stays on the safety path.
132
135
  onEnter:
133
136
  - kind: note
134
137
  options:
@@ -148,21 +151,53 @@ states:
148
151
  echo "wrapup-steps failed closed — script not found — run 'superskill install sp'" >&2;
149
152
  printf 'FAIL\n' > ".spur/run/$__runId-wrapup-resolve.status";
150
153
  fi
151
- # (e) route-reason writer: one jq table lookup over the mode var over the
152
- # validated capture; workflow-local routing glue (0824). Runs AFTER validation
153
- # and reads ONLY .spur/run/<runId>-wrapup-tasks.json — never raw vars.tasks
154
- # (0783 R2/R5). A FAIL resolve exits without writing: the failed reason written
155
- # by validation stands, and the skip reason can never mask it. A missing or
156
- # corrupted capture fails the jq lookup (no skipped claim can be invented);
157
- # the failed defense edge owns the run.
154
+ # (0944 R1-R3) deterministic drift probe — runs ONLY when the caller left mode
155
+ # empty (a caller-set mode is projected verbatim and the probe never runs). It
156
+ # writes <runId>-drift-probe.json {clean,reasons,paths} and projects the wrapup
157
+ # mode by writing <runId>-mode.txt: caller mode verbatim, `fast` when the probe
158
+ # is clean, empty when dirty (the probe script fails safe on any lookup or parse
159
+ # problem; a missing script writes a dirty probe + empty mode here). The next
160
+ # action reads the mode file into vars.mode via file.read.into-var.
158
161
  - kind: shell
159
162
  options:
160
163
  command: >-
161
- mkdir -p .spur/run .spur/memory; RUN_ID="$__runId"; case "$RUN_ID" in '') echo "task-resolve: __runId is empty — refusing to write a route reason" >&2; exit 1 ;; esac;
162
- REASON_FILE=".spur/run/$RUN_ID-route-reason.txt"; case "$(cat ".spur/run/$RUN_ID-wrapup-resolve.status" 2>/dev/null)" in FAIL) exit 0 ;; esac;
163
- N=$(jq length ".spur/run/$RUN_ID-wrapup-tasks.json" 2>/dev/null);
164
- jq -rn --arg m "$mode" --arg n "$N" 'if $n == "0" then "skipped:empty task list" else {"fast":"fast:evidence complete+consistent","":"safety:missing evidence (mode empty)","unknown":"safety:unknown evidence quality","conflict":"safety:conflicting evidence"}[$m] // "safety:unrecognized evidence (mode=\($m))" end' > "$REASON_FILE";
165
- printf '%s %s\n' "$RUN_ID" "$(cat "$REASON_FILE")" >> .spur/memory/wrapup-routes.log
164
+ if [ -n "$mode" ]; then
165
+ printf '%s\n' "$mode" > ".spur/run/$__runId-mode.txt";
166
+ elif [ -f plugins/sp/scripts/wrapup-drift-probe.ts ]; then
167
+ bun plugins/sp/scripts/wrapup-drift-probe.ts;
168
+ elif W="$(superskill script path sp wrapup-drift-probe.mjs 2>/dev/null)" && [ -f "$W" ]; then
169
+ node "$W";
170
+ else
171
+ printf '{"clean":false,"reasons":["probe script not found"],"paths":[]}' > ".spur/run/$__runId-drift-probe.json";
172
+ printf '\n' > ".spur/run/$__runId-mode.txt";
173
+ fi
174
+ - kind: file.read.into-var
175
+ options:
176
+ path: .spur/run/${vars.__runId}-mode.txt
177
+ var: mode
178
+ # (e) route-reason writer: one table lookup over the projected mode var over
179
+ # the validated capture and the drift probe verdict; workflow-local routing
180
+ # glue (0824, moved behind the wrapup-steps locator in 0944 to stay inside the
181
+ # ADR-115 shell caps). Runs AFTER the probe/projection so the reason reflects
182
+ # the projected mode. Reads ONLY .spur/run/<runId>-wrapup-tasks.json and the
183
+ # probe verdict — never raw vars.tasks (0783 R2/R5). A FAIL resolve exits
184
+ # without writing: the failed reason written by validation stands, and the
185
+ # skip reason can never mask it. A clean probe yields `fast:drift-probe-clean`
186
+ # (0944); mode `safety` claims `safety:operator-forced doc-sync`; a missing or
187
+ # corrupted capture fails the lookup (no skipped or clean claim can be
188
+ # invented); the failed defense edge owns the run.
189
+ - kind: shell
190
+ options:
191
+ command: >-
192
+ mkdir -p .spur/run .spur/memory &&
193
+ if [ -f plugins/sp/scripts/wrapup-steps.ts ]; then
194
+ bun plugins/sp/scripts/wrapup-steps.ts route-reason;
195
+ elif W="$(superskill script path sp wrapup-steps.mjs 2>/dev/null)" && [ -f "$W" ]; then
196
+ node "$W" route-reason;
197
+ else
198
+ echo "wrapup-steps failed closed — script not found — run 'superskill install sp'" >&2;
199
+ printf 'FAIL\n' > ".spur/run/$__runId-wrapup-resolve.status";
200
+ fi
166
201
 
167
202
  - id: doc-sync
168
203
  description: >
@@ -367,6 +402,7 @@ transitions:
367
402
  # ── task-resolve: validation fail + proportional route table (0758 R1-R4, 0770) ──
368
403
  - from: task-resolve
369
404
  to: failed
405
+ terminalReason: failed-check
370
406
  description: >
371
407
  Validation FAIL (0770): malformed wrap input, a non-string/empty entry, an
372
408
  unresolved or non-completed task, or an empty __runId. Declared before the
@@ -402,6 +438,7 @@ transitions:
402
438
  command: 'test "$(jq length .spur/run/$__runId-wrapup-tasks.json 2>/dev/null || echo -1)" -gt 0 && test "$mode" != fast'
403
439
  - from: task-resolve
404
440
  to: failed
441
+ terminalReason: failed-check
405
442
  description: >
406
443
  Defense (0770) — no PASS/FAIL resolve status (shell crashed or never ran):
407
444
  route to `failed` rather than claiming a skip.
@@ -429,6 +466,7 @@ transitions:
429
466
  kind: action-ok
430
467
  - from: doc-sync
431
468
  to: failed
469
+ terminalReason: failed-check
432
470
  description: >
433
471
  Executor failure (non-zero exit, signal, or dispatch error) — keep the
434
472
  existing fail semantics; a contract violation never falls through here
@@ -458,6 +496,7 @@ transitions:
458
496
  # the metrics status PASS so a missing status can never claim success.
459
497
  - from: metrics-record
460
498
  to: failed
499
+ terminalReason: failed-check
461
500
  description: >
462
501
  Metrics capture failed for at least one task — a missing metrics row is
463
502
  recorded as failure, never silently omitted as success (0770).
@@ -490,6 +529,7 @@ transitions:
490
529
  # ── feature-transition: fail edge, then branch-cleanup (if merge) or done ──
491
530
  - from: feature-transition
492
531
  to: failed
532
+ terminalReason: failed-check
493
533
  description: >
494
534
  Required synchronization failed (0770): non-zero sync exit, an invalid
495
535
  sync result, or a failed feature gate. The failed status carries the
@@ -507,6 +547,7 @@ transitions:
507
547
  command: 'test "$(cat .spur/run/$__runId-wrapup-sync.status 2>/dev/null)" = PASS'
508
548
  - from: feature-verify
509
549
  to: failed
550
+ terminalReason: failed-check
510
551
  description: >
511
552
  Post-wrapup feature check failed (0915 F1): the bound receipt no longer
512
553
  validates against the current feature contract. Already-written
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.92",
3
+ "version": "0.3.93",
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.5.5",
57
- "@gobing-ai/ts-ai-runner": "^0.5.5",
58
- "@gobing-ai/ts-dual-workflow-engine": "^0.5.5",
59
- "@gobing-ai/ts-infra": "^0.5.5",
60
- "@gobing-ai/ts-llm-jsonl-importer": "^0.5.5",
61
- "@gobing-ai/ts-rule-engine": "^0.5.5",
62
- "@gobing-ai/ts-runtime": "^0.5.5",
63
- "@gobing-ai/ts-utils": "^0.5.5",
56
+ "@gobing-ai/ts-db": "^0.5.7",
57
+ "@gobing-ai/ts-ai-runner": "^0.5.7",
58
+ "@gobing-ai/ts-dual-workflow-engine": "^0.5.7",
59
+ "@gobing-ai/ts-infra": "^0.5.7",
60
+ "@gobing-ai/ts-llm-jsonl-importer": "^0.5.7",
61
+ "@gobing-ai/ts-rule-engine": "^0.5.7",
62
+ "@gobing-ai/ts-runtime": "^0.5.7",
63
+ "@gobing-ai/ts-utils": "^0.5.7",
64
64
  "@types/bun": "1.3.14",
65
65
  "@types/figlet": "^1.7.0",
66
66
  "@types/node-notifier": "8.0.5",
@@ -186,7 +186,7 @@ pipeline step.
186
186
 
187
187
  ```
188
188
  plugins/sp/
189
- ├── skills/ # Domain knowledge + workflow docs (38 skills)
189
+ ├── skills/ # Domain knowledge + workflow docs (39 skills)
190
190
  │ ├── brainstorm/ # Structured ideation workflow
191
191
  │ │ ├── agents/openai.yaml
192
192
  │ │ ├── examples/ideation-example.md
@@ -342,6 +342,7 @@ surface or run one workflow. All skills target the same five core platforms: `cl
342
342
  | `session-review` | 1.1 | Inline review of the active coding-agent session — compact outcomes, evidence-backed resolved/open issue classification, proposal-only improvements, and next actions; no workflow, import, delegation, or mutation |
343
343
  | `spur-composer` | 1.0 | Cross-noun composition — workflow catalog selection, the ephemeral→project→shared ladder, ADR-115 budgets, trace-driven rule tuning; applies accepted `spur-doctor` proposals through `spur` verbs; never judges its own output and never runs a recurring loop |
344
344
  | `spur-doctor` | 1.0 | Cross-noun evaluation — read-only CLI evidence per noun (task/feature/rule/workflow/agent spec), reflection over `history-anatomy` findings through a closed action-class map, and a proposal table; writes nothing; diagnoses artifacts, not runtime environments (`spur agent doctor`'s job) |
345
+ | `spur-check` | 1.0 | Two-tier check primitive (ADR-124) — light changed-scope checks during development, the full `bun run spur-check` chain at the quality boundary, and the fingerprint-bound `<wbs>-check-receipt.json` with the `status` reuse rule; composes with `/sp:dev-fixall --gate-log` |
345
346
  | `redesign-web-ui` | 1.0 | Existing-UI visual upgrade — audit generic AI fingerprints, apply in-stack polish against `DESIGN.md` / live tokens, verify behavior and viewports; does not migrate frameworks |
346
347
  | `taste-refactoring-api` | 1.0 | API design and refactoring for REST/HTTP, RPC/gRPC, GraphQL, event contracts, and CLI surfaces; compatibility, security, and migration review |
347
348
  | `taste-refactoring-ui` | 1.0 | UI design and refactoring with visual hierarchy, typography, spacing, color, and interaction review |
@@ -512,6 +513,7 @@ hold `SKILL.md` and prompt-side companions only.
512
513
  | `scripts/feature-sync-bounded.ts` | Bounded retry-suppression wrapper for `spur feature sync` during batch/wrap-up runs — suppresses identical L4-blocked repeats |
513
514
  | `scripts/stage-registry-adapter.ts` | dev-next golden-path adapter over the canonical stage registry — TABLE A/B/C resolution bridge for the status-aware facade |
514
515
  | `scripts/task-size-precheck.ts` | Pipeline size precheck guard (R2) — evaluates R-item/Plan-count limits to PASS/FAIL |
516
+ | `scripts/task-diffstat.ts` | 0943 triage-lane diffstat producer — numstat + untracked scan vs the run base; writes `<wbs>-diffstat.json` with the sensitive-path flag; fails safe (sensitive) |
515
517
  | `scripts/validate-flag-contracts.ts` | Mechanical consistency gate — compares flag claims across command files, flag-glossary, cross-cutting, dev-operations, and ADR; reports disagreements |
516
518
  | `*.test.ts` | Unit suites — in `hooks/` for guards, in `tests/<skill>/` per ADR-031 pairing |
517
519
 
@@ -629,7 +631,10 @@ pipeline owns one lifecycle phase:
629
631
  | `idea-pipeline.yaml` | Idea/planning → feature + tasks | `/sp:dev-idea`, `/sp:dev-plan` |
630
632
  | `wrapup-pipeline.yaml` | Post-execution wrap-up | `/sp:dev-wrap`, `/sp:dev-wrapall` |
631
633
  | `wayfinder-resolution.yaml` | Wayfinder ticket resolution loop | `spur workflow run` (free-form) |
632
- | `decision-routing-example.yaml` | DecisionMaker routing example (never / evidence / omitted modes, defer → operator pause) | `spur workflow run` (authoring sample) |
634
+
635
+ > 0946 (D64): the `decision-routing-example.yaml` authoring sample was retired from the catalogue
636
+ > (zero runs, example-only — `docs/design/workflow-catalogue-refactor.md` §10); the pattern lives on
637
+ > as a test fixture (`packages/app/tests/services/fixtures/decision-routing-example.yaml`).
633
638
 
634
639
  ### Lifecycle operations
635
640
 
@@ -15,7 +15,7 @@ Wraps the **sp:spur-dev** and **sp:code-implementation** skills.
15
15
  | --- | --- | --- |
16
16
  | `<wbs>` | Task WBS to run. | required |
17
17
  | `--mode` `<full\|implement>` | Full pipeline or single implement step. | full |
18
- | `--agent` `<inline\|auto\|name>` | Who runs the model-bearing stages. omit and explicit `--agent inline` resolve identically (task 0687): eligible stages dispatch once to a native subagent with host-session fallback (0508 eligibility); otherwise every stage executes in the invoking session. `auto` or a name keeps subprocess dispatch.. | omit |
18
+ | `--agent` `<inline\|auto\|name>` | Who runs the model-bearing stages. omit and explicit `--agent inline` resolve identically (task 0687): eligible stages dispatch once to a native subagent with host-session fallback (0508 eligibility); otherwise every stage executes in the invoking session. `auto` or a name keeps subprocess dispatch.. The opt-in `fleet` selector (0942) maps the run to `executor: 'fleet'` so `agent.run` stages dispatch through the fleet control plane (ADR-126) instead of a subprocess. | omit |
19
19
  | `--auto` | Skip objective HITL confirmations. | off |
20
20
  | `--next` | Chain-to-completion via the next-router. | off |
21
21
  | `--wrap` | Run the wrap hop after the main step. The `--agent` selector is preserved into the `/sp:dev-wrap <wbs>` handoff when supplied; omission remains omission. The wrap hop is workflow-backed and reports its trigger-3 subprocess override. | off |
@@ -40,7 +40,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
40
40
 
41
41
  **Flags:**
42
42
 
43
- - `--auto` | `--agent <inline|auto|name>` — Skip objective HITL confirmations (taste/irreversible gates still pause). `--agent` names who does the model-bearing work. Interactive omit/`inline` keeps the controller and implement-only stages in this session; full mode reads `task-pipeline.yaml` as the SSOT and interprets its actions/guards through the inline driver, whose eligible `agent.run` stages dispatch once to a native subagent and otherwise run in the host (0508 eligibility — task 0687 resolved-inline) It records `stage <id> executed inline in session <session-id>` or `stage <id> executed via subagent <agent-id> (host session <session-id>)` in the run log. `auto` or a name is merged into `vars.agent` and `vars.implementAgent` and keeps the existing subprocess workflow. Headless `spur workflow run` / `spur agent run` is unchanged. See the [execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
43
+ - `--auto` | `--agent <inline|auto|name>` — Skip objective HITL confirmations (taste/irreversible gates still pause). `--agent` names who does the model-bearing work. Interactive omit/`inline` keeps the controller and implement-only stages in this session; full mode reads `task-pipeline.yaml` as the SSOT and interprets its actions/guards through the inline driver, whose eligible `agent.run` stages dispatch once to a native subagent and otherwise run in the host (0508 eligibility — task 0687 resolved-inline) It records `stage <id> executed inline in session <session-id>` or `stage <id> executed via subagent <agent-id> (host session <session-id>)` in the run log. `auto` or a name is merged into `vars.agent` and `vars.implementAgent` and keeps the existing subprocess workflow; `--agent fleet` maps the run to `vars.executor: 'fleet'` instead (fleet dispatch, 0942). Headless `spur workflow run` / `spur agent run` is unchanged. See the [execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
44
44
  - `--escalation-file <path>` (implement step only, 0933) — names the Q/A escalation transcript
45
45
  (default `.spur/run/<wbs>-escalation.md`). Absent on a first attempt means "no prior
46
46
  escalations". When the implement agent needs an operator decision it appends its question to
@@ -18,7 +18,7 @@ Wraps the **sp:spur-dev** skill.
18
18
  | `--mode` `<sequential\|parallel>` | Batch execution order. | sequential |
19
19
  | `--keep-going` | Continue past per-task failures. | off |
20
20
  | `--auto` | Skip objective HITL gates. | off |
21
- | `--agent` `<inline\|auto\|name>` | Who runs each task's pipeline stages. Interactive sequential omit/`inline` uses the host-session driver with unified inline semantics (task 0687 — see below)). `auto`, a name, parallel mode, and headless invocation use subprocesses. | omit |
21
+ | `--agent` `<inline\|auto\|name>` | Who runs each task's pipeline stages. Interactive sequential omit/`inline` uses the host-session driver with unified inline semantics (task 0687 — see below)). `auto`, a name, parallel mode, and headless invocation use subprocesses. The opt-in `fleet` selector (0942) maps each task run to `executor: 'fleet'` so `agent.run` stages dispatch through the fleet control plane (ADR-126) instead of a subprocess. | omit |
22
22
  | `--json` | Emit structured JSON. | off |
23
23
  | `--wrap` | Run the wrap hop **once for the batch** over the `done` subset only (Step 6 of execution-batch.md): `vars.feature` is passed only when every frozen task is `done`/`cancelled`; an empty done subset skips the wrap with a reason. The `--agent` selector is preserved into the `/sp:dev-wrap` handoff when supplied; omission remains omission. | off |
24
24
  | `--next` | Chain-to-completion via the next-router. | off |
@@ -49,7 +49,7 @@ only after its in-set dependencies are **integrated** onto the base ref), `--kee
49
49
  halts on first failure), `--auto`
50
50
  (sets `profile=auto` on each per-task run, skipping the HITL approve gate), `--agent <inline|auto|name>`
51
51
  (names who runs the pipeline's stages — interactive sequential omit/`inline` uses the host driver;
52
- `auto` or a name is merged into each per-task `vars.agent` and `vars.implementAgent`; parallel mode
52
+ `auto` or a name is merged into each per-task `vars.agent` and `vars.implementAgent`; `fleet` maps each task run to `executor: 'fleet'` instead (fleet dispatch, 0942); parallel mode
53
53
  retains isolated subprocesses, ADR-047), `--json` (emit the
54
54
  report as JSON), `--wrap` (trigger
55
55
  `wrapup-pipeline.yaml` after the batch completes), `--next`