@gobing-ai/spur 0.3.53 → 0.3.55

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 (96) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/corpus-baseline.json +2783 -2783
  3. package/config/pipeline-budgets.json +42 -0
  4. package/config/plugin-scripts.json +73 -0
  5. package/config/workflow-composition-baseline.json +561 -0
  6. package/config/workflows/docs-pipeline.yaml +29 -13
  7. package/config/workflows/feature-dev.yaml +54 -2
  8. package/config/workflows/idea-pipeline.yaml +15 -0
  9. package/config/workflows/task-pipeline.yaml +77 -65
  10. package/config/workflows/wrapup-pipeline.yaml +40 -45
  11. package/package.json +9 -9
  12. package/plugins/README.md +1 -1
  13. package/plugins/sp/README.md +3 -3
  14. package/plugins/sp/agents/super-planner.md +1 -1
  15. package/plugins/sp/commands/dev-daily.md +1 -1
  16. package/plugins/sp/commands/dev-history-load.md +1 -1
  17. package/plugins/sp/commands/dev-idea.md +10 -2
  18. package/plugins/sp/plugin.json +1 -1
  19. package/plugins/sp/scripts/batch-preflight.mjs +160 -0
  20. package/plugins/sp/scripts/batch-preflight.ts +1 -1
  21. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +479 -0
  22. package/plugins/sp/scripts/daily-summary/daily-summary.ts +75 -37
  23. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +204 -0
  24. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +1 -1
  25. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +141 -0
  26. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +1 -1
  27. package/plugins/sp/scripts/feature-sync-bounded.mjs +290 -0
  28. package/plugins/sp/scripts/feature-sync-bounded.ts +9 -8
  29. package/plugins/sp/scripts/history-load.mjs +268 -0
  30. package/plugins/sp/scripts/pr-reviewing.mjs +685 -0
  31. package/plugins/sp/scripts/pr-reviewing.ts +1 -1
  32. package/plugins/sp/scripts/script-contract-check.ts +319 -0
  33. package/plugins/sp/skills/daily-summary/SKILL.md +3 -3
  34. package/plugins/sp/skills/dogfood-testing/SKILL.md +5 -5
  35. package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
  36. package/plugins/sp/skills/pr-reviewing/SKILL.md +3 -3
  37. package/plugins/sp/skills/spur-dev/SKILL.md +2 -1
  38. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +3 -4
  39. package/plugins/sp/skills/spur-dev/references/dev-operations.md +1 -1
  40. package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
  41. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +4 -4
  42. package/spur.js +4270 -1323
  43. package/web/_astro/{BoardApp.plVXYkKo.js → BoardApp.DCLSB3Zs.js} +63 -63
  44. package/web/_astro/BoardApp.Dx5gzAhb.js +1 -0
  45. package/web/_astro/{TaskDetail.5DIyVQ_N.js → TaskDetail.DN_RxS-2.js} +1 -1
  46. package/web/_astro/{arc.BnUgL7ho.js → arc.CldTHRg-.js} +1 -1
  47. package/web/_astro/{architectureDiagram-3BPJPVTR.B9YMJau-.js → architectureDiagram-3BPJPVTR.DQ4T9oeU.js} +1 -1
  48. package/web/_astro/{blockDiagram-GPEHLZMM.Dw2qgvWJ.js → blockDiagram-GPEHLZMM.CGDaXH8J.js} +1 -1
  49. package/web/_astro/{c4Diagram-AAUBKEIU.jTbU0-Nk.js → c4Diagram-AAUBKEIU.Cqsc2iub.js} +1 -1
  50. package/web/_astro/channel.CrBJYpxo.js +1 -0
  51. package/web/_astro/{chunk-2J33WTMH.Di46EtkU.js → chunk-2J33WTMH.CQSKsRmq.js} +1 -1
  52. package/web/_astro/{chunk-4BX2VUAB.DweHBwqQ.js → chunk-4BX2VUAB.Pv1HCtp8.js} +1 -1
  53. package/web/_astro/{chunk-55IACEB6.rRjTKaa7.js → chunk-55IACEB6.-9MseOIk.js} +1 -1
  54. package/web/_astro/{chunk-727SXJPM.BkEQIkSV.js → chunk-727SXJPM.M3OI1DF7.js} +1 -1
  55. package/web/_astro/{chunk-AQP2D5EJ.Cfe9IvAd.js → chunk-AQP2D5EJ.NfHka5Ca.js} +1 -1
  56. package/web/_astro/{chunk-FMBD7UC4.fI6tLbeN.js → chunk-FMBD7UC4.B2suYe3s.js} +1 -1
  57. package/web/_astro/{chunk-ND2GUHAM.BIjgpq0O.js → chunk-ND2GUHAM.BfJ7aucP.js} +1 -1
  58. package/web/_astro/{chunk-QZHKN3VN.ZwLnVxyz.js → chunk-QZHKN3VN._582hZVc.js} +1 -1
  59. package/web/_astro/{classDiagram-4FO5ZUOK.BJAGYidE.js → classDiagram-4FO5ZUOK._tttU_jk.js} +1 -1
  60. package/web/_astro/{classDiagram-v2-Q7XG4LA2.BJAGYidE.js → classDiagram-v2-Q7XG4LA2._tttU_jk.js} +1 -1
  61. package/web/_astro/{cose-bilkent-S5V4N54A.B5y3nz26.js → cose-bilkent-S5V4N54A.C4rVATLQ.js} +1 -1
  62. package/web/_astro/{dagre-BM42HDAG.DHmw0ZBC.js → dagre-BM42HDAG.8BLRi7f9.js} +1 -1
  63. package/web/_astro/{diagram-2AECGRRQ.10gapJfY.js → diagram-2AECGRRQ.D6lwUZos.js} +1 -1
  64. package/web/_astro/{diagram-5GNKFQAL.Gh33_drM.js → diagram-5GNKFQAL.Cl_rMiVT.js} +1 -1
  65. package/web/_astro/{diagram-KO2AKTUF.8ROWlfcX.js → diagram-KO2AKTUF.DhMExHLp.js} +1 -1
  66. package/web/_astro/{diagram-LMA3HP47.BsP3tKmg.js → diagram-LMA3HP47.3OAxzH_o.js} +1 -1
  67. package/web/_astro/{diagram-OG6HWLK6.DM2WFDhU.js → diagram-OG6HWLK6.Uh9RLucG.js} +1 -1
  68. package/web/_astro/{erDiagram-TEJ5UH35.royaC_lH.js → erDiagram-TEJ5UH35.izzElG0c.js} +1 -1
  69. package/web/_astro/{flowDiagram-I6XJVG4X.C9GEvIie.js → flowDiagram-I6XJVG4X.BlGwp5qM.js} +1 -1
  70. package/web/_astro/{ganttDiagram-6RSMTGT7.CXO4T2U0.js → ganttDiagram-6RSMTGT7.je7Vf8dN.js} +1 -1
  71. package/web/_astro/{gitGraphDiagram-PVQCEYII.Q02KzYH7.js → gitGraphDiagram-PVQCEYII.DNk0Ycop.js} +1 -1
  72. package/web/_astro/{index.DAxu50UF.css → index.V6Q7nhed.css} +1 -1
  73. package/web/_astro/{infoDiagram-5YYISTIA.DNfSDKJt.js → infoDiagram-5YYISTIA.K2HhGyWH.js} +1 -1
  74. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BFttvtgJ.js → ishikawaDiagram-YF4QCWOH.BbbOW4IN.js} +1 -1
  75. package/web/_astro/{journeyDiagram-JHISSGLW.Cfm-2SbG.js → journeyDiagram-JHISSGLW.B1UaZKqN.js} +1 -1
  76. package/web/_astro/{kanban-definition-UN3LZRKU.BWGNW6fV.js → kanban-definition-UN3LZRKU.9tsw5QFd.js} +1 -1
  77. package/web/_astro/{linear.BprdUk2Y.js → linear.I9dvtu-j.js} +1 -1
  78. package/web/_astro/{mermaid.core.B2zRNxtX.js → mermaid.core.5bKJsMgf.js} +4 -4
  79. package/web/_astro/{mindmap-definition-RKZ34NQL.g9jij8sw.js → mindmap-definition-RKZ34NQL.BC5MqSn6.js} +1 -1
  80. package/web/_astro/{pieDiagram-4H26LBE5.CbzR4n5b.js → pieDiagram-4H26LBE5.B8GIMyxD.js} +1 -1
  81. package/web/_astro/{quadrantDiagram-W4KKPZXB.DxV3viWt.js → quadrantDiagram-W4KKPZXB.K_aZSM_u.js} +1 -1
  82. package/web/_astro/{requirementDiagram-4Y6WPE33.DvAb90JS.js → requirementDiagram-4Y6WPE33.B_V5kzfT.js} +1 -1
  83. package/web/_astro/{sankeyDiagram-5OEKKPKP.CIFz5t4D.js → sankeyDiagram-5OEKKPKP.DyV2quLS.js} +1 -1
  84. package/web/_astro/{sequenceDiagram-3UESZ5HK.DzlEQynd.js → sequenceDiagram-3UESZ5HK.BlKvPnBv.js} +1 -1
  85. package/web/_astro/{stateDiagram-AJRCARHV.Dph2V-ts.js → stateDiagram-AJRCARHV.C1lYUMdS.js} +1 -1
  86. package/web/_astro/{stateDiagram-v2-BHNVJYJU.wJhfrLAy.js → stateDiagram-v2-BHNVJYJU.DJWjr3QV.js} +1 -1
  87. package/web/_astro/{timeline-definition-PNZ67QCA.BrSiyubX.js → timeline-definition-PNZ67QCA.CYYiXVlv.js} +1 -1
  88. package/web/_astro/{vennDiagram-CIIHVFJN.COobPrUX.js → vennDiagram-CIIHVFJN.zYjQKa_S.js} +1 -1
  89. package/web/_astro/{wardley-L42UT6IY.CrjeTx8A.js → wardley-L42UT6IY.BGrWTY3D.js} +1 -1
  90. package/web/_astro/{wardleyDiagram-YWT4CUSO.B8_fTQ8E.js → wardleyDiagram-YWT4CUSO.CkXxtcI1.js} +1 -1
  91. package/web/_astro/{xychartDiagram-2RQKCTM6.BPpokgVv.js → xychartDiagram-2RQKCTM6.BhKH3KG3.js} +1 -1
  92. package/web/index.html +2 -2
  93. package/config/workflows/planning-pipeline.yaml +0 -249
  94. package/config/workflows/task-pipeline2.yaml +0 -778
  95. package/web/_astro/BoardApp.pdtYu-yW.js +0 -1
  96. package/web/_astro/channel.DEqvaz-I.js +0 -1
@@ -36,23 +36,35 @@ vars:
36
36
  states:
37
37
  - id: precheck
38
38
  description: >
39
- Soft precheck: task check + agent doctor. Status files always exit 0 so
40
- failures route to `failed` via transitions (not a raw lifecycle abort).
39
+ Soft precheck: task check + agent doctor, each owned by a deterministic `command.gate`
40
+ (D5-J, ADR-069). Both gates run `softFail: true`, so a red probe records FAIL and lets
41
+ the transitions route to `failed` instead of aborting the run — the shipped action
42
+ schema exposes no `onError`, so soft mode is what preserves that routing.
43
+ The canonical `.spur/run/$wbs-docs-precheck.status` is the AND of the two gate files.
41
44
  onEnter:
45
+ - kind: command.gate
46
+ options:
47
+ id: docs-precheck-task
48
+ executable: "${vars.spurBin}"
49
+ args: ["task", "check", "${vars.wbs}"]
50
+ resultFile: .spur/run/${vars.wbs}-docs-precheck-task.status
51
+ softFail: true
52
+ timeoutMs: 120000
53
+ - kind: command.gate
54
+ options:
55
+ id: docs-precheck-agent
56
+ executable: "${vars.spurBin}"
57
+ args: ["agent", "doctor", "${vars.agent}"]
58
+ resultFile: .spur/run/${vars.wbs}-docs-precheck-agent.status
59
+ softFail: true
60
+ timeoutMs: 120000
42
61
  - kind: shell
43
62
  options:
44
63
  command: >-
45
- mkdir -p .spur/run &&
46
- set +e &&
47
- $spurBin task check $wbs; check_rc=$?;
48
- $spurBin agent doctor $agent; doctor_rc=$?;
49
- set -e &&
50
- if [ "$check_rc" -eq 0 ] && [ "$doctor_rc" -eq 0 ]; then
51
- printf 'PASS\n' > ".spur/run/$wbs-docs-precheck.status";
52
- else
53
- printf 'FAIL\n' > ".spur/run/$wbs-docs-precheck.status";
54
- fi &&
55
- exit 0
64
+ if test "$(cat .spur/run/$wbs-docs-precheck-task.status 2>/dev/null)" = PASS &&
65
+ test "$(cat .spur/run/$wbs-docs-precheck-agent.status 2>/dev/null)" = PASS;
66
+ then printf 'PASS\n' > ".spur/run/$wbs-docs-precheck.status";
67
+ else printf 'FAIL\n' > ".spur/run/$wbs-docs-precheck.status"; fi
56
68
  - kind: note
57
69
  options:
58
70
  message: "Docs pipeline start for task ${vars.wbs}."
@@ -109,6 +121,10 @@ states:
109
121
  mkdir -p .spur/run &&
110
122
  printf '%s\n' "{\"wbs\":\"$wbs\",\"verdict\":\"PASS\",\"source\":\"docs-pipeline\",\"requirements\":[{\"id\":\"docs-deliverable\",\"status\":\"MET\",\"evidence\":\"docs-pipeline record step\"}],\"acceptanceCriteria\":[],\"checks\":[{\"name\":\"docs-only\",\"status\":\"pass\",\"evidence\":\"docs-pipeline\"}]}"
111
123
  > ".spur/run/$wbs-verdict.json"
124
+ - kind: run.artifact
125
+ options:
126
+ path: .spur/run/${vars.wbs}-verdict.json
127
+ artifactKind: verify-verdict
112
128
  - kind: shell
113
129
  options:
114
130
  command: "$spurBin task update $wbs done --no-lifecycle"
@@ -48,6 +48,11 @@ vars:
48
48
  profile: "standard"
49
49
  spurBin: "spur"
50
50
  stepTimeoutMs: "1800000"
51
+ # Integration review (D5-P, feature R11). Advisory by default: a pending, timed-out, or
52
+ # quota-unavailable review must never block a locally-verified feature. Set
53
+ # requireCleanReview=true to opt into a hard gate for a branch that must ship clean.
54
+ baseBranch: ""
55
+ requireCleanReview: "false"
51
56
  # Injected by WorkflowAppService.run(); scopes .spur/run artifacts per run (0425 R4).
52
57
  __runId: ""
53
58
 
@@ -136,6 +141,35 @@ states:
136
141
  AC, traceability, and that all linked tasks are done. A clean strict check is the only
137
142
  proof the feature is shippable.
138
143
 
144
+ - id: integration-review
145
+ description: >
146
+ Advisory integration review (D5-P, feature R11). Runs `config/workflows/pr-review.yaml`
147
+ ONCE for this feature/branch HEAD, after the local gates and before wrap merge/cleanup.
148
+ Current-HEAD dedup stays inside pr-review (`mode: full`), so a re-entry on the same HEAD
149
+ is a no-op there rather than a second quota spend here.
150
+
151
+ The gate is `softFail: true`, so `pending`, a wait timeout, a missing pr-review
152
+ definition, or an unavailable review quota records FAIL and still reaches `done`.
153
+ Only `requireCleanReview=true` turns that FAIL into a blocking edge. Findings are
154
+ collected by pr-review itself and re-enter the affected local gates on the next run.
155
+ onEnter:
156
+ - kind: command.gate
157
+ options:
158
+ id: feature-integration-review
159
+ executable: "${vars.spurBin}"
160
+ args:
161
+ - workflow
162
+ - run
163
+ - .spur/workflows/pr-review.yaml
164
+ - --vars
165
+ - '{"mode":"full","baseBranch":"${vars.baseBranch}"}'
166
+ resultFile: .spur/run/${vars.__runId}-integration-review.status
167
+ softFail: true
168
+ timeoutMs: 1800000
169
+ - kind: note
170
+ options:
171
+ message: "Integration review for feature ${vars.featureId} recorded at .spur/run/${vars.__runId}-integration-review.status (advisory unless requireCleanReview=true)."
172
+
139
173
  - id: done
140
174
  description: Terminal — feature planned, all tasks executed, and the feature verified.
141
175
  # Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
@@ -195,12 +229,30 @@ transitions:
195
229
  # ── feature-verify gate: strict check certifies done, else fail ──
196
230
  # Declaration order: the pass guard is tried FIRST.
197
231
  - from: feature-verify
198
- to: done
199
- description: Strict feature check passed — the feature is complete.
232
+ to: integration-review
233
+ description: Strict feature check passed — spend one integration review on this stable HEAD.
200
234
  guard:
201
235
  kind: shell
202
236
  options:
203
237
  command: "$spurBin feature check $featureId --strict"
238
+
239
+ - from: integration-review
240
+ to: failed
241
+ description: >
242
+ Review came back FAIL and this branch opted into a hard gate
243
+ (requireCleanReview=true). Ordered before the advisory edge so the strict
244
+ policy wins when both would match.
245
+ guard:
246
+ kind: shell
247
+ options:
248
+ command: 'test "$requireCleanReview" = true && test "$(cat .spur/run/$__runId-integration-review.status 2>/dev/null)" = FAIL'
249
+ - from: integration-review
250
+ to: done
251
+ description: >
252
+ Review clean, pending, unavailable, or advisory — the feature is complete. A
253
+ non-clean advisory review never blocks a locally-verified feature (feature R11).
254
+ guard:
255
+ kind: always
204
256
  - from: feature-verify
205
257
  to: failed
206
258
  description: Strict feature check failed — block before done; investigate incomplete tasks or AC.
@@ -379,9 +379,20 @@ states:
379
379
  # flipping the recommendation to runall — violating R3's any-fail=>refineall invariant.
380
380
  # Each `jq -c … >> "$CHECKS"` appends under `|| exit 1`, and a row-count assertion
381
381
  # (CHECKS lines == WBS count) gates the recommendation computation.
382
+ # D5-O (R4): the deterministic writer owns finalization wherever it exists.
383
+ # `idea-handoff-cli.ts` calls the tested `finalizeIdeaHandoff` capability and
384
+ # implements this exact contract (equal-length/unique-name zip, `spur task deps`,
385
+ # `spur feature refresh`, per-task check with a row-count assertion, and a single
386
+ # recommended next command). `spur init` never scaffolds `packages/`, so seeded
387
+ # projects fall through to the portable shell program below — the same
388
+ # monorepo-writer/shell-fallback split the wrap-up metrics hop uses (0604 Q&A).
382
389
  - kind: shell
383
390
  options:
384
391
  command: >-
392
+ if [ -f packages/app/src/workflow/idea-handoff-cli.ts ]; then
393
+ bun packages/app/src/workflow/idea-handoff-cli.ts;
394
+ exit $?;
395
+ fi;
385
396
  BATCH=".spur/run/$__runId-idea-task-batch.json" &&
386
397
  RESULT=".spur/run/$__runId-idea-batch-create-result.json" &&
387
398
  ORDER=".spur/run/$__runId-idea-task-order.json" &&
@@ -452,6 +463,10 @@ states:
452
463
  - kind: note
453
464
  options:
454
465
  message: "Idea pipeline handoff. Feature: ${vars.featureId}. See .spur/run/${vars.__runId}-idea-handoff.md for the created task list and recommended next command."
466
+ - kind: run.artifact
467
+ options:
468
+ path: .spur/run/${vars.__runId}-idea-handoff.md
469
+ artifactKind: idea-handoff
455
470
  # Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
456
471
  - kind: shell
457
472
  options:
@@ -78,6 +78,15 @@ vars:
78
78
  # Answer captured by the approve gate's hitl.confirm (R1): "yes" | "no" | "cancel".
79
79
  # Empty by default; only meaningful once the approve state has been entered.
80
80
  __hitlAnswer: ""
81
+ # Proof-state bracket (task 0612, ADR-071). `proofDigest` is captured at verify-exit, once the
82
+ # verdict artifact exists; `proofDigestNow` is the re-capture compared against it immediately
83
+ # before `record`. A mismatch means a proof input changed after the verdict was established, so
84
+ # the run routes to `failed` instead of crossing the completion boundary. `taskSpecPath` carries
85
+ # the task file path because `docs/tasks*` is excluded from the digest's git-tree half — spec
86
+ # content is folded in explicitly or a task-file edit would go undetected.
87
+ proofDigest: ""
88
+ proofDigestNow: ""
89
+ taskSpecPath: ""
81
90
  # Project quality gate for the `test` hop (probe + fixall + recheck). Override per project
82
91
  # with the same package-manager surface (this monorepo is Bun-only):
83
92
  # `--vars '{"qualityGateCmd":"bun run lint && bun run test"}'`. Soft probe, hard recheck,
@@ -124,72 +133,33 @@ states:
124
133
  `spur task check <wbs>`. Failures route to the `failed` terminal state
125
134
  (not a raw lifecycle abort mid-enter).
126
135
  onEnter:
127
- # Soft doctor write status and always exit 0 so transitions can branch to
128
- # `failed` cleanly (same pattern as the quality-gate soft probe).
136
+ # Pre-launch executor doctor probe (task 0608 / D6 R4–R5). The auth classifier
137
+ # per-agent-family classification (omp/pi env-key misses soft, explicit auth
138
+ # failures hard) plus the executor-divergence line — now lives in the
139
+ # `doctor.probe` built-in action kind (`packages/app/src/workflow/actions/doctor-probe.ts`)
140
+ # instead of this shell program; that is the ownership-surface landing for the
141
+ # task-pipeline precheck doctor probe (option c, least-privilege built-in).
129
142
  #
130
- # R2 (0487): probe BOTH resolved executors ($agent and $implementAgent) and
131
- # FAIL on `authenticated: unauthenticated`. Previously only $agent was probed
132
- # and auth was informational, so a run whose implement executor had no
133
- # provider key sailed through precheck into a guaranteed implement failure
134
- # (runs e8cb00e7 / b16bfbf4: "auth: no API key not found for provider
135
- # 'volc'" precheck ✓). `unknown` auth keeps the old soft behavior — some
136
- # agents expose no auth-status verb. `spur agent doctor` CLI exit-code
137
- # semantics are deliberately untouched; the gate lives here.
138
- # R4 (0487): one divergence line when the two executors differ (legitimate
139
- # when only implementAgent is pinned, but it must be visible in the log).
140
- # R2 (0503): omp/pi env-key probe misses are soft because the CLI process
141
- # cannot see relay-owned credentials; explicit non-omp auth failures remain hard.
142
- - kind: shell
143
+ # Semantics preserved from the replaced shell (behavior parity is covered by
144
+ # `packages/app/tests/workflow/actions/doctor-probe.test.ts`):
145
+ # R2 (0487): probe BOTH resolved executors ($agent and $implementAgent) and
146
+ # FAIL on `authenticated: unauthenticated`. Previously only $agent was probed
147
+ # and auth was informational, so a run whose implement executor had no
148
+ # provider key sailed through precheck into a guaranteed implement failure
149
+ # (runs e8cb00e7 / b16bfbf4). `unknown` auth keeps the old soft behavior
150
+ # some agents expose no auth-status verb. `spur agent doctor` CLI exit-code
151
+ # semantics are deliberately untouched; the gate lives here.
152
+ # R4 (0487): one divergence line when the two executors differ.
153
+ # R2 (0503): omp/pi env-key probe misses are soft because the CLI process
154
+ # cannot see relay-owned credentials; explicit non-omp auth failures remain hard.
155
+ # Soft probe: writes PASS/FAIL to the status file and always succeeds so the
156
+ # transition guards below route on the token (never a raw lifecycle abort).
157
+ - kind: doctor.probe
143
158
  options:
144
- command: >-
145
- mkdir -p .spur/run &&
146
- DOCTOR_FILE=".spur/run/$wbs-precheck-doctor.status" &&
147
- classifyDoctorProbe() {
148
- PROBE_DETAIL="$1";
149
- PROBE_AGENT="$2";
150
- case "$PROBE_AGENT" in
151
- omp|omp-*|pi|pi-*)
152
- if printf '%s' "$PROBE_DETAIL" | grep -Eiq 'API key not found for provider|no probe registered'; then
153
- printf 'env-miss\n';
154
- return;
155
- fi;;
156
- esac;
157
- if printf '%s' "$PROBE_DETAIL" | grep -Eiq 'invalid (API )?key|unauthori[sz]ed|forbidden|authentication failed|credential(s)? rejected'; then
158
- printf 'auth-fail\n';
159
- else
160
- printf 'unknown\n';
161
- fi;
162
- } &&
163
- STATUS=PASS;
164
- if [ -n "$implementAgent" ] && [ "$implementAgent" != "$agent" ]; then
165
- echo "precheck: agent=$agent implementAgent=$implementAgent (executors diverge)";
166
- EXECS="$agent $implementAgent";
167
- else
168
- EXECS="$agent";
169
- fi;
170
- for EXE in $EXECS; do
171
- OUT=$($spurBin agent doctor "$EXE" --json 2>&1) || {
172
- echo "precheck: FAIL - doctor exited non-zero for $EXE";
173
- echo "$OUT";
174
- STATUS=FAIL;
175
- continue;
176
- };
177
- AUTH=$(printf '%s' "$OUT" | jq -r '.agents[0].authenticated // "unknown"' 2>/dev/null || echo unknown);
178
- DETAIL=$(printf '%s' "$OUT" | jq -r '.agents[0].modelStatus.detail // ""' 2>/dev/null || echo "");
179
- PROBE=$(classifyDoctorProbe "$DETAIL" "$EXE");
180
- echo "precheck: $EXE auth=$AUTH probe=$PROBE $DETAIL";
181
- if [ "$AUTH" = unauthenticated ]; then
182
- case "$EXE:$PROBE" in
183
- omp*:env-miss|pi*:env-miss|omp*:unknown|pi*:unknown)
184
- echo "precheck: SOFT - executor $EXE auth probe cannot see agent-owned credentials";;
185
- *)
186
- echo "precheck: FAIL - executor $EXE is unauthenticated; fix agent.default or pass --vars '{\"agent\":\"<authenticated-executor>\"}' ($spurBin agent doctor $EXE --json); $DETAIL";
187
- STATUS=FAIL;;
188
- esac;
189
- fi;
190
- done;
191
- echo "$STATUS" > "$DOCTOR_FILE";
192
- exit 0
159
+ resultFile: ".spur/run/${vars.wbs}-precheck-doctor.status"
160
+ spurBin: "${vars.spurBin}"
161
+ agent: "${vars.agent}"
162
+ implementAgent: "${vars.implementAgent}"
193
163
  # R6 (0487): pre-launch hygiene WARNING (never a block) — starting a task on
194
164
  # a tree already dirty with another task's implementation is how 0485's diff
195
165
  # got swept into 0486's run. Corpus dirs are excluded: the pipeline writes
@@ -245,9 +215,14 @@ states:
245
215
  command: >-
246
216
  SIZE_FILE=".spur/run/$wbs-precheck-size.status" &&
247
217
  mkdir -p .spur/run &&
218
+ if [ -f plugins/sp/scripts/task-size-precheck.ts ]; then
248
219
  bun plugins/sp/scripts/task-size-precheck.ts "$wbs"
249
220
  --spur-bin "$spurBin" --max-reqs "$maxImplementReqs" --max-plan-items "$maxImplementPlanItems"
250
- --executor "$implementAgent" &&
221
+ --executor "$implementAgent";
222
+ else
223
+ echo "task-size-precheck skipped — plugins/sp/scripts/task-size-precheck.ts not present in project." >&2 &&
224
+ echo "PASS" > "$SIZE_FILE";
225
+ fi &&
251
226
  exit 0
252
227
 
253
228
  - id: implement
@@ -486,6 +461,27 @@ states:
486
461
  - kind: shell
487
462
  options:
488
463
  command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
464
+ # Proof-state capture (task 0612, ADR-071). Placement is load-bearing: AFTER the verdict
465
+ # artifact exists, never before `verify`. `/sp:dev-verify --fix all` writes to the tree by
466
+ # design when it repairs a row, so an earlier capture would fire on verify's own legitimate
467
+ # repairs instead of on a violation.
468
+ - kind: shell
469
+ options:
470
+ command: '$spurBin task path $wbs --json 2>/dev/null | jq -r ".path // empty" > ".spur/run/$wbs-taskpath.txt" || true; exit 0'
471
+ - kind: file.read.into-var
472
+ options:
473
+ path: .spur/run/${vars.wbs}-taskpath.txt
474
+ var: taskSpecPath
475
+ - kind: proof.fingerprint
476
+ options:
477
+ var: proofDigest
478
+ taskFile: ${vars.taskSpecPath}
479
+ # R3: the evidence must NAME the digest, not just assert proof validity in prose. Stamp it
480
+ # into the verdict artifact's checks[] so the document that aggregates quality, review, and
481
+ # verification results carries the value `record`'s compare then proves unchanged.
482
+ - kind: shell
483
+ options:
484
+ command: 'V=".spur/run/$wbs-verdict.json"; if [ -f "$V" ] && [ -n "$proofDigest" ]; then jq --arg d "$proofDigest" ".checks += [{\"name\":\"proof-input-digest\",\"status\":\"pass\",\"evidence\":\$d}]" "$V" > "$V.tmp" && mv "$V.tmp" "$V"; fi; exit 0'
489
485
 
490
486
  - id: record
491
487
  description: >
@@ -496,6 +492,15 @@ states:
496
492
  retry-suppression) if `feature_id` is present, or appends an orphan link proposal
497
493
  to the run report if absent (task 0328 / ADR-0322).
498
494
  onEnter:
495
+ # Proof-state compare (task 0612, ADR-071) — FIRST action in the state, so it runs before any
496
+ # record write. Re-captures the digest and asserts it equals the value taken at verify-exit.
497
+ # A mismatch means a proof input changed after the verdict was established; the default `fail`
498
+ # policy halts the sequence and routes the run to `failed` rather than crossing into `record`.
499
+ - kind: proof.fingerprint
500
+ options:
501
+ var: proofDigestNow
502
+ taskFile: ${vars.taskSpecPath}
503
+ expect: ${vars.proofDigest}
499
504
  - kind: shell
500
505
  options:
501
506
  command: >-
@@ -558,6 +563,13 @@ states:
558
563
  return "$RETRY_RC";
559
564
  };
560
565
  retry_transient $spurBin task update "$wbs" done --no-lifecycle
566
+ # D5-L: the verify verdict is this pipeline's proof of completion, so its ownership
567
+ # belongs to a deterministic primitive rather than an untracked file on disk.
568
+ # `run.artifact` records path + kind + runId only — never the body (ADR-069).
569
+ - kind: run.artifact
570
+ options:
571
+ path: .spur/run/${vars.wbs}-verdict.json
572
+ artifactKind: verify-verdict
561
573
  - kind: note
562
574
  options:
563
575
  message: "Pipeline complete for task ${vars.wbs} (done gate cleared)."
@@ -7,7 +7,7 @@
7
7
  # apply identically. Branch cleanup is an irreversible HITL gate — it always pauses,
8
8
  # even under --auto (Iron Law #6: irreversible action -> surface to human).
9
9
  #
10
- # Shape: start -> task-resolve -> doc-sync -> learning-capture -> metrics-record
10
+ # Shape: start -> task-resolve -> doc-sync -> metrics-record
11
11
  # -> feature-transition (conditional: if vars.feature set)
12
12
  # -> branch-cleanup (conditional: if vars.merge=true)
13
13
  # -> done
@@ -25,8 +25,10 @@
25
25
  # Reliability (aligned with task-pipeline / ADR-043):
26
26
  # - Prefer pure slash commands when a command exists; free-form inputs remain only
27
27
  # where capture artifacts (answerFile) have no dedicated slash surface yet.
28
- # - learning-capture / metrics-record use expectFile + soft append (empty capture
29
- # does not abort wrap-up after prior steps already landed).
28
+ # - doc-sync / metrics-record use expectFile + soft append (empty capture
29
+ # does not abort wrap-up after prior steps already landed). The doc-sync hop is a
30
+ # single model query that both repairs doc drift (sp:doc-evolve) and captures
31
+ # working learnings (task 0607 R2 — one query answers both; count 2 -> 1).
30
32
  # - feature-transition is a soft shell (always exit 0) so a blocked feature sync
31
33
  # does not abort wrap-up after learnings/metrics already landed.
32
34
  # - branch-cleanup HITL is exhaustive (yes/no/cancel → done; missing answer defense).
@@ -35,7 +37,7 @@
35
37
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
36
38
  kind: state-machine
37
39
  name: wrapup-pipeline
38
- description: "Post-execution wrap-up: doc-sync, learning-capture, metrics, feature-transition, branch-cleanup"
40
+ description: "Post-execution wrap-up: doc-sync (doc drift + learning capture), metrics, feature-transition, branch-cleanup"
39
41
  iterationBound: 10
40
42
  initialState: start
41
43
  terminalStates:
@@ -73,28 +75,24 @@ states:
73
75
 
74
76
  - id: doc-sync
75
77
  description: >
76
- Project-level doc drift repair for the batch (04_DESIGN, 03_ARCHITECTURE,
77
- 00_ADR, docs/design/*). No dedicated slash command yet — free-form skill
78
- pointer until a /sp:doc-evolve command exists (ADR-043 residual).
78
+ ONE model query that both repairs project-level doc drift for the batch
79
+ (04_DESIGN, 03_ARCHITECTURE, 00_ADR, docs/design/*) and captures working
80
+ learnings (task 0607 R2: two adjacent agent.run hops merged, count 2 -> 1;
81
+ no operator-decision pause or verdict boundary sits between them). No dedicated
82
+ slash command yet — free-form skill pointer until a /sp:doc-evolve command
83
+ exists (ADR-043 residual). Capture+shell decoupling preserved: expectFile gates
84
+ a non-empty capture before the append; append is best-effort soft (empty capture
85
+ skips without aborting wrap-up).
79
86
  onEnter:
80
87
  - kind: agent.run
81
88
  options:
82
89
  agent: ${vars.agent}
83
- input: 'Skill(skill="sp:doc-evolve", args="wrapup tasks=${vars.tasks}"). Repair drift in docs/00_ADR.md, docs/03_ARCHITECTURE.md, docs/04_DESIGN.md, docs/design/* per constitution edit rules. Do not write task/feature corpus.'
84
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
85
- role: coder
86
- timeoutMs: ${vars.stepTimeoutMs}
87
-
88
- - id: learning-capture
89
- description: >
90
- Append working learnings to .spur/memory/learnings.md. Capture+shell decoupling:
91
- expectFile gates non-empty capture before append; append is best-effort soft
92
- (empty capture skips without aborting wrap-up).
93
- onEnter:
94
- - kind: agent.run
95
- options:
96
- agent: ${vars.agent}
97
- input: "Extract working learnings from tasks ${vars.tasks}. Output raw markdown (no fences) grouped by date and task WBS: conventions, errors fixed, patterns, gotchas. Captured to .spur/run/wrapup-learnings.md."
90
+ input: >-
91
+ First run Skill(skill="sp:doc-evolve", args="wrapup tasks=${vars.tasks}") to repair drift in
92
+ docs/00_ADR.md, docs/03_ARCHITECTURE.md, docs/04_DESIGN.md, docs/design/* per constitution edit
93
+ rules; do not write task/feature corpus. THEN extract working learnings from tasks ${vars.tasks}
94
+ conventions, errors fixed, patterns, gotchas, grouped by date and task WBS — as raw markdown
95
+ (no fences) and END your final message with that markdown (it is captured to .spur/run/wrapup-learnings.md).
98
96
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
99
97
  role: coder
100
98
  answerFile: .spur/run/wrapup-learnings.md
@@ -114,27 +112,29 @@ states:
114
112
 
115
113
  - id: metrics-record
116
114
  description: >
117
- Append one JSONL row per task to .spur/memory/wrapup-metrics.jsonl. Soft append
118
- (empty capture does not abort wrap-up after learnings already landed).
115
+ Append one JSONL row per task to .spur/memory/wrapup-metrics.jsonl deterministically.
119
116
  onEnter:
120
- - kind: agent.run
121
- options:
122
- agent: ${vars.agent}
123
- input: "For each task in ${vars.tasks}, output one JSONL line (no fences) with fields: wbs, feature_id, status, verdict, timestamp. Captured to .spur/run/wrapup-metrics.jsonl."
124
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
125
- role: scribe
126
- answerFile: .spur/run/wrapup-metrics.jsonl
127
- expectFile: .spur/run/wrapup-metrics.jsonl
128
- timeoutMs: ${vars.stepTimeoutMs}
129
117
  - kind: shell
130
118
  options:
131
119
  command: >-
132
120
  mkdir -p .spur/memory &&
133
- if test -s .spur/run/wrapup-metrics.jsonl; then
134
- cat .spur/run/wrapup-metrics.jsonl >> .spur/memory/wrapup-metrics.jsonl;
135
- else
136
- echo "wrapup: metrics capture missing or empty at $PWD/.spur/run/wrapup-metrics.jsonl - append skipped" >&2;
137
- fi &&
121
+ printf '%s' "$tasks" | jq -r '(if type == "string" then (try fromjson catch .) else . end) | if type == "array" then .[] else empty end' |
122
+ while IFS= read -r wbs; do
123
+ if [ -n "$wbs" ]; then
124
+ task_json=$($spurBin task show "$wbs" --json 2>/dev/null || true);
125
+ if [ -n "$task_json" ]; then
126
+ feat=$(printf '%s' "$task_json" | jq -r '.frontmatter.feature_id // .feature_id // empty' 2>/dev/null || true);
127
+ stat=$(printf '%s' "$task_json" | jq -r '.frontmatter.status // .status // "unknown"' 2>/dev/null || true);
128
+ verdict="UNKNOWN";
129
+ if [ -f ".spur/run/$wbs-verdict.json" ]; then
130
+ v=$(jq -r '.verdict // "UNKNOWN"' ".spur/run/$wbs-verdict.json" 2>/dev/null || true);
131
+ if [ -n "$v" ]; then verdict="$v"; fi;
132
+ fi;
133
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ");
134
+ printf '{"wbs":"%s","feature_id":"%s","status":"%s","verdict":"%s","timestamp":"%s"}\n' "$wbs" "$feat" "$stat" "$verdict" "$ts" >> .spur/memory/wrapup-metrics.jsonl;
135
+ fi;
136
+ fi;
137
+ done &&
138
138
  exit 0
139
139
 
140
140
  - id: feature-transition
@@ -226,15 +226,10 @@ transitions:
226
226
  guard:
227
227
  kind: always
228
228
 
229
- # ── linear body: doc-sync -> learning-capture -> metrics-record ──
229
+ # ── linear body: doc-sync -> metrics-record ──
230
230
  - from: doc-sync
231
- to: learning-capture
232
- description: Doc sync complete — capture learnings.
233
- guard:
234
- kind: always
235
- - from: learning-capture
236
231
  to: metrics-record
237
- description: Learnings captured — record metrics.
232
+ description: Doc drift repair + learning capture complete — record metrics.
238
233
  guard:
239
234
  kind: always
240
235
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.53",
3
+ "version": "0.3.55",
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.38",
57
- "@gobing-ai/ts-ai-runner": "^0.4.38",
58
- "@gobing-ai/ts-dual-workflow-engine": "^0.4.38",
59
- "@gobing-ai/ts-infra": "^0.4.38",
60
- "@gobing-ai/ts-llm-jsonl-importer": "^0.4.38",
61
- "@gobing-ai/ts-rule-engine": "^0.4.38",
62
- "@gobing-ai/ts-runtime": "^0.4.38",
63
- "@gobing-ai/ts-utils": "^0.4.38",
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",
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
@@ -359,8 +359,8 @@ Each pipeline owns one lifecycle phase:
359
359
  | `basic.yaml` | Generic implement/check/fix | direct `spur workflow run` | existing |
360
360
  | `feature-lifecycle.yaml` | Feature status FSM | `spur feature update` | existing |
361
361
  | `task-lifecycle.yaml` | Task status FSM | `spur task update` | existing |
362
- | `planning-pipeline.yaml` | Planning/design from known slug | `/sp:dev-plan` | existing |
363
362
  | `task-pipeline.yaml` | Single-task execution | `/sp:dev-run` | existing |
363
+ | `idea-pipeline.yaml` | Idea/planning → feature + tasks | `/sp:dev-idea`, `/sp:dev-plan` | existing |
364
364
  | `feature-dev.yaml` | Feature umbrella execution | `/sp:dev-runall --feature <id>` | existing |
365
365
  | `idea-pipeline.yaml` | Idea to feature + AC + task batch | `/sp:dev-idea` | new in 0.2.12 |
366
366
  | `wrapup-pipeline.yaml` | Post-execution wrap-up | `/sp:dev-wrap`, `/sp:dev-wrapall` | new in 0.2.12 |
@@ -360,8 +360,8 @@ no per-platform artifacts — only the platform-independent thin wrappers.
360
360
  **Thin-wrapper contract** is enforced by `scripts/validate-commands.ts`:
361
361
 
362
362
  ```bash
363
- bun plugins/sp/scripts/validate-commands.ts # validate all 39 commands
364
- bun plugins/sp/scripts/validate-commands.ts --json # machine-readable output
363
+ bun run validate-commands # validate all 39 commands
364
+ bun run validate-commands --json # machine-readable output
365
365
  ```
366
366
 
367
367
  The validator checks five gates. For **non-`dev-*`** commands: (a) heading whitelist — only
@@ -583,8 +583,8 @@ pipeline owns one lifecycle phase:
583
583
  | `basic.yaml` | Generic implement/check/fix | direct `spur workflow run` |
584
584
  | `feature-lifecycle.yaml` | Feature status FSM | `spur feature update` |
585
585
  | `task-lifecycle.yaml` | Task status FSM | `spur task update` |
586
- | `planning-pipeline.yaml` | Planning/design from known slug | `/sp:dev-plan` |
587
586
  | `task-pipeline.yaml` | Single-task execution | `/sp:dev-run` |
587
+ | `idea-pipeline.yaml` | Idea/planning → feature + tasks | `/sp:dev-idea`, `/sp:dev-plan` |
588
588
  | `feature-dev.yaml` | Feature umbrella execution | `/sp:dev-runall --feature` |
589
589
  | `idea-pipeline.yaml` | Idea to feature + AC + task batch | `/sp:dev-idea` |
590
590
  | `wrapup-pipeline.yaml` | Post-execution wrap-up | `/sp:dev-wrap`, `/sp:dev-wrapall` |
@@ -153,7 +153,7 @@ When you fan out or dispatch a subagent, apply the four disciplines the SSOT
153
153
  - [ ] Drive the loop in execution-batch.md - resolve -> freeze -> order -> **preflight** -> run -> inspect
154
154
  -> optional recovery -> decide -> report.
155
155
  - [ ] **Preflight** each WBS before `workflow run` using
156
- `bun plugins/sp/scripts/batch-preflight.ts` (or the pure `preflightTask` helper) with
156
+ `node "$(superskill script path sp batch-preflight.mjs)"` (or the pure `preflightTask` helper) with
157
157
  `spur task show --json` deps. On `action: skip` (A2/A7/A8/A9), do **not** start the pipeline;
158
158
  record skip in the batch report with the reason string.
159
159
  - [ ] Launch each **ready** per-task pipeline with `--async`. The command/script layer polls
@@ -27,4 +27,4 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
27
27
 
28
28
  ## Implementation
29
29
 
30
- Run the generator per [daily-summary/SKILL.md](../skills/daily-summary/SKILL.md): `bun plugins/sp/scripts/daily-summary/daily-summary.ts $ARGUMENTS`.
30
+ Run the generator per [daily-summary/SKILL.md](../skills/daily-summary/SKILL.md): `node "$(superskill script path sp daily-summary/daily-summary.mjs)" $ARGUMENTS`.
@@ -59,5 +59,5 @@ Run the load-then-analyze sequence per `plugins/sp/scripts/history-load.ts` —
59
59
  only after import exits 0, narrowing forwarded to `analyze` only:
60
60
 
61
61
  ```
62
- bun plugins/sp/scripts/history-load.ts $ARGUMENTS
62
+ node "$(superskill script path sp history-load.mjs)" $ARGUMENTS
63
63
  ```
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Turn a vague idea into a feature with AC and a decomposed task batch — discovery, idea-eval, feature-create, AC, feature-check, system-design, decompose, batch-create (Design by default), handoff
3
3
  role: planner
4
- argument-hint: "\"<idea>\" [--auto] [--skip-design] [--approve-taste]"
4
+ argument-hint: "\"<idea>\" [--auto] [--skip-design] [--approve-taste] [--agent <inline|auto|name>]"
5
5
  allowed-tools: ["Bash", "Read", "AskUserQuestion"]
6
6
  ---
7
7
 
@@ -19,6 +19,7 @@ Wraps the **idea-pipeline.yaml** workflow.
19
19
  | `--approve-taste` | With `--auto`: set idea_approved + design_approved so idea-eval / design-approval do not pause. | off |
20
20
  | `--idea-approved` | Compatibility alias for idea_approved=true (subset of --approve-taste). | off |
21
21
  | `--design-approved` | Compatibility alias for design_approved=true (subset of --approve-taste). | off |
22
+ | `--agent` `<inline\|auto\|name>` | Who runs the model-bearing ideation. The pipeline's `agent.run` stages are headless — they always dispatch a subprocess, so `--agent inline` is rejected there with the stable special error (exit 2). Use `omit` (resolves to `agent.default`), `auto` (tier-resolves an executor), or a name (pins that executor). | agent.default |
22
23
 
23
24
  For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag-glossary.md).
24
25
 
@@ -29,6 +30,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
29
30
  [--auto] # skip objective HITL only (feature-check, batch-create)
30
31
  [--skip-design] # design package off (system-design + task Design)
31
32
  [--approve-taste] # with --auto: skip idea-eval + design-approval pauses
33
+ [--agent <inline|auto|name>] # who runs the model-bearing ideation (default: agent.default)
32
34
  ```
33
35
 
34
36
  There is **no** `--design` force flag. Design is default-on; only `--skip-design` opts out.
@@ -46,6 +48,12 @@ spur workflow run .spur/workflows/idea-pipeline.yaml --vars '{
46
48
  "profile":"interactive|auto",
47
49
  "design":"auto|skip",
48
50
  "design_approved":"false|true",
49
- "idea_approved":"false|true"
51
+ "idea_approved":"false|true",
52
+ "agent":"<executor-or-role>"
50
53
  }'
51
54
  ```
55
+
56
+ Omit `agent` from `--vars` unless the operator passed `--agent`: an absent var lets `agent.default`
57
+ (a Layer-1 role, resolved to its tier's cheapest usable executor) govern, which is the intended
58
+ routing. Passing `--agent <name>` pins that executor for every `agent.run` stage — the escape hatch
59
+ when the resolved executor is unusable (quota exhaustion, auth failure).