@gobing-ai/spur 0.3.85 → 0.3.87

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 (139) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +0 -25
  3. package/config/plugin-scripts.json +4 -5
  4. package/config/rules/boundary/env-var-hygiene.yaml +41 -0
  5. package/config/templates/feature/default.md +2 -0
  6. package/config/templates/task/brainstorm.md +2 -2
  7. package/config/templates/task/feature-impl.md +2 -2
  8. package/config/templates/task/issue.md +2 -2
  9. package/config/templates/task/meta.md +2 -2
  10. package/config/templates/task/review.md +2 -2
  11. package/config/templates/task/standard.md +2 -2
  12. package/config/transition-shims.json +1 -1
  13. package/config/workflow-candidates.json +19 -0
  14. package/config/workflows/feature-lifecycle.yaml +10 -2
  15. package/config/workflows/feature-verification.yaml +71 -0
  16. package/config/workflows/idea-pipeline.yaml +87 -39
  17. package/config/workflows/task-pipeline.yaml +43 -12
  18. package/config/workflows/wrapup-pipeline.yaml +70 -7
  19. package/package.json +9 -9
  20. package/plugins/sp/README.md +7 -7
  21. package/plugins/sp/commands/dev-idea.md +9 -2
  22. package/plugins/sp/commands/dev-refactor.md +33 -0
  23. package/plugins/sp/hooks/agent-hint.ts +5 -4
  24. package/plugins/sp/hooks/careful-guard.ts +3 -1
  25. package/plugins/sp/hooks/context-post-tool.ts +2 -1
  26. package/plugins/sp/hooks/context-session-start.ts +4 -3
  27. package/plugins/sp/hooks/context-session-stop.ts +2 -1
  28. package/plugins/sp/hooks/pi/guard-extension.ts +4 -3
  29. package/plugins/sp/hooks/task-write-guard.ts +4 -3
  30. package/plugins/sp/lib/idea-handoff.generated.mjs +260 -260
  31. package/plugins/sp/plugin.json +1 -1
  32. package/plugins/sp/references/roles.md +1 -1
  33. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +13 -4
  34. package/plugins/sp/scripts/daily-summary/daily-summary.ts +6 -4
  35. package/plugins/sp/scripts/feature-sync-bounded.mjs +10 -2
  36. package/plugins/sp/scripts/feature-sync-bounded.ts +2 -1
  37. package/plugins/sp/scripts/idea-coverage-check.ts +168 -0
  38. package/plugins/sp/scripts/idea-handoff.mjs +6 -1
  39. package/plugins/sp/scripts/idea-handoff.ts +4 -1
  40. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +115 -4
  41. package/plugins/sp/scripts/inline-run-setup.ts +195 -2
  42. package/plugins/sp/scripts/pr-reviewing.mjs +8 -1
  43. package/plugins/sp/scripts/pr-reviewing.ts +2 -1
  44. package/plugins/sp/scripts/quality-gate.mjs +8 -1
  45. package/plugins/sp/scripts/quality-gate.ts +2 -1
  46. package/plugins/sp/scripts/surface-drift-inventory.ts +1 -4
  47. package/plugins/sp/scripts/task-evidence-precheck.ts +2 -1
  48. package/plugins/sp/scripts/task-size-precheck.ts +6 -4
  49. package/plugins/sp/scripts/verify-answer-lint.ts +2 -1
  50. package/plugins/sp/scripts/workflow-step-profile.mjs +10 -2
  51. package/plugins/sp/scripts/workflow-step-profile.ts +2 -1
  52. package/plugins/sp/scripts/wrapup-steps.mjs +8 -1
  53. package/plugins/sp/scripts/wrapup-steps.ts +2 -1
  54. package/plugins/sp/skills/brainstorm/SKILL.md +4 -0
  55. package/plugins/sp/skills/code-refactoring/SKILL.md +155 -0
  56. package/plugins/sp/skills/code-refactoring/references/finding-schema.md +74 -0
  57. package/plugins/sp/skills/code-refactoring/references/fix-ladder.md +52 -0
  58. package/plugins/sp/skills/code-refactoring/references/focus-detection.md +44 -0
  59. package/plugins/sp/skills/code-refactoring/references/refactor-finding.schema.json +95 -0
  60. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +23 -16
  61. package/plugins/sp/skills/spur-cli/references/features/acceptance-criteria.md +4 -2
  62. package/plugins/sp/skills/spur-cli/references/features.md +6 -1
  63. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +2 -2
  64. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +3 -3
  65. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +1 -1
  66. package/plugins/sp/skills/spur-cli/references/workflows.md +8 -7
  67. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +24 -0
  68. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -5
  69. package/plugins/sp/skills/spur-dev/references/dev-operations.md +18 -2
  70. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
  71. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +22 -3
  72. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +2 -2
  73. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +11 -1
  74. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +91 -1
  75. package/plugins/sp/skills/taste-refactoring-api/SKILL.md +44 -1
  76. package/plugins/sp/skills/taste-refactoring-api/references/protocol-modes.md +31 -0
  77. package/plugins/sp/skills/taste-refactoring-architect/SKILL.md +43 -0
  78. package/plugins/sp/skills/taste-refactoring-tests/SKILL.md +42 -0
  79. package/plugins/sp/skills/taste-refactoring-ui/SKILL.md +43 -0
  80. package/schemas/state-machine-workflow.schema.json +5 -0
  81. package/schemas/task-batch.schema.json +2 -2
  82. package/schemas/transition-flow-workflow.schema.json +5 -0
  83. package/spur.js +21106 -20669
  84. package/web/_astro/BoardApp.CJiqp5pS.js +1 -0
  85. package/web/_astro/{BoardApp.yW425dRZ.js → BoardApp.yBBcFXWP.js} +4 -4
  86. package/web/_astro/{TaskDetail.CGwJAinW.js → TaskDetail.DqFJbRFc.js} +1 -1
  87. package/web/_astro/{arc.B-qNXzSO.js → arc.DL-BpHoi.js} +1 -1
  88. package/web/_astro/{architectureDiagram-3BPJPVTR.Bdg-xiji.js → architectureDiagram-3BPJPVTR.9IdQYyDq.js} +1 -1
  89. package/web/_astro/{blockDiagram-GPEHLZMM.DFArg3Kp.js → blockDiagram-GPEHLZMM.BKsFCqTl.js} +1 -1
  90. package/web/_astro/{c4Diagram-AAUBKEIU.BkOLcjyx.js → c4Diagram-AAUBKEIU.DhwI0dh1.js} +1 -1
  91. package/web/_astro/channel.CX5453qQ.js +1 -0
  92. package/web/_astro/{chunk-2J33WTMH.DN3c20V4.js → chunk-2J33WTMH.B3QVmQ9S.js} +1 -1
  93. package/web/_astro/{chunk-4BX2VUAB.B64K0Ttd.js → chunk-4BX2VUAB.DBSuqs9F.js} +1 -1
  94. package/web/_astro/{chunk-55IACEB6.C-AJvNwa.js → chunk-55IACEB6.BSYTWAYD.js} +1 -1
  95. package/web/_astro/{chunk-727SXJPM.DMkr46uS.js → chunk-727SXJPM.cWVuxXfS.js} +1 -1
  96. package/web/_astro/{chunk-AQP2D5EJ.4_WGbI64.js → chunk-AQP2D5EJ.DpU_Ob3d.js} +1 -1
  97. package/web/_astro/{chunk-FMBD7UC4.cbFW_lVE.js → chunk-FMBD7UC4.BykFkyji.js} +1 -1
  98. package/web/_astro/{chunk-ND2GUHAM.CDW_-2O1.js → chunk-ND2GUHAM.DwgHlMdY.js} +1 -1
  99. package/web/_astro/{chunk-QZHKN3VN.BoYOt_yg.js → chunk-QZHKN3VN.CusXUGWM.js} +1 -1
  100. package/web/_astro/{classDiagram-4FO5ZUOK.BNT6XpHQ.js → classDiagram-4FO5ZUOK.fx0ObzkN.js} +1 -1
  101. package/web/_astro/{classDiagram-v2-Q7XG4LA2.BNT6XpHQ.js → classDiagram-v2-Q7XG4LA2.fx0ObzkN.js} +1 -1
  102. package/web/_astro/{cose-bilkent-S5V4N54A.CI1DQxR3.js → cose-bilkent-S5V4N54A.Z4HgOlsd.js} +1 -1
  103. package/web/_astro/{cynefin-OW5HDTMX.A8ARH61p.js → cynefin-OW5HDTMX.B5dIZHJu.js} +1 -1
  104. package/web/_astro/{dagre-BM42HDAG.u28ZmqwR.js → dagre-BM42HDAG.DT70Q_Yw.js} +1 -1
  105. package/web/_astro/{diagram-2AECGRRQ.Dx8JcrGc.js → diagram-2AECGRRQ.DqHA3XBF.js} +1 -1
  106. package/web/_astro/{diagram-5GNKFQAL.BXJUG9GY.js → diagram-5GNKFQAL.BmCem957.js} +1 -1
  107. package/web/_astro/{diagram-KO2AKTUF.BtlMuWqW.js → diagram-KO2AKTUF.sn0-hrE0.js} +1 -1
  108. package/web/_astro/{diagram-LMA3HP47.C4T7qaAy.js → diagram-LMA3HP47.BSHe9tVc.js} +1 -1
  109. package/web/_astro/{diagram-OG6HWLK6.D5HECE10.js → diagram-OG6HWLK6.DHIc-86k.js} +1 -1
  110. package/web/_astro/{erDiagram-TEJ5UH35.B_hMR3yw.js → erDiagram-TEJ5UH35.Bxayrs7v.js} +1 -1
  111. package/web/_astro/{flowDiagram-I6XJVG4X.BAmpcYl3.js → flowDiagram-I6XJVG4X.BkzoE_5I.js} +1 -1
  112. package/web/_astro/{ganttDiagram-6RSMTGT7.DV1bSWK-.js → ganttDiagram-6RSMTGT7.okT6CvTo.js} +1 -1
  113. package/web/_astro/{gitGraphDiagram-PVQCEYII.DxYKkKrJ.js → gitGraphDiagram-PVQCEYII.CJuYbhC7.js} +1 -1
  114. package/web/_astro/{infoDiagram-5YYISTIA.BAWY2xMb.js → infoDiagram-5YYISTIA.RqLy7nBo.js} +1 -1
  115. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CZssRn6V.js → ishikawaDiagram-YF4QCWOH.BwIcoagw.js} +1 -1
  116. package/web/_astro/{journeyDiagram-JHISSGLW.C--muARd.js → journeyDiagram-JHISSGLW.UB1VbWtH.js} +1 -1
  117. package/web/_astro/{kanban-definition-UN3LZRKU.D_QCK4et.js → kanban-definition-UN3LZRKU.AaxMKpTk.js} +1 -1
  118. package/web/_astro/{linear.DzrmTtZ0.js → linear.Nv_xOUjP.js} +1 -1
  119. package/web/_astro/{mermaid.core.Da03W3iu.js → mermaid.core.Bc4LqQgX.js} +4 -4
  120. package/web/_astro/{mindmap-definition-RKZ34NQL.6cy-8hR_.js → mindmap-definition-RKZ34NQL.oKUvU_qi.js} +1 -1
  121. package/web/_astro/{pieDiagram-4H26LBE5.C5rS1pdU.js → pieDiagram-4H26LBE5.DQk0oo03.js} +1 -1
  122. package/web/_astro/{quadrantDiagram-W4KKPZXB.w56GZZ6Q.js → quadrantDiagram-W4KKPZXB.BdDjESDa.js} +1 -1
  123. package/web/_astro/{requirementDiagram-4Y6WPE33.CipX3Pwu.js → requirementDiagram-4Y6WPE33.C2u9hUeH.js} +1 -1
  124. package/web/_astro/{sankeyDiagram-5OEKKPKP.C0VVzgJm.js → sankeyDiagram-5OEKKPKP.CDEoiJST.js} +1 -1
  125. package/web/_astro/{sequenceDiagram-3UESZ5HK.BX2dUUbF.js → sequenceDiagram-3UESZ5HK.D_hT_GAT.js} +1 -1
  126. package/web/_astro/{stateDiagram-AJRCARHV.ypCdgODQ.js → stateDiagram-AJRCARHV.DI8RYG0b.js} +1 -1
  127. package/web/_astro/{stateDiagram-v2-BHNVJYJU.In0baEtg.js → stateDiagram-v2-BHNVJYJU.Bkxz4DnP.js} +1 -1
  128. package/web/_astro/{timeline-definition-PNZ67QCA.CJN4Vkvl.js → timeline-definition-PNZ67QCA.DSY-kH3-.js} +1 -1
  129. package/web/_astro/{vennDiagram-CIIHVFJN.Cf8KkIPY.js → vennDiagram-CIIHVFJN.CpaDtuGr.js} +1 -1
  130. package/web/_astro/{wardleyDiagram-YWT4CUSO.DCZBo9xw.js → wardleyDiagram-YWT4CUSO.DujQWvo8.js} +1 -1
  131. package/web/_astro/{xychartDiagram-2RQKCTM6.Cm4v_MiJ.js → xychartDiagram-2RQKCTM6.DcM5Y4b9.js} +1 -1
  132. package/web/index.html +1 -1
  133. package/config/workflows/basic.yaml +0 -146
  134. package/config/workflows/docs-pipeline.yaml +0 -350
  135. package/config/workflows/feature-dev.yaml +0 -288
  136. package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -146
  137. package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
  138. package/web/_astro/BoardApp.CHenHFia.js +0 -1
  139. package/web/_astro/channel.CbDHK5UQ.js +0 -1
@@ -30,16 +30,29 @@
30
30
  * Usage:
31
31
  * bun plugins/sp/scripts/inline-run-setup.ts --run-id <id> --file <definition> [--spur-bin <path>]
32
32
  * bun plugins/sp/scripts/inline-run-setup.ts --fingerprint --task-file <path> [--feature-file <path>] [--spur-bin <path>]
33
+ * bun plugins/sp/scripts/inline-run-setup.ts --action --run-id <id> --node <state> --kind <kind> \
34
+ * --status <done|failed> --ok <true|false> --duration-ms <n> [--spur-bin <path>]
35
+ * bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--spur-bin <path>]
33
36
  *
34
37
  * The `--fingerprint` mode prints the engine's proof-input digest for the given spec files and
35
38
  * creates nothing (task 0862 R5).
36
39
  *
40
+ * The `--action` / `--close` modes are the inline driver's ADR-117 emission boundary (task
41
+ * 0868): `--action` records one completed action boundary as an `action_runs` row through the
42
+ * shared `WorkflowActionTraceWriter`, `--close` marks the run row terminal through the same
43
+ * writer. `--action` is best-effort: a persistence failure is appended to
44
+ * `.spur/run/<run-id>.log` and the script still exits 0 with `{"ok":false}` on stdout, so
45
+ * observation never wedges the run. `--close` is NOT best-effort: the run-row closure is
46
+ * bookkeeping, so a missing run row or a persistence failure exits 1 with a named error.
47
+ * Exit 2 is reserved for usage errors.
48
+ *
37
49
  * Env: SPUR_BIN
38
50
  */
39
51
 
40
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
52
+ import { appendFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
41
53
  import { dirname, join, resolve } from 'node:path';
42
54
  import { fileURLToPath } from 'node:url';
55
+ import { getEnvVar } from '@gobing-ai/ts-utils';
43
56
 
44
57
  /** Outcome document written to `.spur/run/<run-id>-inline-setup.json`. */
45
58
  interface SetupOutcome {
@@ -63,6 +76,13 @@ function usage(): never {
63
76
  console.error(
64
77
  ' bun plugins/sp/scripts/inline-run-setup.ts --fingerprint --task-file <path> [--feature-file <path>] [--spur-bin <path>]',
65
78
  );
79
+ console.error(
80
+ ' bun plugins/sp/scripts/inline-run-setup.ts --action --run-id <id> --node <state> --kind <kind> ' +
81
+ '--status <done|failed> --ok <true|false> --duration-ms <n> [--spur-bin <path>]',
82
+ );
83
+ console.error(
84
+ ' bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--spur-bin <path>]',
85
+ );
66
86
  process.exit(2);
67
87
  }
68
88
 
@@ -177,13 +197,143 @@ async function printFingerprint(taskFile: string, featureFile: string, spurBin:
177
197
  return 0;
178
198
  }
179
199
 
200
+ /** Terminal statuses the inline driver may declare when closing its run row. */
201
+ const CLOSE_STATUSES = new Set(['done', 'failed', 'paused']);
202
+
203
+ /** Finalize statuses — a finish emission is terminal, so only done|failed are valid (0868 #4). */
204
+ const ACTION_STATUSES = new Set(['done', 'failed']);
205
+
206
+ /** Input for the ADR-117 emission modes (`--action` / `--close`). */
207
+ import type { WorkflowActionTraceWriter } from '@gobing-ai/app';
208
+
209
+ interface TraceModeInput {
210
+ readonly runId: string;
211
+ readonly close: boolean;
212
+ readonly node: string;
213
+ readonly kind: string;
214
+ readonly status: string;
215
+ readonly ok: boolean;
216
+ readonly durationMs: number;
217
+ readonly spurBin: string;
218
+ }
219
+
220
+ /**
221
+ * Append one emission-failure line to `.spur/run/<run-id>.log` — the run log the inline
222
+ * driver already owns. Best-effort and synchronous (the process may exit immediately
223
+ * after), and never throws: an unwritable log must not wedge the run (ADR-117 R3).
224
+ */
225
+ function appendTraceFailureLine(runId: string, detail: string): void {
226
+ try {
227
+ const runDir = join(process.cwd(), '.spur', 'run');
228
+ if (!existsSync(runDir)) mkdirSync(runDir, { recursive: true });
229
+ const safeRunId = runId.replace(/[^A-Za-z0-9._-]/g, '_');
230
+ const stamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
231
+ appendFileSync(join(runDir, `${safeRunId}.log`), `[${stamp}] ${detail}\n`);
232
+ } catch {
233
+ // Best-effort (R3): the run continues even when the failure cannot be recorded.
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Emit one trace write through the SHARED `WorkflowActionTraceWriter` (task 0868 R5/R7).
239
+ * `--action` is best-effort: an emission failure is recorded to the run log and reported
240
+ * on stdout as `{"ok":false}`, and the script exits 0 so the run still reaches its declared
241
+ * terminal state (R3/R12). `--close` is bookkeeping, so a missing run row or a persistence
242
+ * failure fails loudly with exit 1 (review findings #1/#4).
243
+ */
244
+ async function runTraceMode(input: TraceModeInput): Promise<number> {
245
+ const operation = input.close ? 'run.close' : 'action.finish';
246
+ const fail = (error: string): number => {
247
+ appendTraceFailureLine(
248
+ input.runId,
249
+ `trace-emission-failed operation=${operation} run=${input.runId}` +
250
+ `${input.node === '' ? '' : ` node=${input.node}`}${input.kind === '' ? '' : ` kind=${input.kind}`}: ${error}`,
251
+ );
252
+ process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error })}\n`);
253
+ // The action boundary is best-effort (exit 0); the run-row closure fails loudly (exit 1).
254
+ return input.close ? 1 : 0;
255
+ };
256
+
257
+ const { entry, repoRoot, chain } = resolveAppEntry(input.spurBin);
258
+ if (entry === null || repoRoot === null) {
259
+ return fail(
260
+ `no monorepo checkout of spur is reachable via ${chain} — the shared trace writer ` +
261
+ 'lives in packages/app (point SPUR_BIN at a repo checkout)',
262
+ );
263
+ }
264
+
265
+ // Compile-time link (0868 finding #5): the writer half is typed by the real packages/app
266
+ // export (type-only import, erased at runtime) so a signature drift breaks THIS file's
267
+ // typecheck instead of hiding behind the hand-declared cast.
268
+ const app = (await import(entry)) as {
269
+ openInlineRunProjectDb: (workdir: string) => Promise<{ adapter: unknown; close: () => void }>;
270
+ createWorkflowActionTraceWriter: (
271
+ db: unknown,
272
+ recordFailure?: (failure: unknown) => void,
273
+ ) => WorkflowActionTraceWriter;
274
+ };
275
+
276
+ let projectDb: { adapter: unknown; close: () => void } | undefined;
277
+ try {
278
+ projectDb = await app.openInlineRunProjectDb(process.cwd());
279
+ const writer = app.createWorkflowActionTraceWriter(projectDb.adapter, (failure: unknown) => {
280
+ const detail = failure as { operation?: string; error?: string };
281
+ appendTraceFailureLine(
282
+ input.runId,
283
+ `trace-emission-failed operation=${detail.operation ?? operation} run=${input.runId}: ${detail.error ?? 'unknown error'}`,
284
+ );
285
+ });
286
+ const result = (
287
+ input.close
288
+ ? await writer.closeRun(input.runId, input.status)
289
+ : await writer.recordAction({
290
+ runId: input.runId,
291
+ node: input.node,
292
+ kind: input.kind,
293
+ status: input.status,
294
+ ok: input.ok,
295
+ durationMs: input.durationMs,
296
+ })
297
+ ) as Record<string, unknown>;
298
+ if (result.ok !== true && result.failure !== undefined) {
299
+ // One stdout shape for emission failures (0868 finding #1): flatten the guard's
300
+ // nested failure object to the same `{ok, runId, error}` the direct paths emit.
301
+ const failure = result.failure as { error?: string };
302
+ return fail(failure.error ?? 'unknown trace emission failure');
303
+ }
304
+ process.stdout.write(`${JSON.stringify({ ...result, runId: input.runId })}\n`);
305
+ return 0;
306
+ } catch (error) {
307
+ if (input.close && (error as { name?: string }).name === 'RunRowNotFoundError') {
308
+ // The run row must exist before --close can mark it terminal (R6); a missing row
309
+ // is a loud correctness failure, not a best-effort emission failure (finding #4).
310
+ const message = error instanceof Error ? error.message : String(error);
311
+ appendTraceFailureLine(input.runId, `trace-close-failed run=${input.runId}: ${message}`);
312
+ process.stdout.write(
313
+ `${JSON.stringify({ ok: false, runId: input.runId, error: message, code: 'RUN_NOT_FOUND' })}\n`,
314
+ );
315
+ return 1;
316
+ }
317
+ return fail(error instanceof Error ? error.message : String(error));
318
+ } finally {
319
+ projectDb?.close();
320
+ }
321
+ }
322
+
180
323
  async function main(): Promise<void> {
181
324
  let runId = '';
182
325
  let file = '';
183
326
  let fingerprint = false;
184
327
  let taskFile = '';
185
328
  let featureFile = '';
186
- let spurBin = process.env.SPUR_BIN ?? '';
329
+ let action = false;
330
+ let close = false;
331
+ let node = '';
332
+ let kind = '';
333
+ let status = '';
334
+ let okRaw = '';
335
+ let durationRaw = '';
336
+ let spurBin = getEnvVar('SPUR_BIN') ?? '';
187
337
  const argv = process.argv.slice(2);
188
338
  for (let i = 0; i < argv.length; i++) {
189
339
  if (argv[i] === '--run-id') runId = argv[++i] ?? '';
@@ -191,6 +341,13 @@ async function main(): Promise<void> {
191
341
  else if (argv[i] === '--fingerprint') fingerprint = true;
192
342
  else if (argv[i] === '--task-file') taskFile = argv[++i] ?? '';
193
343
  else if (argv[i] === '--feature-file') featureFile = argv[++i] ?? '';
344
+ else if (argv[i] === '--action') action = true;
345
+ else if (argv[i] === '--close') close = true;
346
+ else if (argv[i] === '--node') node = argv[++i] ?? '';
347
+ else if (argv[i] === '--kind') kind = argv[++i] ?? '';
348
+ else if (argv[i] === '--status') status = argv[++i] ?? '';
349
+ else if (argv[i] === '--ok') okRaw = argv[++i] ?? '';
350
+ else if (argv[i] === '--duration-ms') durationRaw = argv[++i] ?? '';
194
351
  else if (argv[i] === '--spur-bin') spurBin = argv[++i] ?? spurBin;
195
352
  }
196
353
 
@@ -200,6 +357,42 @@ async function main(): Promise<void> {
200
357
  if (runId !== '' || file !== '' || taskFile.trim() === '') usage();
201
358
  process.exit(await printFingerprint(taskFile, featureFile, spurBin));
202
359
  }
360
+
361
+ // ADR-117 emission modes (task 0868): the inline driver reports one completed action
362
+ // boundary, or closes its run row at the declared terminal state. Both share the run-id
363
+ // filename guard, the app-entry resolution chain and the best-effort failure contract.
364
+ if (action || close) {
365
+ if (action && close) usage();
366
+ if (runId.trim() === '' || status.trim() === '') usage();
367
+ if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
368
+ if (close) {
369
+ if (!CLOSE_STATUSES.has(status)) usage();
370
+ process.exit(
371
+ await runTraceMode({
372
+ runId,
373
+ close: true,
374
+ node: '',
375
+ kind: '',
376
+ status,
377
+ ok: true,
378
+ durationMs: 0,
379
+ spurBin,
380
+ }),
381
+ );
382
+ }
383
+ if (node.trim() === '' || kind.trim() === '') usage();
384
+ if (!ACTION_STATUSES.has(status)) usage();
385
+ // `--ok` and `--duration-ms` are required and exact for the action mode
386
+ // (review finding #2): a miscased `--ok True` or an omitted `--duration-ms`
387
+ // must be a loud usage error, never a silently-defaulted `ok=0` /
388
+ // `duration_ms=0` row.
389
+ if (okRaw !== 'true' && okRaw !== 'false') usage();
390
+ const ok = okRaw === 'true';
391
+ const durationMs = Number(durationRaw);
392
+ if (durationRaw.trim() === '' || !Number.isFinite(durationMs) || durationMs < 0) usage();
393
+ process.exit(await runTraceMode({ runId, close: false, node, kind, status, ok, durationMs, spurBin }));
394
+ }
395
+
203
396
  if (runId.trim() === '' || file.trim() === '') usage();
204
397
  if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
205
398
 
@@ -5,8 +5,15 @@
5
5
  import { spawnSync } from "child_process";
6
6
  import { existsSync, readFileSync, writeFileSync } from "fs";
7
7
  import { join } from "path";
8
+
9
+ // ../ts-libs/packages/utils/dist/env.js
10
+ function getEnvVars() {
11
+ return process.env;
12
+ }
13
+
14
+ // plugins/sp/scripts/pr-reviewing.ts
8
15
  var spawnRunner = (cmd) => {
9
- const proc = spawnSync(cmd[0] ?? "", [...cmd.slice(1)], { encoding: "utf8", env: process.env });
16
+ const proc = spawnSync(cmd[0] ?? "", [...cmd.slice(1)], { encoding: "utf8", env: getEnvVars() });
10
17
  return {
11
18
  code: proc.status ?? 1,
12
19
  stdout: proc.stdout ?? "",
@@ -22,6 +22,7 @@
22
22
  import { spawnSync } from 'node:child_process';
23
23
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
24
24
  import { join } from 'node:path';
25
+ import { getEnvVars } from '@gobing-ai/ts-utils';
25
26
 
26
27
  // ─── Types ──────────────────────────────────────────────────────────────────
27
28
 
@@ -118,7 +119,7 @@ export type CommandRunner = (cmd: readonly string[]) => CmdResult;
118
119
  /** Real execution: resolve the binary from PATH and capture its output. The default runner. */
119
120
  export const spawnRunner: CommandRunner = (cmd) => {
120
121
  // Pass env explicitly so both Node and Bun resolve git/gh against the caller's PATH.
121
- const proc = spawnSync(cmd[0] ?? '', [...cmd.slice(1)], { encoding: 'utf8', env: process.env });
122
+ const proc = spawnSync(cmd[0] ?? '', [...cmd.slice(1)], { encoding: 'utf8', env: getEnvVars() });
122
123
  return {
123
124
  code: proc.status ?? 1,
124
125
  stdout: proc.stdout ?? '',
@@ -17,6 +17,13 @@ import {
17
17
  } from "fs";
18
18
  import { tmpdir } from "os";
19
19
  import { join } from "path";
20
+
21
+ // ../ts-libs/packages/utils/dist/env.js
22
+ function getEnvVars() {
23
+ return process.env;
24
+ }
25
+
26
+ // plugins/sp/scripts/quality-gate.ts
20
27
  var MAX_GATE_ATTEMPTS = 5;
21
28
  var MAX_FINDINGS = 20;
22
29
  var RETRY_DELAY_MS_DEFAULT = 1e4;
@@ -178,7 +185,7 @@ function runQualityGate(mode, env, options = {}) {
178
185
  return { status, attempts: gateAttempt, logFile, findingsFile, statusFile, attemptFile };
179
186
  }
180
187
  var QUALITY_GATE_USAGE = "usage: quality-gate.ts <run|recheck> (env: wbs, qualityGateCmd, gateProbeCmd, proofDigest)";
181
- function main(argv, env = process.env) {
188
+ function main(argv, env = getEnvVars()) {
182
189
  const mode = argv[0];
183
190
  if (mode !== "run" && mode !== "recheck") {
184
191
  process.stderr.write(`${QUALITY_GATE_USAGE}
@@ -41,6 +41,7 @@ import {
41
41
  } from 'node:fs';
42
42
  import { tmpdir } from 'node:os';
43
43
  import { join } from 'node:path';
44
+ import { getEnvVars } from '@gobing-ai/ts-utils';
44
45
 
45
46
  export const MAX_GATE_ATTEMPTS = 5;
46
47
  export const MAX_FINDINGS = 20;
@@ -268,7 +269,7 @@ export function runQualityGate(
268
269
  export const QUALITY_GATE_USAGE =
269
270
  'usage: quality-gate.ts <run|recheck> (env: wbs, qualityGateCmd, gateProbeCmd, proofDigest)';
270
271
 
271
- export function main(argv: string[], env: QualityGateEnv = process.env): number {
272
+ export function main(argv: string[], env: QualityGateEnv = getEnvVars()): number {
272
273
  const mode = argv[0];
273
274
  if (mode !== 'run' && mode !== 'recheck') {
274
275
  process.stderr.write(`${QUALITY_GATE_USAGE}\n`);
@@ -179,9 +179,6 @@ export function parseInvocation(spanRaw: string): Parsed | null {
179
179
  }
180
180
  }
181
181
  if (nouns.length === 0 && verbs.length === 0 && flags.length === 0) return null;
182
- if (process.env.DEBUG_SPANS === '1') {
183
- process.stderr.write(`SPAN ${JSON.stringify({ spanRaw, nouns, verbs, flags })}\n`);
184
- }
185
182
  return { nouns, verbs, flags };
186
183
  }
187
184
 
@@ -638,7 +635,7 @@ const JSON_PROBES: string[][] = [
638
635
  ['agent', 'list', '--json'],
639
636
  ['agent', 'doctor', 'pi', '--json'],
640
637
  ['workflow', 'list', '--json'],
641
- ['workflow', 'validate', 'basic.yaml', '--json'],
638
+ ['workflow', 'validate', 'task-pipeline.yaml', '--json'],
642
639
  ['status', '--json'],
643
640
  ['projects', 'list', '--json'],
644
641
  ];
@@ -37,6 +37,7 @@ import { execFileSync } from 'node:child_process';
37
37
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
38
38
  import { join } from 'node:path';
39
39
  import { fileURLToPath } from 'node:url';
40
+ import { getEnvVar } from '@gobing-ai/ts-utils';
40
41
 
41
42
  /** Exact task-content declaration that activates the live-evidence gate (0726 R2). */
42
43
  const DECLARATION_PREFIX = 'evidence-channel:';
@@ -58,7 +59,7 @@ function usage(): never {
58
59
  }
59
60
 
60
61
  function defaultSpurBin(): string {
61
- if (process.env.SPUR_BIN) return process.env.SPUR_BIN;
62
+ if (getEnvVar('SPUR_BIN')) return getEnvVar('SPUR_BIN');
62
63
  const local = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
63
64
  if (existsSync(local)) return `bun ${local}`;
64
65
  return 'spur';
@@ -19,13 +19,14 @@
19
19
  * bun plugins/sp/scripts/task-size-precheck.ts <wbs> [--spur-bin <path>]
20
20
  * [--max-reqs <n>] [--max-plan-items <n>]
21
21
  *
22
- * Env: SPUR_BIN, MAX_IMPLEMENT_REQS, MAX_IMPLEMENT_PLAN_ITEMS
22
+ * Env: SPUR_BIN (flags own the size ceilings — env overrides removed, task 0902)
23
23
  */
24
24
 
25
25
  import { execFileSync } from 'node:child_process';
26
26
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
27
27
  import { join } from 'node:path';
28
28
  import { fileURLToPath } from 'node:url';
29
+ import { getEnvVar } from '@gobing-ai/ts-utils';
29
30
 
30
31
  // ─── Counting (sync with packages/app/src/services/task-size-precheck.ts) ────
31
32
 
@@ -88,7 +89,7 @@ function usage(): never {
88
89
  * keeps ad-hoc invocations from silently hitting a stale PATH install.
89
90
  */
90
91
  function defaultSpurBin(): string {
91
- if (process.env.SPUR_BIN) return process.env.SPUR_BIN;
92
+ if (getEnvVar('SPUR_BIN')) return getEnvVar('SPUR_BIN');
92
93
  // scripts/ -> plugins/sp/ -> <repo>/apps/cli/src/index.ts (fileURLToPath — raw pathname breaks
93
94
  // on %-encoded paths, e.g. spaces in the checkout directory)
94
95
  const local = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
@@ -107,8 +108,9 @@ function parseArgs(argv: string[]): {
107
108
  // Doubled deterministic ceiling (0723 operator decision): 10 R-items / 16
108
109
  // Plan items — keep in sync with DEFAULT_TASK_SIZE_LIMITS in
109
110
  // packages/app/src/services/task-size-precheck.ts (asserted by test).
110
- let maxReqs = Number(process.env.MAX_IMPLEMENT_REQS) || 10;
111
- let maxPlanItems = Number(process.env.MAX_IMPLEMENT_PLAN_ITEMS) || 16;
111
+ // Overridable per invocation via --max-reqs / --max-plan-items (no env fallback).
112
+ let maxReqs = 10;
113
+ let maxPlanItems = 16;
112
114
 
113
115
  let i = 0;
114
116
  while (i < argv.length) {
@@ -32,6 +32,7 @@
32
32
  import { execFileSync } from 'node:child_process';
33
33
  import { existsSync, readFileSync } from 'node:fs';
34
34
  import { fileURLToPath } from 'node:url';
35
+ import { getEnvVar } from '@gobing-ai/ts-utils';
35
36
 
36
37
  // ─── CLI (same spur-bin chain as task-evidence-precheck.ts) ─────────────────
37
38
 
@@ -41,7 +42,7 @@ function usage(): never {
41
42
  }
42
43
 
43
44
  function defaultSpurBin(): string {
44
- if (process.env.SPUR_BIN) return process.env.SPUR_BIN;
45
+ if (getEnvVar('SPUR_BIN')) return getEnvVar('SPUR_BIN');
45
46
  const local = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
46
47
  if (existsSync(local)) return `bun ${local}`;
47
48
  return 'spur';
@@ -5,6 +5,14 @@
5
5
  import { spawnSync } from "child_process";
6
6
  import { existsSync } from "fs";
7
7
  import { fileURLToPath } from "url";
8
+
9
+ // ../ts-libs/packages/utils/dist/env.js
10
+ function getEnvVar(name, fallback) {
11
+ const raw = process.env[name];
12
+ return raw === undefined ? fallback : raw;
13
+ }
14
+
15
+ // plugins/sp/scripts/workflow-step-profile.ts
8
16
  var DEFAULT_LAST = 20;
9
17
  var DEFAULT_WINDOW_SEC = 300;
10
18
  function nearestRankP50(values) {
@@ -168,8 +176,8 @@ function formatStepProfileHuman(profile) {
168
176
  `;
169
177
  }
170
178
  function defaultSpurBin() {
171
- if (process.env.SPUR_BIN)
172
- return process.env.SPUR_BIN;
179
+ if (getEnvVar("SPUR_BIN"))
180
+ return getEnvVar("SPUR_BIN");
173
181
  const local = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
174
182
  if (existsSync(local))
175
183
  return `bun ${local}`;
@@ -25,6 +25,7 @@
25
25
  import { spawnSync } from 'node:child_process';
26
26
  import { existsSync } from 'node:fs';
27
27
  import { fileURLToPath } from 'node:url';
28
+ import { getEnvVar } from '@gobing-ai/ts-utils';
28
29
 
29
30
  // ── Input shapes (a subset of the live `spur workflow trace --json` payload) ─────────────
30
31
 
@@ -321,7 +322,7 @@ export interface StepProfileCliArgs {
321
322
  * --spur-bin > SPUR_BIN > monorepo-local CLI entry > PATH `spur`.
322
323
  */
323
324
  export function defaultSpurBin(): string {
324
- if (process.env.SPUR_BIN) return process.env.SPUR_BIN;
325
+ if (getEnvVar('SPUR_BIN')) return getEnvVar('SPUR_BIN');
325
326
  const local = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
326
327
  if (existsSync(local)) return `bun ${local}`;
327
328
  return 'spur';
@@ -5,6 +5,13 @@
5
5
  import { spawnSync } from "child_process";
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
7
7
  import { join } from "path";
8
+
9
+ // ../ts-libs/packages/utils/dist/env.js
10
+ function getEnvVars() {
11
+ return process.env;
12
+ }
13
+
14
+ // plugins/sp/scripts/wrapup-steps.ts
8
15
  function jqPick(...values) {
9
16
  for (const value of values) {
10
17
  if (value !== null && value !== undefined && value !== false)
@@ -319,7 +326,7 @@ function runFeatureTransition(env, options = {}) {
319
326
  return { status: syncStatus, statusFile: relStatusFile, exitCode: 0 };
320
327
  }
321
328
  var WRAPUP_STEPS_USAGE = "usage: wrapup-steps.ts <resolve|metrics|feature-transition> (env: __runId, tasks, feature, featureGateCmd, spurBin)";
322
- function main(argv, env = process.env, options = {}) {
329
+ function main(argv, env = getEnvVars(), options = {}) {
323
330
  const sub = argv[0];
324
331
  if (sub === "resolve")
325
332
  return resolveTasks(env, options).exitCode;
@@ -31,6 +31,7 @@
31
31
  import { spawnSync } from 'node:child_process';
32
32
  import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
33
33
  import { join } from 'node:path';
34
+ import { getEnvVars } from '@gobing-ai/ts-utils';
34
35
 
35
36
  export interface WrapupStepsEnv {
36
37
  __runId?: string;
@@ -449,7 +450,7 @@ export function runFeatureTransition(env: WrapupStepsEnv, options: WrapupStepsOp
449
450
  export const WRAPUP_STEPS_USAGE =
450
451
  'usage: wrapup-steps.ts <resolve|metrics|feature-transition> (env: __runId, tasks, feature, featureGateCmd, spurBin)';
451
452
 
452
- export function main(argv: string[], env: WrapupStepsEnv = process.env, options: WrapupStepsOptions = {}): number {
453
+ export function main(argv: string[], env: WrapupStepsEnv = getEnvVars(), options: WrapupStepsOptions = {}): number {
453
454
  const sub = argv[0];
454
455
  if (sub === 'resolve') return resolveTasks(env, options).exitCode;
455
456
  if (sub === 'metrics') {
@@ -197,6 +197,10 @@ When brainstorm runs under `idea-pipeline` discovery, it MUST also emit a filled
197
197
  [`spur-dev/references/idea-evaluation.md`](../spur-dev/references/idea-evaluation.md):
198
198
 
199
199
  - Enhanced idea statement (sidecar — does **not** overwrite the operator's original idea text)
200
+ - **Requirement inventory** — mandatory `## Requirement inventory` section: numbered `I<n>` items
201
+ quoting or paraphrasing the source lines from the run's verbatim idea artifact
202
+ (`.spur/run/<run-id>-idea-input.md`, 0887 R1/R3), with `[unclear: ...]` markers where the ask is
203
+ ambiguous and `[deferred: <reason>]` markers for explicit out-of-scope items
200
204
  - Urgency and necessity scores (0–5) with one-line rationales
201
205
  - Premises, pros, cons, better alternatives (if any)
202
206
  - Recommendation (`proceed` | `reshape` | `drop`) + stakes