@gobing-ai/spur 0.4.8 → 0.4.9

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 (145) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +2 -2
  3. package/config/rules/boundary/config-loading-ownership.yaml +1 -0
  4. package/config/rules/boundary/sp-plugin-standalone.yaml +1 -1
  5. package/config/rules/boundary/sp-script-placement.yaml +1 -1
  6. package/config/rules/docs/sourced-api-claims.yaml +45 -7
  7. package/config/rules/strict/runtime-boundaries.yaml +40 -1
  8. package/config/rules/structure/protected-files.yaml +1 -1
  9. package/config/rules/surface/check-cli-surface.yaml +33 -0
  10. package/config/rules/typescript/no-debugger.yaml +3 -0
  11. package/config/workflow-candidates.json +2 -2
  12. package/config/workflows/feature-verification.yaml +6 -11
  13. package/config/workflows/history-anatomy.yaml +14 -28
  14. package/config/workflows/idea-pipeline.yaml +6 -19
  15. package/config/workflows/pr-review.yaml +9 -16
  16. package/config/workflows/task-pipeline.yaml +16 -23
  17. package/config/workflows/wrapup-pipeline.yaml +21 -63
  18. package/package.json +3 -3
  19. package/plugins/sp/README.md +70 -37
  20. package/plugins/sp/agents/super-planner.md +5 -5
  21. package/plugins/sp/agents/super-reviewer.md +2 -2
  22. package/plugins/sp/commands/dev-changelog.md +1 -1
  23. package/plugins/sp/commands/dev-daily.md +1 -1
  24. package/plugins/sp/commands/dev-pr-review.md +1 -1
  25. package/plugins/sp/commands/dev-review-session.md +1 -1
  26. package/plugins/sp/commands/dev-runall.md +4 -4
  27. package/plugins/sp/commands/dev-verify.md +4 -6
  28. package/plugins/sp/commands/dev-verifyall.md +4 -5
  29. package/plugins/sp/hooks/hooks.json +11 -1
  30. package/plugins/sp/lib/context-post-tool.ts +1 -1
  31. package/plugins/sp/lib/context-session-start.ts +1 -1
  32. package/plugins/sp/lib/context-session-stop.ts +1 -1
  33. package/plugins/sp/lib/destructive-policy.ts +65 -1
  34. package/plugins/sp/plugin.json +2 -5
  35. package/plugins/sp/{hooks → scripts/hooks}/agent-lifecycle.ts +1 -1
  36. package/plugins/sp/scripts/hooks/careful-guard.ts +94 -0
  37. package/plugins/sp/{hooks → scripts/hooks}/context-post-tool.ts +2 -2
  38. package/plugins/sp/{hooks → scripts/hooks}/context-session-start.ts +1 -1
  39. package/plugins/sp/{hooks → scripts/hooks}/context-session-stop.ts +1 -1
  40. package/plugins/sp/{hooks → scripts/hooks}/fleet-guest-stop.ts +6 -4
  41. package/plugins/sp/{hooks → scripts/hooks}/task-write-guard.ts +38 -21
  42. package/plugins/sp/skills/code-implementation/SKILL.md +1 -1
  43. package/plugins/sp/skills/code-verification/SKILL.md +8 -8
  44. package/plugins/sp/skills/code-verification/references/secu-review.md +4 -3
  45. package/plugins/sp/skills/code-verification/references/verdict-schema.md +9 -1
  46. package/plugins/sp/skills/daily-summary/SKILL.md +4 -5
  47. package/plugins/sp/skills/dogfood-testing/SKILL.md +9 -9
  48. package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
  49. package/plugins/sp/skills/history-anatomy/references/modes.md +2 -2
  50. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  51. package/plugins/sp/skills/parallel-execution/SKILL.md +1 -1
  52. package/plugins/sp/skills/pr-reviewing/SKILL.md +14 -15
  53. package/plugins/sp/skills/session-review/SKILL.md +3 -3
  54. package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
  55. package/plugins/sp/skills/spur-check/SKILL.md +14 -13
  56. package/plugins/sp/skills/spur-cli/SKILL.md +1 -1
  57. package/plugins/sp/skills/spur-cli/references/agent.md +12 -0
  58. package/plugins/sp/skills/spur-cli/references/builder.md +27 -6
  59. package/plugins/sp/skills/spur-cli/references/features.md +6 -1
  60. package/plugins/sp/skills/spur-cli/references/history.md +3 -0
  61. package/plugins/sp/skills/spur-cli/references/self.md +5 -3
  62. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -0
  63. package/plugins/sp/skills/spur-cli/references/tasks.md +13 -3
  64. package/plugins/sp/skills/spur-cli/references/workflows.md +36 -1
  65. package/plugins/sp/skills/spur-dev/SKILL.md +1 -1
  66. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +1 -1
  67. package/plugins/sp/skills/spur-dev/references/dev-operations.md +12 -12
  68. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +10 -11
  69. package/plugins/sp/skills/spur-dev/references/execution-batch.md +16 -17
  70. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +1 -1
  71. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +8 -11
  72. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +6 -8
  73. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -1
  74. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +33 -40
  75. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +24 -24
  76. package/plugins/sp/skills/spur-doctor/SKILL.md +5 -4
  77. package/spur.js +41901 -30598
  78. package/config/plugin-scripts.json +0 -116
  79. package/config/script-placement-baseline.json +0 -33
  80. package/plugins/sp/hooks/careful-guard.ts +0 -74
  81. package/plugins/sp/hooks/pi/guard-extension.ts +0 -304
  82. package/plugins/sp/lib/dogfood-pipeline.ts +0 -265
  83. package/plugins/sp/lib/dogfood-report.ts +0 -278
  84. package/plugins/sp/lib/history-anatomy.generated.d.mts +0 -112
  85. package/plugins/sp/lib/history-anatomy.generated.mjs +0 -691
  86. package/plugins/sp/lib/idea-handoff.generated.d.mts +0 -17
  87. package/plugins/sp/lib/idea-handoff.generated.mjs +0 -1446
  88. package/plugins/sp/lib/inline-run-app.ts +0 -68
  89. package/plugins/sp/lib/inline-run.generated.d.mts +0 -37
  90. package/plugins/sp/lib/inline-run.generated.mjs +0 -3103
  91. package/plugins/sp/lib/plan-projection.generated.d.mts +0 -197
  92. package/plugins/sp/lib/plan-projection.generated.mjs +0 -982
  93. package/plugins/sp/lib/quality-gate.generated.d.mts +0 -139
  94. package/plugins/sp/lib/quality-gate.generated.mjs +0 -30510
  95. package/plugins/sp/lib/residual-scan.generated.d.mts +0 -93
  96. package/plugins/sp/lib/residual-scan.generated.mjs +0 -383
  97. package/plugins/sp/lib/run-summary-core.ts +0 -225
  98. package/plugins/sp/lib/spur-bin.ts +0 -36
  99. package/plugins/sp/lib/step-profile.generated.d.mts +0 -71
  100. package/plugins/sp/lib/step-profile.generated.mjs +0 -174
  101. package/plugins/sp/lib/transcript.ts +0 -112
  102. package/plugins/sp/scripts/batch-plan.mjs +0 -1096
  103. package/plugins/sp/scripts/batch-plan.ts +0 -145
  104. package/plugins/sp/scripts/batch-preflight.mjs +0 -423
  105. package/plugins/sp/scripts/batch-preflight.ts +0 -270
  106. package/plugins/sp/scripts/commit-guard.mjs +0 -226
  107. package/plugins/sp/scripts/commit-guard.ts +0 -248
  108. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +0 -626
  109. package/plugins/sp/scripts/daily-summary/daily-summary.ts +0 -855
  110. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +0 -211
  111. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +0 -110
  112. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +0 -282
  113. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +0 -98
  114. package/plugins/sp/scripts/feature-verification-steps.mjs +0 -82
  115. package/plugins/sp/scripts/feature-verification-steps.ts +0 -164
  116. package/plugins/sp/scripts/history-anatomy-cache.mjs +0 -931
  117. package/plugins/sp/scripts/history-anatomy-cache.ts +0 -247
  118. package/plugins/sp/scripts/idea-handoff.mjs +0 -32
  119. package/plugins/sp/scripts/idea-handoff.ts +0 -47
  120. package/plugins/sp/scripts/inline-run-dispatch.mjs +0 -131
  121. package/plugins/sp/scripts/inline-run-dispatch.ts +0 -115
  122. package/plugins/sp/scripts/inline-run-setup.mjs +0 -746
  123. package/plugins/sp/scripts/inline-run-setup.ts +0 -198
  124. package/plugins/sp/scripts/persist-out-check.mjs +0 -217
  125. package/plugins/sp/scripts/persist-out-check.ts +0 -249
  126. package/plugins/sp/scripts/pr-reviewing.mjs +0 -712
  127. package/plugins/sp/scripts/pr-reviewing.ts +0 -952
  128. package/plugins/sp/scripts/quality-gate.mjs +0 -30491
  129. package/plugins/sp/scripts/quality-gate.ts +0 -80
  130. package/plugins/sp/scripts/residual-scan.mjs +0 -634
  131. package/plugins/sp/scripts/residual-scan.ts +0 -248
  132. package/plugins/sp/scripts/run-summary.mjs +0 -758
  133. package/plugins/sp/scripts/run-summary.ts +0 -107
  134. package/plugins/sp/scripts/script-root.mjs +0 -129
  135. package/plugins/sp/scripts/script-root.ts +0 -199
  136. package/plugins/sp/scripts/session-timeline.mjs +0 -359
  137. package/plugins/sp/scripts/session-timeline.ts +0 -190
  138. package/plugins/sp/scripts/task-diffstat.mjs +0 -181
  139. package/plugins/sp/scripts/task-diffstat.ts +0 -219
  140. package/plugins/sp/scripts/workflow-step-profile.mjs +0 -338
  141. package/plugins/sp/scripts/workflow-step-profile.ts +0 -165
  142. package/plugins/sp/scripts/wrapup-drift-probe.mjs +0 -190
  143. package/plugins/sp/scripts/wrapup-drift-probe.ts +0 -261
  144. package/plugins/sp/scripts/wrapup-steps.mjs +0 -627
  145. package/plugins/sp/scripts/wrapup-steps.ts +0 -891
@@ -1,247 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * history-anatomy-cache — CLI glue (task 1005 R4, ADR-130 lib rule) for the history-anatomy
4
- * report cache (feature I8, HA-S1 0659 / ADR-079): eight verbs, byte-identical argv/stdout/exit.
5
- * Logic lives in packages/app/src/services/history-anatomy.ts, reached through the generated
6
- * standalone bundle `plugins/sp/lib/history-anatomy.generated.mjs`; twin runs under bare `node`
7
- * (`bun run build:scripts`).
8
- */
9
- import { spawnSync } from 'node:child_process';
10
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
11
- import * as core from '../lib/history-anatomy.generated.mjs';
12
-
13
- export * from '../lib/history-anatomy.generated.mjs';
14
-
15
- const VALID_COMMANDS = 'digest, check, paths, assert-clean, probe, stamp, refresh, publish';
16
- const PROBE_USAGE =
17
- '<script> probe --artifact <a.json> --target <report.md> [--baseline <b.json>] [--mode daily|ad-hoc] ' +
18
- '[--date <YYYY-MM-DD>] [--recompute true] [--out <prov.json>] [--skill-dir <d>] [--contract <f>] [--workflow <f>] [--helper <f>]';
19
-
20
- /** `--key value` / `--flag` → record. Bare flags become `"true"` so `--recompute` needs no value. */
21
- function parseFlags(args: string[]): Record<string, string | undefined> {
22
- const out: Record<string, string | undefined> = {};
23
- for (let i = 0; i < args.length; i++) {
24
- const a = args[i] ?? '';
25
- if (!a.startsWith('--')) continue;
26
- const key = a.slice(2);
27
- const next = args[i + 1];
28
- if (next === undefined || next.startsWith('--')) {
29
- out[key] = 'true';
30
- } else {
31
- out[key] = next;
32
- i++;
33
- }
34
- }
35
- return out;
36
- }
37
-
38
- /**
39
- * Run the CLI with captured stdout/stderr (data, not process side-effects) so unit tests invoke
40
- * it in-process without leaking into the test runner's own output.
41
- */
42
- export function runCacheCli(argv: string[]): core.CacheCliResult {
43
- const [cmd, a, b] = argv;
44
- switch (cmd) {
45
- case 'digest': {
46
- if (a === undefined) {
47
- return { exitCode: 1, stdout: '', stderr: 'usage: <script> digest <artifact.json>\n' };
48
- }
49
- let artifact: unknown;
50
- try {
51
- artifact = JSON.parse(readFileSync(a, 'utf8'));
52
- } catch {
53
- return { exitCode: 1, stdout: '', stderr: `could not parse artifact at ${a}\n` };
54
- }
55
- const digest = core.semanticArtifactDigest(artifact);
56
- return { exitCode: 0, stdout: `${digest}\n`, stderr: '' };
57
- }
58
- case 'check': {
59
- if (a === undefined) {
60
- return { exitCode: 1, stdout: '', stderr: 'usage: <script> check <report.md>\n' };
61
- }
62
- const result = core.checkReportStructure(readFileSync(a, 'utf8'));
63
- const stdout = `${result.ok ? 'PASS' : 'FAIL'}\n${result.problems.map((p) => `- ${p}\n`).join('')}`;
64
- return { exitCode: result.ok ? 0 : 1, stdout, stderr: '' };
65
- }
66
- case 'publish': {
67
- if (a === undefined || b === undefined) {
68
- return { exitCode: 1, stdout: '', stderr: 'usage: <script> publish <candidate.md> <target.md>\n' };
69
- }
70
- core.publishAtomically(a, b);
71
- return { exitCode: 0, stdout: '', stderr: '' };
72
- }
73
- case 'assert-clean': {
74
- // 0676 R3: fingerprint diff around a model stage. `--baseline` holds the
75
- // pre-stage `git status --porcelain`; every path present now but absent there
76
- // must be one of the stage's declared outputs, else exit 1 naming each.
77
- const f = parseFlags(argv.slice(1));
78
- if (f.baseline === undefined) {
79
- return {
80
- exitCode: 1,
81
- stdout: '',
82
- stderr: 'usage: <script> assert-clean --baseline <porcelain.txt> [--expect <path>]...\n',
83
- };
84
- }
85
- const expects = new Set<string>();
86
- for (const arg of argv.slice(1)) {
87
- if (arg.startsWith('--expect=')) expects.add(arg.slice('--expect='.length));
88
- }
89
- let now: string;
90
- try {
91
- now =
92
- spawnSync('git', ['status', '--porcelain'], {
93
- encoding: 'utf8',
94
- ...(f.cwd !== undefined ? { cwd: f.cwd } : {}),
95
- }).stdout ?? '';
96
- } catch {
97
- return { exitCode: 0, stdout: '', stderr: 'assert-clean: git unavailable; skipped\n' };
98
- }
99
- const undeclared = core.diffPorcelain(readFileSync(f.baseline, 'utf8'), now, expects);
100
- if (undeclared.length > 0) {
101
- return {
102
- exitCode: 1,
103
- stdout: '',
104
- stderr: undeclared.map((p) => `undeclared write: ${p}\n`).join(''),
105
- };
106
- }
107
- return { exitCode: 0, stdout: 'clean\n', stderr: '' };
108
- }
109
- case 'paths': {
110
- const f = parseFlags(argv.slice(1));
111
- if (f.helper === undefined || f.out === undefined) {
112
- return {
113
- exitCode: 1,
114
- stdout: '',
115
- stderr: 'usage: <script> paths --helper <p> --out <env> [--report-dir <d>] [--date <d>] [--output <p>] [--mode <m>] [--since <s>] [--until <u>] [--focus <text>] [--recompute true|false] [--run-id <id>]\n',
116
- };
117
- }
118
- const v = core.validateSelector({
119
- mode: f.mode,
120
- date: f.date,
121
- since: f.since,
122
- until: f.until,
123
- focus: f.focus,
124
- recompute: f.recompute,
125
- output: f.output,
126
- });
127
- if (!v.ok) return { exitCode: 1, stdout: '', stderr: `${v.errors.join('\n')}\n` };
128
- const env = core.resolvePaths({
129
- helper: f.helper,
130
- reportDir: f['report-dir'] ?? 'docs/report',
131
- date: f.date,
132
- output: f.output,
133
- mode: v.mode,
134
- since: f.since,
135
- until: f.until,
136
- });
137
- writeFileSync(f.out, env);
138
- // 0920: run-scoped selector observation artifact — written only after successful
139
- // validation; historical selector files have incompatible shapes and are not consumed.
140
- if (f['run-id'] !== undefined && f['run-id'] !== '') {
141
- mkdirSync('.spur/run', { recursive: true });
142
- const envVars = Object.fromEntries(
143
- env
144
- .trim()
145
- .split('\n')
146
- .map((l) => [l.slice(0, l.indexOf('=')), l.slice(l.indexOf('=') + 1)]),
147
- );
148
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC';
149
- writeFileSync(
150
- `.spur/run/${f['run-id']}-selector.json`,
151
- `${JSON.stringify(
152
- {
153
- mode: v.mode,
154
- date: envVars.HA_DATE ?? null,
155
- focus: v.focus,
156
- since: envVars.HA_SINCE ?? null,
157
- until: envVars.HA_UNTIL ?? null,
158
- timezone: tz,
159
- },
160
- null,
161
- 2,
162
- )}\n`,
163
- );
164
- }
165
- return { exitCode: 0, stdout: '', stderr: '' };
166
- }
167
- case 'probe': {
168
- const f = parseFlags(argv.slice(1));
169
- if (f.artifact === undefined || f.target === undefined) {
170
- return { exitCode: 1, stdout: '', stderr: `usage: ${PROBE_USAGE}\n` };
171
- }
172
- let result: ReturnType<typeof core.probe>;
173
- try {
174
- result = core.probe({
175
- artifact: f.artifact,
176
- target: f.target,
177
- baseline: f.baseline,
178
- mode: f.mode === 'ad-hoc' ? 'ad-hoc' : 'daily',
179
- date: f.date,
180
- recompute: f.recompute === 'true',
181
- executor: f.executor,
182
- model: f.model,
183
- skillDir: f['skill-dir'],
184
- contractFile: f.contract,
185
- workflowFile: f.workflow,
186
- helperFile: f.helper,
187
- contractVersion: f['contract-version'],
188
- runId: f['run-id'],
189
- spurVersion: f['spur-version'],
190
- });
191
- } catch {
192
- return { exitCode: 1, stdout: '', stderr: `could not read artifact at ${f.artifact}\n` };
193
- }
194
- if (f.out !== undefined) writeFileSync(f.out, `${JSON.stringify(result.current, null, 2)}\n`);
195
- const reasons = result.decision.reasons.map((r) => `- ${r}\n`).join('');
196
- return { exitCode: 0, stdout: `${result.decision.disposition}\n${reasons}`, stderr: '' };
197
- }
198
- case 'stamp': {
199
- const f = parseFlags(argv.slice(1));
200
- if (f.candidate === undefined || f.provenance === undefined || f.out === undefined) {
201
- return {
202
- exitCode: 1,
203
- stdout: '',
204
- stderr: 'usage: <script> stamp --candidate <c.md> --provenance <p.json> --out <o.md>\n',
205
- };
206
- }
207
- try {
208
- const p = JSON.parse(readFileSync(f.provenance, 'utf8')) as core.CacheProvenance;
209
- writeFileSync(f.out, `${core.stampReport(readFileSync(f.candidate, 'utf8'), p)}\n`);
210
- } catch {
211
- return { exitCode: 1, stdout: '', stderr: 'stamp: could not read candidate or provenance\n' };
212
- }
213
- return { exitCode: 0, stdout: '', stderr: '' };
214
- }
215
- case 'refresh': {
216
- const f = parseFlags(argv.slice(1));
217
- if (f.report === undefined || f.out === undefined) {
218
- return {
219
- exitCode: 1,
220
- stdout: '',
221
- stderr: 'usage: <script> refresh --report <published.md> --out <o.md> [--disposition hit]\n',
222
- };
223
- }
224
- try {
225
- const disposition = (f.disposition ?? 'hit') as core.CacheDisposition;
226
- const refreshed = core.refreshReport(
227
- readFileSync(f.report, 'utf8'),
228
- f['validated-at'] ?? new Date().toISOString(),
229
- disposition,
230
- );
231
- writeFileSync(f.out, refreshed.endsWith('\n') ? refreshed : `${refreshed}\n`);
232
- } catch {
233
- return { exitCode: 1, stdout: '', stderr: `refresh: could not read report at ${f.report}\n` };
234
- }
235
- return { exitCode: 0, stdout: '', stderr: '' };
236
- }
237
- default:
238
- return { exitCode: 1, stdout: '', stderr: `valid commands: ${VALID_COMMANDS}\n` };
239
- }
240
- }
241
-
242
- if (import.meta.main) {
243
- const { exitCode, stdout, stderr } = runCacheCli(process.argv.slice(2));
244
- process.stdout.write(stdout);
245
- process.stderr.write(stderr);
246
- process.exitCode = exitCode;
247
- }
@@ -1,32 +0,0 @@
1
- #!/usr/bin/env node
2
- // @bun
3
-
4
- // plugins/sp/lib/env.ts
5
- function getEnvVars() {
6
- return process.env;
7
- }
8
-
9
- // plugins/sp/scripts/idea-handoff.ts
10
- var libUrl = new URL("../lib/idea-handoff.generated.mjs", import.meta.url).href;
11
- async function loadHandoffLib() {
12
- return await import(libUrl);
13
- }
14
- var IDEA_HANDOFF_USAGE = "usage: idea-handoff.ts (env: __runId, featureId, optional spurBin) \u2014 no subcommands";
15
- async function main(argv, env = getEnvVars()) {
16
- if (argv.length > 0) {
17
- process.stderr.write(`${IDEA_HANDOFF_USAGE}
18
- `);
19
- return 2;
20
- }
21
- const { runIdeaHandoffCli } = await loadHandoffLib();
22
- const outcome = await runIdeaHandoffCli(env);
23
- return outcome.exitCode;
24
- }
25
- {
26
- process.exit(await main(process.argv.slice(2)));
27
- }
28
- export {
29
- IDEA_HANDOFF_USAGE,
30
- loadHandoffLib,
31
- main
32
- };
@@ -1,47 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import { getEnvVars } from '../lib/env';
4
-
5
- /**
6
- * idea-handoff — portable entrypoint for the idea-pipeline finalization (task 0824, feature I21).
7
- *
8
- * ADR-065 standard plugin script: the workflow wrapper prefers the monorepo writer
9
- * (`bun packages/app/src/workflow/idea-handoff-cli.ts`) and falls back to this script's
10
- * registered `.mjs` twin under bare `node`, so a seeded project with no monorepo checkout
11
- * still finalizes the handoff (run-scoped report + task-deps ordering + roster refresh) and
12
- * fails closed (exit 1) when neither writer resolves.
13
- *
14
- * The behavior lives in the generated bundle `../lib/idea-handoff.generated.mjs`, produced by
15
- * `bun run build:plugin-lib` from `packages/app/src/workflow/idea-handoff-cli.ts` — the same
16
- * source the monorepo branch executes, so both branches share one contract (0604/0824).
17
- * Declarations: `../lib/idea-handoff.generated.d.mts`. The bundle is imported by computed URL
18
- * on purpose: `superskill script convert` must NOT inline it (its tree carries one
19
- * `Bun.spawn` site in a ts-runtime streaming path this workflow never exercises), so the
20
- * twin stays a thin node-runnable loader over the committed runtime asset.
21
- *
22
- * Environment: `__runId`, `featureId`, optional `spurBin`. Missing required vars exit 1
23
- * (mis-invocation is failed-closed, never a silent skip).
24
- */
25
-
26
- const libUrl = new URL('../lib/idea-handoff.generated.mjs', import.meta.url).href;
27
-
28
- export async function loadHandoffLib(): Promise<typeof import('../lib/idea-handoff.generated.mjs')> {
29
- return (await import(libUrl)) as typeof import('../lib/idea-handoff.generated.mjs');
30
- }
31
-
32
- export const IDEA_HANDOFF_USAGE =
33
- 'usage: idea-handoff.ts (env: __runId, featureId, optional spurBin) — no subcommands';
34
-
35
- export async function main(argv: string[], env: NodeJS.ProcessEnv = getEnvVars()): Promise<number> {
36
- if (argv.length > 0) {
37
- process.stderr.write(`${IDEA_HANDOFF_USAGE}\n`);
38
- return 2;
39
- }
40
- const { runIdeaHandoffCli } = await loadHandoffLib();
41
- const outcome = await runIdeaHandoffCli(env as { __runId?: string; featureId?: string; spurBin?: string });
42
- return outcome.exitCode;
43
- }
44
-
45
- if (import.meta.main) {
46
- process.exit(await main(process.argv.slice(2)));
47
- }
@@ -1,131 +0,0 @@
1
- #!/usr/bin/env node
2
- // @bun
3
-
4
- // plugins/sp/scripts/inline-run-dispatch.ts
5
- import { spawnSync } from "child_process";
6
- import { fileURLToPath as fileURLToPath2 } from "url";
7
-
8
- // plugins/sp/lib/env.ts
9
- function getEnvVar(name, fallback) {
10
- const raw = process.env[name];
11
- return raw === undefined ? fallback : raw;
12
- }
13
-
14
- // plugins/sp/lib/inline-run-app.ts
15
- import { existsSync } from "node:fs";
16
- import { dirname, join, resolve } from "node:path";
17
- import { fileURLToPath } from "node:url";
18
- function resolveAppEntry(spurBin) {
19
- const fallback = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
20
- const candidates = spurBin !== "" ? [spurBin] : [fallback];
21
- for (const candidate of candidates) {
22
- const tokens = candidate.split(/\s+/).filter(Boolean);
23
- const mainModule = [...tokens].reverse().find((t) => t.endsWith(".ts"));
24
- if (mainModule === undefined || !existsSync(mainModule))
25
- continue;
26
- const repoRoot = resolve(dirname(mainModule), "..", "..", "..");
27
- const appEntry = join(repoRoot, "packages", "app", "src", "index.ts");
28
- if (existsSync(appEntry))
29
- return { entry: appEntry, portable: false };
30
- }
31
- const entry = fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url));
32
- if (!existsSync(entry)) {
33
- throw new Error("inline application bundle is missing — rebuild/install the sp plugin before running inline");
34
- }
35
- return { entry, portable: true };
36
- }
37
- async function loadInlineApp(spurBin, resolveAppEntry, required) {
38
- const { entry, portable } = resolveAppEntry(spurBin);
39
- const app = await import(entry);
40
- const missing = portable ? required.filter((name) => typeof app[name] !== "function") : [];
41
- if (missing.length > 0) {
42
- throw new Error(`inline application bundle ${entry} is older than its scripts — missing ${missing.join(", ")}. Reinstall sp with a superskill release that stages lib/ (commit b42961b), or pass --spur-bin <spur checkout>/apps/cli/src/index.ts.`);
43
- }
44
- return { app, portable };
45
- }
46
-
47
- // plugins/sp/scripts/inline-run-dispatch.ts
48
- function usage() {
49
- console.error([
50
- "Usage: bun plugins/sp/scripts/inline-run-dispatch.ts --attribution --run-id <id> --stage <state> [--executor <name>] [--agent <name>] [--model <m>] [--spur-bin <path>]",
51
- " bun plugins/sp/scripts/inline-run-dispatch.ts --dispatch-failure --run-id <id> --stage <state> --decision <escalate|stop|host-inline> (--text <record> | --text-file <path>) [--executor <name>] [--agent <name>] [--model <m>] [--spur-bin <path>]",
52
- " --attribution writes .spur/run/<run-id>-attribution.jsonl; an omitted/empty --executor records the explicit no-attribution marker.",
53
- " --dispatch-failure classifies the record upstream, writes .spur/run/<run-id>-dispatch-fallback.json, and carries an attributed capacity exhaustion onto the durable availability path."
54
- ].join(`
55
- `));
56
- process.exit(2);
57
- }
58
- var SAFE_RUN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
59
- async function main() {
60
- if (!process.versions.bun) {
61
- const child = spawnSync("bun", [fileURLToPath2(import.meta.url), ...process.argv.slice(2)], {
62
- stdio: "inherit"
63
- });
64
- if (child.error)
65
- console.error(`inline-run-dispatch requires Bun on PATH: ${child.error.message}`);
66
- process.exit(child.status ?? 1);
67
- }
68
- const flags = new Map;
69
- let attribution = false;
70
- let dispatchFailure = false;
71
- let spurBin = getEnvVar("SPUR_BIN") ?? "";
72
- const argv = process.argv.slice(2);
73
- for (let i = 0;i < argv.length; i++) {
74
- const flag = argv[i];
75
- if (flag === "--attribution")
76
- attribution = true;
77
- else if (flag === "--dispatch-failure")
78
- dispatchFailure = true;
79
- else if (flag === "--spur-bin")
80
- spurBin = argv[++i] ?? spurBin;
81
- else if (flag !== undefined)
82
- flags.set(flag, argv[++i] ?? "");
83
- }
84
- const runId = flags.get("--run-id") ?? "";
85
- const stage = flags.get("--stage") ?? "";
86
- const decision = flags.get("--decision") ?? "";
87
- const text = flags.get("--text") ?? "";
88
- const textFile = flags.get("--text-file") ?? "";
89
- const executor = flags.get("--executor") ?? "";
90
- const agent = flags.get("--agent") ?? "";
91
- const model = flags.get("--model") ?? "";
92
- if (attribution === dispatchFailure)
93
- usage();
94
- if (runId.trim() === "" || stage.trim() === "" || !SAFE_RUN_ID_RE.test(runId))
95
- usage();
96
- const identity = {
97
- executor: executor.trim() === "" ? null : executor,
98
- agent: agent.trim() === "" ? null : agent,
99
- ...model.trim() === "" ? {} : { model }
100
- };
101
- const { app, portable } = await loadInlineApp(spurBin, resolveAppEntry, [
102
- "recordInlineRunAttribution",
103
- "runInlineRunDispatchFailure"
104
- ]);
105
- if (attribution) {
106
- app.recordInlineRunAttribution(process.cwd(), runId, {
107
- stage,
108
- ...identity,
109
- observedAt: new Date().toISOString()
110
- });
111
- process.stdout.write(`${JSON.stringify({ ok: true, runId, stage })}
112
- `);
113
- process.exit(0);
114
- }
115
- if (decision.trim() === "" || text === "" === (textFile === ""))
116
- usage();
117
- const { app: lib } = await loadInlineApp(spurBin, () => ({ entry: fileURLToPath2(new URL("../lib/inline-run.generated.mjs", import.meta.url)), portable }), ["loadSpurConfig"]);
118
- const spurConfig = await lib.loadSpurConfig(process.cwd(), portable ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined);
119
- process.exit(await app.runInlineRunDispatchFailure({
120
- runId,
121
- stage,
122
- decision,
123
- ...text === "" ? { textFile } : { text },
124
- ...identity,
125
- spurConfig
126
- }));
127
- }
128
- main().catch((e) => {
129
- console.error(`inline-run-dispatch: FAIL \u2014 ${e instanceof Error ? e.message : String(e)}`);
130
- process.exit(1);
131
- });
@@ -1,115 +0,0 @@
1
- #!/usr/bin/env bun
2
- /** Inline dispatch fail-fast facade (task 1134 R1–R4): argv/env only. The classification, the
3
- * decision artifact and the durable availability write all live in the app runners
4
- * (`recordInlineRunAttribution`, `runInlineRunDispatchFailure`), reached through the same
5
- * `loadInlineApp` seam the setup facade uses. It is a SEPARATE script from `inline-run-setup.ts`
6
- * because it is a separate operation and the ADR-130 glue budget is per script. */
7
- import { spawnSync } from 'node:child_process';
8
- import { fileURLToPath } from 'node:url';
9
- // Type-only namespace import (erased before bundling; the smoke scanner exempts `import type`).
10
- import type * as spurApp from '@gobing-ai/spur-app';
11
- import { getEnvVar } from '../lib/env';
12
- import { loadInlineApp, resolveAppEntry } from '../lib/inline-run-app';
13
-
14
- // EMBEDDED_SPUR_SCHEMAS is declared by the bundle twin, never exported by the barrel.
15
- type InlineApp = typeof spurApp & { EMBEDDED_SPUR_SCHEMAS?: ReadonlyMap<string, string> };
16
-
17
- function usage(): never {
18
- console.error(
19
- [
20
- 'Usage: bun plugins/sp/scripts/inline-run-dispatch.ts --attribution --run-id <id> --stage <state> [--executor <name>] [--agent <name>] [--model <m>] [--spur-bin <path>]',
21
- ' bun plugins/sp/scripts/inline-run-dispatch.ts --dispatch-failure --run-id <id> --stage <state> --decision <escalate|stop|host-inline> (--text <record> | --text-file <path>) [--executor <name>] [--agent <name>] [--model <m>] [--spur-bin <path>]',
22
- ' --attribution writes .spur/run/<run-id>-attribution.jsonl; an omitted/empty --executor records the explicit no-attribution marker.',
23
- ' --dispatch-failure classifies the record upstream, writes .spur/run/<run-id>-dispatch-fallback.json, and carries an attributed capacity exhaustion onto the durable availability path.',
24
- ].join('\n'),
25
- );
26
- process.exit(2);
27
- }
28
-
29
- /** Run ids become filenames under `.spur/run/` — single safe component only (0804 R8). */
30
- const SAFE_RUN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
31
-
32
- /** Resolve the app entry: --spur-bin/SPUR_BIN main module if it proves a repo checkout, else the bundle. */
33
-
34
- async function main(): Promise<void> {
35
- // The portable Node twin has no workspace imports; the app bundle still uses Spur's Bun runtime.
36
- if (!process.versions.bun) {
37
- const child = spawnSync('bun', [fileURLToPath(import.meta.url), ...process.argv.slice(2)], {
38
- stdio: 'inherit',
39
- });
40
- if (child.error) console.error(`inline-run-dispatch requires Bun on PATH: ${child.error.message}`);
41
- process.exit(child.status ?? 1);
42
- }
43
- const flags = new Map<string, string>();
44
- let attribution = false;
45
- let dispatchFailure = false;
46
- let spurBin = getEnvVar('SPUR_BIN') ?? '';
47
- const argv = process.argv.slice(2);
48
- for (let i = 0; i < argv.length; i++) {
49
- const flag = argv[i];
50
- if (flag === '--attribution') attribution = true;
51
- else if (flag === '--dispatch-failure') dispatchFailure = true;
52
- else if (flag === '--spur-bin') spurBin = argv[++i] ?? spurBin;
53
- else if (flag !== undefined) flags.set(flag, argv[++i] ?? '');
54
- }
55
- const runId = flags.get('--run-id') ?? '';
56
- const stage = flags.get('--stage') ?? '';
57
- const decision = flags.get('--decision') ?? '';
58
- const text = flags.get('--text') ?? '';
59
- const textFile = flags.get('--text-file') ?? '';
60
- const executor = flags.get('--executor') ?? '';
61
- const agent = flags.get('--agent') ?? '';
62
- const model = flags.get('--model') ?? '';
63
-
64
- if (attribution === dispatchFailure) usage();
65
- if (runId.trim() === '' || stage.trim() === '' || !SAFE_RUN_ID_RE.test(runId)) usage();
66
- // R3: the executor identity the inline stage actually used. An omitted/empty `--executor` is the
67
- // explicit no-attribution marker — never a silent drop.
68
- const identity = {
69
- executor: executor.trim() === '' ? null : executor,
70
- agent: agent.trim() === '' ? null : agent,
71
- ...(model.trim() === '' ? {} : { model }),
72
- };
73
- const { app, portable } = await loadInlineApp<InlineApp>(spurBin, resolveAppEntry, [
74
- 'recordInlineRunAttribution',
75
- 'runInlineRunDispatchFailure',
76
- ]);
77
-
78
- if (attribution) {
79
- app.recordInlineRunAttribution(process.cwd(), runId, {
80
- stage,
81
- ...identity,
82
- observedAt: new Date().toISOString(),
83
- });
84
- process.stdout.write(`${JSON.stringify({ ok: true, runId, stage })}\n`);
85
- process.exit(0);
86
- }
87
-
88
- if (decision.trim() === '' || (text === '') === (textFile === '')) usage();
89
- // ADR-082: this facade is the composition root, so it loads the effective config once and
90
- // threads the merged result into the app runner (never a per-slice load in packages/app).
91
- const { app: lib } = await loadInlineApp<typeof import('../lib/inline-run.generated.mjs')>(
92
- spurBin,
93
- () => ({ entry: fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url)), portable }),
94
- ['loadSpurConfig'],
95
- );
96
- const spurConfig = await lib.loadSpurConfig(
97
- process.cwd(),
98
- portable ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined,
99
- );
100
- process.exit(
101
- await app.runInlineRunDispatchFailure({
102
- runId,
103
- stage,
104
- decision: decision as 'escalate' | 'stop' | 'host-inline',
105
- ...(text === '' ? { textFile } : { text }),
106
- ...identity,
107
- spurConfig,
108
- }),
109
- );
110
- }
111
-
112
- main().catch((e: unknown) => {
113
- console.error(`inline-run-dispatch: FAIL — ${e instanceof Error ? e.message : String(e)}`);
114
- process.exit(1);
115
- });