@gobing-ai/spur 0.3.55 → 0.3.58

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 (91) hide show
  1. package/.claude-plugin/marketplace.json +2 -3
  2. package/config/corpus-baseline.json +2748 -68
  3. package/config/rules/strict/runtime-boundaries.yaml +3 -0
  4. package/config/rules/surface/check-cli-surface.yaml +1 -0
  5. package/config/workflow-composition-baseline.json +242 -23
  6. package/config/workflows/wrapup-pipeline.yaml +31 -1
  7. package/package.json +9 -9
  8. package/plugins/README.md +1 -1
  9. package/plugins/sp/README.md +1 -1
  10. package/plugins/sp/agents/expert-spur.md +4 -1
  11. package/plugins/sp/commands/dev-feature-change.md +2 -2
  12. package/plugins/sp/commands/dev-idea.md +7 -19
  13. package/plugins/sp/plugin.json +1 -1
  14. package/plugins/sp/scripts/task-size-precheck.ts +11 -6
  15. package/plugins/sp/skills/dogfood-testing/references/monitor-ledger.md +4 -4
  16. package/plugins/sp/skills/dogfood-testing/references/report-template.md +4 -2
  17. package/plugins/sp/skills/issue-finding/SKILL.md +23 -11
  18. package/plugins/sp/skills/spur-cli/SKILL.md +23 -15
  19. package/plugins/sp/skills/spur-cli/references/agent.md +5 -0
  20. package/plugins/sp/skills/spur-cli/references/builder.md +49 -0
  21. package/plugins/sp/skills/spur-cli/references/features/hierarchy-mece.md +2 -2
  22. package/plugins/sp/skills/spur-cli/references/features/roadmap-priority.md +1 -1
  23. package/plugins/sp/skills/spur-cli/references/features/verbs.md +1 -1
  24. package/plugins/sp/skills/spur-cli/references/features.md +11 -3
  25. package/plugins/sp/skills/spur-cli/references/message.md +5 -0
  26. package/plugins/sp/skills/spur-cli/references/rules.md +5 -0
  27. package/plugins/sp/skills/spur-cli/references/self.md +101 -0
  28. package/plugins/sp/skills/spur-cli/references/tasks.md +6 -1
  29. package/plugins/sp/skills/spur-cli/references/team.md +5 -0
  30. package/plugins/sp/skills/spur-cli/references/workflows.md +40 -0
  31. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +1 -1
  32. package/plugins/sp/skills/spur-dev/references/dev-operations.md +9 -8
  33. package/plugins/sp/skills/spur-dev/references/execution-batch.md +19 -0
  34. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +1 -1
  35. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +27 -5
  36. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +1 -1
  37. package/spur.js +5060 -553
  38. package/web/_astro/BoardApp.CrusGeQ4.js +1 -0
  39. package/web/_astro/BoardApp.P8SRAD5Q.js +179 -0
  40. package/web/_astro/{TaskDetail.DN_RxS-2.js → TaskDetail.DHjEt5vl.js} +1 -1
  41. package/web/_astro/{arc.CldTHRg-.js → arc.XgRC1Ij_.js} +1 -1
  42. package/web/_astro/{architectureDiagram-3BPJPVTR.DQ4T9oeU.js → architectureDiagram-3BPJPVTR.DOZWrfHc.js} +1 -1
  43. package/web/_astro/{blockDiagram-GPEHLZMM.CGDaXH8J.js → blockDiagram-GPEHLZMM.ClQnCGf_.js} +1 -1
  44. package/web/_astro/{c4Diagram-AAUBKEIU.Cqsc2iub.js → c4Diagram-AAUBKEIU.CKCtUD0Y.js} +1 -1
  45. package/web/_astro/channel.CmK546kO.js +1 -0
  46. package/web/_astro/{chunk-2J33WTMH.CQSKsRmq.js → chunk-2J33WTMH.c7J8OHq5.js} +1 -1
  47. package/web/_astro/{chunk-4BX2VUAB.Pv1HCtp8.js → chunk-4BX2VUAB.C-A1Dv27.js} +1 -1
  48. package/web/_astro/{chunk-55IACEB6.-9MseOIk.js → chunk-55IACEB6.C4kGGALB.js} +1 -1
  49. package/web/_astro/{chunk-727SXJPM.M3OI1DF7.js → chunk-727SXJPM.Bith2NHt.js} +1 -1
  50. package/web/_astro/{chunk-AQP2D5EJ.NfHka5Ca.js → chunk-AQP2D5EJ.qeSXhsVY.js} +1 -1
  51. package/web/_astro/{chunk-FMBD7UC4.B2suYe3s.js → chunk-FMBD7UC4.DdQQnTAy.js} +1 -1
  52. package/web/_astro/{chunk-ND2GUHAM.BfJ7aucP.js → chunk-ND2GUHAM.BZYQVeZd.js} +1 -1
  53. package/web/_astro/{chunk-QZHKN3VN._582hZVc.js → chunk-QZHKN3VN.BiUWwHaV.js} +1 -1
  54. package/web/_astro/{classDiagram-4FO5ZUOK._tttU_jk.js → classDiagram-4FO5ZUOK.OGRhcldh.js} +1 -1
  55. package/web/_astro/{classDiagram-v2-Q7XG4LA2._tttU_jk.js → classDiagram-v2-Q7XG4LA2.OGRhcldh.js} +1 -1
  56. package/web/_astro/{cose-bilkent-S5V4N54A.C4rVATLQ.js → cose-bilkent-S5V4N54A.DxYklM_j.js} +1 -1
  57. package/web/_astro/{dagre-BM42HDAG.8BLRi7f9.js → dagre-BM42HDAG.BSb2dEbo.js} +1 -1
  58. package/web/_astro/{diagram-2AECGRRQ.D6lwUZos.js → diagram-2AECGRRQ.mOrItPK2.js} +1 -1
  59. package/web/_astro/{diagram-5GNKFQAL.Cl_rMiVT.js → diagram-5GNKFQAL.C99r7J3C.js} +1 -1
  60. package/web/_astro/{diagram-KO2AKTUF.DhMExHLp.js → diagram-KO2AKTUF.BKbnisJO.js} +1 -1
  61. package/web/_astro/{diagram-LMA3HP47.3OAxzH_o.js → diagram-LMA3HP47.D8FJGo4V.js} +1 -1
  62. package/web/_astro/{diagram-OG6HWLK6.Uh9RLucG.js → diagram-OG6HWLK6.DVBs7n4Q.js} +1 -1
  63. package/web/_astro/{erDiagram-TEJ5UH35.izzElG0c.js → erDiagram-TEJ5UH35.CddMTl4l.js} +1 -1
  64. package/web/_astro/{flowDiagram-I6XJVG4X.BlGwp5qM.js → flowDiagram-I6XJVG4X.B7WclnjQ.js} +1 -1
  65. package/web/_astro/{ganttDiagram-6RSMTGT7.je7Vf8dN.js → ganttDiagram-6RSMTGT7.CRh7ggvz.js} +1 -1
  66. package/web/_astro/{gitGraphDiagram-PVQCEYII.DNk0Ycop.js → gitGraphDiagram-PVQCEYII.du-L7V8A.js} +1 -1
  67. package/web/_astro/index.B4x8fe52.css +1 -0
  68. package/web/_astro/{infoDiagram-5YYISTIA.K2HhGyWH.js → infoDiagram-5YYISTIA.CClGl9Px.js} +1 -1
  69. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BbbOW4IN.js → ishikawaDiagram-YF4QCWOH.BuTETIPz.js} +1 -1
  70. package/web/_astro/{journeyDiagram-JHISSGLW.B1UaZKqN.js → journeyDiagram-JHISSGLW.Cyg1zCiE.js} +1 -1
  71. package/web/_astro/{kanban-definition-UN3LZRKU.9tsw5QFd.js → kanban-definition-UN3LZRKU.aH8eDXwX.js} +1 -1
  72. package/web/_astro/{linear.I9dvtu-j.js → linear.paE_RY_i.js} +1 -1
  73. package/web/_astro/{mermaid.core.5bKJsMgf.js → mermaid.core.BhdKkI85.js} +4 -4
  74. package/web/_astro/{mindmap-definition-RKZ34NQL.BC5MqSn6.js → mindmap-definition-RKZ34NQL.aZNfv8Nx.js} +1 -1
  75. package/web/_astro/{pieDiagram-4H26LBE5.B8GIMyxD.js → pieDiagram-4H26LBE5.5psni0hC.js} +1 -1
  76. package/web/_astro/{quadrantDiagram-W4KKPZXB.K_aZSM_u.js → quadrantDiagram-W4KKPZXB.DcGPGrFd.js} +1 -1
  77. package/web/_astro/{requirementDiagram-4Y6WPE33.B_V5kzfT.js → requirementDiagram-4Y6WPE33.BwPD2lzi.js} +1 -1
  78. package/web/_astro/{sankeyDiagram-5OEKKPKP.DyV2quLS.js → sankeyDiagram-5OEKKPKP.B3jGVk0l.js} +1 -1
  79. package/web/_astro/{sequenceDiagram-3UESZ5HK.BlKvPnBv.js → sequenceDiagram-3UESZ5HK.BdWsx3LW.js} +1 -1
  80. package/web/_astro/{stateDiagram-AJRCARHV.C1lYUMdS.js → stateDiagram-AJRCARHV.DRpG4ClZ.js} +1 -1
  81. package/web/_astro/{stateDiagram-v2-BHNVJYJU.DJWjr3QV.js → stateDiagram-v2-BHNVJYJU.Su0ML9Wo.js} +1 -1
  82. package/web/_astro/{timeline-definition-PNZ67QCA.CYYiXVlv.js → timeline-definition-PNZ67QCA.Cg_Uv8A5.js} +1 -1
  83. package/web/_astro/{vennDiagram-CIIHVFJN.zYjQKa_S.js → vennDiagram-CIIHVFJN.DVouKT4b.js} +1 -1
  84. package/web/_astro/{wardley-L42UT6IY.BGrWTY3D.js → wardley-L42UT6IY.CexIVQli.js} +1 -1
  85. package/web/_astro/{wardleyDiagram-YWT4CUSO.CkXxtcI1.js → wardleyDiagram-YWT4CUSO.ChSAzcvx.js} +1 -1
  86. package/web/_astro/{xychartDiagram-2RQKCTM6.BhKH3KG3.js → xychartDiagram-2RQKCTM6.CN-Yp50J.js} +1 -1
  87. package/web/index.html +2 -2
  88. package/web/_astro/BoardApp.DCLSB3Zs.js +0 -179
  89. package/web/_astro/BoardApp.Dx5gzAhb.js +0 -1
  90. package/web/_astro/channel.CrBJYpxo.js +0 -1
  91. package/web/_astro/index.V6Q7nhed.css +0 -1
@@ -57,6 +57,7 @@ rules:
57
57
 
58
58
  # Low-level system primitives (O_EXCL locks, FD byte-tailing, FS watcher, atomic registry, Vite glob fallback):
59
59
  - "packages/domain/src/planning/locks.ts" # POSIX atomic O_EXCL file locks
60
+ - "packages/domain/src/retention.ts" # recursive backup-dir reclamation + mtime stat (task 0622 R8); ts-runtime FileSystem seam has no recursive dir remove
60
61
  - "packages/app/src/services/token-ledger-service.ts" # FD byte-window log tailing
61
62
  - "packages/app/src/services/token-ledger-watcher.ts" # node:fs watch() live watcher
62
63
  - "packages/app/src/services/project-registry.ts" # atomic projects.json persistence
@@ -73,3 +74,5 @@ rules:
73
74
  # Synchronous template & workflow resolvers:
74
75
  - "apps/cli/src/commands/task.ts" # sync loadTemplateContent/Bodies callbacks
75
76
  - "apps/cli/src/workflow/make-lifecycle-adapter.ts" # sync resolveWorkflowPath lookup
77
+ - "packages/app/src/services/workflow-service.ts" # sync workflow-composition-baseline.json lookup (walk-up) for the validate composition advisory (task 0614); sync read inside the synchronous validate path
78
+ - "apps/cli/src/release-ops.ts" # sync manifest/workspace reads + Bun.write rewrites inside the git release flow (task 0617); mirrors task.ts sync-template exemption
@@ -11,6 +11,7 @@ include:
11
11
 
12
12
  exclude:
13
13
  - "apps/cli/src/commands/stubs.ts"
14
+ - "apps/cli/src/commands/shared-options.ts" # option registry, not a command module (consistency.test.ts excludes it too)
14
15
 
15
16
  rules:
16
17
  - id: cli-register-pattern
@@ -114,7 +114,8 @@
114
114
  "kind": "shell",
115
115
  "invocation": "mkdir -p .spur/run && DOCTOR_FILE=\".spur/run/$__runId-idea-precheck-doctor.status\" && set +e && $spurBin agent doctor $agent; doctor_rc=$?; set -e && if [ \"$doctor_rc\" -eq 0 ] && test -n \"$idea\"; then\n printf 'PASS\\n' > \"$DOCTOR_FILE\";\nelse\n printf 'FAIL\\n' > \"$DOCTOR_FILE\";\nfi && exit 0",
116
116
  "stateEffect": "read",
117
- "evidenceEffect": "write"
117
+ "evidenceEffect": "write",
118
+ "disposition": "GLUE"
118
119
  },
119
120
  "discovery:onEnter:0": {
120
121
  "kind": "agent.run",
@@ -166,7 +167,8 @@
166
167
  "kind": "shell",
167
168
  "invocation": "if test -s .spur/run/$__runId-idea-ac-content.md; then\n $spurBin feature update \"$featureId\" --section \"Acceptance Criteria\" --from-file .spur/run/$__runId-idea-ac-content.md && date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-ac-done.txt;\nelse\n exit 0;\nfi",
168
169
  "stateEffect": "write",
169
- "evidenceEffect": "none"
170
+ "evidenceEffect": "none",
171
+ "disposition": "GLUE"
170
172
  },
171
173
  "feature-check:onEnter:0": {
172
174
  "kind": "hitl.confirm",
@@ -218,7 +220,8 @@
218
220
  "kind": "shell",
219
221
  "invocation": "BATCH=\".spur/run/$__runId-idea-task-batch.json\" && ORDER=\".spur/run/$__runId-idea-task-order.json\" && jq -e --slurpfile b \"$BATCH\" '\n (type == \"array\") and\n (($b[0] | map(.name) | length) == ($b[0] | map(.name) | unique | length)) and\n ((map(.name) | length) == (map(.name) | unique | length)) and\n ((map(.name) - ($b[0] | map(.name))) | length == 0) and\n ((($b[0] | map(.name)) - map(.name)) | length == 0) and\n (((map(.depends_on_names // []) | flatten | unique) - ($b[0] | map(.name))) | length == 0)\n' \"$ORDER\" >/dev/null",
220
222
  "stateEffect": "read",
221
- "evidenceEffect": "none"
223
+ "evidenceEffect": "none",
224
+ "disposition": "GLUE"
222
225
  },
223
226
  "batch-create:onEnter:0": {
224
227
  "kind": "hitl.confirm",
@@ -229,13 +232,15 @@
229
232
  "kind": "shell",
230
233
  "invocation": "if test -f .spur/run/$__runId-idea-batch-create.done; then exit 0; fi; rm -f .spur/run/$__runId-idea-batch-create.failed .spur/run/$__runId-idea-batch-create-result.json .spur/run/$__runId-idea-batch-create-result.json.tmp; if $spurBin task batch-create --file .spur/run/$__runId-idea-task-batch.json --json > .spur/run/$__runId-idea-batch-create-result.json.tmp && jq -e \".created == (.wbs | length)\" .spur/run/$__runId-idea-batch-create-result.json.tmp >/dev/null 2>&1; then\n mv .spur/run/$__runId-idea-batch-create-result.json.tmp .spur/run/$__runId-idea-batch-create-result.json &&\n date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.done;\nelse\n rm -f .spur/run/$__runId-idea-batch-create-result.json.tmp;\n date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.failed;\nfi",
231
234
  "stateEffect": "write",
232
- "evidenceEffect": "write"
235
+ "evidenceEffect": "write",
236
+ "disposition": "GLUE"
233
237
  },
234
238
  "handoff-finalize:onEnter:0": {
235
239
  "kind": "shell",
236
240
  "invocation": "if [ -f packages/app/src/workflow/idea-handoff-cli.ts ]; then\n bun packages/app/src/workflow/idea-handoff-cli.ts;\n exit $?;\nfi; BATCH=\".spur/run/$__runId-idea-task-batch.json\" && RESULT=\".spur/run/$__runId-idea-batch-create-result.json\" && ORDER=\".spur/run/$__runId-idea-task-order.json\" && REPORT=\".spur/run/$__runId-idea-handoff.md\" && DEPMAP=\".spur/run/$__runId-idea-dep-map.tsv\" && CHECKS=\".spur/run/$__runId-idea-check-results.jsonl\" && rm -f \"$DEPMAP\" \"$CHECKS\" && jq -e --slurpfile b \"$BATCH\" '(.wbs | length) == ($b[0] | length) and (($b[0] | map(.name) | unique | length) == ($b[0] | length))' \"$RESULT\" >/dev/null && jq -r --slurpfile b \"$BATCH\" --slurpfile r \"$RESULT\" --slurpfile o \"$ORDER\" '\n ($b[0] | map(.name)) as $names | ($r[0].wbs) as $wbs |\n $o[0] | .[] |\n (.name as $n | $names | index($n)) as $i |\n (if $i == null or $wbs[$i] == null then \"MISSING\" else $wbs[$i] end) as $own |\n ((.depends_on_names // []) | map(. as $dep | $names | index($dep) as $d | if $d == null then \"MISSING\" else $wbs[$d] end) | join(\" \")) as $deps |\n [.name, $own, $deps] | @tsv\n' \"$ORDER\" > \"$DEPMAP\" && while IFS=\"$(printf '\\t')\" read -r name own deps; do\n test \"$own\" != MISSING || exit 1;\n if test -n \"$deps\"; then\n for d in $deps; do test \"$d\" != MISSING || exit 1; done;\n $spurBin task deps \"$own\" set $deps --json >/dev/null || exit 1;\n fi;\ndone < \"$DEPMAP\" && $spurBin feature refresh --feature \"$featureId\" --json >/dev/null && WBS_LIST=$(jq -r '.wbs | join(\" \")' \"$RESULT\") && for wbs in $WBS_LIST; do\n TMP=\".spur/run/$__runId-idea-check-$wbs.tmp\";\n if $spurBin task check \"$wbs\" --json > \"$TMP\" 2>&1; then P=true; else P=false; fi;\n jq -c --arg w \"$wbs\" --argjson p \"$P\" 'if (type == \"array\" and length > 0) then {wbs: $w, pass: $p, status: (.[0].status // \"unknown\")} else {wbs: $w, pass: $p, status: \"missing\"} end' \"$TMP\" >> \"$CHECKS\" || exit 1;\n rm -f \"$TMP\";\ndone && CHECK_ROWS=$(wc -l < \"$CHECKS\" | tr -d ' ') && WBS_COUNT=$(printf '%s\\n' $WBS_LIST | wc -l | tr -d ' ') && test \"$CHECK_ROWS\" = \"$WBS_COUNT\" && NEXT=$(jq -r --arg feature \"$featureId\" --slurpfile c \"$CHECKS\" 'if ($c | any(.[]; .pass == false)) then \"/sp:dev-refineall --feature \\($feature) --auto --depth ready\" else \"/sp:dev-runall --feature \\($feature) --auto\" end' \"$RESULT\") && {\n echo \"# Idea pipeline handoff report\";\n echo;\n echo \"Feature: $featureId\";\n echo \"Run ID: $__runId\";\n echo;\n echo \"## Created tasks\";\n printf '%s\\n' $WBS_LIST | sed 's/^/ - /';\n echo;\n echo \"## Per-task readiness (spur task check)\";\n echo;\n echo \"| WBS | Outcome |\";\n echo \"|-----|---------|\";\n while IFS= read -r line; do\n w=$(printf '%s' \"$line\" | jq -r '.wbs');\n o=$(printf '%s' \"$line\" | jq -r 'if .pass then \"PASS\" else \"FAIL\" end');\n printf '| %s | %s |\\n' \"$w\" \"$o\";\n done < \"$CHECKS\";\n echo;\n echo \"## Next command\";\n echo;\n echo \"$NEXT\";\n} > \"$REPORT\"",
237
241
  "stateEffect": "write",
238
- "evidenceEffect": "write"
242
+ "evidenceEffect": "write",
243
+ "disposition": "DUAL"
239
244
  },
240
245
  "handoff:onEnter:0": {
241
246
  "kind": "note",
@@ -274,7 +279,8 @@
274
279
  "kind": "shell",
275
280
  "invocation": "DIRTY=$(git status --porcelain -- . ':(exclude)docs/tasks*' ':(exclude)docs/features' 2>/dev/null); if [ -n \"$DIRTY\" ]; then\n echo \"precheck: WARNING - working tree has uncommitted non-corpus changes; commit or stash before starting a new task:\";\n echo \"$DIRTY\";\nfi; CORPUS_DIRTY=$(git status --porcelain -- ':(glob)docs/tasks*/**' 2>/dev/null); if [ -n \"$CORPUS_DIRTY\" ]; then\n echo \"precheck: NOTE - task corpus has uncommitted changes; review before staging with this task:\";\n echo \"$CORPUS_DIRTY\";\nfi; exit 0",
276
281
  "stateEffect": "read",
277
- "evidenceEffect": "none"
282
+ "evidenceEffect": "none",
283
+ "disposition": "GLUE"
278
284
  },
279
285
  "precheck:onEnter:2": {
280
286
  "kind": "note",
@@ -285,13 +291,15 @@
285
291
  "kind": "shell",
286
292
  "invocation": "if [ \"$profile\" = auto ]; then\n FID=$($spurBin task show $wbs --json 2>/dev/null |\n jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null);\n if [ -n \"$FID\" ]; then\n $spurBin feature sync \"$FID\" --force --json 2>/dev/null ||\n $spurBin feature update \"$FID\" active 2>/dev/null || true;\n fi;\nfi; exit 0",
287
293
  "stateEffect": "write",
288
- "evidenceEffect": "none"
294
+ "evidenceEffect": "none",
295
+ "disposition": "GLUE"
289
296
  },
290
297
  "precheck:onEnter:4": {
291
298
  "kind": "shell",
292
299
  "invocation": "SIZE_FILE=\".spur/run/$wbs-precheck-size.status\" && mkdir -p .spur/run && if [ -f plugins/sp/scripts/task-size-precheck.ts ]; then bun plugins/sp/scripts/task-size-precheck.ts \"$wbs\" --spur-bin \"$spurBin\" --max-reqs \"$maxImplementReqs\" --max-plan-items \"$maxImplementPlanItems\" --executor \"$implementAgent\"; else\n echo \"task-size-precheck skipped — plugins/sp/scripts/task-size-precheck.ts not present in project.\" >&2 &&\n echo \"PASS\" > \"$SIZE_FILE\";\nfi && exit 0",
293
300
  "stateEffect": "read",
294
- "evidenceEffect": "write"
301
+ "evidenceEffect": "write",
302
+ "disposition": "EXT"
295
303
  },
296
304
  "implement:onEnter:0": {
297
305
  "kind": "agent.run",
@@ -303,7 +311,8 @@
303
311
  "kind": "shell",
304
312
  "invocation": "retry_transient() {\n RETRY_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_OUTPUT\";\n if [ \"$RETRY_RC\" -eq 0 ]; then return 0; fi;\n if ! printf '%s' \"$RETRY_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return \"$RETRY_RC\"; fi;\n sleep 2;\n RETRY_SECOND_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_SECOND_OUTPUT\";\n if [ \"$RETRY_RC\" -ne 0 ] && printf '%s\\n%s' \"$RETRY_OUTPUT\" \"$RETRY_SECOND_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then\n echo \"node_modules link/dependency broken — run bun install and retry\";\n fi;\n return \"$RETRY_RC\";\n}; retry_transient $spurBin task update \"$wbs\" wip --no-lifecycle",
305
313
  "stateEffect": "write",
306
- "evidenceEffect": "none"
314
+ "evidenceEffect": "none",
315
+ "disposition": "GLUE"
307
316
  },
308
317
  "implement:onEnter:2": {
309
318
  "kind": "shell",
@@ -315,7 +324,8 @@
315
324
  "kind": "shell",
316
325
  "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$wbs-test-gate.status\" && LOG_FILE=\".spur/run/$wbs-test-gate.log\" && ATTEMPT_FILE=\".spur/run/$wbs-test-fix-attempt\" && echo 0 > \"$ATTEMPT_FILE\" && : > \"$LOG_FILE\" && gate_attempt=1; while [ \"$gate_attempt\" -le 5 ]; do\n ATTEMPT_LOG=\"$LOG_FILE.attempt-$gate_attempt\";\n sh -c \"$qualityGateCmd\" > \"$ATTEMPT_LOG\" 2>&1; gate_rc=$?;\n gate_locked=0;\n grep -q 'SQLiteError: database is locked' \"$ATTEMPT_LOG\" && gate_locked=1;\n cat \"$ATTEMPT_LOG\" >> \"$LOG_FILE\";\n rm -f \"$ATTEMPT_LOG\";\n if [ \"$gate_rc\" -eq 0 ] || [ \"$gate_locked\" -ne 1 ] || [ \"$gate_attempt\" -ge 5 ]; then break; fi;\n printf 'quality gate: database is locked; retrying (%s/5) in 10s\\n' \"$gate_attempt\" | tee -a \"$LOG_FILE\";\n sleep 10;\n gate_attempt=$((gate_attempt + 1));\ndone && cat \"$LOG_FILE\" && FINDINGS_FILE=\".spur/run/$wbs-test-gate.findings\" && set +e; grep -oE '[A-Za-z0-9_./-]+\\.[A-Za-z]+:[0-9]+' \"$LOG_FILE\" | sort -u | head -20 | tr '\\n' ' ' > \"$FINDINGS_FILE\"; set -e && if [ \"$gate_rc\" -eq 0 ]; then\n printf 'PASS\\n' > \"$STATUS_FILE\";\nelse\n printf 'FAIL\\n' > \"$STATUS_FILE\";\nfi && exit 0",
317
326
  "stateEffect": "write",
318
- "evidenceEffect": "write"
327
+ "evidenceEffect": "write",
328
+ "disposition": "POLICY"
319
329
  },
320
330
  "test-fix:onEnter:0": {
321
331
  "kind": "shell",
@@ -338,7 +348,8 @@
338
348
  "kind": "shell",
339
349
  "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$wbs-test-gate.status\" && LOG_FILE=\".spur/run/$wbs-test-gate.log\" && FINDINGS_FILE=\".spur/run/$wbs-test-gate.findings\" && : > \"$LOG_FILE\" && probe_rc=0; if [ -n \"$gateProbeCmd\" ]; then\n sh -c \"$gateProbeCmd\" > \"$LOG_FILE.probe\" 2>&1; probe_rc=$?;\n cat \"$LOG_FILE.probe\" >> \"$LOG_FILE\";\n rm -f \"$LOG_FILE.probe\";\nfi; if [ \"$probe_rc\" -ne 0 ]; then\n gate_rc=$probe_rc;\nelse\n gate_attempt=1;\n while [ \"$gate_attempt\" -le 5 ]; do\n ATTEMPT_LOG=\"$LOG_FILE.attempt-$gate_attempt\";\n sh -c \"$qualityGateCmd\" > \"$ATTEMPT_LOG\" 2>&1; gate_rc=$?;\n gate_locked=0;\n grep -q 'SQLiteError: database is locked' \"$ATTEMPT_LOG\" && gate_locked=1;\n cat \"$ATTEMPT_LOG\" >> \"$LOG_FILE\";\n rm -f \"$ATTEMPT_LOG\";\n if [ \"$gate_rc\" -eq 0 ] || [ \"$gate_locked\" -ne 1 ] || [ \"$gate_attempt\" -ge 5 ]; then break; fi;\n printf 'quality gate: database is locked; retrying (%s/5) in 10s\\n' \"$gate_attempt\" | tee -a \"$LOG_FILE\";\n sleep 10;\n gate_attempt=$((gate_attempt + 1));\n done;\nfi && cat \"$LOG_FILE\" && set +e; grep -oE '[A-Za-z0-9_./-]+\\.[A-Za-z]+:[0-9]+' \"$LOG_FILE\" | sort -u | head -20 | tr '\\n' ' ' > \"$FINDINGS_FILE\"; set -e && if [ \"$gate_rc\" -eq 0 ]; then\n printf 'PASS\\n' > \"$STATUS_FILE\";\nelse\n printf 'FAIL\\n' > \"$STATUS_FILE\";\nfi && exit 0",
340
350
  "stateEffect": "write",
341
- "evidenceEffect": "write"
351
+ "evidenceEffect": "write",
352
+ "disposition": "POLICY"
342
353
  },
343
354
  "review:onEnter:0": {
344
355
  "kind": "agent.run",
@@ -394,19 +405,22 @@
394
405
  "kind": "shell",
395
406
  "invocation": "retry_transient() {\n RETRY_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_OUTPUT\";\n if [ \"$RETRY_RC\" -eq 0 ]; then return 0; fi;\n if ! printf '%s' \"$RETRY_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return \"$RETRY_RC\"; fi;\n sleep 2;\n RETRY_SECOND_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_SECOND_OUTPUT\";\n if [ \"$RETRY_RC\" -ne 0 ] && printf '%s\\n%s' \"$RETRY_OUTPUT\" \"$RETRY_SECOND_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then\n echo \"node_modules link/dependency broken — run bun install and retry\";\n fi;\n return \"$RETRY_RC\";\n}; retry_transient $spurBin task record \"$wbs\" --solution-from-diff --transition testing",
396
407
  "stateEffect": "write",
397
- "evidenceEffect": "write"
408
+ "evidenceEffect": "write",
409
+ "disposition": "GLUE"
398
410
  },
399
411
  "record:onEnter:2": {
400
412
  "kind": "shell",
401
413
  "invocation": "FID=$($spurBin task show $wbs --json 2>/dev/null | jq -r \".feature_id // .frontmatter.feature_id // empty\" 2>/dev/null); if [ -n \"$FID\" ]; then\n if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then\n bun plugins/sp/scripts/feature-sync-bounded.ts \"$FID\" --spur-bin \"$spurBin\" --json;\n else\n $spurBin feature sync \"$FID\" --json;\n fi;\nelse\n echo \"Orphan task $wbs — no feature_id linked; proposal: consider linking to a parent feature.\" >> \".spur/run/$wbs-report.txt\";\nfi; exit 0",
402
414
  "stateEffect": "write",
403
- "evidenceEffect": "none"
415
+ "evidenceEffect": "none",
416
+ "disposition": "EXT"
404
417
  },
405
418
  "done:onEnter:0": {
406
419
  "kind": "shell",
407
420
  "invocation": "retry_transient() {\n RETRY_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_OUTPUT\";\n if [ \"$RETRY_RC\" -eq 0 ]; then return 0; fi;\n if ! printf '%s' \"$RETRY_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return \"$RETRY_RC\"; fi;\n sleep 2;\n RETRY_SECOND_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_SECOND_OUTPUT\";\n if [ \"$RETRY_RC\" -ne 0 ] && printf '%s\\n%s' \"$RETRY_OUTPUT\" \"$RETRY_SECOND_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then\n echo \"node_modules link/dependency broken — run bun install and retry\";\n fi;\n return \"$RETRY_RC\";\n}; retry_transient $spurBin task update \"$wbs\" done --no-lifecycle",
408
421
  "stateEffect": "write",
409
- "evidenceEffect": "none"
422
+ "evidenceEffect": "none",
423
+ "disposition": "GLUE"
410
424
  },
411
425
  "done:onEnter:1": {
412
426
  "kind": "run.artifact",
@@ -456,19 +470,22 @@
456
470
  "kind": "shell",
457
471
  "invocation": "mkdir -p .spur/memory && if test -s .spur/run/wrapup-learnings.md; then\n cat .spur/run/wrapup-learnings.md >> .spur/memory/learnings.md &&\n printf '\\n' >> .spur/memory/learnings.md;\nelse\n echo \"wrapup: learning capture missing or empty at $PWD/.spur/run/wrapup-learnings.md - append skipped\" >&2;\nfi && exit 0",
458
472
  "stateEffect": "write",
459
- "evidenceEffect": "write"
473
+ "evidenceEffect": "write",
474
+ "disposition": "GLUE"
460
475
  },
461
476
  "metrics-record:onEnter:0": {
462
477
  "kind": "shell",
463
478
  "invocation": "mkdir -p .spur/memory && printf '%s' \"$tasks\" | jq -r '(if type == \"string\" then (try fromjson catch .) else . end) | if type == \"array\" then .[] else empty end' | while IFS= read -r wbs; do\n if [ -n \"$wbs\" ]; then\n task_json=$($spurBin task show \"$wbs\" --json 2>/dev/null || true);\n if [ -n \"$task_json\" ]; then\n feat=$(printf '%s' \"$task_json\" | jq -r '.frontmatter.feature_id // .feature_id // empty' 2>/dev/null || true);\n stat=$(printf '%s' \"$task_json\" | jq -r '.frontmatter.status // .status // \"unknown\"' 2>/dev/null || true);\n verdict=\"UNKNOWN\";\n if [ -f \".spur/run/$wbs-verdict.json\" ]; then\n v=$(jq -r '.verdict // \"UNKNOWN\"' \".spur/run/$wbs-verdict.json\" 2>/dev/null || true);\n if [ -n \"$v\" ]; then verdict=\"$v\"; fi;\n fi;\n ts=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\");\n printf '{\"wbs\":\"%s\",\"feature_id\":\"%s\",\"status\":\"%s\",\"verdict\":\"%s\",\"timestamp\":\"%s\"}\\n' \"$wbs\" \"$feat\" \"$stat\" \"$verdict\" \"$ts\" >> .spur/memory/wrapup-metrics.jsonl;\n fi;\n fi;\ndone && exit 0",
464
479
  "stateEffect": "write",
465
- "evidenceEffect": "write"
480
+ "evidenceEffect": "write",
481
+ "disposition": "GLUE"
466
482
  },
467
483
  "feature-transition:onEnter:0": {
468
484
  "kind": "shell",
469
- "invocation": "if [ -z \"$feature\" ]; then\n echo \"feature-transition: vars.feature is empty — refusing no-op feature sync (mis-invocation, not a blocked sync)\" >&2;\n exit 1;\nfi; if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then\n bun plugins/sp/scripts/feature-sync-bounded.ts \"$feature\" --spur-bin \"$spurBin\" --json;\nelse\n $spurBin feature sync \"$feature\" --json;\nfi; exit 0",
485
+ "invocation": "if [ -z \"$feature\" ]; then\n echo \"feature-transition: vars.feature is empty — refusing no-op feature sync (mis-invocation, not a blocked sync)\" >&2;\n exit 1;\nfi; SYNC_OUTPUT=$(if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then\n bun plugins/sp/scripts/feature-sync-bounded.ts \"$feature\" --spur-bin \"$spurBin\" --json;\nelse\n $spurBin feature sync \"$feature\" --json;\nfi); SYNC_RC=$?; printf '%s\\n' \"$SYNC_OUTPUT\"; APPLIED=$(printf '%s' \"$SYNC_OUTPUT\" | jq -r '.applied // false' 2>/dev/null || echo false); if [ \"$APPLIED\" = \"true\" ] || [ \"$SYNC_RC\" -ne 0 ]; then\n echo \"feature-transition: sync applied or failed after a possible partial transition for $feature — running corpus-aware gate: $featureGateCmd\";\n if sh -c \"$featureGateCmd\"; then\n echo \"feature-transition: corpus-aware gate PASS for feature $feature\";\n else\n echo \"feature-transition: corpus-aware gate FAIL for feature $feature — inspect findings before reporting the transition complete\" >&2;\n fi;\nelse\n echo \"feature-transition: sync did not apply a transition (rc=$SYNC_RC, applied=$APPLIED) — corpus-aware gate skipped\";\nfi; exit 0",
470
486
  "stateEffect": "write",
471
- "evidenceEffect": "none"
487
+ "evidenceEffect": "none",
488
+ "disposition": "EXT"
472
489
  },
473
490
  "branch-cleanup:onEnter:0": {
474
491
  "kind": "hitl.confirm",
@@ -511,7 +528,8 @@
511
528
  "kind": "shell",
512
529
  "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-preflight.status\" && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" preflight --base \"$baseBranch\" --json > \".spur/run/$__runId-pr-context.json\"; rc=$?; set -e && if [ \"$rc\" -eq 0 ]; then printf 'PASS\\n' > \"$STATUS_FILE\"; else printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0",
513
530
  "stateEffect": "read",
514
- "evidenceEffect": "write"
531
+ "evidenceEffect": "write",
532
+ "disposition": "EXT"
515
533
  },
516
534
  "hygiene:onEnter:0": {
517
535
  "kind": "shell",
@@ -523,7 +541,8 @@
523
541
  "kind": "shell",
524
542
  "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-precheck.status\" && if [ -z \"$preReviewCmd\" ]; then\n printf 'SKIP\\n' > \"$STATUS_FILE\";\nelse\n set +e; sh -c \"$preReviewCmd\"; rc=$?; set -e;\n if [ \"$rc\" -eq 0 ]; then printf 'PASS\\n' > \"$STATUS_FILE\"; else printf 'FAIL\\n' > \"$STATUS_FILE\"; fi;\nfi && exit 0",
525
543
  "stateEffect": "read",
526
- "evidenceEffect": "write"
544
+ "evidenceEffect": "write",
545
+ "disposition": "POLICY"
527
546
  },
528
547
  "push:onEnter:0": {
529
548
  "kind": "shell",
@@ -541,19 +560,219 @@
541
560
  "kind": "shell",
542
561
  "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-request.status\" && FORCE_FLAG=\"\" && if [ \"$mode\" = \"rerun\" ]; then FORCE_FLAG=\"--force\"; fi && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" request --focus \"$focus\" $FORCE_FLAG --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-request.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0",
543
562
  "stateEffect": "write",
544
- "evidenceEffect": "write"
563
+ "evidenceEffect": "write",
564
+ "disposition": "EXT"
545
565
  },
546
566
  "wait:onEnter:0": {
547
567
  "kind": "shell",
548
568
  "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-wait.status\" && set +e && SINCE=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).requestedAt;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && REQUEST_HEAD=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).head;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && bun \"$(superskill script path sp pr-reviewing.ts)\" wait --since \"$SINCE\" --head \"$REQUEST_HEAD\" --timeout \"$waitTimeoutSec\" --interval \"$waitIntervalSec\" --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-wait.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0",
549
569
  "stateEffect": "read",
550
- "evidenceEffect": "write"
570
+ "evidenceEffect": "write",
571
+ "disposition": "EXT"
551
572
  },
552
573
  "collect:onEnter:0": {
553
574
  "kind": "shell",
554
575
  "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-collect.status\" && set +e && SINCE=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).requestedAt;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && REQUEST_HEAD=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).head;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && bun \"$(superskill script path sp pr-reviewing.ts)\" collect --since \"$SINCE\" --head \"$REQUEST_HEAD\" --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-findings.json\"; collect_rc=$?; bun \"$(superskill script path sp pr-reviewing.ts)\" status --since \"$SINCE\" --head \"$REQUEST_HEAD\" --json > \".spur/run/$__runId-pr-status.json\"; status_rc=$?; set -e && if [ \"$collect_rc\" -ne 0 ] || [ \"$status_rc\" -ne 0 ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && { cat \".spur/run/$__runId-pr-findings.json\" 2>/dev/null || true; } && exit 0",
555
576
  "stateEffect": "read",
556
- "evidenceEffect": "write"
577
+ "evidenceEffect": "write",
578
+ "disposition": "EXT"
579
+ }
580
+ }
581
+ },
582
+ "basic": {
583
+ "definition": "config/workflows/basic.yaml",
584
+ "boundary": "generic-execution",
585
+ "disposition": "keep",
586
+ "artifacts": ["fix-attempt-counter", "gate-status"],
587
+ "callers": ["spur workflow run"],
588
+ "terminalStates": ["done", "failed"],
589
+ "failurePolicy": "fail-closed",
590
+ "modelQueries": ["fix"],
591
+ "actions": {
592
+ "implement:onEnter:0": {
593
+ "kind": "note",
594
+ "stateEffect": "read",
595
+ "evidenceEffect": "none"
596
+ },
597
+ "implement:onEnter:1": {
598
+ "kind": "shell",
599
+ "invocation": "mkdir -p .spur/run && printf '0\\n' > .spur/run/$__runId-basic-fix-attempt",
600
+ "stateEffect": "write",
601
+ "evidenceEffect": "write",
602
+ "disposition": "GLUE"
603
+ },
604
+ "check:onEnter:0": {
605
+ "kind": "shell",
606
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-basic-gate.status\" && set +e && ( $qualityGateCmd ); gate_rc=$?; set -e && if [ \"$gate_rc\" -eq 0 ]; then\n printf 'PASS\\n' > \"$STATUS_FILE\";\nelse\n printf 'FAIL\\n' > \"$STATUS_FILE\";\nfi && exit 0",
607
+ "stateEffect": "write",
608
+ "evidenceEffect": "write",
609
+ "disposition": "POLICY"
610
+ },
611
+ "fix:onEnter:0": {
612
+ "kind": "shell",
613
+ "invocation": "mkdir -p .spur/run && ATTEMPT_FILE=\".spur/run/$__runId-basic-fix-attempt\" && n=$(cat \"$ATTEMPT_FILE\" 2>/dev/null || echo 0) && printf '%s\\n' \"$((n + 1))\" > \"$ATTEMPT_FILE\"",
614
+ "stateEffect": "write",
615
+ "evidenceEffect": "write",
616
+ "disposition": "GLUE"
617
+ },
618
+ "fix:onEnter:1": {
619
+ "kind": "agent.run",
620
+ "invocation": "/sp:dev-fixall \"${vars.qualityGateCmd}\"",
621
+ "stateEffect": "read",
622
+ "evidenceEffect": "none"
623
+ }
624
+ }
625
+ },
626
+ "feature-dev": {
627
+ "definition": "config/workflows/feature-dev.yaml",
628
+ "boundary": "feature-umbrella",
629
+ "disposition": "keep",
630
+ "artifacts": ["feature-checkpoint"],
631
+ "callers": ["/sp:dev-runall --feature"],
632
+ "terminalStates": ["done", "failed"],
633
+ "failurePolicy": "fail-closed",
634
+ "modelQueries": ["brainstorm", "plan", "execute-tasks-auto", "execute-tasks"],
635
+ "actions": {
636
+ "precheck:onEnter:0": {
637
+ "kind": "shell",
638
+ "invocation": "mkdir -p .spur/run && set +e && test -n \"$featureId\"; id_rc=$?; $spurBin agent doctor $agent; doctor_rc=$?; set -e && if [ \"$id_rc\" -eq 0 ] && [ \"$doctor_rc\" -eq 0 ]; then\n printf 'PASS\\n' > \".spur/run/$__runId-feature-dev-precheck.status\";\nelse\n printf 'FAIL\\n' > \".spur/run/$__runId-feature-dev-precheck.status\";\nfi && exit 0",
639
+ "stateEffect": "write",
640
+ "evidenceEffect": "write",
641
+ "disposition": "GLUE"
642
+ },
643
+ "precheck:onEnter:1": {
644
+ "kind": "note",
645
+ "stateEffect": "read",
646
+ "evidenceEffect": "none"
647
+ },
648
+ "brainstorm:onEnter:0": {
649
+ "kind": "agent.run",
650
+ "invocation": "/sp:dev-brainstorm --feature ${vars.featureId}",
651
+ "stateEffect": "read",
652
+ "evidenceEffect": "none"
653
+ },
654
+ "plan:onEnter:0": {
655
+ "kind": "agent.run",
656
+ "invocation": "/sp:dev-plan --feature ${vars.featureId}",
657
+ "stateEffect": "read",
658
+ "evidenceEffect": "none"
659
+ },
660
+ "execute-tasks-auto:onEnter:0": {
661
+ "kind": "agent.run",
662
+ "invocation": "/sp:dev-runall --feature ${vars.featureId} --auto",
663
+ "stateEffect": "read",
664
+ "evidenceEffect": "none"
665
+ },
666
+ "execute-tasks:onEnter:0": {
667
+ "kind": "agent.run",
668
+ "invocation": "/sp:dev-runall --feature ${vars.featureId}",
669
+ "stateEffect": "read",
670
+ "evidenceEffect": "none"
671
+ },
672
+ "integration-review:onEnter:0": {
673
+ "kind": "command.gate",
674
+ "stateEffect": "read",
675
+ "evidenceEffect": "none"
676
+ },
677
+ "integration-review:onEnter:1": {
678
+ "kind": "note",
679
+ "stateEffect": "read",
680
+ "evidenceEffect": "none"
681
+ },
682
+ "done:onEnter:0": {
683
+ "kind": "shell",
684
+ "invocation": "mkdir -p .spur/memory/sessions && echo \"checkpoint: feature-dev done featureId=$featureId ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)\" > .spur/memory/sessions/$featureId-checkpoint.md",
685
+ "stateEffect": "write",
686
+ "evidenceEffect": "write",
687
+ "disposition": "GLUE"
688
+ }
689
+ }
690
+ },
691
+ "wayfinder-resolution": {
692
+ "definition": "config/workflows/wayfinder-resolution.yaml",
693
+ "boundary": "wayfinder-resolution",
694
+ "disposition": "keep",
695
+ "artifacts": ["wayfinder-report", "resolution-status"],
696
+ "callers": ["spur workflow run"],
697
+ "terminalStates": ["done", "failed", "cancelled"],
698
+ "failurePolicy": "fail-closed",
699
+ "modelQueries": ["investigate", "verify"],
700
+ "actions": {
701
+ "precheck:onEnter:0": {
702
+ "kind": "shell",
703
+ "invocation": "mkdir -p .spur/run/wayfinder && set +e && $spurBin task check $wbs; check_rc=$?; $spurBin task show $wbs --json > .spur/run/wayfinder/$wbs-show.json; show_rc=$?; set -e && if [ \"$check_rc\" -eq 0 ] && [ \"$show_rc\" -eq 0 ]; then\n printf 'PASS\\n' > .spur/run/wayfinder/$wbs-precheck.status;\nelse\n printf 'FAIL\\n' > .spur/run/wayfinder/$wbs-precheck.status;\nfi && exit 0",
704
+ "stateEffect": "write",
705
+ "evidenceEffect": "write",
706
+ "disposition": "GLUE"
707
+ },
708
+ "precheck:onEnter:1": {
709
+ "kind": "note",
710
+ "stateEffect": "read",
711
+ "evidenceEffect": "none"
712
+ },
713
+ "collect:onEnter:0": {
714
+ "kind": "shell",
715
+ "invocation": "mkdir -p .spur/run/wayfinder && $spurBin task show $wbs --json > .spur/run/wayfinder/$wbs-input.json",
716
+ "stateEffect": "read",
717
+ "evidenceEffect": "write",
718
+ "disposition": "GLUE"
719
+ },
720
+ "collect:onEnter:1": {
721
+ "kind": "shell",
722
+ "invocation": "$spurBin workflow validate .spur/workflows/wayfinder-resolution.yaml > .spur/run/wayfinder/$wbs-workflow-check.txt",
723
+ "stateEffect": "read",
724
+ "evidenceEffect": "write",
725
+ "disposition": "SIMPLE"
726
+ },
727
+ "collect:onEnter:2": {
728
+ "kind": "shell",
729
+ "invocation": "$spurBin task check $wbs > .spur/run/wayfinder/$wbs-precheck.txt",
730
+ "stateEffect": "read",
731
+ "evidenceEffect": "write",
732
+ "disposition": "SIMPLE"
733
+ },
734
+ "investigate:onEnter:0": {
735
+ "kind": "agent.run",
736
+ "invocation": "Resolve task ${vars.wbs} in resolution mode ${vars.resolutionMode} using the prepared local bundle `.spur/run/wayfinder/${vars.wbs}-input.json` and only local shell/file reads plus the `spur` CLI; do not use network search or MCP tools. Do not invoke task-pipeline.yaml, /sp:dev-run, /sp:dev-runall, or code implementation. Read the task file, map its existing Requirements and Acceptance Criteria to the existing Design/Plan/References, then author concise Solution, Testing, and Review sections through `spur task update --section --from-file`. Preserve the research boundary. Finish with a short summary and stop. Evidence policy: ${vars.evidencePolicy}.\nEvidence-anchor and content contract (mandatory — 0300 R1/R2): when the task's evidence lives in a shared evidence file (e.g. `.spur/run/wayfinder-O/implementation-evidence.md`), the `## <wbs>` section you author MUST (a) be substantive — more than 5 lines AND more than 60 words, covering the task's own requirements, not a generic 2–3 sentence placeholder; and (b) be anchored truthfully — after writing the section, resolve its real start line with `grep -n \"^## <wbs> \" <evidence-file>` and use that exact line number in every Solution/Testing `file:line` citation. NEVER copy a line number from another ticket's example (e.g. `:5` from an earlier WBS); each WBS anchors to its own section. Before finishing, re-read each cited line and confirm it names the requirement's subject (0299 R1 line-anchor rule).",
737
+ "stateEffect": "read",
738
+ "evidenceEffect": "none"
739
+ },
740
+ "verify:onEnter:0": {
741
+ "kind": "shell",
742
+ "invocation": "rm -f .spur/run/wayfinder/$wbs-resolution-verdict.txt",
743
+ "stateEffect": "read",
744
+ "evidenceEffect": "write",
745
+ "disposition": "GLUE"
746
+ },
747
+ "verify:onEnter:1": {
748
+ "kind": "agent.run",
749
+ "invocation": "Verify resolved task ${vars.wbs} using only local reads and `spur` CLI. Check its requirements, acceptance scenarios, Solution, Testing, and Review. Confirm workflow orchestration uses `spur workflow` and execution uses `spur` CLI by default. Run `spur task check ${vars.wbs}` and update Testing/Review via the CLI only if needed. Do not implement plugin code. Write the final verdict word (exactly PASS, PARTIAL, or FAIL, nothing else) to `.spur/run/wayfinder/${vars.wbs}-resolution-verdict.txt`, then finish with the same verdict and stop.",
750
+ "stateEffect": "read",
751
+ "evidenceEffect": "none"
752
+ },
753
+ "approve:onEnter:0": {
754
+ "kind": "hitl.confirm",
755
+ "stateEffect": "read",
756
+ "evidenceEffect": "none"
757
+ },
758
+ "record:onEnter:0": {
759
+ "kind": "shell",
760
+ "invocation": "$spurBin task check $wbs ; exit 0",
761
+ "stateEffect": "read",
762
+ "evidenceEffect": "none",
763
+ "disposition": "GLUE"
764
+ },
765
+ "record:onEnter:1": {
766
+ "kind": "shell",
767
+ "invocation": "$spurBin task update $wbs done ; exit 0",
768
+ "stateEffect": "write",
769
+ "evidenceEffect": "none",
770
+ "disposition": "GLUE"
771
+ },
772
+ "done:onEnter:0": {
773
+ "kind": "note",
774
+ "stateEffect": "read",
775
+ "evidenceEffect": "none"
557
776
  }
558
777
  }
559
778
  }
@@ -51,6 +51,15 @@ vars:
51
51
  spurBin: "spur"
52
52
  agent: "omp"
53
53
  stepTimeoutMs: "1800000"
54
+ # Corpus-aware quality gate for the feature-transition hop (R1, task 0625).
55
+ # The per-task gate (`spur-check`) deliberately excludes corpus-check — the only
56
+ # sweep that observes feature-level findings — so a transition that arms one
57
+ # would go green. A feature transition happens once per feature, so the ~41 s
58
+ # corpus sweep is paid once here. Soft by design: the gate reports PASS/FAIL
59
+ # and lets the operator decide; it never hard-fails the wrap-up shell.
60
+ # TRUSTED CONFIG ONLY — this string is executed via `sh -c` (same surface as
61
+ # task-pipeline's qualityGateCmd). Never interpolate untrusted input into it.
62
+ featureGateCmd: "bun run spur-check-new"
54
63
  __hitlAnswer: ""
55
64
 
56
65
  states:
@@ -152,6 +161,14 @@ states:
152
161
  # description promises against). `feature-sync-bounded.ts` is a Spur-monorepo
153
162
  # path (`spur init` does not scaffold `plugins/sp/`), so seeded projects fall
154
163
  # back to the plain `spur feature sync` verb.
164
+ # R1 (0625): a feature transition that changed state may have armed a
165
+ # feature-level finding the per-task fast gate (spur-check) never sees.
166
+ # Run the corpus-aware gate after an applied sync OR a non-zero exit that
167
+ # may follow a partial multi-hop transition, then report PASS/FAIL. Soft
168
+ # by design: the operator decides, and wrap-up never hard-fails here.
169
+ # NOTE: do not add
170
+ # `#` comments INSIDE the folded shell string — YAML `>-` folds them onto
171
+ # the following statement line and comments it out.
155
172
  - kind: shell
156
173
  options:
157
174
  command: >-
@@ -159,10 +176,23 @@ states:
159
176
  echo "feature-transition: vars.feature is empty — refusing no-op feature sync (mis-invocation, not a blocked sync)" >&2;
160
177
  exit 1;
161
178
  fi;
162
- if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then
179
+ SYNC_OUTPUT=$(if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then
163
180
  bun plugins/sp/scripts/feature-sync-bounded.ts "$feature" --spur-bin "$spurBin" --json;
164
181
  else
165
182
  $spurBin feature sync "$feature" --json;
183
+ fi);
184
+ SYNC_RC=$?;
185
+ printf '%s\n' "$SYNC_OUTPUT";
186
+ APPLIED=$(printf '%s' "$SYNC_OUTPUT" | jq -r '.applied // false' 2>/dev/null || echo false);
187
+ if [ "$APPLIED" = "true" ] || [ "$SYNC_RC" -ne 0 ]; then
188
+ echo "feature-transition: sync applied or failed after a possible partial transition for $feature — running corpus-aware gate: $featureGateCmd";
189
+ if sh -c "$featureGateCmd"; then
190
+ echo "feature-transition: corpus-aware gate PASS for feature $feature";
191
+ else
192
+ echo "feature-transition: corpus-aware gate FAIL for feature $feature — inspect findings before reporting the transition complete" >&2;
193
+ fi;
194
+ else
195
+ echo "feature-transition: sync did not apply a transition (rc=$SYNC_RC, applied=$APPLIED) — corpus-aware gate skipped";
166
196
  fi;
167
197
  exit 0
168
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.55",
3
+ "version": "0.3.58",
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.39",
57
- "@gobing-ai/ts-ai-runner": "^0.4.39",
58
- "@gobing-ai/ts-dual-workflow-engine": "^0.4.39",
59
- "@gobing-ai/ts-infra": "^0.4.39",
60
- "@gobing-ai/ts-llm-jsonl-importer": "^0.4.39",
61
- "@gobing-ai/ts-rule-engine": "^0.4.39",
62
- "@gobing-ai/ts-runtime": "^0.4.39",
63
- "@gobing-ai/ts-utils": "^0.4.39",
56
+ "@gobing-ai/ts-db": "^0.4.41",
57
+ "@gobing-ai/ts-ai-runner": "^0.4.41",
58
+ "@gobing-ai/ts-dual-workflow-engine": "^0.4.41",
59
+ "@gobing-ai/ts-infra": "^0.4.41",
60
+ "@gobing-ai/ts-llm-jsonl-importer": "^0.4.41",
61
+ "@gobing-ai/ts-rule-engine": "^0.4.41",
62
+ "@gobing-ai/ts-runtime": "^0.4.41",
63
+ "@gobing-ai/ts-utils": "^0.4.41",
64
64
  "@types/bun": "1.3.14",
65
65
  "@types/figlet": "^1.7.0",
66
66
  "@types/node-notifier": "8.0.5",
package/plugins/README.md CHANGED
@@ -345,7 +345,7 @@ All planning entities (tasks and features) share a common lifecycle, managed by
345
345
  | **check** | `spur task check` | `spur feature check` | 4-layer readiness matrix (schema, sections, traceability, AC) |
346
346
  | **update** | `spur task update <wbs> [status]` | `spur feature update <id> [status]` | Lifecycle transition or scalar field set |
347
347
  | **record** | `spur task record <wbs>` | — | Write Testing/Review from a verify verdict; optional Solution backfill (never transitions to `done`) |
348
- | **refresh** | `spur task refresh` | `spur feature refresh` | Index + feature-tree roll-up regeneration |
348
+ | **refresh** | `spur task refresh` | `spur feature refresh --feature <id> \| --all` | Index + feature-tree roll-up regeneration |
349
349
 
350
350
  The **workflow** and **rule** engines have their own lifecycles (author → validate → run → trace / refine), documented in their respective `sp:spur-cli` references.
351
351
 
@@ -601,7 +601,7 @@ All planning entities (tasks and features) share a common lifecycle, managed by
601
601
  | **check** | `spur task check` | `spur feature check` | 4-layer readiness matrix (schema, sections, traceability, AC) |
602
602
  | **update** | `spur task update <wbs> [status]` | `spur feature update <id> [status]` | Lifecycle transition or scalar field set |
603
603
  | **record** | `spur task record <wbs>` | — | Write Testing/Review from a verify verdict; optional Solution backfill (never transitions to `done`) |
604
- | **refresh** | `spur task refresh` | `spur feature refresh` | Index + feature-tree roll-up regeneration |
604
+ | **refresh** | `spur task refresh` | `spur feature refresh --feature <id> \| --all` | Index + feature-tree roll-up regeneration |
605
605
 
606
606
  The **workflow** and **rule** engines have their own lifecycles (author → validate → run → trace /
607
607
  refine), documented in their respective `sp:spur-cli` references.
@@ -46,6 +46,9 @@ guide and conventions before acting.
46
46
  - **Section-editing campaigns** — update the same section across multiple tasks.
47
47
  - **Rule-catalog work** — author, fine-tune, validate, or harden constraint rules across the catalog.
48
48
  - **Workflow work** — author, validate, dry-run, or refactor one or more workflows.
49
+ When authoring or refactoring, read the composition-measures section in
50
+ `sp:spur-cli` `references/workflows.md` and apply the advisory's fix-path map — findings are
51
+ advisory only; never block a run or edit an executing pipeline over them.
49
52
  - **Corpus health checks** — run `check`/`validate` across a batch and report findings.
50
53
 
51
54
  For a single operation, use the `spur` CLI directly. For the planning/execution lifecycle, use
@@ -68,7 +71,7 @@ Invoke `sp:spur-cli` for verb guidance and per-noun conventions:
68
71
  - [ ] Run the noun's `check`/`validate` verb before and after editing (e.g. `spur task check <wbs> --json`).
69
72
  - [ ] Run the corpus-wide sweep after batch edits: `spur task check --corpus --json` (fails on structural errors outside `config/corpus-baseline.json`).
70
73
  - [ ] Use `spur task update --section --from-file` for all task section edits.
71
- - [ ] Run the noun's `refresh` after batch operations where one exists (`spur task refresh`, `spur feature refresh`).
74
+ - [ ] Run the noun's scoped `refresh` after batch operations where one exists (`spur task refresh`, `spur feature refresh --feature <id>` or `--all`).
72
75
 
73
76
  ### Never
74
77
 
@@ -62,7 +62,7 @@ Do **not** fire independent dry-runs that all claim the same next id. Walk rows
62
62
  3. After predicting `new_id`, add it to the occupancy set so the next sibling under the same parent gets the next free digit.
63
63
  4. Record `mapping` and `tasksUpdated[]` from each dry-run (with multi-folder scan, tasksUpdated should list every linked WBS).
64
64
 
65
- Plan `spur feature refresh` + `spur feature check --json` after apply.
65
+ Plan `spur feature refresh --all` + `spur feature check --json` after apply.
66
66
 
67
67
  **Root docs/\*.md references (high-confidence only)**
68
68
 
@@ -95,7 +95,7 @@ spur feature move <old_id> --parent <new_parent> --json
95
95
  After all moves (or per wave):
96
96
 
97
97
  ```bash
98
- spur feature refresh --json
98
+ spur feature refresh --all --json
99
99
  spur feature check --json
100
100
  ```
101
101