@gobing-ai/spur 0.3.84 → 0.3.86
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/config.global.yaml +11 -6
- package/config/pipeline-budgets.json +0 -25
- package/config/plugin-scripts.json +0 -5
- package/config/rules/boundary/env-var-hygiene.yaml +41 -0
- package/config/transition-shims.json +1 -1
- package/config/workflow-candidates.json +41 -0
- package/config/workflows/feature-lifecycle.yaml +4 -2
- package/config/workflows/feature-verification.yaml +70 -0
- package/config/workflows/idea-pipeline.yaml +25 -13
- package/config/workflows/task-pipeline.yaml +43 -12
- package/config/workflows/wrapup-pipeline.yaml +71 -8
- package/package.json +9 -9
- package/plugins/sp/README.md +0 -3
- package/plugins/sp/agents/super-planner.md +2 -1
- package/plugins/sp/hooks/agent-hint.ts +5 -4
- package/plugins/sp/hooks/careful-guard.ts +3 -1
- package/plugins/sp/hooks/context-post-tool.ts +2 -1
- package/plugins/sp/hooks/context-session-start.ts +4 -3
- package/plugins/sp/hooks/context-session-stop.ts +2 -1
- package/plugins/sp/hooks/pi/guard-extension.ts +4 -3
- package/plugins/sp/hooks/task-write-guard.ts +4 -3
- package/plugins/sp/lib/idea-handoff.generated.mjs +260 -260
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +13 -4
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +6 -4
- package/plugins/sp/scripts/feature-sync-bounded.mjs +10 -2
- package/plugins/sp/scripts/feature-sync-bounded.ts +2 -1
- package/plugins/sp/scripts/idea-handoff.mjs +6 -1
- package/plugins/sp/scripts/idea-handoff.ts +4 -1
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
- package/plugins/sp/scripts/inline-run-setup.ts +185 -2
- package/plugins/sp/scripts/pr-reviewing.mjs +8 -1
- package/plugins/sp/scripts/pr-reviewing.ts +2 -1
- package/plugins/sp/scripts/quality-gate.mjs +8 -1
- package/plugins/sp/scripts/quality-gate.ts +2 -1
- package/plugins/sp/scripts/surface-drift-inventory.ts +2 -5
- package/plugins/sp/scripts/task-evidence-precheck.ts +2 -1
- package/plugins/sp/scripts/task-size-precheck.ts +6 -4
- package/plugins/sp/scripts/verify-answer-lint.ts +2 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +10 -2
- package/plugins/sp/scripts/workflow-step-profile.ts +2 -1
- package/plugins/sp/scripts/wrapup-steps.mjs +8 -1
- package/plugins/sp/scripts/wrapup-steps.ts +2 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +8 -7
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -5
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +2 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +59 -1
- package/schemas/state-machine-workflow.schema.json +5 -0
- package/schemas/transition-flow-workflow.schema.json +5 -0
- package/spur.js +21393 -20831
- package/web/_astro/BoardApp.CJiqp5pS.js +1 -0
- package/web/_astro/{BoardApp.D4mBUGLT.js → BoardApp.yBBcFXWP.js} +60 -60
- package/web/_astro/{TaskDetail.DhCWaOPR.js → TaskDetail.DqFJbRFc.js} +1 -1
- package/web/_astro/{arc.D3X0nfq-.js → arc.DL-BpHoi.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.BQDoiGme.js → architectureDiagram-3BPJPVTR.9IdQYyDq.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.BjHekBbM.js → blockDiagram-GPEHLZMM.BKsFCqTl.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.BvHt171l.js → c4Diagram-AAUBKEIU.DhwI0dh1.js} +1 -1
- package/web/_astro/channel.CX5453qQ.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CwxGXlDR.js → chunk-2J33WTMH.B3QVmQ9S.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.Dia1YtwU.js → chunk-4BX2VUAB.DBSuqs9F.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.Oz9epCXa.js → chunk-55IACEB6.BSYTWAYD.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.DKFYJjYJ.js → chunk-727SXJPM.cWVuxXfS.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.AD7w5oVz.js → chunk-AQP2D5EJ.DpU_Ob3d.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.D0OqTj52.js → chunk-FMBD7UC4.BykFkyji.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.CU5ppR7j.js → chunk-ND2GUHAM.DwgHlMdY.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.Bu1rFztw.js → chunk-QZHKN3VN.CusXUGWM.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.C0Pgm5yE.js → classDiagram-4FO5ZUOK.fx0ObzkN.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.C0Pgm5yE.js → classDiagram-v2-Q7XG4LA2.fx0ObzkN.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.C0ecLb1e.js → cose-bilkent-S5V4N54A.Z4HgOlsd.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.CpStVmhO.js → cynefin-OW5HDTMX.B5dIZHJu.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.BPWN3W9f.js → dagre-BM42HDAG.DT70Q_Yw.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DnXbEot4.js → diagram-2AECGRRQ.DqHA3XBF.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.BYJsgUiI.js → diagram-5GNKFQAL.BmCem957.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CU4Bqprx.js → diagram-KO2AKTUF.sn0-hrE0.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BQLOANIu.js → diagram-LMA3HP47.BSHe9tVc.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.Cn1UVhqj.js → diagram-OG6HWLK6.DHIc-86k.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.C8FdZyvA.js → erDiagram-TEJ5UH35.Bxayrs7v.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.QSZCR8Uo.js → flowDiagram-I6XJVG4X.BkzoE_5I.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.O1Py31MY.js → ganttDiagram-6RSMTGT7.okT6CvTo.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.DoeJRBfY.js → gitGraphDiagram-PVQCEYII.CJuYbhC7.js} +1 -1
- package/web/_astro/{index.TPF1FerP.css → index.CcU5weKX.css} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.9npLJecW.js → infoDiagram-5YYISTIA.RqLy7nBo.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.DXanvOr3.js → ishikawaDiagram-YF4QCWOH.BwIcoagw.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.DxbVjxAv.js → journeyDiagram-JHISSGLW.UB1VbWtH.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BDk6_iq0.js → kanban-definition-UN3LZRKU.AaxMKpTk.js} +1 -1
- package/web/_astro/{linear.C9cCDjT3.js → linear.Nv_xOUjP.js} +1 -1
- package/web/_astro/{mermaid.core.CpHRe5nH.js → mermaid.core.Bc4LqQgX.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.D-6C85bn.js → mindmap-definition-RKZ34NQL.oKUvU_qi.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.B3Oxodty.js → pieDiagram-4H26LBE5.DQk0oo03.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.QZ_9Hshu.js → quadrantDiagram-W4KKPZXB.BdDjESDa.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.BimvuK53.js → requirementDiagram-4Y6WPE33.C2u9hUeH.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.D5Kkpx2C.js → sankeyDiagram-5OEKKPKP.CDEoiJST.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.B4YR6fAE.js → sequenceDiagram-3UESZ5HK.D_hT_GAT.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.Bt2wkZok.js → stateDiagram-AJRCARHV.DI8RYG0b.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.YZ4X7dfQ.js → stateDiagram-v2-BHNVJYJU.Bkxz4DnP.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.DmiTBL8w.js → timeline-definition-PNZ67QCA.DSY-kH3-.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.KNJV40mf.js → vennDiagram-CIIHVFJN.CpaDtuGr.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.BVAaKgWH.js → wardleyDiagram-YWT4CUSO.DujQWvo8.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.D5Jx7zB2.js → xychartDiagram-2RQKCTM6.DcM5Y4b9.js} +1 -1
- package/web/index.html +2 -2
- package/config/workflows/basic.yaml +0 -146
- package/config/workflows/docs-pipeline.yaml +0 -350
- package/config/workflows/feature-dev.yaml +0 -288
- package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -146
- package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
- package/web/_astro/BoardApp.DFPYpI5O.js +0 -1
- package/web/_astro/channel.BlQwxyoI.js +0 -1
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.86",
|
|
4
4
|
"description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
|
|
5
5
|
"extensions": {
|
|
6
6
|
"pi": ["./hooks/pi/guard-extension.ts"]
|
|
@@ -8,6 +8,15 @@ import { spawn, spawnSync } from "child_process";
|
|
|
8
8
|
import { existsSync, mkdirSync, readlinkSync, writeFileSync } from "fs";
|
|
9
9
|
import { dirname, isAbsolute, join, resolve } from "path";
|
|
10
10
|
|
|
11
|
+
// ../ts-libs/packages/utils/dist/env.js
|
|
12
|
+
function getEnvVar(name, fallback) {
|
|
13
|
+
const raw = process.env[name];
|
|
14
|
+
return raw === undefined ? fallback : raw;
|
|
15
|
+
}
|
|
16
|
+
function getEnvVars() {
|
|
17
|
+
return process.env;
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
// plugins/sp/scripts/daily-summary/logger.ts
|
|
12
21
|
var state = { console: true, file: false };
|
|
13
22
|
function emit(stream, message) {
|
|
@@ -103,7 +112,7 @@ var defaultProcessSpawner = (cmd, args, env) => {
|
|
|
103
112
|
try {
|
|
104
113
|
const proc = spawn(cmd, args, {
|
|
105
114
|
stdio: ["ignore", "pipe", "pipe"],
|
|
106
|
-
env: env ??
|
|
115
|
+
env: env ?? getEnvVars()
|
|
107
116
|
});
|
|
108
117
|
const stdoutChunks = [];
|
|
109
118
|
const stderrChunks = [];
|
|
@@ -130,7 +139,7 @@ function setProcessSpawner(next) {
|
|
|
130
139
|
}
|
|
131
140
|
async function getCcusageData(date) {
|
|
132
141
|
try {
|
|
133
|
-
const env = { ...
|
|
142
|
+
const env = { ...getEnvVars() };
|
|
134
143
|
const ccusageCheck = await processSpawner("ccusage", ["--version"], env);
|
|
135
144
|
if (ccusageCheck.exitCode !== 0) {
|
|
136
145
|
return null;
|
|
@@ -279,10 +288,10 @@ async function getGitCommits(date) {
|
|
|
279
288
|
}
|
|
280
289
|
}
|
|
281
290
|
async function promptUser() {
|
|
282
|
-
if (
|
|
291
|
+
if (getEnvVar("SP_DAILY_SUMMARY_NO_PROMPT") === "1") {
|
|
283
292
|
return { learnings: "", issuesFixed: "", pending: "" };
|
|
284
293
|
}
|
|
285
|
-
if (
|
|
294
|
+
if (getEnvVar("RD3_DAILY_SUMMARY_NO_PROMPT") === "1") {
|
|
286
295
|
logger.warn("[deprecate] RD3_DAILY_SUMMARY_NO_PROMPT is deprecated; use SP_DAILY_SUMMARY_NO_PROMPT");
|
|
287
296
|
return { learnings: "", issuesFixed: "", pending: "" };
|
|
288
297
|
}
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import { spawn, spawnSync } from 'node:child_process';
|
|
12
12
|
import { existsSync, mkdirSync, readlinkSync, writeFileSync } from 'node:fs';
|
|
13
13
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
14
|
+
import { getEnvVar, getEnvVars } from '@gobing-ai/ts-utils';
|
|
15
|
+
|
|
14
16
|
import { logger } from './logger';
|
|
15
17
|
|
|
16
18
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
@@ -225,7 +227,7 @@ export const defaultProcessSpawner: ProcessSpawner = (cmd, args, env) => {
|
|
|
225
227
|
try {
|
|
226
228
|
const proc = spawn(cmd, args, {
|
|
227
229
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
228
|
-
env: env ??
|
|
230
|
+
env: env ?? getEnvVars(),
|
|
229
231
|
});
|
|
230
232
|
const stdoutChunks: Buffer[] = [];
|
|
231
233
|
const stderrChunks: Buffer[] = [];
|
|
@@ -258,7 +260,7 @@ export function setProcessSpawner(next?: ProcessSpawner): void {
|
|
|
258
260
|
export async function getCcusageData(date: string): Promise<CcusageData | null> {
|
|
259
261
|
try {
|
|
260
262
|
// Check if ccusage is available
|
|
261
|
-
const env = { ...
|
|
263
|
+
const env = { ...getEnvVars() };
|
|
262
264
|
const ccusageCheck = await processSpawner('ccusage', ['--version'], env);
|
|
263
265
|
if (ccusageCheck.exitCode !== 0) {
|
|
264
266
|
return null;
|
|
@@ -473,10 +475,10 @@ export async function getGitCommits(date: string): Promise<GitCommit[]> {
|
|
|
473
475
|
// ─── User Input ─────────────────────────────────────────────────────────────
|
|
474
476
|
|
|
475
477
|
export async function promptUser(): Promise<UserAnnotations> {
|
|
476
|
-
if (
|
|
478
|
+
if (getEnvVar('SP_DAILY_SUMMARY_NO_PROMPT') === '1') {
|
|
477
479
|
return { learnings: '', issuesFixed: '', pending: '' };
|
|
478
480
|
}
|
|
479
|
-
if (
|
|
481
|
+
if (getEnvVar('RD3_DAILY_SUMMARY_NO_PROMPT') === '1') {
|
|
480
482
|
logger.warn('[deprecate] RD3_DAILY_SUMMARY_NO_PROMPT is deprecated; use SP_DAILY_SUMMARY_NO_PROMPT');
|
|
481
483
|
return { learnings: '', issuesFixed: '', pending: '' };
|
|
482
484
|
}
|
|
@@ -8,6 +8,14 @@ import { createHash } from "node:crypto";
|
|
|
8
8
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { dirname } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
// ../ts-libs/packages/utils/dist/env.js
|
|
13
|
+
function getEnvVar(name, fallback) {
|
|
14
|
+
const raw = process.env[name];
|
|
15
|
+
return raw === undefined ? fallback : raw;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// plugins/sp/scripts/feature-sync-bounded.ts
|
|
11
19
|
function classifySyncResult(result) {
|
|
12
20
|
if (result.proposal.gateBlocked === true)
|
|
13
21
|
return "blocked";
|
|
@@ -95,8 +103,8 @@ through unchanged.
|
|
|
95
103
|
|
|
96
104
|
Exit: 0 = sync handled (applied / no-op / suppressed-blocked / live-blocked).`;
|
|
97
105
|
function defaultSpurBin() {
|
|
98
|
-
if (
|
|
99
|
-
return
|
|
106
|
+
if (getEnvVar("SPUR_BIN"))
|
|
107
|
+
return getEnvVar("SPUR_BIN");
|
|
100
108
|
const local = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
|
|
101
109
|
if (existsSync(local))
|
|
102
110
|
return `bun ${local}`;
|
|
@@ -24,6 +24,7 @@ import { createHash } from 'node:crypto';
|
|
|
24
24
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
25
25
|
import { dirname } from 'node:path';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import { getEnvVar } from '@gobing-ai/ts-utils';
|
|
27
28
|
|
|
28
29
|
// ── Local types (match packages/app FeatureService shapes; no package import) ───────────
|
|
29
30
|
|
|
@@ -250,7 +251,7 @@ Exit: 0 = sync handled (applied / no-op / suppressed-blocked / live-blocked).`;
|
|
|
250
251
|
* keeps ad-hoc invocations from silently hitting a stale PATH install.
|
|
251
252
|
*/
|
|
252
253
|
export function defaultSpurBin(): string {
|
|
253
|
-
if (
|
|
254
|
+
if (getEnvVar('SPUR_BIN')) return getEnvVar('SPUR_BIN');
|
|
254
255
|
// scripts/ -> plugins/sp/ -> <repo>/apps/cli/src/index.ts (fileURLToPath — raw pathname breaks
|
|
255
256
|
// on %-encoded paths, e.g. spaces in the checkout directory)
|
|
256
257
|
const local = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
|
+
// ../ts-libs/packages/utils/dist/env.js
|
|
5
|
+
function getEnvVars() {
|
|
6
|
+
return process.env;
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
// plugins/sp/scripts/idea-handoff.ts
|
|
5
10
|
var libUrl = new URL("../lib/idea-handoff.generated.mjs", import.meta.url).href;
|
|
6
11
|
async function loadHandoffLib() {
|
|
7
12
|
return await import(libUrl);
|
|
8
13
|
}
|
|
9
14
|
var IDEA_HANDOFF_USAGE = "usage: idea-handoff.ts (env: __runId, featureId, optional spurBin) \u2014 no subcommands";
|
|
10
|
-
async function main(argv, env =
|
|
15
|
+
async function main(argv, env = getEnvVars()) {
|
|
11
16
|
if (argv.length > 0) {
|
|
12
17
|
process.stderr.write(`${IDEA_HANDOFF_USAGE}
|
|
13
18
|
`);
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { getEnvVars } from '@gobing-ai/ts-utils';
|
|
4
|
+
|
|
2
5
|
/**
|
|
3
6
|
* idea-handoff — portable entrypoint for the idea-pipeline finalization (task 0824, feature I21).
|
|
4
7
|
*
|
|
@@ -29,7 +32,7 @@ export async function loadHandoffLib(): Promise<typeof import('../lib/idea-hando
|
|
|
29
32
|
export const IDEA_HANDOFF_USAGE =
|
|
30
33
|
'usage: idea-handoff.ts (env: __runId, featureId, optional spurBin) — no subcommands';
|
|
31
34
|
|
|
32
|
-
export async function main(argv: string[], env: NodeJS.ProcessEnv =
|
|
35
|
+
export async function main(argv: string[], env: NodeJS.ProcessEnv = getEnvVars()): Promise<number> {
|
|
33
36
|
if (argv.length > 0) {
|
|
34
37
|
process.stderr.write(`${IDEA_HANDOFF_USAGE}\n`);
|
|
35
38
|
return 2;
|
|
@@ -43,7 +43,7 @@ const DOCUMENTED = {
|
|
|
43
43
|
'run.artifact',
|
|
44
44
|
'command.gate',
|
|
45
45
|
]),
|
|
46
|
-
guards: new Set(['always', 'shell']),
|
|
46
|
+
guards: new Set(['always', 'shell', 'action-ok', 'contract-violation']),
|
|
47
47
|
} as const;
|
|
48
48
|
|
|
49
49
|
/** Directory of workflow definitions the driver is responsible for. */
|
|
@@ -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|running|paused> --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|running|paused> --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,133 @@ 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
|
+
/** Action-row statuses the engine's `action_runs.status` column accepts. */
|
|
204
|
+
const ACTION_STATUSES = new Set(['running', 'done', 'failed', 'paused']);
|
|
205
|
+
|
|
206
|
+
/** Input for the ADR-117 emission modes (`--action` / `--close`). */
|
|
207
|
+
interface TraceModeInput {
|
|
208
|
+
readonly runId: string;
|
|
209
|
+
readonly close: boolean;
|
|
210
|
+
readonly node: string;
|
|
211
|
+
readonly kind: string;
|
|
212
|
+
readonly status: string;
|
|
213
|
+
readonly ok: boolean;
|
|
214
|
+
readonly durationMs: number;
|
|
215
|
+
readonly spurBin: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Append one emission-failure line to `.spur/run/<run-id>.log` — the run log the inline
|
|
220
|
+
* driver already owns. Best-effort and synchronous (the process may exit immediately
|
|
221
|
+
* after), and never throws: an unwritable log must not wedge the run (ADR-117 R3).
|
|
222
|
+
*/
|
|
223
|
+
function appendTraceFailureLine(runId: string, detail: string): void {
|
|
224
|
+
try {
|
|
225
|
+
const runDir = join(process.cwd(), '.spur', 'run');
|
|
226
|
+
if (!existsSync(runDir)) mkdirSync(runDir, { recursive: true });
|
|
227
|
+
const safeRunId = runId.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
228
|
+
const stamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
229
|
+
appendFileSync(join(runDir, `${safeRunId}.log`), `[${stamp}] ${detail}\n`);
|
|
230
|
+
} catch {
|
|
231
|
+
// Best-effort (R3): the run continues even when the failure cannot be recorded.
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Emit one trace write through the SHARED `WorkflowActionTraceWriter` (task 0868 R5/R7).
|
|
237
|
+
* `--action` is best-effort: an emission failure is recorded to the run log and reported
|
|
238
|
+
* on stdout as `{"ok":false}`, and the script exits 0 so the run still reaches its declared
|
|
239
|
+
* terminal state (R3/R12). `--close` is bookkeeping, so a missing run row or a persistence
|
|
240
|
+
* failure fails loudly with exit 1 (review findings #1/#4).
|
|
241
|
+
*/
|
|
242
|
+
async function runTraceMode(input: TraceModeInput): Promise<number> {
|
|
243
|
+
const operation = input.close ? 'run.close' : 'action.finish';
|
|
244
|
+
const fail = (error: string): number => {
|
|
245
|
+
appendTraceFailureLine(
|
|
246
|
+
input.runId,
|
|
247
|
+
`trace-emission-failed operation=${operation} run=${input.runId}` +
|
|
248
|
+
`${input.node === '' ? '' : ` node=${input.node}`}${input.kind === '' ? '' : ` kind=${input.kind}`}: ${error}`,
|
|
249
|
+
);
|
|
250
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error })}\n`);
|
|
251
|
+
// The action boundary is best-effort (exit 0); the run-row closure fails loudly (exit 1).
|
|
252
|
+
return input.close ? 1 : 0;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const { entry, repoRoot, chain } = resolveAppEntry(input.spurBin);
|
|
256
|
+
if (entry === null || repoRoot === null) {
|
|
257
|
+
return fail(
|
|
258
|
+
`no monorepo checkout of spur is reachable via ${chain} — the shared trace writer ` +
|
|
259
|
+
'lives in packages/app (point SPUR_BIN at a repo checkout)',
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const app = (await import(entry)) as {
|
|
264
|
+
openInlineRunProjectDb: (workdir: string) => Promise<{ adapter: unknown; close: () => void }>;
|
|
265
|
+
createWorkflowActionTraceWriter: (
|
|
266
|
+
db: unknown,
|
|
267
|
+
recordFailure?: (failure: unknown) => void,
|
|
268
|
+
) => {
|
|
269
|
+
recordAction: (boundary: Record<string, unknown>) => Promise<{ ok: boolean; actionId?: string }>;
|
|
270
|
+
closeRun: (runId: string, status: string) => Promise<{ ok: boolean }>;
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
let projectDb: { adapter: unknown; close: () => void } | undefined;
|
|
275
|
+
try {
|
|
276
|
+
projectDb = await app.openInlineRunProjectDb(process.cwd());
|
|
277
|
+
const writer = app.createWorkflowActionTraceWriter(projectDb.adapter, (failure: unknown) => {
|
|
278
|
+
const detail = failure as { operation?: string; error?: string };
|
|
279
|
+
appendTraceFailureLine(
|
|
280
|
+
input.runId,
|
|
281
|
+
`trace-emission-failed operation=${detail.operation ?? operation} run=${input.runId}: ${detail.error ?? 'unknown error'}`,
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
const result = input.close
|
|
285
|
+
? await writer.closeRun(input.runId, input.status)
|
|
286
|
+
: await writer.recordAction({
|
|
287
|
+
runId: input.runId,
|
|
288
|
+
node: input.node,
|
|
289
|
+
kind: input.kind,
|
|
290
|
+
status: input.status,
|
|
291
|
+
ok: input.ok,
|
|
292
|
+
durationMs: input.durationMs,
|
|
293
|
+
});
|
|
294
|
+
process.stdout.write(`${JSON.stringify({ ...result, runId: input.runId })}\n`);
|
|
295
|
+
return 0;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
if (input.close && (error as { name?: string }).name === 'RunRowNotFoundError') {
|
|
298
|
+
// The run row must exist before --close can mark it terminal (R6); a missing row
|
|
299
|
+
// is a loud correctness failure, not a best-effort emission failure (finding #4).
|
|
300
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
301
|
+
appendTraceFailureLine(input.runId, `trace-close-failed run=${input.runId}: ${message}`);
|
|
302
|
+
process.stdout.write(
|
|
303
|
+
`${JSON.stringify({ ok: false, runId: input.runId, error: message, code: 'RUN_NOT_FOUND' })}\n`,
|
|
304
|
+
);
|
|
305
|
+
return 1;
|
|
306
|
+
}
|
|
307
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
308
|
+
} finally {
|
|
309
|
+
projectDb?.close();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
180
313
|
async function main(): Promise<void> {
|
|
181
314
|
let runId = '';
|
|
182
315
|
let file = '';
|
|
183
316
|
let fingerprint = false;
|
|
184
317
|
let taskFile = '';
|
|
185
318
|
let featureFile = '';
|
|
186
|
-
let
|
|
319
|
+
let action = false;
|
|
320
|
+
let close = false;
|
|
321
|
+
let node = '';
|
|
322
|
+
let kind = '';
|
|
323
|
+
let status = '';
|
|
324
|
+
let okRaw = '';
|
|
325
|
+
let durationRaw = '';
|
|
326
|
+
let spurBin = getEnvVar('SPUR_BIN') ?? '';
|
|
187
327
|
const argv = process.argv.slice(2);
|
|
188
328
|
for (let i = 0; i < argv.length; i++) {
|
|
189
329
|
if (argv[i] === '--run-id') runId = argv[++i] ?? '';
|
|
@@ -191,6 +331,13 @@ async function main(): Promise<void> {
|
|
|
191
331
|
else if (argv[i] === '--fingerprint') fingerprint = true;
|
|
192
332
|
else if (argv[i] === '--task-file') taskFile = argv[++i] ?? '';
|
|
193
333
|
else if (argv[i] === '--feature-file') featureFile = argv[++i] ?? '';
|
|
334
|
+
else if (argv[i] === '--action') action = true;
|
|
335
|
+
else if (argv[i] === '--close') close = true;
|
|
336
|
+
else if (argv[i] === '--node') node = argv[++i] ?? '';
|
|
337
|
+
else if (argv[i] === '--kind') kind = argv[++i] ?? '';
|
|
338
|
+
else if (argv[i] === '--status') status = argv[++i] ?? '';
|
|
339
|
+
else if (argv[i] === '--ok') okRaw = argv[++i] ?? '';
|
|
340
|
+
else if (argv[i] === '--duration-ms') durationRaw = argv[++i] ?? '';
|
|
194
341
|
else if (argv[i] === '--spur-bin') spurBin = argv[++i] ?? spurBin;
|
|
195
342
|
}
|
|
196
343
|
|
|
@@ -200,6 +347,42 @@ async function main(): Promise<void> {
|
|
|
200
347
|
if (runId !== '' || file !== '' || taskFile.trim() === '') usage();
|
|
201
348
|
process.exit(await printFingerprint(taskFile, featureFile, spurBin));
|
|
202
349
|
}
|
|
350
|
+
|
|
351
|
+
// ADR-117 emission modes (task 0868): the inline driver reports one completed action
|
|
352
|
+
// boundary, or closes its run row at the declared terminal state. Both share the run-id
|
|
353
|
+
// filename guard, the app-entry resolution chain and the best-effort failure contract.
|
|
354
|
+
if (action || close) {
|
|
355
|
+
if (action && close) usage();
|
|
356
|
+
if (runId.trim() === '' || status.trim() === '') usage();
|
|
357
|
+
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
358
|
+
if (close) {
|
|
359
|
+
if (!CLOSE_STATUSES.has(status)) usage();
|
|
360
|
+
process.exit(
|
|
361
|
+
await runTraceMode({
|
|
362
|
+
runId,
|
|
363
|
+
close: true,
|
|
364
|
+
node: '',
|
|
365
|
+
kind: '',
|
|
366
|
+
status,
|
|
367
|
+
ok: true,
|
|
368
|
+
durationMs: 0,
|
|
369
|
+
spurBin,
|
|
370
|
+
}),
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
if (node.trim() === '' || kind.trim() === '') usage();
|
|
374
|
+
if (!ACTION_STATUSES.has(status)) usage();
|
|
375
|
+
// `--ok` and `--duration-ms` are required and exact for the action mode
|
|
376
|
+
// (review finding #2): a miscased `--ok True` or an omitted `--duration-ms`
|
|
377
|
+
// must be a loud usage error, never a silently-defaulted `ok=0` /
|
|
378
|
+
// `duration_ms=0` row.
|
|
379
|
+
if (okRaw !== 'true' && okRaw !== 'false') usage();
|
|
380
|
+
const ok = okRaw === 'true';
|
|
381
|
+
const durationMs = Number(durationRaw);
|
|
382
|
+
if (durationRaw.trim() === '' || !Number.isFinite(durationMs) || durationMs < 0) usage();
|
|
383
|
+
process.exit(await runTraceMode({ runId, close: false, node, kind, status, ok, durationMs, spurBin }));
|
|
384
|
+
}
|
|
385
|
+
|
|
203
386
|
if (runId.trim() === '' || file.trim() === '') usage();
|
|
204
387
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
205
388
|
|
|
@@ -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:
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
|
|
@@ -636,9 +633,9 @@ const JSON_PROBES: string[][] = [
|
|
|
636
633
|
['feature', 'show', 'I3', '--json'],
|
|
637
634
|
['rule', 'list', '--json'],
|
|
638
635
|
['agent', 'list', '--json'],
|
|
639
|
-
['agent', 'doctor', '
|
|
636
|
+
['agent', 'doctor', 'pi', '--json'],
|
|
640
637
|
['workflow', 'list', '--json'],
|
|
641
|
-
['workflow', 'validate', '
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
-
|
|
111
|
-
let
|
|
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 (
|
|
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 (
|
|
172
|
-
return
|
|
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 (
|
|
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';
|