@gobing-ai/spur 0.4.1 → 0.4.3
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 +4 -2
- package/config/decisions/maker-liveness.yaml +39 -0
- package/config/plugin-scripts.json +0 -4
- package/config/rules/README.md +1 -0
- package/config/rules/docs/sourced-api-claims.yaml +56 -12
- package/config/rules/strict/runtime-boundaries.yaml +7 -0
- package/config/script-placement-baseline.json +1 -19
- package/config/templates/AGENTS.md +1 -1
- package/config/workflows/feature-lifecycle.yaml +46 -1
- package/config/workflows/feature-verification.yaml +11 -0
- package/config/workflows/wrapup-pipeline.yaml +1 -1
- package/package.json +10 -10
- package/plugins/sp/README.md +23 -18
- package/plugins/sp/agents/super-planner.md +5 -6
- package/plugins/sp/agents/super-reviewer.md +43 -0
- package/plugins/sp/bunfig.toml +8 -5
- package/plugins/sp/commands/dev-review-session.md +2 -1
- package/plugins/sp/commands/dev-runall.md +18 -9
- package/plugins/sp/hooks/careful-guard.ts +1 -1
- package/plugins/sp/hooks/context-post-tool.ts +7 -404
- package/plugins/sp/hooks/context-session-start.ts +6 -192
- package/plugins/sp/hooks/context-session-stop.ts +6 -91
- package/plugins/sp/hooks/pi/guard-extension.ts +26 -12
- package/plugins/sp/hooks/task-write-guard.ts +1 -1
- package/plugins/sp/{hooks → lib}/agent-hint.ts +1 -1
- package/plugins/sp/lib/context-owner.ts +56 -0
- package/plugins/sp/lib/context-post-tool.ts +432 -0
- package/plugins/sp/lib/context-session-start.ts +214 -0
- package/plugins/sp/lib/context-session-stop.ts +196 -0
- package/plugins/sp/lib/dogfood-pipeline.ts +265 -0
- package/plugins/sp/lib/dogfood-report.ts +278 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +6 -4
- package/plugins/sp/lib/inline-run-app.ts +40 -0
- package/plugins/sp/lib/inline-run.generated.d.mts +3 -3
- package/plugins/sp/lib/inline-run.generated.mjs +1361 -87
- package/plugins/sp/lib/plan-projection.generated.d.mts +169 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +704 -1
- package/plugins/sp/lib/quality-gate.generated.d.mts +0 -12
- package/plugins/sp/lib/quality-gate.generated.mjs +62 -231
- package/plugins/sp/lib/residual-scan.generated.d.mts +7 -1
- package/plugins/sp/lib/residual-scan.generated.mjs +71 -6
- package/plugins/sp/lib/run-summary-core.ts +34 -370
- package/plugins/sp/lib/transcript.ts +37 -200
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +740 -1
- package/plugins/sp/scripts/batch-plan.ts +48 -3
- package/plugins/sp/scripts/batch-preflight.mjs +23 -1
- package/plugins/sp/scripts/batch-preflight.ts +35 -295
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +108 -87
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +111 -104
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -1
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +7 -265
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +3 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +6 -272
- package/plugins/sp/scripts/feature-verification-steps.mjs +7 -123
- package/plugins/sp/scripts/feature-verification-steps.ts +29 -181
- package/plugins/sp/scripts/inline-run-dispatch.mjs +27 -26
- package/plugins/sp/scripts/inline-run-dispatch.ts +1 -21
- package/plugins/sp/scripts/inline-run-setup.mjs +158 -120
- package/plugins/sp/scripts/inline-run-setup.ts +1 -21
- package/plugins/sp/scripts/pr-reviewing.mjs +19 -5
- package/plugins/sp/scripts/pr-reviewing.ts +34 -9
- package/plugins/sp/scripts/quality-gate.mjs +38 -208
- package/plugins/sp/scripts/residual-scan.mjs +73 -6
- package/plugins/sp/scripts/residual-scan.ts +8 -5
- package/plugins/sp/scripts/run-summary.mjs +160 -95
- package/plugins/sp/scripts/run-summary.ts +7 -5
- package/plugins/sp/scripts/session-timeline.mjs +57 -29
- package/plugins/sp/scripts/session-timeline.ts +4 -18
- package/plugins/sp/scripts/task-diffstat.mjs +43 -30
- package/plugins/sp/scripts/task-diffstat.ts +40 -71
- package/plugins/sp/skills/code-implementation/SKILL.md +7 -1
- package/plugins/sp/skills/code-verification/references/secu-review.md +2 -1
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +4 -0
- package/plugins/sp/skills/dogfood-testing/SKILL.md +2 -2
- package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
- package/plugins/sp/skills/next-feature/SKILL.md +1 -1
- package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
- package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
- package/plugins/sp/skills/session-review/SKILL.md +5 -3
- package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
- package/plugins/sp/skills/spur-cli/references/builder.md +3 -2
- package/plugins/sp/skills/spur-cli/references/decision.md +40 -8
- package/plugins/sp/skills/spur-cli/references/features.md +46 -5
- package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +27 -0
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -3
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +6 -1
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +56 -7
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +4 -1
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +22 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +14 -0
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +13 -11
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +37 -25
- package/spur.js +89792 -76992
- package/web/_astro/{BoardApp.D66k_s_T.js → BoardApp.5IIl2O-z.js} +1 -1
- package/web/_astro/BoardApp.Drnx-KeM.js +324 -0
- package/web/_astro/TaskDetail.Ci4LNn9S.js +1 -0
- package/web/_astro/{arc._BUadrdH.js → arc.j2EVkYVd.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.Dh7xS1Pp.js → architectureDiagram-3BPJPVTR.DZWSFqEG.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DFDVpfXm.js → blockDiagram-GPEHLZMM.DE6GpXCN.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.V7_3_glh.js → c4Diagram-AAUBKEIU.DoVR1G6B.js} +1 -1
- package/web/_astro/channel.DmdDxGsq.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.De1yx7Jz.js → chunk-2J33WTMH.Do6-Gbyf.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BDhHFuxx.js → chunk-4BX2VUAB.BYV1HGHx.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DDLkWnmC.js → chunk-55IACEB6.CvI1zkrc.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.CwfiLnA7.js → chunk-727SXJPM.DNGSCRlX.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.CtGDmUnM.js → chunk-AQP2D5EJ.DXhhgT5z.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.BOLjpw73.js → chunk-FMBD7UC4.dzkRKP2u.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BDhQls6O.js → chunk-ND2GUHAM.DujGLeEf.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.76qBynDF.js → chunk-QZHKN3VN.BWtIT3ZU.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.N0mpPHT0.js → classDiagram-4FO5ZUOK.DyApg0Nn.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.N0mpPHT0.js → classDiagram-v2-Q7XG4LA2.DyApg0Nn.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.CKknFfjB.js → cose-bilkent-S5V4N54A.DJQB-u_l.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.Cw3ZWBAh.js → cynefin-OW5HDTMX.bi3D_siV.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.D3yIKCRD.js → dagre-BM42HDAG.C-f7TrRg.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.D51dO83Z.js → diagram-2AECGRRQ.DrPfjHQB.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Bb-FViDj.js → diagram-5GNKFQAL.C9S4WUHO.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.D3mN8S6x.js → diagram-KO2AKTUF.Dzzye8QH.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.dMMyPcbJ.js → diagram-LMA3HP47.DX-LRiof.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.AzbvzY4C.js → diagram-OG6HWLK6.Z-kAKwIz.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.BR5I4OZb.js → erDiagram-TEJ5UH35.D6dyClSv.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DFqvhGkn.js → flowDiagram-I6XJVG4X.Cx3q2cxD.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.ByJbhH6J.js → ganttDiagram-6RSMTGT7.DU3gH6CT.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CInHjQR3.js → gitGraphDiagram-PVQCEYII.ISmCnJ87.js} +1 -1
- package/web/_astro/index.Do2lLdgo.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.Cm0dGVjJ.js → infoDiagram-5YYISTIA.DR6_iNOK.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js → ishikawaDiagram-YF4QCWOH.D7wY-ZGA.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.4IvLa2MW.js → journeyDiagram-JHISSGLW.DkP9-WK8.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DS87Cl-k.js → kanban-definition-UN3LZRKU.wtgY98M2.js} +1 -1
- package/web/_astro/{linear.MzmRrNER.js → linear.CI6djNi7.js} +1 -1
- package/web/_astro/{mermaid.core.fK-WNswS.js → mermaid.core.CuQPxS8b.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BqipOHhY.js → mindmap-definition-RKZ34NQL.5HNIEG7R.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.0nBiPqFG.js → pieDiagram-4H26LBE5.CchhDFjE.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.Szj7Zpx5.js → quadrantDiagram-W4KKPZXB.PYXXJPSJ.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.D4UR6TWo.js → requirementDiagram-4Y6WPE33.cUQuNAKU.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DAE7BdOs.js → sankeyDiagram-5OEKKPKP.BO7MnRIA.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.BP9ZVq25.js → sequenceDiagram-3UESZ5HK.D9bElyD6.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.BQW7mIxk.js → stateDiagram-AJRCARHV.DhGdRz7I.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.PbK4nDih.js → stateDiagram-v2-BHNVJYJU.5GvLeAoL.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.Ci_GvUpo.js → timeline-definition-PNZ67QCA.LtAT9haM.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DNw1Y5qx.js → vennDiagram-CIIHVFJN.DRth1yLA.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CwlkaaNe.js → wardleyDiagram-YWT4CUSO.h9MlZfcr.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.MI6ZuiH_.js → xychartDiagram-2RQKCTM6.DHqy9DYA.js} +1 -1
- package/web/index.html +2 -2
- package/config/proportional-route-table.ts +0 -155
- package/plugins/sp/commands/dev-parallel.md +0 -37
- package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
- package/web/_astro/BoardApp.D6sjAJr_.js +0 -324
- package/web/_astro/TaskDetail.BynLnMNc.js +0 -1
- package/web/_astro/channel.DZVfto-r.js +0 -1
- package/web/_astro/index.COh_kBxN.css +0 -1
- /package/plugins/sp/{hooks → lib}/destructive-policy.ts +0 -0
- /package/plugins/sp/{hooks → lib}/task-file-policy.ts +0 -0
|
@@ -2,20 +2,7 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// plugins/sp/scripts/feature-verification-steps.ts
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
closeSync,
|
|
8
|
-
existsSync,
|
|
9
|
-
mkdirSync,
|
|
10
|
-
openSync,
|
|
11
|
-
readdirSync,
|
|
12
|
-
readFileSync,
|
|
13
|
-
realpathSync,
|
|
14
|
-
renameSync,
|
|
15
|
-
statSync,
|
|
16
|
-
writeFileSync
|
|
17
|
-
} from "fs";
|
|
18
|
-
import { join } from "path";
|
|
5
|
+
import { existsSync } from "fs";
|
|
19
6
|
import { fileURLToPath } from "url";
|
|
20
7
|
|
|
21
8
|
// plugins/sp/lib/env.ts
|
|
@@ -25,12 +12,11 @@ function getEnvVar(name, fallback) {
|
|
|
25
12
|
}
|
|
26
13
|
|
|
27
14
|
// plugins/sp/scripts/feature-verification-steps.ts
|
|
28
|
-
var SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
29
15
|
function bundleEntry() {
|
|
30
16
|
return fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url));
|
|
31
17
|
}
|
|
32
18
|
function sourceModeError(spurBin) {
|
|
33
|
-
return new Error(`source mode: refusing to load packages/app/src/index.ts (spurBin=${JSON.stringify(spurBin)}). ` + "That entry is not the feature-verification contract surface \u2014 it does not export " + "
|
|
19
|
+
return new Error(`source mode: refusing to load packages/app/src/index.ts (spurBin=${JSON.stringify(spurBin)}). ` + "That entry is not the feature-verification contract surface \u2014 it does not export " + "runFeatureVerification. Fix: run in bundle mode so " + "plugins/sp/lib/inline-run.generated.mjs is loaded (omit --module-mode, or pass --module-mode bundle) " + "and regenerate that bundle if it is missing. Reinstalling the sp plugin does not add the operation to the app source entry.");
|
|
34
20
|
}
|
|
35
21
|
function resolveModuleMode(raw) {
|
|
36
22
|
if (raw === "" || raw === "bundle")
|
|
@@ -50,123 +36,21 @@ async function loadModule(spurBin, mode) {
|
|
|
50
36
|
}
|
|
51
37
|
async function requireModule(entry, mode) {
|
|
52
38
|
const mod = await import(entry);
|
|
53
|
-
const missing = [
|
|
54
|
-
"startFeatureVerificationReceipt",
|
|
55
|
-
"completeFeatureVerificationReceipt",
|
|
56
|
-
"captureFeatureReceiptDigest",
|
|
57
|
-
"resolveWorkflowDefinition",
|
|
58
|
-
"splitLaunchCommand",
|
|
59
|
-
"openInlineRunProjectDb",
|
|
60
|
-
"ArtifactDao"
|
|
61
|
-
].filter((k) => typeof mod[k] !== "function");
|
|
39
|
+
const missing = ["runFeatureVerification"].filter((k) => typeof mod[k] !== "function");
|
|
62
40
|
if (missing.length > 0) {
|
|
63
41
|
throw new Error(`feature-verification-steps: mode=${mode} \u2014 application entry ${entry} is missing feature-verification seams (${missing.join(", ")}). ` + "Fix: the generated inline bundle is stale or incomplete \u2014 run `bun run build:bundle` in a source checkout, or reinstall the sp plugin.");
|
|
64
42
|
}
|
|
65
43
|
return mod;
|
|
66
44
|
}
|
|
67
|
-
function assertSafeId(kind, id) {
|
|
68
|
-
if (!SAFE_ID_RE.test(id) || id.includes("..")) {
|
|
69
|
-
throw new Error(`refusing unsafe ${kind}: ${id}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function findFeatureFile(featureDir, featureId) {
|
|
73
|
-
if (!existsSync(featureDir))
|
|
74
|
-
return;
|
|
75
|
-
const name = readdirSync(featureDir).find((n) => n.startsWith(`${featureId}_`) && n.endsWith(".md"));
|
|
76
|
-
return name === undefined ? undefined : join(featureDir, name);
|
|
77
|
-
}
|
|
78
|
-
var nodeFsShim = {
|
|
79
|
-
realPath: realpathSync,
|
|
80
|
-
readDir: readdirSync,
|
|
81
|
-
stat: (path) => {
|
|
82
|
-
try {
|
|
83
|
-
return statSync(path);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
if (error.code === "ENOENT")
|
|
86
|
-
return null;
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
ensureDir: (dir) => {
|
|
91
|
-
mkdirSync(dir, { recursive: true });
|
|
92
|
-
},
|
|
93
|
-
writeFile: (path, body) => {
|
|
94
|
-
writeFileSync(path, body);
|
|
95
|
-
},
|
|
96
|
-
rename: (src, dest) => {
|
|
97
|
-
renameSync(src, dest);
|
|
98
|
-
},
|
|
99
|
-
readFile: (path) => {
|
|
100
|
-
return readFileSync(path, "utf8");
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
45
|
async function verify(featureId, runId, cmdOverride, spurBin, mode) {
|
|
104
|
-
assertSafeId("feature id", featureId);
|
|
105
|
-
assertSafeId("run id", runId);
|
|
106
|
-
const cwd = process.cwd();
|
|
107
46
|
const mod = await loadModule(spurBin, mode);
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
const selected = await mod.resolveWorkflowDefinition(cwd, "feature-verification");
|
|
111
|
-
const vars = selected.workflow.vars;
|
|
112
|
-
const configuredCmd = typeof vars?.verificationCmd === "string" && vars.verificationCmd.length > 0 ? vars.verificationCmd : "bun run spur-check-feature";
|
|
113
|
-
const effectiveCmd = cmdOverride !== "" ? cmdOverride : configuredCmd;
|
|
114
|
-
const featureFile = findFeatureFile(join(cwd, "docs", "features"), featureId);
|
|
115
|
-
if (featureFile === undefined) {
|
|
116
|
-
throw new Error(`feature ${featureId} not found under ${join(cwd, "docs", "features")}`);
|
|
117
|
-
}
|
|
118
|
-
const featureContent = readFileSync(featureFile, "utf8");
|
|
119
|
-
const learningsPath = join(cwd, ".spur", "context", "learnings.md");
|
|
120
|
-
const learningsContent = existsSync(learningsPath) ? readFileSync(learningsPath, "utf8") : undefined;
|
|
121
|
-
const beforeDigest = await mod.captureFeatureReceiptDigest(cwd, featureContent, learningsContent);
|
|
122
|
-
const receipt = await mod.startFeatureVerificationReceipt(nodeFsShim, runDir, {
|
|
47
|
+
const result = await mod.runFeatureVerification({
|
|
48
|
+
workdir: process.cwd(),
|
|
123
49
|
featureId,
|
|
124
50
|
runId,
|
|
125
|
-
|
|
126
|
-
verifier: {
|
|
127
|
-
name: "feature-verification",
|
|
128
|
-
sourcePath: selected.path,
|
|
129
|
-
layer: selected.layer,
|
|
130
|
-
definitionDigest: selected.digest
|
|
131
|
-
},
|
|
132
|
-
verificationCmd: effectiveCmd,
|
|
133
|
-
inputDigest: beforeDigest
|
|
51
|
+
cmdOverride
|
|
134
52
|
});
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
await new mod.ArtifactDao(db.adapter).record({
|
|
138
|
-
runId,
|
|
139
|
-
path: mod.featureReceiptPaths(runDir, featureId, runId).runScoped,
|
|
140
|
-
kind: "feature-verification"
|
|
141
|
-
});
|
|
142
|
-
} finally {
|
|
143
|
-
db.close();
|
|
144
|
-
}
|
|
145
|
-
const logPath = join(runDir, `${runId}-feature-verification.log`);
|
|
146
|
-
const launch = mod.splitLaunchCommand(effectiveCmd, "feature-verification verificationCmd");
|
|
147
|
-
if ("error" in launch) {
|
|
148
|
-
await mod.completeFeatureVerificationReceipt(nodeFsShim, runDir, receipt, {
|
|
149
|
-
status: "FAIL",
|
|
150
|
-
inputDigest: beforeDigest
|
|
151
|
-
});
|
|
152
|
-
console.log(`feature-verification-steps: ${featureId} verification FAIL (${launch.error})`);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
const logFd = openSync(logPath, "a");
|
|
156
|
-
try {
|
|
157
|
-
const result = spawnSync(launch.command, launch.leadingArgs, {
|
|
158
|
-
cwd,
|
|
159
|
-
stdio: ["ignore", logFd, logFd],
|
|
160
|
-
timeout: 14400000
|
|
161
|
-
});
|
|
162
|
-
const exit = result.status ?? 1;
|
|
163
|
-
const afterDigest = await mod.captureFeatureReceiptDigest(cwd, featureContent, learningsContent);
|
|
164
|
-
const status = exit === 0 && afterDigest === beforeDigest ? "PASS" : "FAIL";
|
|
165
|
-
await mod.completeFeatureVerificationReceipt(nodeFsShim, runDir, receipt, { status, inputDigest: afterDigest });
|
|
166
|
-
console.log(`feature-verification-steps: ${featureId} verification ${status} (run ${runId}, log ${logPath})`);
|
|
167
|
-
} finally {
|
|
168
|
-
closeSync(logFd);
|
|
169
|
-
}
|
|
53
|
+
console.log(`feature-verification-steps: ${featureId} ${result.message}`);
|
|
170
54
|
}
|
|
171
55
|
function main() {
|
|
172
56
|
const args = process.argv.slice(2);
|
|
@@ -1,62 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* feature-verification-steps —
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* feature-verification-steps — argv adapter for the feature-verification pass (task 0915; reduced to
|
|
4
|
+
* an adapter in task 1176).
|
|
5
|
+
*
|
|
6
|
+
* The pass itself — resolve the selected `feature-verification` definition, capture the before
|
|
7
|
+
* proof-input digest, record and register the RUNNING receipt, run the effective command through the
|
|
8
|
+
* safe argv splitter (never `sh -c`), compare the after digest, complete PASS/FAIL plus the coarse
|
|
9
|
+
* status file — lives in the application operation
|
|
10
|
+
* `packages/app/src/workflow/feature-verification-run.ts`, reached through the generated inline
|
|
11
|
+
* bundle. This entry owns argv, usage and exit codes only: it opens no project DB, constructs no
|
|
12
|
+
* `ArtifactDao`, and scans no feature directory (that lookup is app-owned too).
|
|
6
13
|
*
|
|
7
14
|
* Usage (workflow onEnter, vars: featureId, verificationCmd, spurBin, __runId):
|
|
8
15
|
* bun plugins/sp/scripts/feature-verification-steps.ts verify \
|
|
9
|
-
* --feature-id D63 --run-id <runId> --cmd "bun run spur-check-feature"
|
|
16
|
+
* --feature-id D63 --run-id <runId> [--cmd "bun run spur-check-feature"] [--module-mode bundle]
|
|
10
17
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* wins) for the verifier identity + configured command,
|
|
14
|
-
* 2. capture the before proof-input digest, record the RUNNING receipt (both
|
|
15
|
-
* copies) and register the run-scoped receipt as a run artifact,
|
|
16
|
-
* 3. run the effective command via the safe launch splitter (never `sh -c`
|
|
17
|
-
* interpolation), output to the run-scoped log,
|
|
18
|
-
* 4. capture the after digest — a mismatch records FAIL,
|
|
19
|
-
* 5. complete the receipt PASS/FAIL (both copies) and the coarse status file.
|
|
18
|
+
* Always exits 0 after a completed pass; the transition guard reads the status file and the
|
|
19
|
+
* completion boundary validates the receipt (fail-closed). Unsafe ids are refused by the operation.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* refused outright. Node-builtin imports only (ADR-065); the application seams come
|
|
24
|
-
* from the generated inline bundle (`plugins/sp/lib/inline-run.generated.mjs`) — never
|
|
25
|
-
* from `packages/app/src/index.ts`, which is not a plugin-script contract surface (0948 R3).
|
|
21
|
+
* Node-builtin imports only (ADR-065); the application seams come from the generated inline bundle,
|
|
22
|
+
* never from `packages/app/src/index.ts`, which is not a plugin-script contract surface (0948 R3).
|
|
26
23
|
*/
|
|
27
24
|
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
closeSync,
|
|
31
|
-
existsSync,
|
|
32
|
-
mkdirSync,
|
|
33
|
-
openSync,
|
|
34
|
-
readdirSync,
|
|
35
|
-
readFileSync,
|
|
36
|
-
realpathSync,
|
|
37
|
-
renameSync,
|
|
38
|
-
statSync,
|
|
39
|
-
writeFileSync,
|
|
40
|
-
} from 'node:fs';
|
|
41
|
-
import { join } from 'node:path';
|
|
25
|
+
import { existsSync } from 'node:fs';
|
|
42
26
|
import { fileURLToPath } from 'node:url';
|
|
43
27
|
import { getEnvVar } from '../lib/env';
|
|
44
28
|
|
|
45
|
-
/** Single safe filename component — same class as the inline-run-setup run-id guard. */
|
|
46
|
-
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
47
|
-
|
|
48
29
|
/** Shape of the generated inline bundle / repo app source this script consumes. */
|
|
49
30
|
interface VerificationModule {
|
|
50
|
-
|
|
51
|
-
startFeatureVerificationReceipt: typeof import('../../../packages/app/src/workflow/feature-verification-receipt')['startFeatureVerificationReceipt'];
|
|
52
|
-
completeFeatureVerificationReceipt: typeof import('../../../packages/app/src/workflow/feature-verification-receipt')['completeFeatureVerificationReceipt'];
|
|
53
|
-
captureFeatureReceiptDigest: typeof import('../../../packages/app/src/workflow/feature-verification-receipt')['captureFeatureReceiptDigest'];
|
|
54
|
-
resolveWorkflowDefinition: typeof import('../../../packages/app/src/workflow/workflow-resolver')['resolveWorkflowDefinition'];
|
|
55
|
-
splitLaunchCommand: typeof import('../../../packages/app/src/workflow/split-launch-command')['splitLaunchCommand'];
|
|
56
|
-
openInlineRunProjectDb: typeof import('../../../packages/app/src/services/inline-run-setup')['openInlineRunProjectDb'];
|
|
57
|
-
ArtifactDao: new (
|
|
58
|
-
adapter: never,
|
|
59
|
-
) => { record: (input: { runId: string; path: string; kind: string }) => Promise<unknown> };
|
|
31
|
+
runFeatureVerification: typeof import('../../../packages/app/src/workflow/feature-verification-run')['runFeatureVerification'];
|
|
60
32
|
}
|
|
61
33
|
|
|
62
34
|
/** Explicit module surface. Source is not the contract; the generated bundle is (0948 R3). */
|
|
@@ -67,16 +39,16 @@ function bundleEntry(): string {
|
|
|
67
39
|
}
|
|
68
40
|
|
|
69
41
|
/**
|
|
70
|
-
* Source mode names the mode and the fix. The app source entry does not export
|
|
71
|
-
*
|
|
42
|
+
* Source mode names the mode and the fix. The app source entry does not export the verification
|
|
43
|
+
* operation, and "rebuild/install the sp plugin" cannot add it.
|
|
72
44
|
*/
|
|
73
45
|
function sourceModeError(spurBin: string): Error {
|
|
74
46
|
return new Error(
|
|
75
47
|
`source mode: refusing to load packages/app/src/index.ts (spurBin=${JSON.stringify(spurBin)}). ` +
|
|
76
48
|
'That entry is not the feature-verification contract surface — it does not export ' +
|
|
77
|
-
'
|
|
49
|
+
'runFeatureVerification. Fix: run in bundle mode so ' +
|
|
78
50
|
'plugins/sp/lib/inline-run.generated.mjs is loaded (omit --module-mode, or pass --module-mode bundle) ' +
|
|
79
|
-
'and regenerate that bundle if it is missing. Reinstalling the sp plugin does not add
|
|
51
|
+
'and regenerate that bundle if it is missing. Reinstalling the sp plugin does not add the operation to the app source entry.',
|
|
80
52
|
);
|
|
81
53
|
}
|
|
82
54
|
|
|
@@ -89,8 +61,8 @@ function resolveModuleMode(raw: string): ModuleMode {
|
|
|
89
61
|
/**
|
|
90
62
|
* Bundle mode loads the generated inline bundle. Source mode fails closed with a named fix.
|
|
91
63
|
*
|
|
92
|
-
* `spurBin` no longer selects the module (it named the *caller's* CLI entry, not this
|
|
93
|
-
*
|
|
64
|
+
* `spurBin` no longer selects the module (it named the *caller's* CLI entry, not this script's runtime
|
|
65
|
+
* surface); it is kept only to make the failure message actionable.
|
|
94
66
|
*/
|
|
95
67
|
async function loadModule(spurBin: string, mode: ModuleMode): Promise<VerificationModule> {
|
|
96
68
|
if (mode === 'source') throw sourceModeError(spurBin);
|
|
@@ -105,20 +77,10 @@ async function loadModule(spurBin: string, mode: ModuleMode): Promise<Verificati
|
|
|
105
77
|
return requireModule(bundle, mode);
|
|
106
78
|
}
|
|
107
79
|
|
|
108
|
-
/** Dynamic-import the entry and refuse
|
|
80
|
+
/** Dynamic-import the entry and refuse a module missing the verification operation. */
|
|
109
81
|
async function requireModule(entry: string, mode: ModuleMode): Promise<VerificationModule> {
|
|
110
82
|
const mod = (await import(entry)) as Partial<VerificationModule> & Record<string, unknown>;
|
|
111
|
-
const missing = (
|
|
112
|
-
[
|
|
113
|
-
'startFeatureVerificationReceipt',
|
|
114
|
-
'completeFeatureVerificationReceipt',
|
|
115
|
-
'captureFeatureReceiptDigest',
|
|
116
|
-
'resolveWorkflowDefinition',
|
|
117
|
-
'splitLaunchCommand',
|
|
118
|
-
'openInlineRunProjectDb',
|
|
119
|
-
'ArtifactDao',
|
|
120
|
-
] as const
|
|
121
|
-
).filter((k) => typeof mod[k] !== 'function');
|
|
83
|
+
const missing = (['runFeatureVerification'] as const).filter((k) => typeof mod[k] !== 'function');
|
|
122
84
|
if (missing.length > 0) {
|
|
123
85
|
throw new Error(
|
|
124
86
|
`feature-verification-steps: mode=${mode} — application entry ${entry} is missing feature-verification seams (${missing.join(', ')}). ` +
|
|
@@ -128,48 +90,6 @@ async function requireModule(entry: string, mode: ModuleMode): Promise<Verificat
|
|
|
128
90
|
return mod as VerificationModule;
|
|
129
91
|
}
|
|
130
92
|
|
|
131
|
-
/** Refuse ids that could escape the run directory through receipt filenames. */
|
|
132
|
-
function assertSafeId(kind: string, id: string): void {
|
|
133
|
-
if (!SAFE_ID_RE.test(id) || id.includes('..')) {
|
|
134
|
-
throw new Error(`refusing unsafe ${kind}: ${id}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Locate `<id>_<slug>.md` under the features directory. */
|
|
139
|
-
function findFeatureFile(featureDir: string, featureId: string): string | undefined {
|
|
140
|
-
if (!existsSync(featureDir)) return undefined;
|
|
141
|
-
const name = readdirSync(featureDir).find((n) => n.startsWith(`${featureId}_`) && n.endsWith('.md'));
|
|
142
|
-
return name === undefined ? undefined : join(featureDir, name);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ponytail: the receipt service is filesystem-pure over the ts-runtime
|
|
146
|
-
// FileSystem port; only ensureDir/writeFile/rename are exercised on the
|
|
147
|
-
// start/complete paths. Swap to createNodeFileSystem if more surface is needed.
|
|
148
|
-
const nodeFsShim = {
|
|
149
|
-
realPath: realpathSync,
|
|
150
|
-
readDir: readdirSync,
|
|
151
|
-
stat: (path: string) => {
|
|
152
|
-
try {
|
|
153
|
-
return statSync(path);
|
|
154
|
-
} catch (error) {
|
|
155
|
-
if ((error as { code?: string }).code === 'ENOENT') return null;
|
|
156
|
-
throw error;
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
ensureDir: (dir: string) => {
|
|
160
|
-
mkdirSync(dir, { recursive: true });
|
|
161
|
-
},
|
|
162
|
-
writeFile: (path: string, body: string) => {
|
|
163
|
-
writeFileSync(path, body);
|
|
164
|
-
},
|
|
165
|
-
rename: (src: string, dest: string) => {
|
|
166
|
-
renameSync(src, dest);
|
|
167
|
-
},
|
|
168
|
-
readFile: (path: string) => {
|
|
169
|
-
return readFileSync(path, 'utf8');
|
|
170
|
-
},
|
|
171
|
-
} as never;
|
|
172
|
-
|
|
173
93
|
/** One full verification pass for one feature. Always exits 0. */
|
|
174
94
|
async function verify(
|
|
175
95
|
featureId: string,
|
|
@@ -178,86 +98,14 @@ async function verify(
|
|
|
178
98
|
spurBin: string,
|
|
179
99
|
mode: ModuleMode,
|
|
180
100
|
): Promise<void> {
|
|
181
|
-
assertSafeId('feature id', featureId);
|
|
182
|
-
assertSafeId('run id', runId);
|
|
183
|
-
const cwd = process.cwd();
|
|
184
101
|
const mod = await loadModule(spurBin, mode);
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// 1. Selected verifier definition (project override wins); its configured
|
|
189
|
-
// command is the completion contract — an override records but can't complete.
|
|
190
|
-
const selected = await mod.resolveWorkflowDefinition(cwd, 'feature-verification');
|
|
191
|
-
const vars = selected.workflow.vars as { verificationCmd?: unknown } | undefined;
|
|
192
|
-
const configuredCmd =
|
|
193
|
-
typeof vars?.verificationCmd === 'string' && vars.verificationCmd.length > 0
|
|
194
|
-
? vars.verificationCmd
|
|
195
|
-
: 'bun run spur-check-feature';
|
|
196
|
-
const effectiveCmd = cmdOverride !== '' ? cmdOverride : configuredCmd;
|
|
197
|
-
|
|
198
|
-
const featureFile = findFeatureFile(join(cwd, 'docs', 'features'), featureId);
|
|
199
|
-
if (featureFile === undefined) {
|
|
200
|
-
throw new Error(`feature ${featureId} not found under ${join(cwd, 'docs', 'features')}`);
|
|
201
|
-
}
|
|
202
|
-
const featureContent = readFileSync(featureFile, 'utf8');
|
|
203
|
-
const learningsPath = join(cwd, '.spur', 'context', 'learnings.md');
|
|
204
|
-
const learningsContent = existsSync(learningsPath) ? readFileSync(learningsPath, 'utf8') : undefined;
|
|
205
|
-
|
|
206
|
-
// 2. Before digest + RUNNING receipt (both copies) + artifact registration.
|
|
207
|
-
const beforeDigest = await mod.captureFeatureReceiptDigest(cwd, featureContent, learningsContent);
|
|
208
|
-
const receipt = await mod.startFeatureVerificationReceipt(nodeFsShim, runDir, {
|
|
102
|
+
const result = await mod.runFeatureVerification({
|
|
103
|
+
workdir: process.cwd(),
|
|
209
104
|
featureId,
|
|
210
105
|
runId,
|
|
211
|
-
|
|
212
|
-
verifier: {
|
|
213
|
-
name: 'feature-verification',
|
|
214
|
-
sourcePath: selected.path,
|
|
215
|
-
layer: selected.layer,
|
|
216
|
-
definitionDigest: selected.digest,
|
|
217
|
-
},
|
|
218
|
-
verificationCmd: effectiveCmd,
|
|
219
|
-
inputDigest: beforeDigest,
|
|
106
|
+
cmdOverride,
|
|
220
107
|
});
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
await new mod.ArtifactDao(db.adapter as never).record({
|
|
224
|
-
runId,
|
|
225
|
-
path: mod.featureReceiptPaths(runDir, featureId, runId).runScoped,
|
|
226
|
-
kind: 'feature-verification',
|
|
227
|
-
});
|
|
228
|
-
} finally {
|
|
229
|
-
db.close();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 3. Run the effective command via the safe splitter (no `sh -c`).
|
|
233
|
-
const logPath = join(runDir, `${runId}-feature-verification.log`);
|
|
234
|
-
const launch = mod.splitLaunchCommand(effectiveCmd, 'feature-verification verificationCmd');
|
|
235
|
-
if ('error' in launch) {
|
|
236
|
-
await mod.completeFeatureVerificationReceipt(nodeFsShim, runDir, receipt, {
|
|
237
|
-
status: 'FAIL',
|
|
238
|
-
inputDigest: beforeDigest,
|
|
239
|
-
});
|
|
240
|
-
console.log(`feature-verification-steps: ${featureId} verification FAIL (${launch.error})`);
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
const logFd = openSync(logPath, 'a');
|
|
244
|
-
try {
|
|
245
|
-
const result = spawnSync(launch.command, launch.leadingArgs, {
|
|
246
|
-
cwd,
|
|
247
|
-
stdio: ['ignore', logFd, logFd],
|
|
248
|
-
timeout: 4 * 60 * 60 * 1000,
|
|
249
|
-
});
|
|
250
|
-
const exit = result.status ?? 1;
|
|
251
|
-
|
|
252
|
-
// 4/5. After digest — a mismatch records FAIL (the pass did not verify
|
|
253
|
-
// the tree it left behind); otherwise the exit code decides.
|
|
254
|
-
const afterDigest = await mod.captureFeatureReceiptDigest(cwd, featureContent, learningsContent);
|
|
255
|
-
const status: 'PASS' | 'FAIL' = exit === 0 && afterDigest === beforeDigest ? 'PASS' : 'FAIL';
|
|
256
|
-
await mod.completeFeatureVerificationReceipt(nodeFsShim, runDir, receipt, { status, inputDigest: afterDigest });
|
|
257
|
-
console.log(`feature-verification-steps: ${featureId} verification ${status} (run ${runId}, log ${logPath})`);
|
|
258
|
-
} finally {
|
|
259
|
-
closeSync(logFd);
|
|
260
|
-
}
|
|
108
|
+
console.log(`feature-verification-steps: ${featureId} ${result.message}`);
|
|
261
109
|
}
|
|
262
110
|
|
|
263
111
|
function main(): void {
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// plugins/sp/scripts/inline-run-dispatch.ts
|
|
5
5
|
import { spawnSync } from "child_process";
|
|
6
|
-
import {
|
|
7
|
-
import { dirname, join, resolve } from "path";
|
|
8
|
-
import { fileURLToPath } from "url";
|
|
6
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9
7
|
|
|
10
8
|
// plugins/sp/lib/env.ts
|
|
11
9
|
function getEnvVar(name, fallback) {
|
|
@@ -14,8 +12,30 @@ function getEnvVar(name, fallback) {
|
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
// plugins/sp/lib/inline-run-app.ts
|
|
17
|
-
|
|
18
|
-
|
|
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, resolveAppEntry2, required) {
|
|
38
|
+
const { entry, portable } = resolveAppEntry2(spurBin);
|
|
19
39
|
const app = await import(entry);
|
|
20
40
|
const missing = portable ? required.filter((name) => typeof app[name] !== "function") : [];
|
|
21
41
|
if (missing.length > 0) {
|
|
@@ -36,28 +56,9 @@ function usage() {
|
|
|
36
56
|
process.exit(2);
|
|
37
57
|
}
|
|
38
58
|
var SAFE_RUN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
39
|
-
function resolveAppEntry(spurBin) {
|
|
40
|
-
const fallback = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
|
|
41
|
-
const candidates = spurBin !== "" ? [spurBin] : [fallback];
|
|
42
|
-
for (const candidate of candidates) {
|
|
43
|
-
const tokens = candidate.split(/\s+/).filter(Boolean);
|
|
44
|
-
const mainModule = [...tokens].reverse().find((t) => t.endsWith(".ts"));
|
|
45
|
-
if (mainModule === undefined || !existsSync(mainModule))
|
|
46
|
-
continue;
|
|
47
|
-
const repoRoot = resolve(dirname(mainModule), "..", "..", "..");
|
|
48
|
-
const appEntry = join(repoRoot, "packages", "app", "src", "index.ts");
|
|
49
|
-
if (existsSync(appEntry))
|
|
50
|
-
return { entry: appEntry, portable: false };
|
|
51
|
-
}
|
|
52
|
-
const entry = fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url));
|
|
53
|
-
if (!existsSync(entry)) {
|
|
54
|
-
throw new Error("inline application bundle is missing \u2014 rebuild/install the sp plugin before running inline");
|
|
55
|
-
}
|
|
56
|
-
return { entry, portable: true };
|
|
57
|
-
}
|
|
58
59
|
async function main() {
|
|
59
60
|
if (!process.versions.bun) {
|
|
60
|
-
const child = spawnSync("bun", [
|
|
61
|
+
const child = spawnSync("bun", [fileURLToPath2(import.meta.url), ...process.argv.slice(2)], {
|
|
61
62
|
stdio: "inherit"
|
|
62
63
|
});
|
|
63
64
|
if (child.error)
|
|
@@ -113,7 +114,7 @@ async function main() {
|
|
|
113
114
|
}
|
|
114
115
|
if (decision.trim() === "" || text === "" === (textFile === ""))
|
|
115
116
|
usage();
|
|
116
|
-
const { app: lib } = await loadInlineApp(spurBin, () => ({ entry:
|
|
117
|
+
const { app: lib } = await loadInlineApp(spurBin, () => ({ entry: fileURLToPath2(new URL("../lib/inline-run.generated.mjs", import.meta.url)), portable }), ["loadSpurConfig"]);
|
|
117
118
|
const spurConfig = await lib.loadSpurConfig(process.cwd(), portable ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined);
|
|
118
119
|
process.exit(await app.runInlineRunDispatchFailure({
|
|
119
120
|
runId,
|
|
@@ -5,13 +5,11 @@
|
|
|
5
5
|
* `loadInlineApp` seam the setup facade uses. It is a SEPARATE script from `inline-run-setup.ts`
|
|
6
6
|
* because it is a separate operation and the ADR-130 glue budget is per script. */
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
-
import { existsSync } from 'node:fs';
|
|
9
|
-
import { dirname, join, resolve } from 'node:path';
|
|
10
8
|
import { fileURLToPath } from 'node:url';
|
|
11
9
|
// Type-only namespace import (erased before bundling; the smoke scanner exempts `import type`).
|
|
12
10
|
import type * as spurApp from '@gobing-ai/spur-app';
|
|
13
11
|
import { getEnvVar } from '../lib/env';
|
|
14
|
-
import { loadInlineApp } from '../lib/inline-run-app';
|
|
12
|
+
import { loadInlineApp, resolveAppEntry } from '../lib/inline-run-app';
|
|
15
13
|
|
|
16
14
|
// EMBEDDED_SPUR_SCHEMAS is declared by the bundle twin, never exported by the barrel.
|
|
17
15
|
type InlineApp = typeof spurApp & { EMBEDDED_SPUR_SCHEMAS?: ReadonlyMap<string, string> };
|
|
@@ -32,24 +30,6 @@ function usage(): never {
|
|
|
32
30
|
const SAFE_RUN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
33
31
|
|
|
34
32
|
/** Resolve the app entry: --spur-bin/SPUR_BIN main module if it proves a repo checkout, else the bundle. */
|
|
35
|
-
function resolveAppEntry(spurBin: string): { entry: string; portable: boolean } {
|
|
36
|
-
const fallback = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
|
|
37
|
-
const candidates = spurBin !== '' ? [spurBin] : [fallback];
|
|
38
|
-
for (const candidate of candidates) {
|
|
39
|
-
const tokens = candidate.split(/\s+/).filter(Boolean);
|
|
40
|
-
// Main module = last path-like token; only a .ts source entry proves a repo checkout.
|
|
41
|
-
const mainModule = [...tokens].reverse().find((t) => t.endsWith('.ts'));
|
|
42
|
-
if (mainModule === undefined || !existsSync(mainModule)) continue;
|
|
43
|
-
const repoRoot = resolve(dirname(mainModule), '..', '..', '..');
|
|
44
|
-
const appEntry = join(repoRoot, 'packages', 'app', 'src', 'index.ts');
|
|
45
|
-
if (existsSync(appEntry)) return { entry: appEntry, portable: false };
|
|
46
|
-
}
|
|
47
|
-
const entry = fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url));
|
|
48
|
-
if (!existsSync(entry)) {
|
|
49
|
-
throw new Error('inline application bundle is missing — rebuild/install the sp plugin before running inline');
|
|
50
|
-
}
|
|
51
|
-
return { entry, portable: true };
|
|
52
|
-
}
|
|
53
33
|
|
|
54
34
|
async function main(): Promise<void> {
|
|
55
35
|
// The portable Node twin has no workspace imports; the app bundle still uses Spur's Bun runtime.
|