@gobing-ai/spur 0.3.60 → 0.3.61

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 (92) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +2 -2
  3. package/config/config.global.yaml +11 -7
  4. package/config/corpus-baseline.json +96 -8
  5. package/config/workflow-composition-baseline.json +1 -1
  6. package/config/workflows/wayfinder-resolution.yaml +4 -6
  7. package/package.json +1 -1
  8. package/plugins/README.md +5 -4
  9. package/plugins/sp/README.md +3 -3
  10. package/plugins/sp/agents/super-planner.md +1 -1
  11. package/plugins/sp/commands/dev-pr-review.md +1 -1
  12. package/plugins/sp/commands/dev-wrap.md +2 -2
  13. package/plugins/sp/commands/dev-wrapall.md +2 -2
  14. package/plugins/sp/plugin.json +1 -1
  15. package/plugins/sp/references/roles.md +12 -9
  16. package/plugins/sp/scripts/surface-drift-inventory.ts +51 -17
  17. package/plugins/sp/scripts/validate-commands.ts +11 -4
  18. package/plugins/sp/skills/code-verification/SKILL.md +1 -1
  19. package/plugins/sp/skills/code-verification/references/verdict-schema.md +1 -1
  20. package/plugins/sp/skills/issue-finding/SKILL.md +1 -1
  21. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  22. package/plugins/sp/skills/pr-reviewing/SKILL.md +2 -2
  23. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +1 -1
  24. package/plugins/sp/skills/spur-cli/references/init.md +1 -1
  25. package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +1 -1
  26. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +1 -1
  27. package/plugins/sp/skills/spur-cli/references/workflows.md +4 -4
  28. package/plugins/sp/skills/spur-dev/SKILL.md +4 -4
  29. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -4
  30. package/plugins/sp/skills/spur-dev/references/dev-operations.md +9 -9
  31. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
  32. package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
  33. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +1 -1
  34. package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -2
  35. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -1
  36. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +5 -2
  37. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +2 -2
  38. package/schemas/spur-config.schema.json +1 -1
  39. package/spur.js +203 -83
  40. package/web/_astro/BoardApp.BtRfVADq.js +1 -0
  41. package/web/_astro/{BoardApp.BJs3l7O8.js → BoardApp.CBIzcvqi.js} +100 -101
  42. package/web/_astro/{TaskDetail.B0l1uzsB.js → TaskDetail.D2N60cfE.js} +1 -1
  43. package/web/_astro/{arc.DPruHC3P.js → arc.D-EfJJwf.js} +1 -1
  44. package/web/_astro/{architectureDiagram-3BPJPVTR.CuOqAck6.js → architectureDiagram-3BPJPVTR.CgvTTzqp.js} +1 -1
  45. package/web/_astro/{blockDiagram-GPEHLZMM.Cxb7HDIA.js → blockDiagram-GPEHLZMM.CnOohvvn.js} +1 -1
  46. package/web/_astro/{c4Diagram-AAUBKEIU.Dw7wa-0S.js → c4Diagram-AAUBKEIU.BFcBGUy_.js} +1 -1
  47. package/web/_astro/channel.kIu33Gui.js +1 -0
  48. package/web/_astro/{chunk-2J33WTMH.MECQitzo.js → chunk-2J33WTMH.C-6MR-XY.js} +1 -1
  49. package/web/_astro/{chunk-4BX2VUAB.p3ofhIGE.js → chunk-4BX2VUAB.BN4LP5AR.js} +1 -1
  50. package/web/_astro/{chunk-55IACEB6.BmQGsPZP.js → chunk-55IACEB6.tUJ_CTtZ.js} +1 -1
  51. package/web/_astro/{chunk-727SXJPM.CwrK23OA.js → chunk-727SXJPM.BNCk-sxv.js} +1 -1
  52. package/web/_astro/{chunk-AQP2D5EJ.CUKI_QXp.js → chunk-AQP2D5EJ.BkpFIbNa.js} +1 -1
  53. package/web/_astro/{chunk-FMBD7UC4.7AtDgShM.js → chunk-FMBD7UC4.DcSZ87PN.js} +1 -1
  54. package/web/_astro/{chunk-ND2GUHAM.f_YJbTs3.js → chunk-ND2GUHAM.BkLvHFFf.js} +1 -1
  55. package/web/_astro/{chunk-QZHKN3VN.0q7V3gEg.js → chunk-QZHKN3VN.HOdgszax.js} +1 -1
  56. package/web/_astro/{classDiagram-4FO5ZUOK.C_hG3HMK.js → classDiagram-4FO5ZUOK.CWfRiY6b.js} +1 -1
  57. package/web/_astro/{classDiagram-v2-Q7XG4LA2.C_hG3HMK.js → classDiagram-v2-Q7XG4LA2.CWfRiY6b.js} +1 -1
  58. package/web/_astro/{cose-bilkent-S5V4N54A.vsaaaEMy.js → cose-bilkent-S5V4N54A.C6j4PxoN.js} +1 -1
  59. package/web/_astro/{dagre-BM42HDAG.BN7XFqQ4.js → dagre-BM42HDAG.BATZG1II.js} +1 -1
  60. package/web/_astro/{diagram-2AECGRRQ.ED6AfryZ.js → diagram-2AECGRRQ.B0f5yY6x.js} +1 -1
  61. package/web/_astro/{diagram-5GNKFQAL.Dxllzg8R.js → diagram-5GNKFQAL.Bewupqv0.js} +1 -1
  62. package/web/_astro/{diagram-KO2AKTUF.RuWppieD.js → diagram-KO2AKTUF.C23r1tB2.js} +1 -1
  63. package/web/_astro/{diagram-LMA3HP47.CL32Xn6s.js → diagram-LMA3HP47.DUErdBOR.js} +1 -1
  64. package/web/_astro/{diagram-OG6HWLK6.CUneEhqB.js → diagram-OG6HWLK6.BubmwAjb.js} +1 -1
  65. package/web/_astro/{erDiagram-TEJ5UH35.DI-92Lec.js → erDiagram-TEJ5UH35.BE_Qs5mv.js} +1 -1
  66. package/web/_astro/{flowDiagram-I6XJVG4X.69Aqa1FF.js → flowDiagram-I6XJVG4X.COWdbzon.js} +1 -1
  67. package/web/_astro/{ganttDiagram-6RSMTGT7.DdpClZpa.js → ganttDiagram-6RSMTGT7.5EGYC4MK.js} +1 -1
  68. package/web/_astro/{gitGraphDiagram-PVQCEYII.xqWfkvB4.js → gitGraphDiagram-PVQCEYII.bUUV7iEw.js} +1 -1
  69. package/web/_astro/index.Cestp9nh.css +1 -0
  70. package/web/_astro/{infoDiagram-5YYISTIA.B3G5sfKm.js → infoDiagram-5YYISTIA.CbiInFvz.js} +1 -1
  71. package/web/_astro/{ishikawaDiagram-YF4QCWOH.ahVNsoAQ.js → ishikawaDiagram-YF4QCWOH.CB7lrSsJ.js} +1 -1
  72. package/web/_astro/{journeyDiagram-JHISSGLW.PmFHLc-X.js → journeyDiagram-JHISSGLW.rhGZgWt8.js} +1 -1
  73. package/web/_astro/{kanban-definition-UN3LZRKU.Co1qOPTd.js → kanban-definition-UN3LZRKU.DPeZD_lP.js} +1 -1
  74. package/web/_astro/{linear.DoHZ-iRt.js → linear.l60Nyp5b.js} +1 -1
  75. package/web/_astro/{mermaid.core.DNRElYN2.js → mermaid.core.DmtMJcmL.js} +4 -4
  76. package/web/_astro/{mindmap-definition-RKZ34NQL.CdfJZidJ.js → mindmap-definition-RKZ34NQL.Cc47AcCn.js} +1 -1
  77. package/web/_astro/{pieDiagram-4H26LBE5.C1A8dby4.js → pieDiagram-4H26LBE5.B8cN-S9g.js} +1 -1
  78. package/web/_astro/{quadrantDiagram-W4KKPZXB.NmZqjG_c.js → quadrantDiagram-W4KKPZXB.DY1FN89L.js} +1 -1
  79. package/web/_astro/{requirementDiagram-4Y6WPE33.tY36zYtK.js → requirementDiagram-4Y6WPE33.CnM6VbV8.js} +1 -1
  80. package/web/_astro/{sankeyDiagram-5OEKKPKP.rOEGZYjo.js → sankeyDiagram-5OEKKPKP.B9DcGfNV.js} +1 -1
  81. package/web/_astro/{sequenceDiagram-3UESZ5HK.Dpnw3S9J.js → sequenceDiagram-3UESZ5HK.ymxaNlAY.js} +1 -1
  82. package/web/_astro/{stateDiagram-AJRCARHV.DGpvQ3Wz.js → stateDiagram-AJRCARHV.DDOJ3d8P.js} +1 -1
  83. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CbwQdEfu.js → stateDiagram-v2-BHNVJYJU.Ck0Nb_KY.js} +1 -1
  84. package/web/_astro/{timeline-definition-PNZ67QCA.DAYUpUJT.js → timeline-definition-PNZ67QCA.BNOvZwAN.js} +1 -1
  85. package/web/_astro/{vennDiagram-CIIHVFJN.C7JCCd6-.js → vennDiagram-CIIHVFJN.D3k5ivSF.js} +1 -1
  86. package/web/_astro/{wardley-L42UT6IY.BGgwOH5_.js → wardley-L42UT6IY.BylaxVqn.js} +1 -1
  87. package/web/_astro/{wardleyDiagram-YWT4CUSO.oIZqzI2l.js → wardleyDiagram-YWT4CUSO.HLNtFUm9.js} +1 -1
  88. package/web/_astro/{xychartDiagram-2RQKCTM6.DohcJzqm.js → xychartDiagram-2RQKCTM6.B-WooKSU.js} +1 -1
  89. package/web/index.html +2 -2
  90. package/web/_astro/BoardApp.D8InuCYx.js +0 -1
  91. package/web/_astro/channel.XCf1DcsL.js +0 -1
  92. package/web/_astro/index.DJl2RAVZ.css +0 -1
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.3.60",
10
+ "version": "0.3.61",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -4,11 +4,11 @@
4
4
  # Copy / adapt into a project as `.spur/config.yaml`.
5
5
 
6
6
  $schema: "@gobing-ai/spur/schemas/spur-config.schema.json"
7
- # Config schema version (string). Current recommended: "1.1".
7
+ # Config schema version (string). Current recommended: "1.2".
8
8
  # "1" remains accepted; there is no hard migrator yet. Prefer "1.1" for
9
9
  # new projects so editors and operators know the file targets the ADR-033
10
10
  # + planning shape.
11
- version: "1.1"
11
+ version: "1.2"
12
12
  name: my-project
13
13
 
14
14
  # ── Portable bootstrap (consumed by @gobing-ai/ts-infra
@@ -18,6 +18,10 @@
18
18
 
19
19
  $schema: "@gobing-ai/spur/schemas/spur-config.schema.json"
20
20
 
21
+ # Inert label (task 0649 R6 ruling): nothing branches on `version` today.
22
+ # Reunified at "1.2" across every shipped artifact (init stamp, JSON Schema).
23
+ version: "1.2"
24
+
21
25
  agent:
22
26
  # `default` is the role used for `--agent auto` when the caller declares
23
27
  # nothing (task 0542 R2 — the value domain moved from executor names to
@@ -124,10 +128,10 @@ agent:
124
128
  # `workflows.paths` is deliberately NOT set here. Its values are search paths
125
129
  # resolved against a project's own root (0639 classifies it project-relative), so
126
130
  # a machine-wide value would point every project at a directory that mostly does
127
- # not exist. The global workflow tree is already reachable without it: the
128
- # lifecycle adapter resolves bundled project `.spur/workflows/`
129
- # `~/.config/spur/workflows/` on its own (make-lifecycle-adapter.ts:44).
130
- # Set it per project when a project keeps workflows somewhere non-standard.
131
- # workflows:
132
- # paths:
133
- # - .spur/workflows/
131
+ # not exist. The key is present (the A4 goal names it among the shipped global
132
+ # defaults) but empty: `workflow list` falls back to default `.spur/workflows/`
133
+ # discovery, and the lifecycle adapter resolves bundled → project
134
+ # `.spur/workflows/` on its own (make-lifecycle-adapter.ts:24, two tiers since
135
+ # task 0648). Set paths per project when a project keeps workflows somewhere
136
+ # non-standard.
137
+ workflows: {}
@@ -447,6 +447,102 @@
447
447
  "reason": "Accepted backlog: L4.orphan-scenarios",
448
448
  "since": "2026-08-19"
449
449
  },
450
+ {
451
+ "kind": "feature",
452
+ "id": "B1",
453
+ "code": "L4.dogfood-missing",
454
+ "severity": "warning",
455
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
456
+ "since": "2026-08-24"
457
+ },
458
+ {
459
+ "kind": "feature",
460
+ "id": "B3",
461
+ "code": "L4.dogfood-missing",
462
+ "severity": "warning",
463
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
464
+ "since": "2026-08-24"
465
+ },
466
+ {
467
+ "kind": "feature",
468
+ "id": "E5",
469
+ "code": "L4.dogfood-missing",
470
+ "severity": "warning",
471
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
472
+ "since": "2026-08-24"
473
+ },
474
+ {
475
+ "kind": "feature",
476
+ "id": "F",
477
+ "code": "L4.dogfood-missing",
478
+ "severity": "warning",
479
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
480
+ "since": "2026-08-24"
481
+ },
482
+ {
483
+ "kind": "feature",
484
+ "id": "F31",
485
+ "code": "L4.dogfood-missing",
486
+ "severity": "warning",
487
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
488
+ "since": "2026-08-24"
489
+ },
490
+ {
491
+ "kind": "feature",
492
+ "id": "F82",
493
+ "code": "L4.dogfood-missing",
494
+ "severity": "warning",
495
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
496
+ "since": "2026-08-24"
497
+ },
498
+ {
499
+ "kind": "feature",
500
+ "id": "G4",
501
+ "code": "L4.dogfood-missing",
502
+ "severity": "warning",
503
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
504
+ "since": "2026-08-24"
505
+ },
506
+ {
507
+ "kind": "feature",
508
+ "id": "H11",
509
+ "code": "L4.dogfood-missing",
510
+ "severity": "warning",
511
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
512
+ "since": "2026-08-24"
513
+ },
514
+ {
515
+ "kind": "feature",
516
+ "id": "I",
517
+ "code": "L4.dogfood-missing",
518
+ "severity": "warning",
519
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
520
+ "since": "2026-08-24"
521
+ },
522
+ {
523
+ "kind": "feature",
524
+ "id": "I4",
525
+ "code": "L4.dogfood-missing",
526
+ "severity": "warning",
527
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
528
+ "since": "2026-08-24"
529
+ },
530
+ {
531
+ "kind": "feature",
532
+ "id": "I5",
533
+ "code": "L4.dogfood-missing",
534
+ "severity": "warning",
535
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
536
+ "since": "2026-08-24"
537
+ },
538
+ {
539
+ "kind": "feature",
540
+ "id": "J",
541
+ "code": "L4.dogfood-missing",
542
+ "severity": "warning",
543
+ "reason": "Feature remains verifying; direct linked tasks touch self-referential workflow infrastructure, but no dogfood report exists. Accepted as pre-A4 corpus debt only; the strict feature completion gate still requires real dogfood evidence.",
544
+ "since": "2026-08-24"
545
+ },
450
546
  {
451
547
  "kind": "task",
452
548
  "id": "0001",
@@ -12213,14 +12309,6 @@
12213
12309
  "reason": "Accumulated anchor drift, surfaced for the first time by task 0583's content-matching rule. The cited lines resolve and are in bounds, but no longer name the requirement row's subject — a later task edited the file and pushed the anchor onto unrelated code. Spot-verified true positives (an import block cited for a component's hooks; a severity-parsing block cited for `runL4Rollup`). Repairing 1,300+ historical citations is its own campaign; the rule ships at warning severity until then (0583 R6).",
12214
12310
  "since": "2026-08-18"
12215
12311
  },
12216
- {
12217
- "kind": "task",
12218
- "id": "0416",
12219
- "code": "L4.stale-line-anchor",
12220
- "severity": "warning",
12221
- "reason": "Anchor written with an incomplete path; qualification migration (task 0583) owns the mechanical repair — see note § L4.stale-line-anchor.",
12222
- "since": "2026-08-17"
12223
- },
12224
12312
  {
12225
12313
  "kind": "task",
12226
12314
  "id": "0416",
@@ -719,7 +719,7 @@
719
719
  },
720
720
  "collect:onEnter:1": {
721
721
  "kind": "shell",
722
- "invocation": "$spurBin workflow validate .spur/workflows/wayfinder-resolution.yaml > .spur/run/wayfinder/$wbs-workflow-check.txt",
722
+ "invocation": "$spurBin workflow validate wayfinder-resolution.yaml > .spur/run/wayfinder/$wbs-workflow-check.txt",
723
723
  "stateEffect": "read",
724
724
  "evidenceEffect": "write",
725
725
  "disposition": "SIMPLE"
@@ -64,13 +64,11 @@ states:
64
64
  - kind: shell
65
65
  options:
66
66
  command: "mkdir -p .spur/run/wayfinder && $spurBin task show $wbs --json > .spur/run/wayfinder/$wbs-input.json"
67
- # `.spur/workflows/` is the runtime read path in every layout — a symlink to
68
- # config/workflows/ inside the Spur monorepo, a real directory in a seeded
69
- # project. The previous `config/workflows/…` literal only resolved in the
70
- # monorepo and hard-failed `collect` anywhere else.
67
+ # `wayfinder-resolution.yaml` uses the project→bundled resolver (tasks 0648/0650):
68
+ # a bare name falls back to the bundled tree; an explicit project path wins.
71
69
  - kind: shell
72
70
  options:
73
- command: "$spurBin workflow validate .spur/workflows/wayfinder-resolution.yaml > .spur/run/wayfinder/$wbs-workflow-check.txt"
71
+ command: "$spurBin workflow validate wayfinder-resolution.yaml > .spur/run/wayfinder/$wbs-workflow-check.txt"
74
72
  - kind: shell
75
73
  options:
76
74
  command: "$spurBin task check $wbs > .spur/run/wayfinder/$wbs-precheck.txt"
@@ -112,7 +110,7 @@ states:
112
110
  onEnter:
113
111
  - kind: shell
114
112
  options:
115
- command: 'rm -f .spur/run/wayfinder/$wbs-resolution-verdict.txt'
113
+ command: "rm -f .spur/run/wayfinder/$wbs-resolution-verdict.txt"
116
114
  - kind: agent.run
117
115
  options:
118
116
  agent: ${vars.agent}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.60",
3
+ "version": "0.3.61",
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",
package/plugins/README.md CHANGED
@@ -228,7 +228,7 @@ graph TB
228
228
  subgraph "Corpus Layer"
229
229
  CORPUS["docs/tasks/ + docs/features/<br/>Markdown planning corpus"]
230
230
  RULES[".spur/rules/<br/>YAML constraint rules"]
231
- WFS[".spur/workflows/<br/>YAML workflow definitions"]
231
+ WFS["bundled workflows tree<br/>YAML workflow definitions"]
232
232
  end
233
233
 
234
234
  %% Command → Skill delegations
@@ -309,7 +309,7 @@ Tier 3 — Execution Layer (spur CLI + Guard Scripts)
309
309
 
310
310
  1. User types `/sp:dev-run 0090`
311
311
  2. **Command** delegates to `sp:spur-dev` skill (execution half)
312
- 3. **Skill** reads the task, loads `.spur/workflows/task-pipeline.yaml`, and runs `spur workflow run` with HITL surfacing
312
+ 3. **Skill** reads the task, loads `task-pipeline.yaml`, and runs `spur workflow run` with HITL surfacing
313
313
  4. **CLI** executes the workflow engine (`@gobing-ai/ts-dual-workflow-engine`), pauses at HITL gates, persists run state
314
314
  5. Result: task driven through implement → check → fix → verify lifecycle
315
315
 
@@ -351,8 +351,9 @@ The **workflow** and **rule** engines have their own lifecycles (author → vali
351
351
 
352
352
  ## Workflow Pipelines
353
353
 
354
- The plugin ships workflow YAMLs under `.spur/workflows/`.
355
- Each pipeline owns one lifecycle phase:
354
+ The plugin references workflow YAMLs by bare name; the CLI resolves them through the
355
+ bundled tree (two-tier model, task 0648/0650) — a project-local override wins when
356
+ present, otherwise the bundled copy is used. Each pipeline owns one lifecycle phase:
356
357
 
357
358
  | Workflow | Phase | Entry command | Status |
358
359
  | ------------------------ | --------------------------------- | --------------------------------- | ------------- |
@@ -509,7 +509,7 @@ graph TB
509
509
  subgraph "Corpus layer"
510
510
  CORPUS["docs/tasks/ + docs/features/<br/>markdown planning corpus"]
511
511
  RULES[".spur/rules/<br/>YAML constraint rules"]
512
- WFS[".spur/workflows/<br/>YAML workflow definitions"]
512
+ WFS["bundled workflows tree<br/>YAML workflow definitions"]
513
513
  end
514
514
 
515
515
  CMD -->|"Skill(sp:spur-dev, ...)"| SKILL_DEV
@@ -557,7 +557,7 @@ graph TB
557
557
 
558
558
  1. User types `/sp:dev-run 0090`.
559
559
  2. **Command** delegates to `sp:spur-dev` skill (execution half).
560
- 3. **Skill** reads the task, loads `.spur/workflows/task-pipeline.yaml`, and runs
560
+ 3. **Skill** reads the task, loads `task-pipeline.yaml`, and runs
561
561
  `spur workflow run` with HITL surfacing.
562
562
  4. **CLI** executes the workflow engine (`@gobing-ai/ts-dual-workflow-engine`), pauses at HITL gates,
563
563
  persists run state.
@@ -575,7 +575,7 @@ graph TB
575
575
 
576
576
  ### Workflow pipelines
577
577
 
578
- The plugin ships workflow YAMLs under `.spur/workflows/`. Each
578
+ The plugin references workflow YAMLs by bare name; the CLI resolves them through the bundled tree (two-tier model, task 0648/0650) — project-local overrides win when present. Each
579
579
  pipeline owns one lifecycle phase:
580
580
 
581
581
  | Workflow | Phase | Entry command |
@@ -78,7 +78,7 @@ You own the spaces **between** task runs:
78
78
  - **Preflight (Step 2.5 / 3.0)** - before each pipeline launch, evaluate TABLE A STOP rows
79
79
  (`batch-preflight.ts` or equivalent). Skip A2/A7/A8/A9; still **launch `task-pipeline.yaml`** for
80
80
  ready WBS (never substitute a `dev-next` loop for the happy path).
81
- - **Run each ready task** through `.spur/workflows/task-pipeline.yaml` via `spur workflow run --async`
81
+ - **Run each ready task** through `task-pipeline.yaml` via `spur workflow run --async`
82
82
  (Step 3). The command/script layer polls `spur workflow trace` to terminal state - polling is
83
83
  transport, not planner reasoning (R3: the poll loop must not live in this agent's body; the planner
84
84
  inspects the terminal verdict, not the poll iterations). Follow the
@@ -31,7 +31,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
31
31
 
32
32
  - Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
33
33
  - Delegate everything: `Skill(skill="sp:pr-reviewing", args="$ARGUMENTS")`. The skill owns mode
34
- routing, finding triage, fix, and rules authoring; `.spur/workflows/pr-review.yaml` is the SSOT
34
+ routing, finding triage, fix, and rules authoring; `pr-review.yaml` is the SSOT
35
35
  for the review spine's state order and guards; the staged `pr-reviewing.ts` is the
36
36
  deterministic git/gh core every spine step shells out to.
37
37
  - The external review goes through the GitHub PR and an `@codex review` comment only — never local
@@ -28,7 +28,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
28
28
  ## Implementation
29
29
 
30
30
  - Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
31
- - Wrap stays **workflow-backed** — `spur workflow run .spur/workflows/wrapup-pipeline.yaml` is the only implementation; there is no inline wrap driver.
31
+ - Wrap stays **workflow-backed** — `spur workflow run wrapup-pipeline.yaml` is the only implementation; there is no inline wrap driver.
32
32
  - Resolve the executor **before** launching the workflow:
33
33
  - `--agent <name>` → pass the name unchanged into `vars.agent`.
34
34
  - `--agent auto` → tier-resolve a concrete executor first, then merge it into `vars.agent`.
@@ -42,7 +42,7 @@ AGENT=… # resolved above: agent.default for omitted, tier-resolved for auto, u
42
42
  echo "execution surface: subprocess; reason: trigger 3 — durable auditable run record required; requested agent: <inline|auto|name>; executor: $AGENT"
43
43
  VARS=$(jq -nc --arg tasks "[\"$WBS\"]" --arg agent "$AGENT" --arg profile "$PROFILE" --arg merge "$MERGE" \
44
44
  '{tasks:$tasks, agent:$agent, profile:$profile, merge:$merge}')
45
- spur workflow run .spur/workflows/wrapup-pipeline.yaml --vars "$VARS" [--dry-run]
45
+ spur workflow run wrapup-pipeline.yaml --vars "$VARS" [--dry-run]
46
46
  ```
47
47
 
48
48
  The executor resolution is described in the bullets above; the snippet's `AGENT` variable carries
@@ -30,7 +30,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
30
30
  ## Implementation
31
31
 
32
32
  - Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
33
- - Wrap stays **workflow-backed** — `spur workflow run .spur/workflows/wrapup-pipeline.yaml` is the only implementation; there is no inline wrap driver.
33
+ - Wrap stays **workflow-backed** — `spur workflow run wrapup-pipeline.yaml` is the only implementation; there is no inline wrap driver.
34
34
  - Resolve the executor **before** launching the workflow:
35
35
  - `--agent <name>` → pass the name unchanged into `vars.agent`.
36
36
  - `--agent auto` → tier-resolve a concrete executor first, then merge it into `vars.agent`.
@@ -44,7 +44,7 @@ AGENT=… # resolved above: agent.default for omitted, tier-resolved for auto, u
44
44
  echo "execution surface: subprocess; reason: trigger 3 — durable auditable run record required; requested agent: <inline|auto|name>; executor: $AGENT"
45
45
  VARS=$(jq -nc --arg tasks "$TASKS" --arg feature "$FEATURE" --arg agent "$AGENT" --arg profile "$PROFILE" --arg merge "$MERGE" \
46
46
  '{tasks:$tasks, feature:$feature, agent:$agent, profile:$profile, merge:$merge}')
47
- spur workflow run .spur/workflows/wrapup-pipeline.yaml --vars "$VARS" [--dry-run]
47
+ spur workflow run wrapup-pipeline.yaml --vars "$VARS" [--dry-run]
48
48
  ```
49
49
 
50
50
  The executor resolution is described in the bullets above; the snippet's `AGENT` variable carries
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.3.60",
3
+ "version": "0.3.61",
4
4
  "description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
5
5
  "extensions": {
6
6
  "pi": ["./hooks/pi/guard-extension.ts"]
@@ -10,8 +10,9 @@ see_also:
10
10
  # Roles — the Layer-1 role-to-tier table
11
11
 
12
12
  The executor-selection contract is two layers. **Layer 1 (this file)** projects *role → tier*;
13
- its SSOT is `DEFAULT_AGENT_ROLES` in `packages/config/src/index.ts` (task 0572 / ADR-061) — this
14
- file is the agent/human-facing view plus the plugin-owned command→role mapping. **Layer 2** maps
13
+ its SSOT is `agent.roles` in `config/config.global.yaml` (task 0647 / ADR-078) — this file is the
14
+ agent/human-facing view plus the plugin-owned command→role mapping. `DEFAULT_AGENT_ROLES` in
15
+ `packages/config/src/index.ts` is the no-filesystem fallback and must remain identical. **Layer 2** maps
15
16
  *tier → executor* and is owned by the operator in `.spur/config.yaml`. This file never names an
16
17
  executor, a model, or a vendor — it declares only what tier a role's work needs, and the
17
18
  operator's config decides which executor serves that tier.
@@ -38,9 +39,10 @@ collapse, named as people so they stay addressable in `--agent`.
38
39
 
39
40
  ## The table
40
41
 
41
- <!-- PROJECTION (task 0572 / ADR-061): the tier/stages half of the block below is a generated view
42
- of DEFAULT_AGENT_ROLES in packages/config/src/index.ts — edit that constant, not this file.
43
- plugins/sp/tests/roles.test.ts (R9) fails the suite on any drift between the two. The
42
+ <!-- PROJECTION (task 0647 / ADR-078): the tier/stages half of the block below is a generated view
43
+ of agent.roles in config/config.global.yaml — edit that SSOT, not this file.
44
+ plugins/sp/tests/roles.test.ts (R9) fails the suite on any three-way drift with the
45
+ DEFAULT_AGENT_ROLES no-filesystem fallback. The
44
46
  `commands:` half is plugin data (command frontmatter is its SSOT). -->
45
47
 
46
48
  ```yaml
@@ -98,9 +100,10 @@ driving the external PR review and triaging its findings folds the `review` stag
98
100
 
99
101
  **Consistency is a test, not a convention.** `plugins/sp/tests/roles.test.ts` parses this YAML and
100
102
  asserts the tier-distinctness, command closure, stage-floor, and boundary invariants against the
101
- real command directory, the real stage registry, and the real operator config — plus parity with
102
- `DEFAULT_AGENT_ROLES` (R9, 0572): the table above must equal the code SSOT byte-for-byte on
103
- id/tier/stages. When the table and the registry disagree, fix the table or the registry — never the
104
- test. When the table and `DEFAULT_AGENT_ROLES` disagree, fix the constant (or regenerate this view).
103
+ real command directory, the real stage registry, and the real operator config — plus three-way parity
104
+ with `config/config.global.yaml` and `DEFAULT_AGENT_ROLES` (R9, 0647): the table above must equal the
105
+ shipped SSOT and no-filesystem fallback on id/tier/stages. When the table and the registry disagree,
106
+ fix the table or the registry — never the test. When the three role tables disagree, fix the shipped
107
+ SSOT first, then regenerate the projections.
105
108
  A project may re-tier/re-stage a role at config time via `agent.roles` (closed vocabulary) — that
106
109
  override never flows back into this file.
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
3
  * surface-drift-inventory — re-runnable drift inventory for `plugins/sp` and
4
- * the tracked workflow-YAML SSOT tree (`.spur/workflows` symlink target)
5
- * against the live source-local `spur` CLI
6
- * (feature I3, task 0539 R1/R2).
4
+ * the tracked workflow-YAML SSOT tree against the live
5
+ * source-local `spur` CLI (feature I3, task 0539 R1/R2).
7
6
  *
8
7
  * Method (mechanical first, prose second — task 0539 Design):
9
8
  * A. Plugin assertions: extract every `spur`-invocation (backticked spans, fenced
@@ -19,7 +18,7 @@
19
18
  * in this repo — recorded unverified.
20
19
  * D. Workflows: `spur workflow validate` (live engine, schema + semantic) and a
21
20
  * `--dry-run` transition walk for all ten definitions; `.spur/workflows`
22
- * symlink realpath vs its tracked SSOT target tree.
21
+ * symlink is asserted absent (two-tier project→bundled model, task 0650).
23
22
  *
24
23
  * Output: markdown inventory on stdout-path via `--out`; exit 1 when any
25
24
  * CONFIRMED mismatch remains (ok after repair); unverified entries never fail.
@@ -35,7 +34,6 @@ import {
35
34
  mkdtempSync,
36
35
  readdirSync,
37
36
  readFileSync,
38
- realpathSync,
39
37
  rmSync,
40
38
  writeFileSync,
41
39
  } from 'node:fs';
@@ -130,6 +128,7 @@ interface Parsed {
130
128
  }
131
129
 
132
130
  const PLACEHOLDER = /^([<[{($#"'`]|…|\.\.\.)/;
131
+ const LEGACY_SELF_ALIASES = new Set(['init', 'migrate', 'serve', 'status']);
133
132
 
134
133
  /** Normalize an asserted invocation span into nouns / verbs / flags. Null when it asserts nothing. */
135
134
  export function parseInvocation(spanRaw: string): Parsed | null {
@@ -259,6 +258,32 @@ export function checkNounVerbFlags(
259
258
  const root = surface([]);
260
259
  for (const noun of nouns) {
261
260
  if (noun === 'help' || /^(foo|bar|baz)$/.test(noun)) continue;
261
+ if (LEGACY_SELF_ALIASES.has(noun)) {
262
+ const self = surface(['self']);
263
+ const command = surface(['self', noun]);
264
+ const present = self.commands.includes(noun);
265
+ record(
266
+ `spur ${noun}`,
267
+ 'help-capture(spur self; hidden root alias)',
268
+ present ? 'ok' : 'mismatch',
269
+ present
270
+ ? `legacy alias; canonical self/${noun} command is present`
271
+ : `canonical self/${noun} command is absent`,
272
+ occ,
273
+ );
274
+ for (const f of flags) {
275
+ record(
276
+ `spur ${noun} ${f}`,
277
+ `help-capture(spur self ${noun}; hidden root alias)`,
278
+ present && command.flags.includes(f) ? 'ok' : 'mismatch',
279
+ command.flags.includes(f)
280
+ ? 'flag present on canonical command'
281
+ : `flag absent (${command.flags.join(', ')})`,
282
+ occ,
283
+ );
284
+ }
285
+ continue;
286
+ }
262
287
  if (!root.commands.includes(noun)) {
263
288
  record(
264
289
  `spur ${noun}`,
@@ -315,6 +340,10 @@ export function checkNounVerbFlags(
315
340
  }
316
341
  }
317
342
  for (const verb of verbs) {
343
+ if (nouns.length === 0 && LEGACY_SELF_ALIASES.has(verb)) {
344
+ checkNounVerbFlags([verb], [], flags, occ);
345
+ continue;
346
+ }
318
347
  if (nouns.length === 0 && root.commands.includes(verb)) {
319
348
  record(`spur ${verb}`, 'help-capture(root)', 'ok', 'root verb present', occ);
320
349
  }
@@ -383,7 +412,10 @@ export function sweepPluginTrees(root: string = PLUGIN_ROOT): void {
383
412
  const cells = line.split('|').map((c) => c.trim());
384
413
  const lastCell = cells[cells.length - 2] ?? line;
385
414
  if (first && first !== 'Verb' && first !== 'spur') {
386
- if (rootCommands.includes(first)) {
415
+ const nounCommands = surface([refNoun]).commands;
416
+ if (nounCommands.includes(first)) {
417
+ checkNounVerbFlags([refNoun], [first], flagNames(lastCell), occ);
418
+ } else if (rootCommands.includes(first) || LEGACY_SELF_ALIASES.has(first)) {
387
419
  checkNounVerbFlags([], [first], flagNames(lastCell), occ);
388
420
  } else {
389
421
  checkNounVerbFlags([refNoun], [first], flagNames(lastCell), occ);
@@ -611,7 +643,7 @@ const JSON_PROBES: string[][] = [
611
643
  ['agent', 'list', '--json'],
612
644
  ['agent', 'doctor', 'omp', '--json'],
613
645
  ['workflow', 'list', '--json'],
614
- ['workflow', 'validate', '.spur/workflows/basic.yaml', '--json'],
646
+ ['workflow', 'validate', 'basic.yaml', '--json'],
615
647
  ['status', '--json'],
616
648
  ['team', 'status', '--json'],
617
649
  ['projects', 'list', '--json'],
@@ -758,18 +790,20 @@ export function sweepWorkflows(opts: { run?: CliRunner; wfDir?: string; link?: s
758
790
  }
759
791
  });
760
792
  }
793
+ // Two-tier model (task 0648/0650): `.spur/workflows` is no longer a symlink and
794
+ // must not exist — the CLI resolves an explicit project path, then bundled fallback. Assert absence.
761
795
  const link = opts.link ?? join(REPO_ROOT, '.spur', 'workflows');
762
- let status: Row['status'] = 'mismatch';
763
- let actual = '';
796
+ let status: Row['status'] = 'ok';
797
+ let actual = 'absent (two-tier project→bundled model, task 0650)';
764
798
  try {
765
- const rp = realpathSync(link);
766
- const tracked = realpathSync(wfDir);
767
- status = rp === tracked ? 'ok' : 'mismatch';
768
- actual = `resolves to ${rel(rp)}${status === 'ok' ? ' (== tracked SSOT target)' : ` — expected ${rel(tracked)}`}`;
799
+ if (existsSync(link)) {
800
+ status = 'mismatch';
801
+ actual = 'still present should be removed (task 0650 R3)';
802
+ }
769
803
  } catch (e) {
770
- actual = `cannot resolve .spur/workflows: ${String(e).slice(0, 120)}`;
804
+ actual = `exists-check failed: ${String(e).slice(0, 120)}`;
771
805
  }
772
- record('.spur/workflows symlink', 'symlink-realpath', status, actual, { file: '.spur/workflows', line: 1 });
806
+ record('.spur/workflows symlink', 'symlink-absent', status, actual, { file: '.spur/workflows', line: 1 });
773
807
  }
774
808
 
775
809
  // ─── Report ─────────────────────────────────────────────────────────────────
@@ -805,7 +839,7 @@ export function render(): string {
805
839
  L.push(
806
840
  "R1 scope: `plugins/sp/{commands,skills,scripts,hooks}` (tests/ and evals/ are the harness itself; agents/ prose is outside R1's named surface list).",
807
841
  );
808
- L.push('R2 scope: all workflow YAML via the `.spur/workflows` runtime symlink (tracked SSOT tree).');
842
+ L.push('R2 scope: all workflow YAML via the bundled tree (two-tier project→bundled model since task 0648/0650).');
809
843
  L.push('');
810
844
  L.push('## Check methods');
811
845
  L.push('');
@@ -825,7 +859,7 @@ export function render(): string {
825
859
  L.push(
826
860
  '| `workflow-validate` / `workflow-dry-run` | Live engine: schema+semantic validate; `--dry-run` transition walk |',
827
861
  );
828
- L.push('| `symlink-realpath` | realpath of `.spur/workflows` vs its tracked SSOT target |');
862
+ L.push('| `symlink-absent` | `.spur/workflows` is asserted absent (two-tier project→bundled model, task 0650) |');
829
863
  L.push('');
830
864
  for (const [method, list] of byMethod) {
831
865
  const mm = list.filter((r) => r.status === 'mismatch').length;
@@ -281,15 +281,22 @@ function checkTargetResolution(cmd: ParsedCommand, skillsDir: string, root: stri
281
281
  }
282
282
  }
283
283
 
284
- // Workflow file refs — patterns like .spur/workflows/<name>.yaml
285
- const workflowRefs = [...implSection.matchAll(/\.spur\/workflows\/([^\s)"\]]+\.yaml)/g)];
284
+ // Workflow file refs — bare names (`spur workflow run <name>.yaml`, two-tier model
285
+ // 0648/0650) resolve against the bundled workflow-definition asset tree; legacy
286
+ // `.spur/workflows/<name>.yaml` paths are also caught as unification drift.
287
+ const wfDir = join(root, 'config', 'workflows');
288
+ const workflowRefs = [
289
+ ...[...implSection.matchAll(/\.spur\/workflows\/([^\s)"\]]+\.yaml)/g)],
290
+ ...[...implSection.matchAll(/spur workflow (?:run|validate) ([a-z0-9-]+\.yaml)/g)],
291
+ ];
286
292
  for (const ref of workflowRefs) {
287
- const wfPath = join(root, '.spur', 'workflows', ref[1]);
293
+ const bare = (ref[1] ?? '').split('/').pop() ?? '';
294
+ const wfPath = join(wfDir, bare);
288
295
  if (!existsSync(wfPath)) {
289
296
  violations.push({
290
297
  command: cmd.name,
291
298
  gate: 'c',
292
- message: `unresolved workflow file ${ref[0]} (missing ${wfPath})`,
299
+ message: `unresolved workflow file ${bare} (missing ${wfPath})`,
293
300
  });
294
301
  }
295
302
  }
@@ -521,7 +521,7 @@ Do **not** use this skill for:
521
521
  rubric.
522
522
  - `plugins/sp/references/roles.md` — the Layer-1 role→tier table; this skill's verify/review modes
523
523
  run under the `reviewer` role (0538 R4: this file points at roles.md, it does not restate tiers).
524
- - `.spur/workflows/task-pipeline.yaml` — the `verify → record` gate that consumes the verdict.
524
+ - `task-pipeline.yaml` — the `verify → record` gate that consumes the verdict.
525
525
  - **`sp:spur-dev`** — the execution-half umbrella that drives the pipeline this skill gates.
526
526
  - [references/code-improvement.md](references/code-improvement.md) — architecture-improvement lens
527
527
  for module depth, seam placement, locality, coupling, and testability.
@@ -120,7 +120,7 @@ Wave C verification can emit the following additive `checks[]` rows:
120
120
 
121
121
  ## How the gate reads it
122
122
 
123
- `.spur/workflows/task-pipeline.yaml`, transition `verify → record`:
123
+ `task-pipeline.yaml`, transition `verify → record`:
124
124
 
125
125
  ```yaml
126
126
  guard:
@@ -233,7 +233,7 @@ Also note **what worked well** so efficient patterns are preserved.
233
233
  1. **Fix description** — documentation, skill reference, pipeline comment, hook, etc.
234
234
  2. **Target location** — exact file path
235
235
  - Skill guidance: `skills/<skill-name>/SKILL.md` or `references/<name>.md`
236
- - Pipeline comments: runtime workflow under `.spur/workflows/` (e.g. `task-pipeline.yaml`)
236
+ - Pipeline comments: runtime workflow resolves by bare name (`task-pipeline.yaml`)
237
237
  - CLI reference: `skills/spur-cli/references/tasks/…`
238
238
  3. **Proposed content** — text or code block to add
239
239
  4. **Expected impact** — estimated time saved per future run
@@ -212,5 +212,5 @@ A6 → C5/C3 → unit first if verdict says so; else verify.
212
212
  - Dev operations map: `plugins/sp/skills/spur-dev/references/dev-operations.md:37-56`
213
213
  - `--next` chain on run: `plugins/sp/commands/dev-run.md:104-127`
214
214
  - Refine skip-gate sections: `plugins/sp/skills/spur-dev/references/dev-operations.md:114-128`
215
- - Task lifecycle wip→testing guard: `.spur/workflows/task-lifecycle.yaml:48-54`
215
+ - Task lifecycle wip→testing guard: `task-lifecycle.yaml:48-54`
216
216
  - Decision-brief format: `plugins/sp/skills/spur-dev/references/decision-brief.md`
@@ -39,7 +39,7 @@ that review effort happens on the PR, through Codex — not in the local session
39
39
  sp:pr-reviewing (this skill — mode routing, triage, fix, rules)
40
40
  │ deterministic spine, state order + guards:
41
41
 
42
- .spur/workflows/pr-review.yaml ← workflow SSOT (seeded by spur init; project-tunable)
42
+ pr-review.yaml ← workflow SSOT (seeded by spur init; project-tunable)
43
43
  │ every state resolves with `superskill script path` and shells out to:
44
44
 
45
45
  staged pr-reviewing.ts ← Bun entrypoint for the tested git/gh core
@@ -280,7 +280,7 @@ or review requests.
280
280
 
281
281
  ## Additional Resources
282
282
 
283
- - **Workflow SSOT:** `.spur/workflows/pr-review.yaml` (seeded by `spur init`; tune per project —
283
+ - **Workflow SSOT:** `pr-review.yaml` (seeded by `spur init`; tune per project —
284
284
  wait budget, `preReviewCmd`, dedupe policy)
285
285
  - **Script source:** [scripts/pr-reviewing.ts](../../scripts/pr-reviewing.ts) — subcommand CLI
286
286
  - **Tests:** [tests/pr-reviewing.test.ts](../../tests/pr-reviewing.test.ts) — stubbed git/gh suites