@gobing-ai/spur 0.4.9 → 0.4.11
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/rules/README.md +1 -1
- package/config/rules/boundary/env-var-hygiene.yaml +0 -1
- package/config/rules/boundary/sp-script-placement.yaml +3 -3
- package/config/rules/docs/sourced-api-claims.yaml +23 -96
- package/config/workflows/feature-verification.yaml +1 -9
- package/config/workflows/pr-review.yaml +72 -224
- package/config/workflows/task-pipeline.yaml +2 -2
- package/package.json +1 -1
- package/plugins/sp/agents/expert-spur.md +0 -1
- package/plugins/sp/commands/dev-changelog.md +1 -1
- package/plugins/sp/commands/dev-pr-review.md +27 -17
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/brainstorm/SKILL.md +12 -11
- package/plugins/sp/skills/brainstorm/agents/openai.yaml +1 -1
- package/plugins/sp/skills/brainstorm/examples/ideation-example.md +0 -4
- package/plugins/sp/skills/brainstorm/metadata.openclaw +1 -1
- package/plugins/sp/skills/brainstorm/references/workflows.md +4 -14
- package/plugins/sp/skills/code-verification/references/secu-review.md +7 -4
- package/plugins/sp/skills/history-anatomy/references/operations.md +3 -4
- package/plugins/sp/skills/history-anatomy/references/report-contract.md +2 -3
- package/plugins/sp/skills/issue-finding/SKILL.md +1 -1
- package/plugins/sp/skills/parallel-execution/SKILL.md +1 -1
- package/plugins/sp/skills/parallel-execution/references/result-synthesis.md +9 -10
- package/plugins/sp/skills/pr-reviewing/SKILL.md +157 -210
- package/plugins/sp/skills/reverse-engineering/SKILL.md +4 -6
- package/plugins/sp/skills/session-review/SKILL.md +10 -26
- package/plugins/sp/skills/source-driven-development/SKILL.md +35 -31
- package/plugins/sp/skills/spur-cli/SKILL.md +1 -1
- package/plugins/sp/skills/spur-cli/references/agent.md +5 -2
- package/plugins/sp/skills/spur-cli/references/builder.md +86 -13
- package/plugins/sp/skills/spur-cli/references/features.md +1 -1
- package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +1 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +5 -6
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +1 -1
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +3 -2
- package/spur.js +1280 -417
- package/web/_astro/{BoardApp.CaNvQBD_.js → BoardApp.B5rTVNd_.js} +1 -1
- package/web/_astro/{BoardApp.C5jnkkKP.js → BoardApp.BMprmPRc.js} +141 -141
- package/web/_astro/{TaskDetail.DGKndhI8.js → TaskDetail.Ddsur8qd.js} +1 -1
- package/web/_astro/{arc.DHrwdg3G.js → arc.DLqVVBxP.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.pvKpK1s5.js → architectureDiagram-3BPJPVTR.C-Pk3uAi.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.BQmRcZsS.js → blockDiagram-GPEHLZMM.BRO1i_Nm.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.CCLjHeH1.js → c4Diagram-AAUBKEIU.BU_cWu6e.js} +1 -1
- package/web/_astro/channel.Bs3Illhy.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.DL6Yuoma.js → chunk-2J33WTMH.D2VXiQ2t.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.CzlyEI7l.js → chunk-4BX2VUAB.BcHT772J.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.B60EWxp1.js → chunk-55IACEB6.DhDtICmo.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.c7v_fRLo.js → chunk-727SXJPM.BoKHp7gV.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.8BxLWdDc.js → chunk-AQP2D5EJ.Chu4JRpE.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DmKuxGFT.js → chunk-FMBD7UC4.wHdwKPlq.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.1Zwd4mqV.js → chunk-ND2GUHAM.ffN-M1JB.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.BvYLu-A1.js → chunk-QZHKN3VN.FRubwg4S.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.CEqRt0WU.js → classDiagram-4FO5ZUOK.c22tQsc_.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.CEqRt0WU.js → classDiagram-v2-Q7XG4LA2.c22tQsc_.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.C7JiUdj2.js → cose-bilkent-S5V4N54A.B9c6fiSj.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.B6mZ-1be.js → cynefin-OW5HDTMX.DrZsrUHl.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.BuryfDy6.js → dagre-BM42HDAG.DG1U4b3y.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.dao39YQM.js → diagram-2AECGRRQ.CH15b-K3.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.lnGZe4At.js → diagram-5GNKFQAL.BH1j7twR.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.uWfhzEfT.js → diagram-KO2AKTUF.q2zCkbAj.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.ppq8-198.js → diagram-LMA3HP47.BLaaZnHV.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.CKSCqDt5.js → diagram-OG6HWLK6.BcpRdQwI.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.sFSf2yBN.js → erDiagram-TEJ5UH35.D7KBlqfM.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.BJQjKLbz.js → flowDiagram-I6XJVG4X.D_gmuRqg.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.DAHKNj2N.js → ganttDiagram-6RSMTGT7.DzgWYniD.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.BlmzcW_O.js → gitGraphDiagram-PVQCEYII.576i3hm3.js} +1 -1
- package/web/_astro/index.DiC1a6lh.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.DGhjaqsi.js → infoDiagram-5YYISTIA.DMCclBiz.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.gKIpgt6F.js → ishikawaDiagram-YF4QCWOH.9Kyca4SH.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.DG3tmP0R.js → journeyDiagram-JHISSGLW.chbdG-8q.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.CgHDSxkR.js → kanban-definition-UN3LZRKU.Bds54-e0.js} +1 -1
- package/web/_astro/{linear.BuGEpcuM.js → linear.D5ND9GN_.js} +1 -1
- package/web/_astro/{mermaid.core.DBVqkWBp.js → mermaid.core.D9N_PGbj.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.D-kOcji9.js → mindmap-definition-RKZ34NQL.t0xop5vp.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.BvGPwtZt.js → pieDiagram-4H26LBE5.DqgKh6hM.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.BKb_9FV6.js → quadrantDiagram-W4KKPZXB.DeuHVi2t.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.Cx0fvD8v.js → requirementDiagram-4Y6WPE33.BdYFSG4W.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.fatULi0W.js → sankeyDiagram-5OEKKPKP.k1LPPETJ.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.ChHyKqco.js → sequenceDiagram-3UESZ5HK.DdyfpzoG.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.BdUpKWyj.js → stateDiagram-AJRCARHV.DSEI8il0.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.fmJxqIm5.js → stateDiagram-v2-BHNVJYJU.CoMnQRKo.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.BayDn5-V.js → timeline-definition-PNZ67QCA.DM1AHNm-.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.Bm2Aggwa.js → vennDiagram-CIIHVFJN.C-cJmVtl.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.DI75O-uG.js → wardleyDiagram-YWT4CUSO.D0xMzKPb.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.DL90E7Q7.js → xychartDiagram-2RQKCTM6.BH5xJcEq.js} +1 -1
- package/web/index.html +2 -2
- package/web/_astro/channel.C7wG9RxA.js +0 -1
- package/web/_astro/index.BCY1JJHd.css +0 -1
package/spur.js
CHANGED
|
@@ -14967,7 +14967,6 @@ var init_finding_codes = __esm(() => {
|
|
|
14967
14967
|
"L4.feature-receipt-run",
|
|
14968
14968
|
"L4.feature-receipt-stale",
|
|
14969
14969
|
"L4.feature-receipt-contract",
|
|
14970
|
-
"L4.feature-receipt-confidence",
|
|
14971
14970
|
"precheck-size",
|
|
14972
14971
|
"precheck-evidence",
|
|
14973
14972
|
"inventory-coverage",
|
|
@@ -15040,7 +15039,6 @@ var init_finding_codes = __esm(() => {
|
|
|
15040
15039
|
L4_FEATURE_RECEIPT_RUN: "L4.feature-receipt-run",
|
|
15041
15040
|
L4_FEATURE_RECEIPT_STALE: "L4.feature-receipt-stale",
|
|
15042
15041
|
L4_FEATURE_RECEIPT_CONTRACT: "L4.feature-receipt-contract",
|
|
15043
|
-
L4_FEATURE_RECEIPT_CONFIDENCE: "L4.feature-receipt-confidence",
|
|
15044
15042
|
PRECHECK_SIZE: "precheck-size",
|
|
15045
15043
|
PRECHECK_EVIDENCE: "precheck-evidence",
|
|
15046
15044
|
INVENTORY_COVERAGE: "inventory-coverage",
|
|
@@ -15065,6 +15063,7 @@ var init_projects = __esm(() => {
|
|
|
15065
15063
|
projectEntrySchema = object2({
|
|
15066
15064
|
name: string2().min(1),
|
|
15067
15065
|
path: string2().min(1),
|
|
15066
|
+
host: string2().optional(),
|
|
15068
15067
|
port: number2().int().nonnegative().default(0)
|
|
15069
15068
|
});
|
|
15070
15069
|
projectsFileSchema = object2({
|
|
@@ -85638,6 +85637,12 @@ function normalizeProjectPath(pathInput) {
|
|
|
85638
85637
|
function setPortProbeForTests(probe) {
|
|
85639
85638
|
testPortProbe = probe;
|
|
85640
85639
|
}
|
|
85640
|
+
function isLocalHost(host) {
|
|
85641
|
+
if (!host)
|
|
85642
|
+
return true;
|
|
85643
|
+
const trimmed = host.trim().toLowerCase();
|
|
85644
|
+
return trimmed === "localhost" || trimmed === "127.0.0.1" || trimmed === "::1" || trimmed === "";
|
|
85645
|
+
}
|
|
85641
85646
|
function classifyPortBindError(err) {
|
|
85642
85647
|
const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
|
|
85643
85648
|
if (code === "EADDRINUSE" || code === "EADDRNOTAVAIL") {
|
|
@@ -85648,31 +85653,34 @@ function classifyPortBindError(err) {
|
|
|
85648
85653
|
async function isPortLiveOnHost(port, host, timeoutMs) {
|
|
85649
85654
|
return new Promise((resolve) => {
|
|
85650
85655
|
const socket = connect({ port, host, timeout: timeoutMs });
|
|
85651
|
-
socket.
|
|
85652
|
-
|
|
85653
|
-
|
|
85654
|
-
|
|
85655
|
-
|
|
85656
|
-
|
|
85657
|
-
resolve(false);
|
|
85658
|
-
});
|
|
85659
|
-
socket.on("error", () => {
|
|
85656
|
+
socket.unref();
|
|
85657
|
+
let settled = false;
|
|
85658
|
+
const cleanup = (res) => {
|
|
85659
|
+
if (settled)
|
|
85660
|
+
return;
|
|
85661
|
+
settled = true;
|
|
85660
85662
|
socket.destroy();
|
|
85661
|
-
resolve(
|
|
85662
|
-
}
|
|
85663
|
+
resolve(res);
|
|
85664
|
+
};
|
|
85665
|
+
socket.on("connect", () => cleanup(true));
|
|
85666
|
+
socket.on("timeout", () => cleanup(false));
|
|
85667
|
+
socket.on("error", () => cleanup(false));
|
|
85663
85668
|
});
|
|
85664
85669
|
}
|
|
85665
|
-
async function isPortLive(port, timeoutMs = 200) {
|
|
85670
|
+
async function isPortLive(port, timeoutMs = 200, host = "localhost") {
|
|
85666
85671
|
if (testPortProbe) {
|
|
85667
|
-
return await testPortProbe(port) === "in-use";
|
|
85672
|
+
return await testPortProbe(port, host) === "in-use";
|
|
85668
85673
|
}
|
|
85669
|
-
if (port <= 0)
|
|
85674
|
+
if (port <= 0 || port > 65535)
|
|
85670
85675
|
return false;
|
|
85671
|
-
if (
|
|
85672
|
-
|
|
85673
|
-
|
|
85674
|
-
|
|
85675
|
-
|
|
85676
|
+
if (isLocalHost(host)) {
|
|
85677
|
+
if (await isPortLiveOnHost(port, "127.0.0.1", timeoutMs))
|
|
85678
|
+
return true;
|
|
85679
|
+
if (await isPortLiveOnHost(port, "::1", timeoutMs))
|
|
85680
|
+
return true;
|
|
85681
|
+
return false;
|
|
85682
|
+
}
|
|
85683
|
+
return isPortLiveOnHost(port, host, timeoutMs);
|
|
85676
85684
|
}
|
|
85677
85685
|
async function probePort(port) {
|
|
85678
85686
|
if (testPortProbe) {
|
|
@@ -85943,10 +85951,12 @@ class ProjectRegistry {
|
|
|
85943
85951
|
const data = this.readRaw();
|
|
85944
85952
|
let changed = false;
|
|
85945
85953
|
for (const project of data.projects) {
|
|
85946
|
-
|
|
85947
|
-
|
|
85948
|
-
project.path
|
|
85949
|
-
|
|
85954
|
+
if (isLocalHost(project.host)) {
|
|
85955
|
+
const normalized = normalizeProjectPath(project.path);
|
|
85956
|
+
if (normalized !== project.path) {
|
|
85957
|
+
project.path = normalized;
|
|
85958
|
+
changed = true;
|
|
85959
|
+
}
|
|
85950
85960
|
}
|
|
85951
85961
|
}
|
|
85952
85962
|
if (changed) {
|
|
@@ -85957,13 +85967,17 @@ class ProjectRegistry {
|
|
|
85957
85967
|
async list() {
|
|
85958
85968
|
await this.healTildePaths();
|
|
85959
85969
|
await this.refreshProjects();
|
|
85960
|
-
await this.healStale();
|
|
85961
85970
|
return this.readRaw().projects;
|
|
85962
85971
|
}
|
|
85963
85972
|
async getByPath(pathInput) {
|
|
85964
85973
|
const normalized = normalizeProjectPath(pathInput);
|
|
85965
85974
|
const projects = await this.list();
|
|
85966
|
-
return projects.find((p) =>
|
|
85975
|
+
return projects.find((p) => {
|
|
85976
|
+
if (isLocalHost(p.host)) {
|
|
85977
|
+
return normalizeProjectPath(p.path) === normalized;
|
|
85978
|
+
}
|
|
85979
|
+
return p.path === pathInput || p.path === normalized;
|
|
85980
|
+
});
|
|
85967
85981
|
}
|
|
85968
85982
|
async getByName(nameInput) {
|
|
85969
85983
|
const trimmed = nameInput.trim();
|
|
@@ -85973,11 +85987,20 @@ class ProjectRegistry {
|
|
|
85973
85987
|
async upsert(entry) {
|
|
85974
85988
|
return this.withLock(async () => {
|
|
85975
85989
|
const currentData = this.readRaw();
|
|
85976
|
-
const
|
|
85977
|
-
const
|
|
85990
|
+
const host = entry.host && entry.host.trim() !== "" ? entry.host.trim() : "localhost";
|
|
85991
|
+
const isLocal = isLocalHost(host);
|
|
85992
|
+
const normalizedPath = isLocal ? normalizeProjectPath(entry.path) : entry.path;
|
|
85993
|
+
const index = currentData.projects.findIndex((p) => {
|
|
85994
|
+
const pHost = p.host && p.host.trim() !== "" ? p.host.trim() : "localhost";
|
|
85995
|
+
if (isLocal) {
|
|
85996
|
+
return isLocalHost(pHost) && normalizeProjectPath(p.path) === normalizedPath;
|
|
85997
|
+
}
|
|
85998
|
+
return pHost.toLowerCase() === host.toLowerCase() && p.path === normalizedPath;
|
|
85999
|
+
});
|
|
85978
86000
|
const newEntry = {
|
|
85979
86001
|
name: entry.name,
|
|
85980
86002
|
path: normalizedPath,
|
|
86003
|
+
host,
|
|
85981
86004
|
port: entry.port ?? 0
|
|
85982
86005
|
};
|
|
85983
86006
|
if (index >= 0 && currentData.projects[index]) {
|
|
@@ -85985,13 +86008,20 @@ class ProjectRegistry {
|
|
|
85985
86008
|
currentData.projects[index] = {
|
|
85986
86009
|
...existing,
|
|
85987
86010
|
name: entry.name,
|
|
86011
|
+
host: entry.host ?? existing.host ?? "localhost",
|
|
85988
86012
|
port: entry.port ?? existing.port
|
|
85989
86013
|
};
|
|
85990
86014
|
} else {
|
|
85991
86015
|
currentData.projects.push(newEntry);
|
|
85992
86016
|
}
|
|
85993
86017
|
this.writeRaw(currentData);
|
|
85994
|
-
const res = currentData.projects.find((p) =>
|
|
86018
|
+
const res = currentData.projects.find((p) => {
|
|
86019
|
+
const pHost = p.host && p.host.trim() !== "" ? p.host.trim() : "localhost";
|
|
86020
|
+
if (isLocal) {
|
|
86021
|
+
return isLocalHost(pHost) && normalizeProjectPath(p.path) === normalizedPath;
|
|
86022
|
+
}
|
|
86023
|
+
return pHost.toLowerCase() === host.toLowerCase() && p.path === normalizedPath;
|
|
86024
|
+
});
|
|
85995
86025
|
if (!res) {
|
|
85996
86026
|
throw new Error(`Failed to upsert project: ${normalizedPath}`);
|
|
85997
86027
|
}
|
|
@@ -86004,9 +86034,10 @@ class ProjectRegistry {
|
|
|
86004
86034
|
const normalized = normalizeProjectPath(nameOrPath);
|
|
86005
86035
|
const initialLen = currentData.projects.length;
|
|
86006
86036
|
currentData.projects = currentData.projects.filter((p) => {
|
|
86007
|
-
const pNorm = normalizeProjectPath(p.path);
|
|
86037
|
+
const pNorm = isLocalHost(p.host) ? normalizeProjectPath(p.path) : p.path;
|
|
86038
|
+
const pathMatch = pNorm === normalized || p.path === nameOrPath;
|
|
86008
86039
|
const nameMatch = p.name.toLowerCase() === nameOrPath.trim().toLowerCase();
|
|
86009
|
-
return
|
|
86040
|
+
return !pathMatch && !nameMatch;
|
|
86010
86041
|
});
|
|
86011
86042
|
if (currentData.projects.length !== initialLen) {
|
|
86012
86043
|
this.writeRaw(currentData);
|
|
@@ -86019,7 +86050,10 @@ class ProjectRegistry {
|
|
|
86019
86050
|
return this.withLock(async () => {
|
|
86020
86051
|
const currentData = this.readRaw();
|
|
86021
86052
|
const normalized = normalizeProjectPath(pathInput);
|
|
86022
|
-
const project = currentData.projects.find((p) =>
|
|
86053
|
+
const project = currentData.projects.find((p) => {
|
|
86054
|
+
const pNorm = isLocalHost(p.host) ? normalizeProjectPath(p.path) : p.path;
|
|
86055
|
+
return pNorm === normalized || p.path === pathInput;
|
|
86056
|
+
});
|
|
86023
86057
|
if (project) {
|
|
86024
86058
|
project.port = port;
|
|
86025
86059
|
this.writeRaw(currentData);
|
|
@@ -86057,10 +86091,13 @@ class ProjectRegistry {
|
|
|
86057
86091
|
return this.withLock(async () => {
|
|
86058
86092
|
const data = this.readRaw();
|
|
86059
86093
|
let changed = false;
|
|
86094
|
+
const resetProjects = [];
|
|
86060
86095
|
for (const project of data.projects) {
|
|
86061
86096
|
if (project.port > 0) {
|
|
86062
|
-
const
|
|
86097
|
+
const host = project.host ?? "localhost";
|
|
86098
|
+
const live = await isPortLive(project.port, 200, host);
|
|
86063
86099
|
if (!live) {
|
|
86100
|
+
resetProjects.push({ ...project });
|
|
86064
86101
|
project.port = 0;
|
|
86065
86102
|
changed = true;
|
|
86066
86103
|
}
|
|
@@ -86069,6 +86106,7 @@ class ProjectRegistry {
|
|
|
86069
86106
|
if (changed) {
|
|
86070
86107
|
this.writeRaw(data);
|
|
86071
86108
|
}
|
|
86109
|
+
return resetProjects;
|
|
86072
86110
|
});
|
|
86073
86111
|
}
|
|
86074
86112
|
async refreshProjects(options) {
|
|
@@ -86078,9 +86116,15 @@ class ProjectRegistry {
|
|
|
86078
86116
|
await Promise.resolve().then(() => init_project_stop());
|
|
86079
86117
|
const removed = await this.withLock(async () => {
|
|
86080
86118
|
const data = this.readRaw();
|
|
86081
|
-
const purged = data.projects.filter((p) =>
|
|
86119
|
+
const purged = data.projects.filter((p) => {
|
|
86120
|
+
if (isLocalHost(p.host)) {
|
|
86121
|
+
return !existsSync8(normalizeProjectPath(p.path));
|
|
86122
|
+
}
|
|
86123
|
+
return false;
|
|
86124
|
+
});
|
|
86082
86125
|
if (purged.length > 0) {
|
|
86083
|
-
|
|
86126
|
+
const purgedPaths = new Set(purged.map((p) => `${p.host ?? "localhost"}:${normalizeProjectPath(p.path)}`));
|
|
86127
|
+
data.projects = data.projects.filter((p) => !purgedPaths.has(`${p.host ?? "localhost"}:${normalizeProjectPath(p.path)}`));
|
|
86084
86128
|
this.writeRaw(data);
|
|
86085
86129
|
}
|
|
86086
86130
|
return purged;
|
|
@@ -86088,7 +86132,7 @@ class ProjectRegistry {
|
|
|
86088
86132
|
const terminated = [];
|
|
86089
86133
|
if (terminate) {
|
|
86090
86134
|
for (const project of removed) {
|
|
86091
|
-
if (project.port > 0) {
|
|
86135
|
+
if (project.port > 0 && isLocalHost(project.host)) {
|
|
86092
86136
|
const stop = await stopRegisteredProject(project, {
|
|
86093
86137
|
processExecutor: executor,
|
|
86094
86138
|
killTimeoutMs,
|
|
@@ -86101,11 +86145,13 @@ class ProjectRegistry {
|
|
|
86101
86145
|
}
|
|
86102
86146
|
}
|
|
86103
86147
|
}
|
|
86148
|
+
const resetPorts = await this.healStale();
|
|
86104
86149
|
return {
|
|
86105
86150
|
removed,
|
|
86106
86151
|
terminated,
|
|
86107
86152
|
purgedProjects: removed,
|
|
86108
|
-
terminatedProcesses: terminated
|
|
86153
|
+
terminatedProcesses: terminated,
|
|
86154
|
+
resetPorts
|
|
86109
86155
|
};
|
|
86110
86156
|
}
|
|
86111
86157
|
}
|
|
@@ -89363,22 +89409,11 @@ async function completeFeatureVerificationReceipt(fs, runDir, running, options)
|
|
|
89363
89409
|
if (running.status !== "RUNNING") {
|
|
89364
89410
|
throw new Error(`receipt for run ${running.runId} is already ${running.status}`);
|
|
89365
89411
|
}
|
|
89366
|
-
if (options.confidence !== undefined) {
|
|
89367
|
-
if (!FEATURE_RECEIPT_CONFIDENCE_LEVELS.includes(options.confidence)) {
|
|
89368
|
-
throw new Error(`confidence must be HIGH, MEDIUM or LOW (got ${JSON.stringify(options.confidence)})`);
|
|
89369
|
-
}
|
|
89370
|
-
if (options.confidenceBasis === undefined || options.confidenceBasis.length === 0) {
|
|
89371
|
-
throw new Error(`confidence ${options.confidence} requires a non-empty confidenceBasis`);
|
|
89372
|
-
}
|
|
89373
|
-
}
|
|
89374
89412
|
const receipt = {
|
|
89375
89413
|
...running,
|
|
89376
89414
|
status: options.status,
|
|
89377
89415
|
inputDigest: options.inputDigest ?? running.inputDigest,
|
|
89378
|
-
completedAt: options.completedAt ?? new Date().toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
89379
|
-
...options.confidence === undefined ? {} : { confidence: options.confidence },
|
|
89380
|
-
...options.confidenceBasis === undefined ? {} : { confidenceBasis: [...options.confidenceBasis] },
|
|
89381
|
-
...options.confidenceAcknowledged === true ? { confidenceAcknowledged: true } : {}
|
|
89416
|
+
completedAt: options.completedAt ?? new Date().toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
89382
89417
|
};
|
|
89383
89418
|
const paths = featureReceiptPaths(runDir, receipt.featureId, receipt.runId);
|
|
89384
89419
|
await fs.ensureDir(runDir);
|
|
@@ -89406,23 +89441,6 @@ function parseFeatureVerificationReceipt(raw) {
|
|
|
89406
89441
|
if (typeof r.featureId !== "string" || typeof r.runId !== "string" || typeof r.workdir !== "string" || typeof v?.name !== "string" || typeof v?.sourcePath !== "string" || typeof v?.layer !== "string" || typeof v?.definitionDigest !== "string" || typeof r.verificationCmd !== "string" || typeof r.inputDigest !== "string" || r.status !== "RUNNING" && r.status !== "PASS" && r.status !== "FAIL" || typeof r.startedAt !== "string" || r.completedAt !== null && typeof r.completedAt !== "string") {
|
|
89407
89442
|
throw new Error("required fields missing or mistyped");
|
|
89408
89443
|
}
|
|
89409
|
-
if (r.confidence !== undefined) {
|
|
89410
|
-
if (!FEATURE_RECEIPT_CONFIDENCE_LEVELS.includes(r.confidence)) {
|
|
89411
|
-
throw new Error(`confidence must be HIGH, MEDIUM or LOW (got ${JSON.stringify(r.confidence)})`);
|
|
89412
|
-
}
|
|
89413
|
-
if (!Array.isArray(r.confidenceBasis) || r.confidenceBasis.length === 0) {
|
|
89414
|
-
throw new Error("confidence requires a non-empty confidenceBasis");
|
|
89415
|
-
}
|
|
89416
|
-
if (!r.confidenceBasis.every((entry) => typeof entry === "string" && entry.length > 0)) {
|
|
89417
|
-
throw new Error("confidenceBasis entries must be non-empty strings");
|
|
89418
|
-
}
|
|
89419
|
-
}
|
|
89420
|
-
if (r.confidenceBasis !== undefined && !Array.isArray(r.confidenceBasis)) {
|
|
89421
|
-
throw new Error("confidenceBasis must be an array");
|
|
89422
|
-
}
|
|
89423
|
-
if (r.confidenceAcknowledged !== undefined && typeof r.confidenceAcknowledged !== "boolean") {
|
|
89424
|
-
throw new Error("confidenceAcknowledged must be a boolean");
|
|
89425
|
-
}
|
|
89426
89444
|
return parsed;
|
|
89427
89445
|
}
|
|
89428
89446
|
async function readReceiptCopy(fs, path, label) {
|
|
@@ -89560,15 +89578,6 @@ async function validateFeatureVerificationReceipt(fs, options) {
|
|
|
89560
89578
|
detail: `receipt digest ${latest.inputDigest} \u2260 current inputs ${options.currentDigest} \u2014 tree, spec, learnings or check contract changed after the pass`
|
|
89561
89579
|
};
|
|
89562
89580
|
}
|
|
89563
|
-
const confidence = latest.confidence;
|
|
89564
|
-
const certified = confidence === "HIGH" || confidence === "MEDIUM";
|
|
89565
|
-
if (!certified && latest.confidenceAcknowledged !== true) {
|
|
89566
|
-
return {
|
|
89567
|
-
ok: false,
|
|
89568
|
-
reason: "unacknowledged-low-confidence",
|
|
89569
|
-
detail: confidence === undefined ? "receipt records a PASS with no confidence level (written before the field existed) \u2014 re-run the feature-scoped verification pass, which derives HIGH or MEDIUM from its own evidence" : `receipt records a ${confidence} confidence${(latest.confidenceBasis ?? []).length > 0 ? ` (basis: ${(latest.confidenceBasis ?? []).join("; ")})` : ""} \u2014 re-run the pass where the level can be HIGH or MEDIUM, or re-run it with the acknowledgement (ackLowConfidence) when LOW is the honest level`
|
|
89570
|
-
};
|
|
89571
|
-
}
|
|
89572
89581
|
return { ok: true, receipt: latest };
|
|
89573
89582
|
}
|
|
89574
89583
|
async function captureFeatureReceiptDigest(repoRoot, featureContent, learningsContent) {
|
|
@@ -89577,12 +89586,11 @@ async function captureFeatureReceiptDigest(repoRoot, featureContent, learningsCo
|
|
|
89577
89586
|
function isProofCaptureError(err) {
|
|
89578
89587
|
return err instanceof ProofCaptureError;
|
|
89579
89588
|
}
|
|
89580
|
-
var FEATURE_RECEIPT_SCHEMA_VERSION = 1, DEFAULT_FEATURE_VERIFICATION_CMD = "bun run spur-check-feature",
|
|
89589
|
+
var FEATURE_RECEIPT_SCHEMA_VERSION = 1, DEFAULT_FEATURE_VERIFICATION_CMD = "bun run spur-check-feature", RECEIPT_ID_PATTERN;
|
|
89581
89590
|
var init_feature_verification_receipt = __esm(() => {
|
|
89582
89591
|
init_durable_plane_ignore();
|
|
89583
89592
|
init_run_path();
|
|
89584
89593
|
init_proof_input_fingerprint();
|
|
89585
|
-
FEATURE_RECEIPT_CONFIDENCE_LEVELS = ["HIGH", "MEDIUM", "LOW"];
|
|
89586
89594
|
RECEIPT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
89587
89595
|
});
|
|
89588
89596
|
|
|
@@ -92018,7 +92026,6 @@ var init_planning_check_base = __esm(() => {
|
|
|
92018
92026
|
FINDING_CODES.L4_FEATURE_RECEIPT_RUN,
|
|
92019
92027
|
FINDING_CODES.L4_FEATURE_RECEIPT_STALE,
|
|
92020
92028
|
FINDING_CODES.L4_FEATURE_RECEIPT_CONTRACT,
|
|
92021
|
-
FINDING_CODES.L4_FEATURE_RECEIPT_CONFIDENCE,
|
|
92022
92029
|
FINDING_CODES.L3_REVIEW_TESTING_CONTRADICTION,
|
|
92023
92030
|
FINDING_CODES.L4_ROLLUP_SUBTASKS_OPEN,
|
|
92024
92031
|
FINDING_CODES.L4_ROLLUP_MISSING_ROSTER,
|
|
@@ -93604,8 +93611,7 @@ var init_feature_check = __esm(() => {
|
|
|
93604
93611
|
failed: FINDING_CODES.L4_FEATURE_RECEIPT_FAILED,
|
|
93605
93612
|
run: FINDING_CODES.L4_FEATURE_RECEIPT_RUN,
|
|
93606
93613
|
stale: FINDING_CODES.L4_FEATURE_RECEIPT_STALE,
|
|
93607
|
-
"contract-mismatch": FINDING_CODES.L4_FEATURE_RECEIPT_CONTRACT
|
|
93608
|
-
"unacknowledged-low-confidence": FINDING_CODES.L4_FEATURE_RECEIPT_CONFIDENCE
|
|
93614
|
+
"contract-mismatch": FINDING_CODES.L4_FEATURE_RECEIPT_CONTRACT
|
|
93609
93615
|
};
|
|
93610
93616
|
findings.push({
|
|
93611
93617
|
layer: "L4",
|
|
@@ -101835,39 +101841,6 @@ var init_proof_fingerprint = __esm(() => {
|
|
|
101835
101841
|
ACCEPTED_OPTION_KEYS = ["expect", "featureFile", "taskFile", "var"];
|
|
101836
101842
|
});
|
|
101837
101843
|
|
|
101838
|
-
// ../../packages/app/src/workflow/actions/response-validate.ts
|
|
101839
|
-
class ResponseValidateActionRunner {
|
|
101840
|
-
kind = KIND14;
|
|
101841
|
-
engine;
|
|
101842
|
-
constructor(engine) {
|
|
101843
|
-
this.engine = engine;
|
|
101844
|
-
}
|
|
101845
|
-
async execute(options, _context) {
|
|
101846
|
-
const text = asString5(options.text);
|
|
101847
|
-
if (text === undefined) {
|
|
101848
|
-
return {
|
|
101849
|
-
ok: false,
|
|
101850
|
-
error: "response.validate: text is required (use a template like {{ steps.generate.answer }})"
|
|
101851
|
-
};
|
|
101852
|
-
}
|
|
101853
|
-
const result = this.engine.validate(text);
|
|
101854
|
-
return {
|
|
101855
|
-
ok: result.ok,
|
|
101856
|
-
data: {
|
|
101857
|
-
reason: result.reason,
|
|
101858
|
-
...result.issues !== undefined ? { issues: result.issues } : {}
|
|
101859
|
-
},
|
|
101860
|
-
error: result.ok ? undefined : result.reason
|
|
101861
|
-
};
|
|
101862
|
-
}
|
|
101863
|
-
}
|
|
101864
|
-
function asString5(value) {
|
|
101865
|
-
if (value === undefined || value === null)
|
|
101866
|
-
return;
|
|
101867
|
-
return String(value);
|
|
101868
|
-
}
|
|
101869
|
-
var KIND14 = "response.validate";
|
|
101870
|
-
|
|
101871
101844
|
// ../../packages/app/src/workflow/utils.ts
|
|
101872
101845
|
var identity5 = (s) => s, NO_COLOR;
|
|
101873
101846
|
var init_utils3 = __esm(() => {
|
|
@@ -101883,15 +101856,15 @@ var init_utils3 = __esm(() => {
|
|
|
101883
101856
|
|
|
101884
101857
|
// ../../packages/app/src/workflow/actions/rule-check.ts
|
|
101885
101858
|
class RuleCheckActionRunner {
|
|
101886
|
-
kind =
|
|
101859
|
+
kind = KIND14;
|
|
101887
101860
|
ruleService;
|
|
101888
101861
|
constructor(ruleService) {
|
|
101889
101862
|
this.ruleService = ruleService;
|
|
101890
101863
|
}
|
|
101891
101864
|
async execute(options, _context) {
|
|
101892
|
-
const preset =
|
|
101893
|
-
const rule =
|
|
101894
|
-
const failOn =
|
|
101865
|
+
const preset = asString5(options.preset) ?? "recommended-pre-check";
|
|
101866
|
+
const rule = asString5(options.rule);
|
|
101867
|
+
const failOn = asString5(options.failOn) ?? "error";
|
|
101895
101868
|
const result = await this.ruleService.evaluate({
|
|
101896
101869
|
preset,
|
|
101897
101870
|
failOn,
|
|
@@ -101913,12 +101886,12 @@ class RuleCheckActionRunner {
|
|
|
101913
101886
|
};
|
|
101914
101887
|
}
|
|
101915
101888
|
}
|
|
101916
|
-
function
|
|
101889
|
+
function asString5(value) {
|
|
101917
101890
|
if (value === undefined || value === null)
|
|
101918
101891
|
return;
|
|
101919
101892
|
return String(value);
|
|
101920
101893
|
}
|
|
101921
|
-
var
|
|
101894
|
+
var KIND14 = "rule.check";
|
|
101922
101895
|
var init_rule_check = __esm(() => {
|
|
101923
101896
|
init_utils3();
|
|
101924
101897
|
});
|
|
@@ -101986,7 +101959,7 @@ class RunArtifactActionRunner {
|
|
|
101986
101959
|
fileSystem;
|
|
101987
101960
|
artifactDao;
|
|
101988
101961
|
processExecutor;
|
|
101989
|
-
kind =
|
|
101962
|
+
kind = KIND15;
|
|
101990
101963
|
constructor(getDb, fileSystem = createNodeFileSystem2(), artifactDao, processExecutor) {
|
|
101991
101964
|
this.getDb = getDb;
|
|
101992
101965
|
this.fileSystem = fileSystem;
|
|
@@ -102012,7 +101985,7 @@ class RunArtifactActionRunner {
|
|
|
102012
101985
|
if (proofBinding !== undefined && proofBinding !== "current") {
|
|
102013
101986
|
return {
|
|
102014
101987
|
ok: false,
|
|
102015
|
-
error: `${
|
|
101988
|
+
error: `${KIND15}: unsupported proofBinding "${String(proofBinding)}" - only "current" is defined (ADR-071)`
|
|
102016
101989
|
};
|
|
102017
101990
|
}
|
|
102018
101991
|
const workdir = context.workdir ?? process.cwd();
|
|
@@ -102046,7 +102019,7 @@ class RunArtifactActionRunner {
|
|
|
102046
102019
|
try {
|
|
102047
102020
|
registeredPath = await persistDurableArtifact(normalized, workdir, context.runId, this.fileSystem, !requireExisting);
|
|
102048
102021
|
} catch (error) {
|
|
102049
|
-
return { ok: false, error: `${
|
|
102022
|
+
return { ok: false, error: `${KIND15}: durable persist failed: ${error.message}` };
|
|
102050
102023
|
}
|
|
102051
102024
|
}
|
|
102052
102025
|
let dao = this.artifactDao;
|
|
@@ -102077,24 +102050,24 @@ class RunArtifactActionRunner {
|
|
|
102077
102050
|
if (artifactKind !== "verify-verdict") {
|
|
102078
102051
|
return {
|
|
102079
102052
|
ok: false,
|
|
102080
|
-
error: `${
|
|
102053
|
+
error: `${KIND15}: proofBinding "current" is only supported for the verify-verdict artifact kind (got "${artifactKind}")`
|
|
102081
102054
|
};
|
|
102082
102055
|
}
|
|
102083
102056
|
if (!this.getDb) {
|
|
102084
102057
|
return {
|
|
102085
102058
|
ok: false,
|
|
102086
|
-
error: `${
|
|
102059
|
+
error: `${KIND15}: proofBinding "current" requires the composed database context (RunDao run identity) \u2014 refusing an unverifiable binding`
|
|
102087
102060
|
};
|
|
102088
102061
|
}
|
|
102089
102062
|
if (typeof options.taskFile !== "string" || options.taskFile.trim() === "") {
|
|
102090
102063
|
return {
|
|
102091
102064
|
ok: false,
|
|
102092
|
-
error: `${
|
|
102065
|
+
error: `${KIND15}: proofBinding "current" requires a non-empty "taskFile" option \u2014 the fresh digest must cover the canonical task spec`
|
|
102093
102066
|
};
|
|
102094
102067
|
}
|
|
102095
102068
|
const inputs = await readProofInputContents(this.fileSystem, workdir, options);
|
|
102096
102069
|
if (!inputs.ok) {
|
|
102097
|
-
return { ok: false, error: `${
|
|
102070
|
+
return { ok: false, error: `${KIND15}: ${inputs.error}` };
|
|
102098
102071
|
}
|
|
102099
102072
|
let digest;
|
|
102100
102073
|
try {
|
|
@@ -102106,19 +102079,19 @@ class RunArtifactActionRunner {
|
|
|
102106
102079
|
fileSystem: this.fileSystem
|
|
102107
102080
|
});
|
|
102108
102081
|
} catch (error) {
|
|
102109
|
-
return { ok: false, error: `${
|
|
102082
|
+
return { ok: false, error: `${KIND15}: fresh proof capture failed: ${error.message}` };
|
|
102110
102083
|
}
|
|
102111
102084
|
const varDigest = context.vars.proofDigestNow ?? context.vars.proofDigest;
|
|
102112
102085
|
if (typeof varDigest !== "string" || !PROOF_DIGEST_RE.test(varDigest)) {
|
|
102113
102086
|
return {
|
|
102114
102087
|
ok: false,
|
|
102115
|
-
error: `${
|
|
102088
|
+
error: `${KIND15}: proofBinding "current" does not hold - the run has no current proof input ` + `digest (vars.proofDigestNow/proofDigest missing or malformed), so the artifact cannot be ` + `bound to a proof that was never captured; capture one with proof.fingerprint first`
|
|
102116
102089
|
};
|
|
102117
102090
|
}
|
|
102118
102091
|
if (varDigest !== digest) {
|
|
102119
102092
|
return {
|
|
102120
102093
|
ok: false,
|
|
102121
|
-
error: `${
|
|
102094
|
+
error: `${KIND15}: the run's declared proof digest (${varDigest}) does not match the freshly ` + `captured digest (${digest}) \u2014 a stale or forged var cannot bless artifact content (0785 R3)`
|
|
102122
102095
|
};
|
|
102123
102096
|
}
|
|
102124
102097
|
let normalized;
|
|
@@ -102148,7 +102121,7 @@ class RunArtifactActionRunner {
|
|
|
102148
102121
|
try {
|
|
102149
102122
|
raw = await this.fileSystem.readFile(normalized);
|
|
102150
102123
|
} catch (error) {
|
|
102151
|
-
return { ok: false, error: `${
|
|
102124
|
+
return { ok: false, error: `${KIND15}: verdict artifact unreadable: ${error.message}` };
|
|
102152
102125
|
}
|
|
102153
102126
|
const taskWbs = String(context.vars.wbs ?? "");
|
|
102154
102127
|
const outcome = parseVerifyVerdict(raw, taskWbs);
|
|
@@ -102156,33 +102129,33 @@ class RunArtifactActionRunner {
|
|
|
102156
102129
|
const detail = outcome.kind === "invalid" && outcome.reason !== undefined ? `: ${outcome.reason}` : "";
|
|
102157
102130
|
return {
|
|
102158
102131
|
ok: false,
|
|
102159
|
-
error: `${
|
|
102132
|
+
error: `${KIND15}: verdict artifact is ${outcome.kind}${detail} \u2014 refusing to bind (0785 R3)`
|
|
102160
102133
|
};
|
|
102161
102134
|
}
|
|
102162
102135
|
const verdict = outcome.verdict;
|
|
102163
102136
|
if (verdict.wbs !== taskWbs) {
|
|
102164
102137
|
return {
|
|
102165
102138
|
ok: false,
|
|
102166
|
-
error: `${
|
|
102139
|
+
error: `${KIND15}: verdict artifact names wbs "${verdict.wbs}" but the run carries task "${taskWbs}"`
|
|
102167
102140
|
};
|
|
102168
102141
|
}
|
|
102169
102142
|
if (verdict.verdict !== "PASS") {
|
|
102170
102143
|
return {
|
|
102171
102144
|
ok: false,
|
|
102172
|
-
error: `${
|
|
102145
|
+
error: `${KIND15}: verdict aggregate is ${verdict.verdict}, not PASS \u2014 refusing to bind`
|
|
102173
102146
|
};
|
|
102174
102147
|
}
|
|
102175
102148
|
let root;
|
|
102176
102149
|
try {
|
|
102177
102150
|
root = JSON.parse(raw);
|
|
102178
102151
|
} catch {
|
|
102179
|
-
return { ok: false, error: `${
|
|
102152
|
+
return { ok: false, error: `${KIND15}: verdict artifact is malformed JSON` };
|
|
102180
102153
|
}
|
|
102181
102154
|
const proof = root.proof;
|
|
102182
102155
|
if (proof === null || typeof proof !== "object" || Array.isArray(proof)) {
|
|
102183
102156
|
return {
|
|
102184
102157
|
ok: false,
|
|
102185
|
-
error: `${
|
|
102158
|
+
error: `${KIND15}: verdict artifact carries no raw proof block \u2014 binding refused (0785 R3)`
|
|
102186
102159
|
};
|
|
102187
102160
|
}
|
|
102188
102161
|
const p = proof;
|
|
@@ -102196,13 +102169,13 @@ class RunArtifactActionRunner {
|
|
|
102196
102169
|
if (p.digest !== digest) {
|
|
102197
102170
|
return {
|
|
102198
102171
|
ok: false,
|
|
102199
|
-
error: `${
|
|
102172
|
+
error: `${KIND15}: proof.digest ${String(p.digest)} does not equal the freshly captured digest (${digest})`
|
|
102200
102173
|
};
|
|
102201
102174
|
}
|
|
102202
102175
|
if (p.runId !== context.runId) {
|
|
102203
102176
|
return {
|
|
102204
102177
|
ok: false,
|
|
102205
|
-
error: `${
|
|
102178
|
+
error: `${KIND15}: proof.runId ${String(p.runId)} does not match the certifying run ${context.runId}`
|
|
102206
102179
|
};
|
|
102207
102180
|
}
|
|
102208
102181
|
const db = await this.getDb();
|
|
@@ -102210,21 +102183,21 @@ class RunArtifactActionRunner {
|
|
|
102210
102183
|
if (runRow === undefined) {
|
|
102211
102184
|
return {
|
|
102212
102185
|
ok: false,
|
|
102213
|
-
error: `${
|
|
102186
|
+
error: `${KIND15}: run ${context.runId} has no authoritative row \u2014 refusing binding (0785 R3)`
|
|
102214
102187
|
};
|
|
102215
102188
|
}
|
|
102216
102189
|
let metadata = {};
|
|
102217
102190
|
try {
|
|
102218
102191
|
metadata = JSON.parse(runRow.metadata_json || "{}");
|
|
102219
102192
|
} catch {
|
|
102220
|
-
return { ok: false, error: `${
|
|
102193
|
+
return { ok: false, error: `${KIND15}: run ${context.runId} metadata_json is malformed \u2014 refusing binding` };
|
|
102221
102194
|
}
|
|
102222
102195
|
const resumeDigest = metadata.resumeDefinitionDigest;
|
|
102223
102196
|
const expectedDefinition = typeof resumeDigest === "string" && resumeDigest !== "" ? resumeDigest : metadata.definitionDigest;
|
|
102224
102197
|
if (typeof expectedDefinition !== "string" || p.definitionDigest !== expectedDefinition) {
|
|
102225
102198
|
return {
|
|
102226
102199
|
ok: false,
|
|
102227
|
-
error: `${
|
|
102200
|
+
error: `${KIND15}: proof.definitionDigest ${String(p.definitionDigest)} does not match the run's ` + `${typeof resumeDigest === "string" && resumeDigest !== "" ? "resume " : ""}definition digest \u2014 ` + `a stale-definition artifact cannot certify this run (0785 R3)`
|
|
102228
102201
|
};
|
|
102229
102202
|
}
|
|
102230
102203
|
const qualityGate = stageOf("qualityGate");
|
|
@@ -102233,7 +102206,7 @@ class RunArtifactActionRunner {
|
|
|
102233
102206
|
if (qualityGate === null || review === null || verification === null) {
|
|
102234
102207
|
return {
|
|
102235
102208
|
ok: false,
|
|
102236
|
-
error: `${
|
|
102209
|
+
error: `${KIND15}: proof.stages is missing qualityGate/review/verification evidence \u2014 binding refused`
|
|
102237
102210
|
};
|
|
102238
102211
|
}
|
|
102239
102212
|
for (const [name, stage] of [
|
|
@@ -102244,26 +102217,26 @@ class RunArtifactActionRunner {
|
|
|
102244
102217
|
if (stage.digest !== digest) {
|
|
102245
102218
|
return {
|
|
102246
102219
|
ok: false,
|
|
102247
|
-
error: `${
|
|
102220
|
+
error: `${KIND15}: proof.stages.${name}.digest does not equal the freshly captured digest`
|
|
102248
102221
|
};
|
|
102249
102222
|
}
|
|
102250
102223
|
}
|
|
102251
102224
|
if (qualityGate.status !== "PASS") {
|
|
102252
102225
|
return {
|
|
102253
102226
|
ok: false,
|
|
102254
|
-
error: `${
|
|
102227
|
+
error: `${KIND15}: proof.stages.qualityGate.status is ${String(qualityGate.status)}, not PASS`
|
|
102255
102228
|
};
|
|
102256
102229
|
}
|
|
102257
102230
|
if (verification.status !== "PASS") {
|
|
102258
102231
|
return {
|
|
102259
102232
|
ok: false,
|
|
102260
|
-
error: `${
|
|
102233
|
+
error: `${KIND15}: proof.stages.verification.status is ${String(verification.status)}, not PASS`
|
|
102261
102234
|
};
|
|
102262
102235
|
}
|
|
102263
102236
|
if (review.status !== "completed") {
|
|
102264
102237
|
return {
|
|
102265
102238
|
ok: false,
|
|
102266
|
-
error: `${
|
|
102239
|
+
error: `${KIND15}: proof.stages.review.status is "${String(review.status)}" \u2014 a skipped or stale review is not completed evidence (0785 R4)`
|
|
102267
102240
|
};
|
|
102268
102241
|
}
|
|
102269
102242
|
const markerPath = join41(resolve15(workdir), ".spur", "run", `${context.runId}-review-proof.digest`);
|
|
@@ -102276,7 +102249,7 @@ class RunArtifactActionRunner {
|
|
|
102276
102249
|
if (marker === null || marker.trim() !== digest) {
|
|
102277
102250
|
return {
|
|
102278
102251
|
ok: false,
|
|
102279
|
-
error: `${
|
|
102252
|
+
error: `${KIND15}: review-proof marker ${context.runId}-review-proof.digest is missing or does not name ` + `the current digest \u2014 review completion is not evidenced for this run (0785 R4)`
|
|
102280
102253
|
};
|
|
102281
102254
|
}
|
|
102282
102255
|
const dao = this.artifactDao ?? new ArtifactDao(db);
|
|
@@ -102287,7 +102260,7 @@ class RunArtifactActionRunner {
|
|
|
102287
102260
|
registeredPath = await persistDurableArtifact(normalized, workdir, context.runId, this.fileSystem);
|
|
102288
102261
|
}
|
|
102289
102262
|
} catch (error) {
|
|
102290
|
-
return { ok: false, error: `${
|
|
102263
|
+
return { ok: false, error: `${KIND15}: durable persist failed: ${error.message}` };
|
|
102291
102264
|
}
|
|
102292
102265
|
const record = await dao.record({
|
|
102293
102266
|
path: registeredPath,
|
|
@@ -102308,7 +102281,7 @@ class RunArtifactActionRunner {
|
|
|
102308
102281
|
};
|
|
102309
102282
|
}
|
|
102310
102283
|
}
|
|
102311
|
-
var copyFile2, link, mkdir2, readFile5, unlink2, writeFile3,
|
|
102284
|
+
var copyFile2, link, mkdir2, readFile5, unlink2, writeFile3, KIND15 = "run.artifact", PROOF_DIGEST_RE;
|
|
102312
102285
|
var init_run_artifact = __esm(() => {
|
|
102313
102286
|
init_src2();
|
|
102314
102287
|
init_dist5();
|
|
@@ -102322,7 +102295,7 @@ var init_run_artifact = __esm(() => {
|
|
|
102322
102295
|
|
|
102323
102296
|
// ../../packages/app/src/workflow/guards/contract-violation.ts
|
|
102324
102297
|
class ContractViolationGuardRunner {
|
|
102325
|
-
kind =
|
|
102298
|
+
kind = KIND16;
|
|
102326
102299
|
constructor() {}
|
|
102327
102300
|
async evaluate(_options, context) {
|
|
102328
102301
|
const data = context.lastActionResult?.data;
|
|
@@ -102339,7 +102312,7 @@ class ContractViolationGuardRunner {
|
|
|
102339
102312
|
};
|
|
102340
102313
|
}
|
|
102341
102314
|
}
|
|
102342
|
-
var
|
|
102315
|
+
var KIND16 = "contract-violation";
|
|
102343
102316
|
|
|
102344
102317
|
// ../../packages/app/src/workflow/builtins.ts
|
|
102345
102318
|
function registerSpurBuiltins(host, options) {
|
|
@@ -102370,9 +102343,6 @@ function registerSpurBuiltins(host, options) {
|
|
|
102370
102343
|
if (options.httpRequester) {
|
|
102371
102344
|
host.registerAction(new HttpRequestActionRunner(options.httpRequester, options.hostAllowlist ?? new Set), "builtin");
|
|
102372
102345
|
}
|
|
102373
|
-
if (options.responseValidateEngine) {
|
|
102374
|
-
host.registerAction(new ResponseValidateActionRunner(options.responseValidateEngine), "builtin");
|
|
102375
|
-
}
|
|
102376
102346
|
}
|
|
102377
102347
|
var init_builtins2 = __esm(() => {
|
|
102378
102348
|
init_dist5();
|
|
@@ -108278,11 +108248,15 @@ function classifyProviderUsage(usage) {
|
|
|
108278
108248
|
const hasWindow = RATE_WINDOW_NAMES.some((n) => usage[n] !== null && usage[n] !== undefined);
|
|
108279
108249
|
return hasWindow ? { status: "headroom", exhaustedWindow: undefined } : { status: "no-usage", exhaustedWindow: undefined };
|
|
108280
108250
|
}
|
|
108251
|
+
function agentProviderSlug(agent) {
|
|
108252
|
+
const lower = (agent ?? "").toLowerCase();
|
|
108253
|
+
return PROVIDER_ALIASES[lower] ?? lower;
|
|
108254
|
+
}
|
|
108281
108255
|
function mapProvidersToExecutors(providers, executors) {
|
|
108282
108256
|
const mapping = new Map;
|
|
108283
108257
|
for (const provider of providers) {
|
|
108284
108258
|
const lower = provider.toLowerCase();
|
|
108285
|
-
const names = executors.filter((e) => (e.agent
|
|
108259
|
+
const names = executors.filter((e) => agentProviderSlug(e.agent) === lower || typeof e.model === "string" && e.model.split("/")[0]?.toLowerCase() === lower).map((e) => e.name);
|
|
108286
108260
|
if (names.length > 0)
|
|
108287
108261
|
mapping.set(provider, names);
|
|
108288
108262
|
}
|
|
@@ -108534,7 +108508,7 @@ async function finalizeOutcomes(entries, context) {
|
|
|
108534
108508
|
}
|
|
108535
108509
|
}
|
|
108536
108510
|
}
|
|
108537
|
-
var codexbarWindowSchema, codexbarEntrySchema, RATE_WINDOW_NAMES, AgentUsageProducerError;
|
|
108511
|
+
var codexbarWindowSchema, codexbarEntrySchema, RATE_WINDOW_NAMES, AgentUsageProducerError, PROVIDER_ALIASES;
|
|
108538
108512
|
var init_agent_usage_producer = __esm(() => {
|
|
108539
108513
|
init_src();
|
|
108540
108514
|
init_agent_quota_events();
|
|
@@ -108566,6 +108540,9 @@ var init_agent_usage_producer = __esm(() => {
|
|
|
108566
108540
|
this.name = "AgentUsageProducerError";
|
|
108567
108541
|
}
|
|
108568
108542
|
};
|
|
108543
|
+
PROVIDER_ALIASES = {
|
|
108544
|
+
"antigravity-cli": "antigravity"
|
|
108545
|
+
};
|
|
108569
108546
|
});
|
|
108570
108547
|
|
|
108571
108548
|
// ../../packages/app/src/services/agent-usage-source.ts
|
|
@@ -119962,36 +119939,6 @@ async function createFeatureLookup(workdir, fileSystem = createNodeFileSystem2()
|
|
|
119962
119939
|
return { path, content: await fileSystem.readFile(path) };
|
|
119963
119940
|
};
|
|
119964
119941
|
}
|
|
119965
|
-
function deriveFeatureConfidence(input) {
|
|
119966
|
-
const override = input.override === undefined || input.override === "" ? undefined : input.override;
|
|
119967
|
-
const measured = [];
|
|
119968
|
-
if (input.status === "PASS")
|
|
119969
|
-
measured.push("verification command exited 0");
|
|
119970
|
-
else
|
|
119971
|
-
measured.push("verification command did not exit 0");
|
|
119972
|
-
if (input.afterDigest !== undefined) {
|
|
119973
|
-
measured.push(input.afterDigest === input.inputDigest ? "proof-input digest unchanged across the pass" : "proof-input digest changed across the pass");
|
|
119974
|
-
}
|
|
119975
|
-
if (input.status !== "PASS")
|
|
119976
|
-
return { confidence: "LOW", basis: measured };
|
|
119977
|
-
const derived = input.effectiveCmd === input.configuredCmd ? "HIGH" : "MEDIUM";
|
|
119978
|
-
if (input.effectiveCmd !== input.configuredCmd) {
|
|
119979
|
-
measured.push(`caller command override: ${input.effectiveCmd}`);
|
|
119980
|
-
measured.push(`the selected definition declares: ${input.configuredCmd}`);
|
|
119981
|
-
}
|
|
119982
|
-
if (override === undefined)
|
|
119983
|
-
return { confidence: derived, basis: measured };
|
|
119984
|
-
return { confidence: override, basis: [`operator-declared ${override}`, ...measured] };
|
|
119985
|
-
}
|
|
119986
|
-
function assertConfidenceOverride(raw) {
|
|
119987
|
-
if (raw === undefined || raw === "")
|
|
119988
|
-
return;
|
|
119989
|
-
const upper = raw.toUpperCase();
|
|
119990
|
-
if (!FEATURE_RECEIPT_CONFIDENCE_LEVELS.includes(upper)) {
|
|
119991
|
-
throw new Error(`confidence must be HIGH, MEDIUM or LOW (got ${JSON.stringify(raw)})`);
|
|
119992
|
-
}
|
|
119993
|
-
return upper;
|
|
119994
|
-
}
|
|
119995
119942
|
async function defaultRegisterArtifact(workdir, artifact) {
|
|
119996
119943
|
const db = await openInlineRunProjectDb(workdir);
|
|
119997
119944
|
try {
|
|
@@ -120027,7 +119974,6 @@ async function runFeatureVerification(input) {
|
|
|
120027
119974
|
const vars = selected.workflow.vars;
|
|
120028
119975
|
const configuredCmd = typeof vars?.verificationCmd === "string" && vars.verificationCmd.length > 0 ? vars.verificationCmd : "bun run spur-check-feature";
|
|
120029
119976
|
const effectiveCmd = input.cmdOverride !== undefined && input.cmdOverride !== "" ? input.cmdOverride : configuredCmd;
|
|
120030
|
-
const confidenceOverride = assertConfidenceOverride(input.confidenceOverride);
|
|
120031
119977
|
const spec = await lookup(input.featureId);
|
|
120032
119978
|
if (spec === null) {
|
|
120033
119979
|
throw new Error(`feature ${input.featureId} not found in the configured features directory`);
|
|
@@ -120053,25 +119999,13 @@ async function runFeatureVerification(input) {
|
|
|
120053
119999
|
const base = { featureId: input.featureId, runId: input.runId, effectiveCmd, logPath, receiptPath: runScoped };
|
|
120054
120000
|
const launchSplit = split(effectiveCmd, "feature-verification verificationCmd");
|
|
120055
120001
|
if ("error" in launchSplit) {
|
|
120056
|
-
const { confidence, basis } = deriveFeatureConfidence({
|
|
120057
|
-
status: "FAIL",
|
|
120058
|
-
configuredCmd,
|
|
120059
|
-
effectiveCmd,
|
|
120060
|
-
inputDigest: beforeDigest,
|
|
120061
|
-
override: confidenceOverride
|
|
120062
|
-
});
|
|
120063
120002
|
await receipts.complete(fileSystem, runDir, receipt, {
|
|
120064
120003
|
status: "FAIL",
|
|
120065
|
-
inputDigest: beforeDigest
|
|
120066
|
-
confidence,
|
|
120067
|
-
confidenceBasis: basis,
|
|
120068
|
-
confidenceAcknowledged: input.ackLowConfidence === true
|
|
120004
|
+
inputDigest: beforeDigest
|
|
120069
120005
|
});
|
|
120070
120006
|
return {
|
|
120071
120007
|
...base,
|
|
120072
120008
|
status: "FAIL",
|
|
120073
|
-
confidence,
|
|
120074
|
-
confidenceBasis: basis,
|
|
120075
120009
|
message: `verification FAIL (${launchSplit.error})`
|
|
120076
120010
|
};
|
|
120077
120011
|
}
|
|
@@ -120085,27 +120019,14 @@ async function runFeatureVerification(input) {
|
|
|
120085
120019
|
const exit = result.status ?? 1;
|
|
120086
120020
|
const afterDigest = await receipts.captureDigest(input.workdir, spec.content, learningsContent);
|
|
120087
120021
|
const status = exit === 0 && afterDigest === beforeDigest ? "PASS" : "FAIL";
|
|
120088
|
-
const { confidence, basis } = deriveFeatureConfidence({
|
|
120089
|
-
status,
|
|
120090
|
-
configuredCmd,
|
|
120091
|
-
effectiveCmd,
|
|
120092
|
-
inputDigest: beforeDigest,
|
|
120093
|
-
afterDigest,
|
|
120094
|
-
override: confidenceOverride
|
|
120095
|
-
});
|
|
120096
120022
|
await receipts.complete(fileSystem, runDir, receipt, {
|
|
120097
120023
|
status,
|
|
120098
|
-
inputDigest: afterDigest
|
|
120099
|
-
confidence,
|
|
120100
|
-
confidenceBasis: basis,
|
|
120101
|
-
confidenceAcknowledged: input.ackLowConfidence === true
|
|
120024
|
+
inputDigest: afterDigest
|
|
120102
120025
|
});
|
|
120103
120026
|
return {
|
|
120104
120027
|
...base,
|
|
120105
120028
|
status,
|
|
120106
|
-
|
|
120107
|
-
confidenceBasis: basis,
|
|
120108
|
-
message: `verification ${status} (run ${input.runId}, confidence ${confidence}, log ${logPath})`
|
|
120029
|
+
message: `verification ${status} (run ${input.runId}, log ${logPath})`
|
|
120109
120030
|
};
|
|
120110
120031
|
} finally {
|
|
120111
120032
|
closeLog(logFd);
|
|
@@ -120950,7 +120871,6 @@ __export(exports_src, {
|
|
|
120950
120871
|
FEATURE_ACTION_NAMES: () => FEATURE_ACTION_NAMES,
|
|
120951
120872
|
FEATURE_LIFECYCLE_PROFILE: () => FEATURE_LIFECYCLE_PROFILE,
|
|
120952
120873
|
FEATURE_LIFECYCLE_WORKFLOW: () => FEATURE_LIFECYCLE_WORKFLOW,
|
|
120953
|
-
FEATURE_RECEIPT_CONFIDENCE_LEVELS: () => FEATURE_RECEIPT_CONFIDENCE_LEVELS,
|
|
120954
120874
|
FEATURE_RECEIPT_SCHEMA_VERSION: () => FEATURE_RECEIPT_SCHEMA_VERSION,
|
|
120955
120875
|
FINDING_CODES: () => FINDING_CODES,
|
|
120956
120876
|
FLEET_AUTO_TAG: () => FLEET_AUTO_TAG,
|
|
@@ -121014,7 +120934,6 @@ __export(exports_src, {
|
|
|
121014
120934
|
RUN_STORE_LIST_MAX_LIMIT: () => RUN_STORE_LIST_MAX_LIMIT,
|
|
121015
120935
|
RUN_STORE_WBS_DEFAULT_LIMIT: () => RUN_STORE_WBS_DEFAULT_LIMIT,
|
|
121016
120936
|
RUN_STORE_WBS_MAX_LIMIT: () => RUN_STORE_WBS_MAX_LIMIT,
|
|
121017
|
-
ResponseValidateActionRunner: () => ResponseValidateActionRunner,
|
|
121018
120937
|
RetroCorrelator: () => RetroCorrelator,
|
|
121019
120938
|
RuleCheckActionRunner: () => RuleCheckActionRunner,
|
|
121020
120939
|
RuleService: () => RuleService,
|
|
@@ -121142,7 +121061,6 @@ __export(exports_src, {
|
|
|
121142
121061
|
decodeRunListCursor: () => decodeRunListCursor,
|
|
121143
121062
|
decorateWorkflowEvent: () => decorateWorkflowEvent,
|
|
121144
121063
|
deduceCommandCategory: () => deduceCommandCategory,
|
|
121145
|
-
deriveFeatureConfidence: () => deriveFeatureConfidence,
|
|
121146
121064
|
deriveVerdict: () => deriveVerdict,
|
|
121147
121065
|
discoverEnabledClaudePlugins: () => discoverEnabledClaudePlugins,
|
|
121148
121066
|
disposeDecisionServices: () => disposeDecisionServices,
|
|
@@ -121205,6 +121123,7 @@ __export(exports_src, {
|
|
|
121205
121123
|
isInlineRunActionStatus: () => isInlineRunActionStatus,
|
|
121206
121124
|
isInlineRunCloseStatus: () => isInlineRunCloseStatus,
|
|
121207
121125
|
isInsideRoot: () => isInsideRoot,
|
|
121126
|
+
isLocalHost: () => isLocalHost,
|
|
121208
121127
|
isPortAvailable: () => isPortAvailable,
|
|
121209
121128
|
isPortLive: () => isPortLive,
|
|
121210
121129
|
isProofCaptureError: () => isProofCaptureError,
|
|
@@ -128491,7 +128410,7 @@ import {
|
|
|
128491
128410
|
statSync as statSync17,
|
|
128492
128411
|
writeFileSync as writeFileSync17
|
|
128493
128412
|
} from "fs";
|
|
128494
|
-
import { join as
|
|
128413
|
+
import { join as join81 } from "path";
|
|
128495
128414
|
function parseScalar(raw) {
|
|
128496
128415
|
const t = raw.trim();
|
|
128497
128416
|
if (t.startsWith('"') && t.endsWith('"'))
|
|
@@ -128700,10 +128619,6 @@ function checkReportStructure(reportMarkdown) {
|
|
|
128700
128619
|
if (!/(^|[\s`])P[123]([\s`.]|$)/.test(block) && !block.includes("symbolic-severity")) {
|
|
128701
128620
|
problems.push("finding-invalid-severity");
|
|
128702
128621
|
}
|
|
128703
|
-
const confValue = block.match(/`confidence`\s*[:=]\s*`?([^`\n]*?)`?\s*(?:\n|$)/)?.[1]?.trim();
|
|
128704
|
-
if (confValue !== undefined && confValue !== "" && !FINDING_CONFIDENCES.includes(confValue.toLowerCase())) {
|
|
128705
|
-
problems.push(`finding-invalid-confidence:${confValue}`);
|
|
128706
|
-
}
|
|
128707
128622
|
const catValue = block.match(/`category`\s*[:=]\s*`?([^`\n]+?)`?\s*(?:\n|$)/)?.[1];
|
|
128708
128623
|
if (catValue && !FINDING_CATEGORIES.includes(catValue.trim())) {
|
|
128709
128624
|
problems.push(`finding-invalid-category:${catValue.trim()}`);
|
|
@@ -128737,7 +128652,7 @@ function logicDigest(path) {
|
|
|
128737
128652
|
try {
|
|
128738
128653
|
const h = createHash17("sha256");
|
|
128739
128654
|
if (statSync17(path).isDirectory()) {
|
|
128740
|
-
const walk = (dir) => readdirSync13(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? walk(
|
|
128655
|
+
const walk = (dir) => readdirSync13(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? walk(join81(dir, e.name)) : /\.(md|ya?ml)$/.test(e.name) ? [join81(dir, e.name)] : []).sort();
|
|
128741
128656
|
for (const f of walk(path)) {
|
|
128742
128657
|
h.update(f.slice(path.length));
|
|
128743
128658
|
h.update(readFileSync25(f));
|
|
@@ -129042,7 +128957,7 @@ function diffPorcelain(before, now, expects) {
|
|
|
129042
128957
|
const beforePaths = porcelainPaths(before);
|
|
129043
128958
|
return [...porcelainPaths(now)].filter((p) => !beforePaths.has(p) && !expects.has(p)).sort();
|
|
129044
128959
|
}
|
|
129045
|
-
var REPORT_SECTIONS, FINDING_FIELDS, DISPOSITIONS, FINDING_CATEGORIES,
|
|
128960
|
+
var REPORT_SECTIONS, FINDING_FIELDS, DISPOSITIONS, FINDING_CATEGORIES, NOT_AVAILABLE = "not available", YAML_KEYS;
|
|
129046
128961
|
var init_history_anatomy = __esm(() => {
|
|
129047
128962
|
init_artifact_digest();
|
|
129048
128963
|
REPORT_SECTIONS = [
|
|
@@ -129066,7 +128981,6 @@ var init_history_anatomy = __esm(() => {
|
|
|
129066
128981
|
"trend",
|
|
129067
128982
|
"observation",
|
|
129068
128983
|
"inference",
|
|
129069
|
-
"confidence",
|
|
129070
128984
|
"contradictions",
|
|
129071
128985
|
"evidenceAnchor",
|
|
129072
128986
|
"severity",
|
|
@@ -129083,7 +128997,6 @@ var init_history_anatomy = __esm(() => {
|
|
|
129083
128997
|
"telemetry",
|
|
129084
128998
|
"positive"
|
|
129085
128999
|
];
|
|
129086
|
-
FINDING_CONFIDENCES = ["high", "medium", "low"];
|
|
129087
129000
|
YAML_KEYS = [
|
|
129088
129001
|
"windowState",
|
|
129089
129002
|
"generatedAt",
|
|
@@ -129108,7 +129021,7 @@ var init_history_anatomy = __esm(() => {
|
|
|
129108
129021
|
// ../../packages/app/src/services/history-anatomy-cli.ts
|
|
129109
129022
|
import { existsSync as existsSync29, mkdirSync as mkdirSync21, readFileSync as readFileSync26, writeFileSync as writeFileSync18 } from "fs";
|
|
129110
129023
|
import { homedir as homedir13 } from "os";
|
|
129111
|
-
import { join as
|
|
129024
|
+
import { join as join82 } from "path";
|
|
129112
129025
|
function parseFlags(args) {
|
|
129113
129026
|
const out = {};
|
|
129114
129027
|
for (let i = 0;i < args.length; i++) {
|
|
@@ -129130,9 +129043,9 @@ function resolveSkillDir(deps = {}) {
|
|
|
129130
129043
|
const cwd = deps.cwd ?? process.cwd();
|
|
129131
129044
|
const home = deps.home ?? homedir13();
|
|
129132
129045
|
const candidates = [
|
|
129133
|
-
|
|
129134
|
-
|
|
129135
|
-
|
|
129046
|
+
join82(cwd, "plugins", "sp", "skills", "history-anatomy"),
|
|
129047
|
+
join82(home, ".agents", "skills", "sp-history-anatomy"),
|
|
129048
|
+
join82(home, ".claude", "skills", "sp-history-anatomy")
|
|
129136
129049
|
];
|
|
129137
129050
|
return candidates.find((dir) => existsSync29(dir)) ?? candidates[0];
|
|
129138
129051
|
}
|
|
@@ -129363,7 +129276,7 @@ var init_history_anatomy_cli = __esm(() => {
|
|
|
129363
129276
|
|
|
129364
129277
|
// ../../packages/app/src/services/daily-summary.ts
|
|
129365
129278
|
import { existsSync as existsSync30, mkdirSync as mkdirSync22, readlinkSync as readlinkSync2, writeFileSync as writeFileSync19 } from "fs";
|
|
129366
|
-
import { dirname as dirname40, isAbsolute as isAbsolute14, join as
|
|
129279
|
+
import { dirname as dirname40, isAbsolute as isAbsolute14, join as join83, resolve as resolve30 } from "path";
|
|
129367
129280
|
function parseArgs2(argv) {
|
|
129368
129281
|
const args = argv;
|
|
129369
129282
|
const options = {
|
|
@@ -129750,8 +129663,8 @@ function ensureDir(path) {
|
|
|
129750
129663
|
}
|
|
129751
129664
|
function writeSummary(markdown, options) {
|
|
129752
129665
|
const filename = `summary_${options.date.replace(/-/g, "")}.md`;
|
|
129753
|
-
const outputPath = options.outputPath ||
|
|
129754
|
-
ensureDir(
|
|
129666
|
+
const outputPath = options.outputPath || join83(DAILY_DIR, filename);
|
|
129667
|
+
ensureDir(join83(outputPath, ".."));
|
|
129755
129668
|
writeFileSync19(outputPath, markdown, "utf-8");
|
|
129756
129669
|
return outputPath;
|
|
129757
129670
|
}
|
|
@@ -129900,7 +129813,7 @@ var init_daily_summary = __esm(() => {
|
|
|
129900
129813
|
// ../../packages/app/src/workflow/transcript.ts
|
|
129901
129814
|
import { existsSync as existsSync31, readdirSync as readdirSync14 } from "fs";
|
|
129902
129815
|
import { homedir as homedir14 } from "os";
|
|
129903
|
-
import { join as
|
|
129816
|
+
import { join as join84 } from "path";
|
|
129904
129817
|
function sniffTranscriptFormat(rows) {
|
|
129905
129818
|
for (const row of rows) {
|
|
129906
129819
|
if (row.type === "message" && PI_ROLES.has(row.message?.role ?? ""))
|
|
@@ -129929,7 +129842,7 @@ function parseTranscriptRows(lines) {
|
|
|
129929
129842
|
}
|
|
129930
129843
|
return { rows, skippedLines, format: sniffTranscriptFormat(rows.map(([r]) => r)) };
|
|
129931
129844
|
}
|
|
129932
|
-
function resolveTranscript(env, projectsRoot =
|
|
129845
|
+
function resolveTranscript(env, projectsRoot = join84(homedir14(), ".claude", "projects"), override) {
|
|
129933
129846
|
if (override)
|
|
129934
129847
|
return existsSync31(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
|
|
129935
129848
|
const id = env.CLAUDE_CODE_SESSION_ID;
|
|
@@ -129948,7 +129861,7 @@ function resolveTranscript(env, projectsRoot = join83(homedir14(), ".claude", "p
|
|
|
129948
129861
|
if (!existsSync31(projectsRoot))
|
|
129949
129862
|
return { ok: false, reason: `no transcript root ${projectsRoot}` };
|
|
129950
129863
|
for (const dir of readdirSync14(projectsRoot)) {
|
|
129951
|
-
const path =
|
|
129864
|
+
const path = join84(projectsRoot, dir, `${id}.jsonl`);
|
|
129952
129865
|
if (existsSync31(path))
|
|
129953
129866
|
return { ok: true, path };
|
|
129954
129867
|
}
|
|
@@ -130143,7 +130056,7 @@ var require_src9 = __commonJS(function(exports, module) {
|
|
|
130143
130056
|
|
|
130144
130057
|
// ../../packages/app/src/workflow/run-summary-render.ts
|
|
130145
130058
|
import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync29, writeFileSync as writeFileSync21 } from "fs";
|
|
130146
|
-
import { join as
|
|
130059
|
+
import { join as join99 } from "path";
|
|
130147
130060
|
function renderSummaryMarkdown(summary) {
|
|
130148
130061
|
const line = (r, bold = false) => {
|
|
130149
130062
|
const stage = bold ? `**${r.stage}**` : r.stage;
|
|
@@ -130229,9 +130142,9 @@ function renderRollupMarkdown(rows) {
|
|
|
130229
130142
|
}
|
|
130230
130143
|
function writeCloseSummary(run, opts = {}) {
|
|
130231
130144
|
const cwd = opts.cwd ?? process.cwd();
|
|
130232
|
-
const runDir =
|
|
130233
|
-
const rel =
|
|
130234
|
-
const abs =
|
|
130145
|
+
const runDir = join99(cwd, ".spur", "run");
|
|
130146
|
+
const rel = join99(".spur", "run", `${run.runId}-summary.md`);
|
|
130147
|
+
const abs = join99(cwd, rel);
|
|
130235
130148
|
const na = (reason) => {
|
|
130236
130149
|
mkdirSync24(runDir, { recursive: true });
|
|
130237
130150
|
writeFileSync21(abs, `Execution summary: n/a (${reason})
|
|
@@ -130256,14 +130169,14 @@ function writeCloseSummary(run, opts = {}) {
|
|
|
130256
130169
|
mkdirSync24(runDir, { recursive: true });
|
|
130257
130170
|
writeFileSync21(abs, `${renderSummaryMarkdown(summary)}
|
|
130258
130171
|
`);
|
|
130259
|
-
writeFileSync21(
|
|
130172
|
+
writeFileSync21(join99(runDir, `${run.runId}-summary.json`), JSON.stringify({ ...summary, runId: run.runId, startedAt: run.startedAt, completedAt: run.completedAt }, null, 2));
|
|
130260
130173
|
return rel;
|
|
130261
130174
|
} catch (error) {
|
|
130262
130175
|
return na(error instanceof Error ? error.message : String(error));
|
|
130263
130176
|
}
|
|
130264
130177
|
}
|
|
130265
130178
|
function gateFromReceipt(cwd, runId) {
|
|
130266
|
-
const path =
|
|
130179
|
+
const path = join99(cwd, ".spur", "run", `${runId}-check-receipt.json`);
|
|
130267
130180
|
if (!existsSync32(path))
|
|
130268
130181
|
return { ms: null, reason: "no matching check receipt" };
|
|
130269
130182
|
try {
|
|
@@ -138207,8 +138120,18 @@ init_src();
|
|
|
138207
138120
|
init_dist5();
|
|
138208
138121
|
import { homedir as homedir12 } from "os";
|
|
138209
138122
|
import { join as join67 } from "path";
|
|
138210
|
-
var CODEXBAR_ARGV = ["codexbar", "usage", "--format", "json"
|
|
138211
|
-
var DEFAULT_CAPTURE_TIMEOUT_MS =
|
|
138123
|
+
var CODEXBAR_ARGV = ["codexbar", "usage", "--format", "json"];
|
|
138124
|
+
var DEFAULT_CAPTURE_TIMEOUT_MS = 300000;
|
|
138125
|
+
var CAPTURE_TIMEOUT_OPTION = "agentUsageCaptureTimeoutMs";
|
|
138126
|
+
var MAX_TIMER_MS = 2147483647;
|
|
138127
|
+
function resolveCaptureTimeoutMs(spurConfig) {
|
|
138128
|
+
const raw = getAppOptions(spurConfig, CAPTURE_TIMEOUT_OPTION, undefined);
|
|
138129
|
+
if (raw === undefined)
|
|
138130
|
+
return DEFAULT_CAPTURE_TIMEOUT_MS;
|
|
138131
|
+
if (typeof raw === "number" && Number.isInteger(raw) && raw > 0 && raw <= MAX_TIMER_MS)
|
|
138132
|
+
return raw;
|
|
138133
|
+
throw new UsageSourceError(`bootstrap.options.${CAPTURE_TIMEOUT_OPTION} must be a positive integer number of milliseconds (at most ${MAX_TIMER_MS}); received ${JSON.stringify(raw)}`);
|
|
138134
|
+
}
|
|
138212
138135
|
|
|
138213
138136
|
class CodexbarUsageSource {
|
|
138214
138137
|
command;
|
|
@@ -138238,7 +138161,7 @@ class CodexbarUsageSource {
|
|
|
138238
138161
|
}
|
|
138239
138162
|
const outcome = result.outcome ?? "exit";
|
|
138240
138163
|
if (outcome === "timeout" || outcome === "cancelled") {
|
|
138241
|
-
throw new UsageSourceError(`\`${this.command.join(" ")}\` capture exceeded the ${this.timeoutMs} ms deadline and was terminated \u2014 ` +
|
|
138164
|
+
throw new UsageSourceError(`\`${this.command.join(" ")}\` capture exceeded the ${this.timeoutMs} ms deadline and was terminated \u2014 ` + `result is unusable (fail-closed); retry, raise bootstrap.options.${CAPTURE_TIMEOUT_OPTION}, or investigate the codexbar hang`);
|
|
138242
138165
|
}
|
|
138243
138166
|
if (outcome === "signal" || outcome === "error") {
|
|
138244
138167
|
throw new UsageSourceError(`\`${this.command.join(" ")}\` capture ended abnormally (${outcome}) \u2014 capture is unusable (fail-closed)`);
|
|
@@ -142424,13 +142347,13 @@ async function runAgentUsage(context, flags, deps) {
|
|
|
142424
142347
|
}
|
|
142425
142348
|
return 2;
|
|
142426
142349
|
}
|
|
142427
|
-
const options = {
|
|
142428
|
-
source: deps?.source ?? _testUsageDeps?.source ?? new CodexbarUsageSource,
|
|
142429
|
-
snapshotPath: deps?.snapshotPath ?? _testUsageDeps?.snapshotPath ?? defaultAgentUsageSnapshotPath(),
|
|
142430
|
-
dryRun: flags.dryRun === true
|
|
142431
|
-
};
|
|
142432
142350
|
let result;
|
|
142433
142351
|
try {
|
|
142352
|
+
const options = {
|
|
142353
|
+
source: deps?.source ?? _testUsageDeps?.source ?? new CodexbarUsageSource(undefined, resolveCaptureTimeoutMs(context.spurConfig)),
|
|
142354
|
+
snapshotPath: deps?.snapshotPath ?? _testUsageDeps?.snapshotPath ?? defaultAgentUsageSnapshotPath(),
|
|
142355
|
+
dryRun: flags.dryRun === true
|
|
142356
|
+
};
|
|
142434
142357
|
result = await runAgentUsageProducer({
|
|
142435
142358
|
getDb: () => context.getDb(),
|
|
142436
142359
|
projectRoot: context.cwd,
|
|
@@ -142725,12 +142648,33 @@ function registerAgentCommand(program, context) {
|
|
|
142725
142648
|
init_src();
|
|
142726
142649
|
init_dist5();
|
|
142727
142650
|
import { existsSync as existsSync26, readdirSync as readdirSync12, readFileSync as readFileSync23 } from "fs";
|
|
142728
|
-
import { join as
|
|
142651
|
+
import { join as join79 } from "path";
|
|
142652
|
+
|
|
142653
|
+
// ../../scripts/commands/bundle-plugins.ts
|
|
142654
|
+
import { cp, rm as rm4 } from "fs/promises";
|
|
142655
|
+
import { join as join77 } from "path";
|
|
142656
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
142657
|
+
var REPO_ROOT = fileURLToPath6(new URL("../../", import.meta.url));
|
|
142658
|
+
var PLUGIN_SOURCE = join77(REPO_ROOT, "plugins");
|
|
142659
|
+
var PLUGIN_TARGET = join77(REPO_ROOT, "apps/cli/plugins");
|
|
142660
|
+
var MARKETPLACE_SOURCE = join77(REPO_ROOT, ".claude-plugin");
|
|
142661
|
+
var MARKETPLACE_TARGET = join77(REPO_ROOT, "apps/cli/.claude-plugin");
|
|
142662
|
+
var EXCLUDE = /(^|\/)(\.DS_Store|tests|evals)($|\/)|\.test\.ts$/;
|
|
142663
|
+
async function bundlePlugins(pluginTarget = PLUGIN_TARGET, marketplaceTarget = MARKETPLACE_TARGET, pluginSource = PLUGIN_SOURCE, marketplaceSource = MARKETPLACE_SOURCE) {
|
|
142664
|
+
await rm4(pluginTarget, { recursive: true, force: true });
|
|
142665
|
+
await rm4(marketplaceTarget, { recursive: true, force: true });
|
|
142666
|
+
await cp(pluginSource, pluginTarget, {
|
|
142667
|
+
recursive: true,
|
|
142668
|
+
filter: (src) => !EXCLUDE.test(src.slice(pluginSource.length))
|
|
142669
|
+
});
|
|
142670
|
+
await cp(marketplaceSource, marketplaceTarget, { recursive: true });
|
|
142671
|
+
return { pluginTarget, marketplaceTarget };
|
|
142672
|
+
}
|
|
142729
142673
|
|
|
142730
142674
|
// src/version-carriers.ts
|
|
142731
142675
|
init_zod();
|
|
142732
142676
|
import { existsSync as existsSync25 } from "fs";
|
|
142733
|
-
import { join as
|
|
142677
|
+
import { join as join78 } from "path";
|
|
142734
142678
|
var defaultLiteralProbe = { file: "src/config.ts", identifier: "binaryVersion" };
|
|
142735
142679
|
var registry3 = new Map;
|
|
142736
142680
|
function registerCarrierType(type, def) {
|
|
@@ -142789,7 +142733,7 @@ registerCarrierType("plugin-manifest", {
|
|
|
142789
142733
|
schema: PluginManifestCarrierSchema,
|
|
142790
142734
|
async syncRepoWide(carrier, { repoRoot }, version, staged, output) {
|
|
142791
142735
|
for (const rel of carrier.paths) {
|
|
142792
|
-
const absPath =
|
|
142736
|
+
const absPath = join78(repoRoot, rel);
|
|
142793
142737
|
const manifest = await readJson(absPath);
|
|
142794
142738
|
if (manifest === null) {
|
|
142795
142739
|
output.write(` \u26A0 ${rel}: not found or malformed \u2014 skipping`);
|
|
@@ -142849,7 +142793,7 @@ async function run2(repoRoot, cmd, timeoutMs) {
|
|
|
142849
142793
|
};
|
|
142850
142794
|
}
|
|
142851
142795
|
async function verifyLockfile(ctx, output) {
|
|
142852
|
-
const lockPath =
|
|
142796
|
+
const lockPath = join79(ctx.repoRoot, "bun.lock");
|
|
142853
142797
|
if (!existsSync26(lockPath) || Bun.file(lockPath).size === 0)
|
|
142854
142798
|
return;
|
|
142855
142799
|
const result = await run2(ctx.repoRoot, ["bun", "install", "--frozen-lockfile"]);
|
|
@@ -142859,7 +142803,7 @@ async function verifyLockfile(ctx, output) {
|
|
|
142859
142803
|
output.write(" \u21B3 bun.lock: verified (bun install --frozen-lockfile)");
|
|
142860
142804
|
}
|
|
142861
142805
|
async function assertPluginInstallable(ctx, output) {
|
|
142862
|
-
const smokePath =
|
|
142806
|
+
const smokePath = join79(ctx.repoRoot, "scripts", "commands", "plugin-install-smoke.ts");
|
|
142863
142807
|
if (!existsSync26(smokePath)) {
|
|
142864
142808
|
output.write(" \u21B3 plugin-install-smoke: gate script not present in this repo \u2014 skipped");
|
|
142865
142809
|
return;
|
|
@@ -142901,7 +142845,7 @@ async function readJson2(path) {
|
|
|
142901
142845
|
function rootWorkspaceGlobs(repoRoot) {
|
|
142902
142846
|
let root;
|
|
142903
142847
|
try {
|
|
142904
|
-
root = JSON.parse(readFileSync23(
|
|
142848
|
+
root = JSON.parse(readFileSync23(join79(repoRoot, "package.json"), "utf8"));
|
|
142905
142849
|
} catch {
|
|
142906
142850
|
return [];
|
|
142907
142851
|
}
|
|
@@ -142918,11 +142862,11 @@ function workspaceDirs(repoRoot) {
|
|
|
142918
142862
|
if (!glob.endsWith("/*"))
|
|
142919
142863
|
continue;
|
|
142920
142864
|
const parent = glob.slice(0, -2);
|
|
142921
|
-
const parentAbs =
|
|
142865
|
+
const parentAbs = join79(repoRoot, parent);
|
|
142922
142866
|
if (!existsSync26(parentAbs))
|
|
142923
142867
|
continue;
|
|
142924
142868
|
for (const entry of readdirSync12(parentAbs, { withFileTypes: true })) {
|
|
142925
|
-
if (entry.isDirectory() && existsSync26(
|
|
142869
|
+
if (entry.isDirectory() && existsSync26(join79(parentAbs, entry.name, "package.json"))) {
|
|
142926
142870
|
dirs.push(`${parent}/${entry.name}`);
|
|
142927
142871
|
}
|
|
142928
142872
|
}
|
|
@@ -142930,14 +142874,14 @@ function workspaceDirs(repoRoot) {
|
|
|
142930
142874
|
return dirs;
|
|
142931
142875
|
}
|
|
142932
142876
|
async function releaseContext(repoRoot, bumpVerConfig = resolveBuilderBumpVerConfig(null)) {
|
|
142933
|
-
const rootManifest = await readJson2(
|
|
142877
|
+
const rootManifest = await readJson2(join79(repoRoot, "package.json"));
|
|
142934
142878
|
if (rootManifest === null) {
|
|
142935
142879
|
throw new Error(`no package.json found at ${repoRoot} \u2014 builder operates on a package workspace root.`);
|
|
142936
142880
|
}
|
|
142937
142881
|
const rootName = typeof rootManifest.name === "string" ? rootManifest.name : "root";
|
|
142938
142882
|
const workspaces = [];
|
|
142939
142883
|
for (const dir of workspaceDirs(repoRoot)) {
|
|
142940
|
-
const manifest = await readJson2(
|
|
142884
|
+
const manifest = await readJson2(join79(repoRoot, dir, "package.json"));
|
|
142941
142885
|
if (manifest === null)
|
|
142942
142886
|
continue;
|
|
142943
142887
|
workspaces.push({
|
|
@@ -142952,8 +142896,8 @@ async function releaseContext(repoRoot, bumpVerConfig = resolveBuilderBumpVerCon
|
|
|
142952
142896
|
const packages = new Map;
|
|
142953
142897
|
for (const ws of workspaces) {
|
|
142954
142898
|
const probe = findLiteralProbe(versionCarriers, ws.dir) ?? defaultLiteralProbe;
|
|
142955
|
-
const literalProbe =
|
|
142956
|
-
const versionSourceFile = existsSync26(
|
|
142899
|
+
const literalProbe = join79(ws.dir, probe.file);
|
|
142900
|
+
const versionSourceFile = existsSync26(join79(repoRoot, literalProbe)) ? literalProbe : undefined;
|
|
142957
142901
|
packages.set(shortName(ws.name), {
|
|
142958
142902
|
packageDir: ws.dir,
|
|
142959
142903
|
packageName: ws.name,
|
|
@@ -142991,7 +142935,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output)
|
|
|
142991
142935
|
const changed = [];
|
|
142992
142936
|
for (const dir of [".", ...workspaceDirs(ctx.repoRoot)]) {
|
|
142993
142937
|
const relPath = dir === "." ? "package.json" : `${dir}/package.json`;
|
|
142994
|
-
const manifest = await readJson2(
|
|
142938
|
+
const manifest = await readJson2(join79(ctx.repoRoot, relPath));
|
|
142995
142939
|
if (manifest === null)
|
|
142996
142940
|
continue;
|
|
142997
142941
|
let dirty = false;
|
|
@@ -143008,7 +142952,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output)
|
|
|
143008
142952
|
}
|
|
143009
142953
|
}
|
|
143010
142954
|
if (dirty) {
|
|
143011
|
-
await Bun.write(
|
|
142955
|
+
await Bun.write(join79(ctx.repoRoot, relPath), `${JSON.stringify(manifest, null, 4)}
|
|
143012
142956
|
`);
|
|
143013
142957
|
output.write(` \u21B3 ${relPath}: ${pkgName} workspace pin ${oldVersion} \u2192 ${newVersion}`);
|
|
143014
142958
|
changed.push(relPath);
|
|
@@ -143016,7 +142960,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output)
|
|
|
143016
142960
|
}
|
|
143017
142961
|
if (changed.length === 0)
|
|
143018
142962
|
return changed;
|
|
143019
|
-
const lockPath =
|
|
142963
|
+
const lockPath = join79(ctx.repoRoot, "bun.lock");
|
|
143020
142964
|
if (existsSync26(lockPath)) {
|
|
143021
142965
|
const lock = await Bun.file(lockPath).text();
|
|
143022
142966
|
const staleSpec = `"${pkgName}": "workspace:${oldVersion}"`;
|
|
@@ -143028,7 +142972,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output)
|
|
|
143028
142972
|
return changed;
|
|
143029
142973
|
}
|
|
143030
142974
|
async function updateVersionSourceFile(ctx, filePath, previous, next, output, identifier = "binaryVersion") {
|
|
143031
|
-
const absPath =
|
|
142975
|
+
const absPath = join79(ctx.repoRoot, filePath);
|
|
143032
142976
|
const content = await Bun.file(absPath).text();
|
|
143033
142977
|
const pattern = new RegExp(`(${identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\s*['"])\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-.]+)?(?:\\+[0-9A-Za-z-.]+)?(['"])`);
|
|
143034
142978
|
const updated = content.replace(pattern, `$1${next}$2`);
|
|
@@ -143046,7 +142990,7 @@ async function syncExtraCarriers(ctx, version, staged, output) {
|
|
|
143046
142990
|
}
|
|
143047
142991
|
}
|
|
143048
142992
|
async function syncMarketplaceAndPlugins(ctx, version, staged, output) {
|
|
143049
|
-
const marketplacePath =
|
|
142993
|
+
const marketplacePath = join79(ctx.repoRoot, ".claude-plugin", "marketplace.json");
|
|
143050
142994
|
const marketplaceFile = Bun.file(marketplacePath);
|
|
143051
142995
|
if (!await marketplaceFile.exists())
|
|
143052
142996
|
return;
|
|
@@ -143071,7 +143015,7 @@ async function syncMarketplaceAndPlugins(ctx, version, staged, output) {
|
|
|
143071
143015
|
output.write(`Bumped marketplace plugins to ${version}`);
|
|
143072
143016
|
}
|
|
143073
143017
|
for (const entry of plugins) {
|
|
143074
|
-
const pluginJsonPath =
|
|
143018
|
+
const pluginJsonPath = join79(ctx.repoRoot, entry.source, "plugin.json");
|
|
143075
143019
|
const pluginJson = await readJson2(pluginJsonPath);
|
|
143076
143020
|
if (pluginJson === null) {
|
|
143077
143021
|
output.write(` \u26A0 plugin.json not found at ${entry.source}/plugin.json \u2014 skipping`);
|
|
@@ -143085,6 +143029,14 @@ async function syncMarketplaceAndPlugins(ctx, version, staged, output) {
|
|
|
143085
143029
|
output.write(`Bumped ${entry.source}/plugin.json to ${version}`);
|
|
143086
143030
|
}
|
|
143087
143031
|
}
|
|
143032
|
+
for (const config of ctx.packages.values()) {
|
|
143033
|
+
const pkgDir = join79(ctx.repoRoot, config.packageDir);
|
|
143034
|
+
const stagedMarketplace = join79(pkgDir, ".claude-plugin", "marketplace.json");
|
|
143035
|
+
if (!existsSync26(stagedMarketplace))
|
|
143036
|
+
continue;
|
|
143037
|
+
await bundlePlugins(join79(pkgDir, "plugins"), join79(pkgDir, ".claude-plugin"), join79(ctx.repoRoot, "plugins"), join79(ctx.repoRoot, ".claude-plugin"));
|
|
143038
|
+
output.write(`Refreshed staged plugin copy in ${config.packageDir}`);
|
|
143039
|
+
}
|
|
143088
143040
|
}
|
|
143089
143041
|
async function assertCleanTreeOnBranch(ctx) {
|
|
143090
143042
|
const RUNTIME_NOISE = /^\?\? \.spur\/(?:spur\.db|spur\.db-shm|spur\.db-wal|logs\/)/;
|
|
@@ -143125,7 +143077,7 @@ async function bumpVersion(ctx, config, version, options, output) {
|
|
|
143125
143077
|
throw new Error(`${config.packageName}@${version} is already published on npm. Use a new version.`);
|
|
143126
143078
|
}
|
|
143127
143079
|
await assertPluginInstallable(ctx, output);
|
|
143128
|
-
const manifestPath =
|
|
143080
|
+
const manifestPath = join79(ctx.repoRoot, config.packageDir, "package.json");
|
|
143129
143081
|
const manifest = await readJson2(manifestPath);
|
|
143130
143082
|
if (manifest === null)
|
|
143131
143083
|
throw new Error(`missing or malformed manifest at ${config.packageDir}/package.json`);
|
|
@@ -143144,7 +143096,7 @@ async function bumpVersion(ctx, config, version, options, output) {
|
|
|
143144
143096
|
await syncMarketplaceAndPlugins(ctx, version, staged, output);
|
|
143145
143097
|
await syncExtraCarriers(ctx, version, staged, output);
|
|
143146
143098
|
await verifyLockfile(ctx, output);
|
|
143147
|
-
const lockPath =
|
|
143099
|
+
const lockPath = join79(ctx.repoRoot, "bun.lock");
|
|
143148
143100
|
if (existsSync26(lockPath) && Bun.file(lockPath).size > 0)
|
|
143149
143101
|
staged.push("bun.lock");
|
|
143150
143102
|
await git2(ctx.repoRoot, ["add", ...staged]);
|
|
@@ -143201,7 +143153,7 @@ async function bumpAll(ctx, version, options, output) {
|
|
|
143201
143153
|
}
|
|
143202
143154
|
const staged = [];
|
|
143203
143155
|
for (const config of configs) {
|
|
143204
|
-
const manifestPath =
|
|
143156
|
+
const manifestPath = join79(ctx.repoRoot, config.packageDir, "package.json");
|
|
143205
143157
|
const manifest = await readJson2(manifestPath);
|
|
143206
143158
|
if (manifest === null) {
|
|
143207
143159
|
throw new Error(`missing or malformed manifest at ${config.packageDir}/package.json`);
|
|
@@ -143222,7 +143174,7 @@ async function bumpAll(ctx, version, options, output) {
|
|
|
143222
143174
|
await syncMarketplaceAndPlugins(ctx, version, staged, output);
|
|
143223
143175
|
await syncExtraCarriers(ctx, version, staged, output);
|
|
143224
143176
|
await verifyLockfile(ctx, output);
|
|
143225
|
-
const lockPath =
|
|
143177
|
+
const lockPath = join79(ctx.repoRoot, "bun.lock");
|
|
143226
143178
|
if (existsSync26(lockPath) && Bun.file(lockPath).size > 0)
|
|
143227
143179
|
staged.push("bun.lock");
|
|
143228
143180
|
await git2(ctx.repoRoot, ["add", ...staged]);
|
|
@@ -143378,9 +143330,9 @@ async function checkVersionDrift(target, repoRoot = process.cwd(), output = cons
|
|
|
143378
143330
|
if (config === undefined) {
|
|
143379
143331
|
throw releaseUsage(target === undefined ? "bump-ver --check needs a package id (no aggregate package is configured)" : `unknown package "${target}". Package IDs: ${[...ctx.packages.keys()].join(", ")}`);
|
|
143380
143332
|
}
|
|
143381
|
-
const manifest = await readJson2(
|
|
143333
|
+
const manifest = await readJson2(join79(repoRoot, config.packageDir, "package.json"));
|
|
143382
143334
|
const pkgVersion = typeof manifest?.version === "string" ? manifest.version : "";
|
|
143383
|
-
const marketplacePath =
|
|
143335
|
+
const marketplacePath = join79(repoRoot, ".claude-plugin", "marketplace.json");
|
|
143384
143336
|
const marketplaceFile = Bun.file(marketplacePath);
|
|
143385
143337
|
if (!await marketplaceFile.exists()) {
|
|
143386
143338
|
output.write("No .claude-plugin/marketplace.json \u2014 nothing to check.");
|
|
@@ -143393,8 +143345,8 @@ async function checkVersionDrift(target, repoRoot = process.cwd(), output = cons
|
|
|
143393
143345
|
for (const entry of parsed.plugins ?? []) {
|
|
143394
143346
|
if (typeof entry.source !== "string")
|
|
143395
143347
|
continue;
|
|
143396
|
-
const label =
|
|
143397
|
-
const pluginJson = await readJson2(
|
|
143348
|
+
const label = join79(entry.source, "plugin.json").replace(/^\.\//, "");
|
|
143349
|
+
const pluginJson = await readJson2(join79(repoRoot, entry.source, "plugin.json"));
|
|
143398
143350
|
plugins.push({ label, version: typeof pluginJson?.version === "string" ? pluginJson.version : "" });
|
|
143399
143351
|
}
|
|
143400
143352
|
} catch {}
|
|
@@ -143462,8 +143414,8 @@ async function publishPackage(target, opts = {}) {
|
|
|
143462
143414
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
143463
143415
|
const output = opts.output ?? consoleOutput;
|
|
143464
143416
|
const runner = opts.runner ?? defaultPublishRunner;
|
|
143465
|
-
const dir = (target.startsWith("/") ? target :
|
|
143466
|
-
const manifestPath =
|
|
143417
|
+
const dir = (target.startsWith("/") ? target : join79(repoRoot, target)).replace(/\/+$/, "");
|
|
143418
|
+
const manifestPath = join79(dir, "package.json");
|
|
143467
143419
|
const manifestFile = Bun.file(manifestPath);
|
|
143468
143420
|
if (!await manifestFile.exists())
|
|
143469
143421
|
throw new Error(`no package.json at ${dir}`);
|
|
@@ -143471,7 +143423,7 @@ async function publishPackage(target, opts = {}) {
|
|
|
143471
143423
|
const manifest = JSON.parse(original);
|
|
143472
143424
|
const workspaces = new Map;
|
|
143473
143425
|
for (const wsDir of workspaceDirs(repoRoot)) {
|
|
143474
|
-
const pkg = await readJson2(
|
|
143426
|
+
const pkg = await readJson2(join79(repoRoot, wsDir, "package.json"));
|
|
143475
143427
|
if (pkg !== null && typeof pkg.name === "string") {
|
|
143476
143428
|
workspaces.set(pkg.name, {
|
|
143477
143429
|
...typeof pkg.version === "string" ? { version: pkg.version } : {},
|
|
@@ -143479,7 +143431,7 @@ async function publishPackage(target, opts = {}) {
|
|
|
143479
143431
|
});
|
|
143480
143432
|
}
|
|
143481
143433
|
}
|
|
143482
|
-
const root = await readJson2(
|
|
143434
|
+
const root = await readJson2(join79(repoRoot, "package.json"));
|
|
143483
143435
|
const catalog = root?.workspaces?.catalog ?? {};
|
|
143484
143436
|
const changed = resolvePublishRanges(manifest, workspaces, catalog);
|
|
143485
143437
|
if (changed > 0) {
|
|
@@ -143514,7 +143466,7 @@ async function publishPackage(target, opts = {}) {
|
|
|
143514
143466
|
init_src();
|
|
143515
143467
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
143516
143468
|
import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "fs";
|
|
143517
|
-
import { join as
|
|
143469
|
+
import { join as join80, resolve as resolve28 } from "path";
|
|
143518
143470
|
function createSpawnRunner(context = {}) {
|
|
143519
143471
|
return (cmd) => {
|
|
143520
143472
|
const proc = spawnSync7(cmd[0] ?? "", [...cmd.slice(1)], {
|
|
@@ -143657,7 +143609,7 @@ function extractSeverity2(body) {
|
|
|
143657
143609
|
}
|
|
143658
143610
|
var DEFAULT_FOCUS = "for correctness and regressions, security boundaries, data-loss risks, concurrency or race conditions, " + "API/backward compatibility, migration safety, error handling, and missing high-value tests. " + "Prioritize actionable issues over style or nits.";
|
|
143659
143611
|
function hasCodeReviewRules(repoRoot) {
|
|
143660
|
-
const agentsPath =
|
|
143612
|
+
const agentsPath = join80(repoRoot, "AGENTS.md");
|
|
143661
143613
|
if (!existsSync27(agentsPath))
|
|
143662
143614
|
return false;
|
|
143663
143615
|
return /^## Code Review Rules\s*$/m.test(readFileSync24(agentsPath, "utf8"));
|
|
@@ -144215,10 +144167,333 @@ function runSpine(argv, rt) {
|
|
|
144215
144167
|
return 2;
|
|
144216
144168
|
}
|
|
144217
144169
|
}
|
|
144170
|
+
function obj(value) {
|
|
144171
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
144172
|
+
}
|
|
144173
|
+
function text4(value) {
|
|
144174
|
+
return typeof value === "string" ? value : "";
|
|
144175
|
+
}
|
|
144176
|
+
function list(value) {
|
|
144177
|
+
return Array.isArray(value) ? value : [];
|
|
144178
|
+
}
|
|
144179
|
+
function word(payload, fallback = "FAIL") {
|
|
144180
|
+
const verdict = payload?.verdict;
|
|
144181
|
+
return typeof verdict === "string" && verdict !== "" ? verdict : fallback;
|
|
144182
|
+
}
|
|
144183
|
+
function findingsArg(value) {
|
|
144184
|
+
return list(value);
|
|
144185
|
+
}
|
|
144186
|
+
function runVerbOp(argv, rt, sink) {
|
|
144187
|
+
const out = [];
|
|
144188
|
+
const previous = io;
|
|
144189
|
+
io = { out: (line) => out.push(line), err: (line) => sink.err(line) };
|
|
144190
|
+
try {
|
|
144191
|
+
const code = runSpine(argv, rt);
|
|
144192
|
+
let payload = null;
|
|
144193
|
+
for (let i = out.length - 1;i >= 0; i--) {
|
|
144194
|
+
const line = (out[i] ?? "").trim();
|
|
144195
|
+
if (!line.startsWith("{"))
|
|
144196
|
+
continue;
|
|
144197
|
+
try {
|
|
144198
|
+
payload = JSON.parse(line);
|
|
144199
|
+
} catch {
|
|
144200
|
+
payload = null;
|
|
144201
|
+
}
|
|
144202
|
+
break;
|
|
144203
|
+
}
|
|
144204
|
+
return { code, payload };
|
|
144205
|
+
} finally {
|
|
144206
|
+
io = previous;
|
|
144207
|
+
}
|
|
144208
|
+
}
|
|
144209
|
+
function validPollBudget(opts, sink) {
|
|
144210
|
+
if (opts.timeout !== undefined && !(Number.isFinite(Number(opts.timeout)) && Number(opts.timeout) >= 0)) {
|
|
144211
|
+
sink.err("error: --timeout must be a non-negative number");
|
|
144212
|
+
return false;
|
|
144213
|
+
}
|
|
144214
|
+
if (opts.interval !== undefined && !(Number.isFinite(Number(opts.interval)) && Number(opts.interval) > 0)) {
|
|
144215
|
+
sink.err("error: --interval must be a positive number");
|
|
144216
|
+
return false;
|
|
144217
|
+
}
|
|
144218
|
+
return true;
|
|
144219
|
+
}
|
|
144220
|
+
function writeVerbStatus(rt, opts, verdict) {
|
|
144221
|
+
if (opts.statusFile !== undefined)
|
|
144222
|
+
rt.writeStatus(opts.statusFile, `${verdict}
|
|
144223
|
+
`);
|
|
144224
|
+
}
|
|
144225
|
+
function currentHead(rt) {
|
|
144226
|
+
const res = run3(rt, ["git", "rev-parse", "HEAD"]);
|
|
144227
|
+
const head = res.stdout.trim();
|
|
144228
|
+
return res.code === 0 && head !== "" ? head : undefined;
|
|
144229
|
+
}
|
|
144230
|
+
function composeStatus(rt, opts, sink) {
|
|
144231
|
+
const flags = ["--json"];
|
|
144232
|
+
if (opts.since !== undefined)
|
|
144233
|
+
flags.push("--since", opts.since);
|
|
144234
|
+
const head = opts.head ?? currentHead(rt);
|
|
144235
|
+
if (head !== undefined)
|
|
144236
|
+
flags.push("--head", head);
|
|
144237
|
+
const status = runVerbOp(["status", ...flags], rt, sink);
|
|
144238
|
+
if (status.code !== 0)
|
|
144239
|
+
return { code: status.code, payload: { ...obj(status.payload), review: null } };
|
|
144240
|
+
if (status.payload?.pr === null || status.payload?.pr === undefined) {
|
|
144241
|
+
return { code: 0, payload: { ...obj(status.payload), review: null } };
|
|
144242
|
+
}
|
|
144243
|
+
const collect = runVerbOp(["collect", ...flags], rt, sink);
|
|
144244
|
+
return { code: collect.code, payload: { ...obj(status.payload), review: collect.payload } };
|
|
144245
|
+
}
|
|
144246
|
+
function composeCheck(rt, opts, sink) {
|
|
144247
|
+
const flags = ["--json"];
|
|
144248
|
+
if (opts.statusFile !== undefined)
|
|
144249
|
+
flags.push("--status-file", opts.statusFile);
|
|
144250
|
+
if (opts.base !== undefined)
|
|
144251
|
+
flags.push("--base", opts.base);
|
|
144252
|
+
const preflight = opts.scanOnly === true ? null : runVerbOp(["preflight", ...flags], rt, sink);
|
|
144253
|
+
if (preflight !== null && preflight.code !== 0) {
|
|
144254
|
+
return {
|
|
144255
|
+
code: preflight.code,
|
|
144256
|
+
payload: { ok: false, verdict: "FAIL", preflight: preflight.payload, hygiene: null }
|
|
144257
|
+
};
|
|
144258
|
+
}
|
|
144259
|
+
const hygiene = runVerbOp(["hygiene", ...flags], rt, sink);
|
|
144260
|
+
const opWord = word(hygiene.payload, "PASS");
|
|
144261
|
+
const verdict = hygiene.code === 0 ? opWord : opWord === "BLOCK" ? "BLOCK" : "FAIL";
|
|
144262
|
+
return {
|
|
144263
|
+
code: hygiene.code,
|
|
144264
|
+
payload: { ok: hygiene.code === 0, verdict, preflight: preflight?.payload ?? null, hygiene: hygiene.payload }
|
|
144265
|
+
};
|
|
144266
|
+
}
|
|
144267
|
+
function composeOpen(rt, opts, sink) {
|
|
144268
|
+
const flags = ["--json"];
|
|
144269
|
+
if (opts.statusFile !== undefined)
|
|
144270
|
+
flags.push("--status-file", opts.statusFile);
|
|
144271
|
+
const push = runVerbOp(["push", ...flags], rt, sink);
|
|
144272
|
+
if (push.code !== 0) {
|
|
144273
|
+
return { code: push.code, payload: { ok: false, verdict: "FAIL", push: push.payload, pr: null } };
|
|
144274
|
+
}
|
|
144275
|
+
const ensureFlags = [...flags];
|
|
144276
|
+
if (opts.base !== undefined)
|
|
144277
|
+
ensureFlags.push("--base", opts.base);
|
|
144278
|
+
const ensure = runVerbOp(["ensure", ...ensureFlags], rt, sink);
|
|
144279
|
+
const verdict = ensure.code === 0 ? ensure.payload?.created === true ? "CREATED" : "FOUND" : "FAIL";
|
|
144280
|
+
return {
|
|
144281
|
+
code: ensure.code,
|
|
144282
|
+
payload: { ok: ensure.code === 0, verdict, push: push.payload, pr: ensure.payload?.pr ?? null }
|
|
144283
|
+
};
|
|
144284
|
+
}
|
|
144285
|
+
function composeReview(rt, opts, sink) {
|
|
144286
|
+
const statusFlags = opts.statusFile !== undefined ? ["--status-file", opts.statusFile] : [];
|
|
144287
|
+
if (!validPollBudget(opts, sink)) {
|
|
144288
|
+
return { code: 1, payload: { ok: false, verdict: "FAIL", request: null, findings: [] } };
|
|
144289
|
+
}
|
|
144290
|
+
const requestFlags = ["--json", ...statusFlags];
|
|
144291
|
+
if (opts.force === true)
|
|
144292
|
+
requestFlags.push("--force");
|
|
144293
|
+
if (opts.focus !== undefined)
|
|
144294
|
+
requestFlags.push("--focus", opts.focus);
|
|
144295
|
+
const request = runVerbOp(["request", ...requestFlags], rt, sink);
|
|
144296
|
+
if (request.code !== 0) {
|
|
144297
|
+
return { code: request.code, payload: { ok: false, verdict: "FAIL", request: request.payload, findings: [] } };
|
|
144298
|
+
}
|
|
144299
|
+
const record = obj(request.payload);
|
|
144300
|
+
const correlation = [];
|
|
144301
|
+
if (text4(record.requestedAt) !== "")
|
|
144302
|
+
correlation.push("--since", text4(record.requestedAt));
|
|
144303
|
+
if (text4(record.head) !== "")
|
|
144304
|
+
correlation.push("--head", text4(record.head));
|
|
144305
|
+
const collected = () => {
|
|
144306
|
+
const collect = runVerbOp(["collect", ...correlation, "--json", ...statusFlags], rt, sink);
|
|
144307
|
+
return {
|
|
144308
|
+
code: collect.code,
|
|
144309
|
+
payload: {
|
|
144310
|
+
ok: collect.code === 0,
|
|
144311
|
+
verdict: collect.code === 0 ? word(collect.payload) : "FAIL",
|
|
144312
|
+
request: request.payload,
|
|
144313
|
+
findings: collect.code === 0 ? list(collect.payload?.findings) : []
|
|
144314
|
+
}
|
|
144315
|
+
};
|
|
144316
|
+
};
|
|
144317
|
+
const pending = (code) => {
|
|
144318
|
+
writeVerbStatus(rt, opts, "PENDING");
|
|
144319
|
+
return { code, payload: { ok: true, verdict: "PENDING", request: request.payload, findings: [] } };
|
|
144320
|
+
};
|
|
144321
|
+
if (record.alreadyReviewed === true)
|
|
144322
|
+
return collected();
|
|
144323
|
+
if (record.pending === true || record.requested === true && opts.noWait === true)
|
|
144324
|
+
return pending(0);
|
|
144325
|
+
const waitFlags = [...correlation, "--json", ...statusFlags];
|
|
144326
|
+
if (opts.timeout !== undefined)
|
|
144327
|
+
waitFlags.push("--timeout", opts.timeout);
|
|
144328
|
+
if (opts.interval !== undefined)
|
|
144329
|
+
waitFlags.push("--interval", opts.interval);
|
|
144330
|
+
const waited = runVerbOp(["wait", ...waitFlags], rt, sink);
|
|
144331
|
+
if (waited.code === 3)
|
|
144332
|
+
return pending(3);
|
|
144333
|
+
if (waited.code !== 0) {
|
|
144334
|
+
return { code: waited.code, payload: { ok: false, verdict: "FAIL", request: request.payload, findings: [] } };
|
|
144335
|
+
}
|
|
144336
|
+
return collected();
|
|
144337
|
+
}
|
|
144338
|
+
function renderPrVerb(verb, payload) {
|
|
144339
|
+
if (verb === "status") {
|
|
144340
|
+
const pr = payload.pr === null || payload.pr === undefined ? null : obj(payload.pr);
|
|
144341
|
+
const review = payload.review === null || payload.review === undefined ? null : obj(payload.review);
|
|
144342
|
+
const lines = [
|
|
144343
|
+
`Repository: ${text4(payload.repo) || "unknown"}`,
|
|
144344
|
+
`PR: ${pr === null ? "none" : `#${String(pr.number ?? "")} ${text4(pr.url)}`}`,
|
|
144345
|
+
`Branch: ${text4(payload.branch)}`,
|
|
144346
|
+
`HEAD: ${text4(payload.head)}`,
|
|
144347
|
+
`Base: ${text4(payload.base)}`,
|
|
144348
|
+
`Local: ${text4(payload.local)}`,
|
|
144349
|
+
`CI: ${text4(payload.ci)}`,
|
|
144350
|
+
`Codex: ${text4(payload.codex)}`
|
|
144351
|
+
];
|
|
144352
|
+
if (review === null)
|
|
144353
|
+
return [...lines, "Review: no PR \u2014 nothing collected"];
|
|
144354
|
+
lines.push(`Review: ${text4(review.verdict) || "unknown"}`);
|
|
144355
|
+
const findings = findingsArg(review.findings);
|
|
144356
|
+
if (findings.length > 0)
|
|
144357
|
+
lines.push("", renderFindings(findings));
|
|
144358
|
+
return lines;
|
|
144359
|
+
}
|
|
144360
|
+
if (verb === "check") {
|
|
144361
|
+
const preflight = payload.preflight === null || payload.preflight === undefined ? null : obj(payload.preflight);
|
|
144362
|
+
const hygiene = payload.hygiene === null || payload.hygiene === undefined ? null : obj(payload.hygiene);
|
|
144363
|
+
const lines = [
|
|
144364
|
+
preflight === null ? "Preflight: skipped (--scan-only)" : preflight.ok === true ? `Preflight: ${text4(preflight.nameWithOwner)} ${text4(preflight.branch)} @ ${text4(preflight.shortHead)}` : `Preflight: FAIL \u2014 ${text4(preflight.error) || "probe failed"}`
|
|
144365
|
+
];
|
|
144366
|
+
if (hygiene === null)
|
|
144367
|
+
return lines;
|
|
144368
|
+
lines.push(`Hygiene: ${text4(hygiene.verdict) || "FAIL"}`);
|
|
144369
|
+
for (const blocker of list(hygiene.blockers))
|
|
144370
|
+
lines.push(` BLOCK ${String(blocker)}`);
|
|
144371
|
+
for (const warning of list(hygiene.warnings))
|
|
144372
|
+
lines.push(` WARN ${String(warning)}`);
|
|
144373
|
+
return lines;
|
|
144374
|
+
}
|
|
144375
|
+
if (verb === "open") {
|
|
144376
|
+
const push = obj(payload.push);
|
|
144377
|
+
const pr = payload.pr === null || payload.pr === undefined ? null : obj(payload.pr);
|
|
144378
|
+
return [
|
|
144379
|
+
`Push: ${push.pushed === true ? "PUSHED" : "UP_TO_DATE"} ${text4(push.head).slice(0, 7)}`,
|
|
144380
|
+
`PR: ${pr === null ? "none" : `#${String(pr.number ?? "")} ${text4(pr.url)} (${text4(payload.verdict)})`}`
|
|
144381
|
+
];
|
|
144382
|
+
}
|
|
144383
|
+
const request = obj(payload.request);
|
|
144384
|
+
const outcome = request.requested === true ? "REQUESTED" : request.alreadyReviewed === true ? "ALREADY_REVIEWED" : request.pending === true ? "ALREADY_REQUESTED" : "FAIL";
|
|
144385
|
+
const lines = [
|
|
144386
|
+
`Request: ${outcome} PR #${String(request.pr ?? "?")} ${text4(request.url)}`,
|
|
144387
|
+
`HEAD: ${text4(request.head).slice(0, 7)}`,
|
|
144388
|
+
`Review: ${text4(payload.verdict) || "unknown"}`
|
|
144389
|
+
];
|
|
144390
|
+
const findings = findingsArg(payload.findings);
|
|
144391
|
+
if (findings.length > 0)
|
|
144392
|
+
lines.push("", renderFindings(findings));
|
|
144393
|
+
return lines;
|
|
144394
|
+
}
|
|
144395
|
+
var PR_VERB_COMPOSERS = { status: composeStatus, check: composeCheck, open: composeOpen, review: composeReview };
|
|
144396
|
+
function runPrVerb(verb, opts, sink, context = {}) {
|
|
144397
|
+
const rt = createRuntime(context);
|
|
144398
|
+
const result = PR_VERB_COMPOSERS[verb](rt, opts, sink);
|
|
144399
|
+
if (opts.json === true)
|
|
144400
|
+
sink.out(JSON.stringify(result.payload));
|
|
144401
|
+
else
|
|
144402
|
+
for (const line of renderPrVerb(verb, result.payload))
|
|
144403
|
+
sink.out(line);
|
|
144404
|
+
return result.code;
|
|
144405
|
+
}
|
|
144218
144406
|
|
|
144219
144407
|
// src/commands/builder/pr.ts
|
|
144220
|
-
|
|
144221
|
-
|
|
144408
|
+
var PR_VERB_FOR_OP = {
|
|
144409
|
+
preflight: "pr-check",
|
|
144410
|
+
hygiene: "pr-check",
|
|
144411
|
+
push: "pr-open",
|
|
144412
|
+
ensure: "pr-open",
|
|
144413
|
+
"ensure-pr": "pr-open",
|
|
144414
|
+
request: "pr-review",
|
|
144415
|
+
wait: "pr-review",
|
|
144416
|
+
collect: "pr-status",
|
|
144417
|
+
status: "pr-status"
|
|
144418
|
+
};
|
|
144419
|
+
function prDeprecationLine(op) {
|
|
144420
|
+
const replacement = op === undefined ? undefined : PR_VERB_FOR_OP[op];
|
|
144421
|
+
return replacement !== undefined ? `spur builder pr ${op} is deprecated; use spur builder ${replacement}` : "spur builder pr is deprecated; use spur builder pr-status | pr-check | pr-open | pr-review";
|
|
144422
|
+
}
|
|
144423
|
+
function registerBuilderPrCommands(builder, context) {
|
|
144424
|
+
const io = () => ({
|
|
144425
|
+
out: (line) => context.output.write(line),
|
|
144426
|
+
err: (line) => context.output.error(line)
|
|
144427
|
+
});
|
|
144428
|
+
const ctx = () => ({ cwd: context.cwd, env: context.env });
|
|
144429
|
+
const run = (verb, opts) => {
|
|
144430
|
+
context.setExitCode(runPrVerb(verb, opts, io(), ctx()));
|
|
144431
|
+
};
|
|
144432
|
+
builder.command("pr-status").summary("read-only PR/CI/Codex status with the current-HEAD review as `review`").description([
|
|
144433
|
+
"Read-only composition of the status and collect operations:",
|
|
144434
|
+
"repository, branch, PR/base, local changes, CI and Codex state, plus",
|
|
144435
|
+
"`review` \u2014 the current-HEAD Codex record when the branch has a PR,",
|
|
144436
|
+
"else null. Writes no verdict: a status probe is not a gate.",
|
|
144437
|
+
"Exit codes: 0 ok \xB7 2 probe failure."
|
|
144438
|
+
].join(`
|
|
144439
|
+
`)).option("--since <iso>", "only count review output submitted at or after this time").option("--head <sha>", "pin the reviewed commit (default: the current local HEAD)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "accepted for symmetry; a read-only status writes no verdict").action((options) => {
|
|
144440
|
+
run("status", {
|
|
144441
|
+
...options.since !== undefined ? { since: options.since } : {},
|
|
144442
|
+
...options.head !== undefined ? { head: options.head } : {},
|
|
144443
|
+
...options.json === true ? { json: true } : {},
|
|
144444
|
+
...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
|
|
144445
|
+
});
|
|
144446
|
+
});
|
|
144447
|
+
builder.command("pr-check").summary("PR submission gate: preflight then hygiene (PASS | WARN | BLOCK | FAIL)").description([
|
|
144448
|
+
"Preflight (git/gh/repo checks, clean tree, not the base branch) then",
|
|
144449
|
+
"the hygiene scan of base...HEAD for secrets/.env/conflict markers",
|
|
144450
|
+
"(BLOCK) and debug residue (WARN). A preflight FAIL stops before",
|
|
144451
|
+
"hygiene. `--scan-only` runs hygiene alone, for a local review on a",
|
|
144452
|
+
"dirty tree. Exit codes: 0 ok \xB7 2 BLOCK/preflight FAIL/probe failure."
|
|
144453
|
+
].join(`
|
|
144454
|
+
`)).option("--base <branch>", "base branch for the base...HEAD diff (default: the PR base, else the repo default)").option("--scan-only", "hygiene only; skip preflight (a dirty tree is expected)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "write the one-word verdict (PASS | WARN | BLOCK | FAIL)").action((options) => {
|
|
144455
|
+
run("check", {
|
|
144456
|
+
...options.base !== undefined ? { base: options.base } : {},
|
|
144457
|
+
...options.scanOnly === true ? { scanOnly: true } : {},
|
|
144458
|
+
...options.json === true ? { json: true } : {},
|
|
144459
|
+
...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
|
|
144460
|
+
});
|
|
144461
|
+
});
|
|
144462
|
+
builder.command("pr-open").summary("publish the branch and reuse or create its pull request (FOUND | CREATED | FAIL)").description([
|
|
144463
|
+
"Push the branch (normal push only \u2014 never force; sets upstream when",
|
|
144464
|
+
"missing), then reuse the branch PR or create one with",
|
|
144465
|
+
"`gh pr create --fill`. A rejected push stops before any PR work.",
|
|
144466
|
+
"Exit codes: 0 ok \xB7 2 push rejected or ensure failed."
|
|
144467
|
+
].join(`
|
|
144468
|
+
`)).option("--base <branch>", "base for a newly created PR (default: the repo default branch)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "write the one-word verdict (FOUND | CREATED | FAIL)").action((options) => {
|
|
144469
|
+
run("open", {
|
|
144470
|
+
...options.base !== undefined ? { base: options.base } : {},
|
|
144471
|
+
...options.json === true ? { json: true } : {},
|
|
144472
|
+
...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
|
|
144473
|
+
});
|
|
144474
|
+
});
|
|
144475
|
+
builder.command("pr-review").summary("request the GitHub Codex review and collect its result (CLEAN | FINDINGS | PENDING | FAIL)").description([
|
|
144476
|
+
"Post the `@codex review` request (per-HEAD dedupe unless --force),",
|
|
144477
|
+
"poll for the result, then collect it. An already-reviewed HEAD is",
|
|
144478
|
+
"collected without a new request; an in-flight request or --no-wait",
|
|
144479
|
+
"returns PENDING. Exit codes: 0 ok \xB7 2 hard failure \xB7 3 wait timeout",
|
|
144480
|
+
"(verdict PENDING \u2014 pending, never failure)."
|
|
144481
|
+
].join(`
|
|
144482
|
+
`)).option("--force", "request a fresh review even when the current HEAD is already reviewed").option("--focus <text>", "extra review focus appended to the request").option("--no-wait", "post the request and return PENDING without polling").option("--timeout <s>", "wait budget in seconds (service default 600)").option("--interval <s>", "poll interval in seconds (service default 30)").option(...SHARED_OPTIONS.json).option("--status-file <path>", "write the one-word verdict (CLEAN | FINDINGS | PENDING | FAIL)").action((options) => {
|
|
144483
|
+
run("review", {
|
|
144484
|
+
...options.force === true ? { force: true } : {},
|
|
144485
|
+
...options.focus !== undefined ? { focus: options.focus } : {},
|
|
144486
|
+
...options.wait === false ? { noWait: true } : {},
|
|
144487
|
+
...options.timeout !== undefined ? { timeout: options.timeout } : {},
|
|
144488
|
+
...options.interval !== undefined ? { interval: options.interval } : {},
|
|
144489
|
+
...options.json === true ? { json: true } : {},
|
|
144490
|
+
...options.statusFile !== undefined ? { statusFile: options.statusFile } : {}
|
|
144491
|
+
});
|
|
144492
|
+
});
|
|
144493
|
+
}
|
|
144494
|
+
function registerBuilderPrAliasCommand(builder, context) {
|
|
144495
|
+
builder.command("pr", { hidden: true }).summary("deprecated alias for the flat pr-<verb> commands").description(PR_REVIEW_HELP).allowUnknownOption(true).allowExcessArguments(true).argument("[argv...]", "subcommand and flags (see description)").action((argv) => {
|
|
144496
|
+
context.output.error(prDeprecationLine(argv[0]));
|
|
144222
144497
|
context.setExitCode(main(argv, {
|
|
144223
144498
|
out: (line) => context.output.write(line),
|
|
144224
144499
|
err: (line) => context.output.error(line)
|
|
@@ -144226,9 +144501,574 @@ function registerBuilderPrCommand(builder, context) {
|
|
|
144226
144501
|
});
|
|
144227
144502
|
}
|
|
144228
144503
|
|
|
144504
|
+
// ../../packages/app/src/services/branch-topology/render.ts
|
|
144505
|
+
var INDENT = " ";
|
|
144506
|
+
var MERMAID_DEFAULT_BRANCH = "main";
|
|
144507
|
+
var TABLE_HEADERS = ["branch", "ahead", "behind", "contained", "last commit", "worktree", "PR"];
|
|
144508
|
+
function renderMarkdown2(topology, graph) {
|
|
144509
|
+
const lines = [
|
|
144510
|
+
`## Branch topology vs \`${markdownCell(topology.base)}\``,
|
|
144511
|
+
"",
|
|
144512
|
+
`Generated ${topology.generatedAt}.`
|
|
144513
|
+
];
|
|
144514
|
+
for (const warning of topology.warnings)
|
|
144515
|
+
lines.push("", `> warning: ${warning}`);
|
|
144516
|
+
lines.push("", ...markdownTable(topology.branches), ...omittedFooter(topology, "_", "_"));
|
|
144517
|
+
lines.push("", "```mermaid", renderGitGraph(topology, graph), "```");
|
|
144518
|
+
return `${lines.join(`
|
|
144519
|
+
`)}
|
|
144520
|
+
`;
|
|
144521
|
+
}
|
|
144522
|
+
function renderText(topology) {
|
|
144523
|
+
const rows = [TABLE_HEADERS.map((header) => header), ...topology.branches.map(rowCells)];
|
|
144524
|
+
const widths = TABLE_HEADERS.map((_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length)));
|
|
144525
|
+
const lines = [`base: ${topology.base}`, `generated: ${topology.generatedAt}`];
|
|
144526
|
+
for (const warning of topology.warnings)
|
|
144527
|
+
lines.push(`warning: ${warning}`);
|
|
144528
|
+
lines.push("", ...rows.map((row) => alignRow(row, widths)), ...omittedFooter(topology, "", ""));
|
|
144529
|
+
return `${lines.join(`
|
|
144530
|
+
`)}
|
|
144531
|
+
`;
|
|
144532
|
+
}
|
|
144533
|
+
function markdownTable(branches) {
|
|
144534
|
+
const lines = [`| ${TABLE_HEADERS.join(" | ")} |`, `| ${TABLE_HEADERS.map(() => "---").join(" | ")} |`];
|
|
144535
|
+
for (const row of branches) {
|
|
144536
|
+
lines.push(`| ${rowCells(row).map(markdownCell).join(" | ")} |`);
|
|
144537
|
+
}
|
|
144538
|
+
return lines;
|
|
144539
|
+
}
|
|
144540
|
+
function rowCells(row) {
|
|
144541
|
+
return [
|
|
144542
|
+
row.name,
|
|
144543
|
+
String(row.ahead),
|
|
144544
|
+
String(row.behind),
|
|
144545
|
+
row.contained ? "yes" : "no",
|
|
144546
|
+
row.lastCommitAt.slice(0, 10),
|
|
144547
|
+
row.worktree ?? "-",
|
|
144548
|
+
row.pr === null ? "-" : `#${row.pr.number} ${row.pr.state}${row.pr.reviewDecision === null ? "" : ` ${row.pr.reviewDecision}`}`
|
|
144549
|
+
];
|
|
144550
|
+
}
|
|
144551
|
+
function alignRow(cells, widths) {
|
|
144552
|
+
return cells.map((cell, column) => {
|
|
144553
|
+
const width = widths[column] ?? cell.length;
|
|
144554
|
+
return column === 0 ? cell.padEnd(width) : cell.padStart(width);
|
|
144555
|
+
}).join(" ").trimEnd();
|
|
144556
|
+
}
|
|
144557
|
+
function omittedFooter(topology, open, close) {
|
|
144558
|
+
if (topology.omitted === 0)
|
|
144559
|
+
return [];
|
|
144560
|
+
const noun = topology.omitted === 1 ? "branch" : "branches";
|
|
144561
|
+
return ["", `${open}Omitted: ${topology.omitted} ${noun} beyond --limit.${close}`];
|
|
144562
|
+
}
|
|
144563
|
+
function markdownCell(value) {
|
|
144564
|
+
return value.replaceAll("|", "\\|").replaceAll("`", "'");
|
|
144565
|
+
}
|
|
144566
|
+
function renderGitGraph(topology, graph) {
|
|
144567
|
+
const lines = [];
|
|
144568
|
+
if (graph.theme !== undefined && graph.theme.length > 0) {
|
|
144569
|
+
lines.push(`%%{init: { 'theme': '${sanitizeId(graph.theme)}' } }%%`);
|
|
144570
|
+
}
|
|
144571
|
+
lines.push("gitGraph");
|
|
144572
|
+
const baseId = sanitizeId(topology.base);
|
|
144573
|
+
const usedRefs = new Set;
|
|
144574
|
+
const base = refToken(topology.base, usedRefs);
|
|
144575
|
+
const baseRow = baseRowOf(topology);
|
|
144576
|
+
if (baseId !== MERMAID_DEFAULT_BRANCH)
|
|
144577
|
+
lines.push(`${INDENT}branch ${base}`);
|
|
144578
|
+
const tagsBySha = collectTags(topology.branches);
|
|
144579
|
+
const usedIds = new Set;
|
|
144580
|
+
const usedTags = new Set;
|
|
144581
|
+
const window2 = (baseRow?.commits ?? []).slice(0, Math.max(graph.maxCommits, 0));
|
|
144582
|
+
for (const { sha } of deepForkPoints(topology, baseRow, window2.length, graph.hideContained)) {
|
|
144583
|
+
lines.push(`${INDENT}${commitStatement({ sha, subject: "", tags: tagsBySha.get(sha) ?? [] }, usedIds, usedTags, undefined)}`);
|
|
144584
|
+
}
|
|
144585
|
+
for (const [index, commit] of window2.slice().reverse().entries()) {
|
|
144586
|
+
const isTip = index === window2.length - 1;
|
|
144587
|
+
lines.push(`${INDENT}${commitStatement(commit, usedIds, usedTags, isTip ? "HIGHLIGHT" : commitType(commit))}`);
|
|
144588
|
+
}
|
|
144589
|
+
for (const row of topology.branches) {
|
|
144590
|
+
if (row === baseRow)
|
|
144591
|
+
continue;
|
|
144592
|
+
if (row.contained && graph.hideContained)
|
|
144593
|
+
continue;
|
|
144594
|
+
lines.push(`${INDENT}checkout ${base}`);
|
|
144595
|
+
const branch = refToken(row.name, usedRefs);
|
|
144596
|
+
lines.push(branch === MERMAID_DEFAULT_BRANCH ? `${INDENT}checkout ${branch}` : `${INDENT}branch ${branch}`);
|
|
144597
|
+
if (row.contained) {
|
|
144598
|
+
lines.push(`${INDENT}${commitStatement({ sha: row.tip, subject: "", tags: tagsBySha.get(row.tip) ?? [] }, usedIds, usedTags, undefined)}`);
|
|
144599
|
+
lines.push(`${INDENT}checkout ${base}`);
|
|
144600
|
+
lines.push(`${INDENT}merge ${branch}`);
|
|
144601
|
+
continue;
|
|
144602
|
+
}
|
|
144603
|
+
const shown = row.commits.slice(0, Math.max(Math.min(graph.maxCommits, row.ahead), 0)).reverse();
|
|
144604
|
+
const hidden = Math.max(row.ahead - shown.length, 0);
|
|
144605
|
+
if (hidden > 0) {
|
|
144606
|
+
lines.push(`${INDENT}commit id:"${uniqueId(`+${hidden} earlier`, usedIds)}"`);
|
|
144607
|
+
}
|
|
144608
|
+
for (const [index, commit] of shown.entries()) {
|
|
144609
|
+
const isTip = index === shown.length - 1;
|
|
144610
|
+
lines.push(`${INDENT}${commitStatement(commit, usedIds, usedTags, isTip ? "HIGHLIGHT" : commitType(commit))}`);
|
|
144611
|
+
}
|
|
144612
|
+
}
|
|
144613
|
+
return lines.join(`
|
|
144614
|
+
`);
|
|
144615
|
+
}
|
|
144616
|
+
function commitStatement(commit, usedIds, usedTags, type) {
|
|
144617
|
+
const sha = short(commit.sha);
|
|
144618
|
+
const id = uniqueId(sanitizeId(sha), usedIds);
|
|
144619
|
+
const tag = claimTag(commit.tags[0], usedTags);
|
|
144620
|
+
let line = `commit id:"${id}"`;
|
|
144621
|
+
if (tag !== undefined)
|
|
144622
|
+
line += ` tag:"${tag}"`;
|
|
144623
|
+
if (type !== undefined)
|
|
144624
|
+
line += ` type: ${type}`;
|
|
144625
|
+
return line;
|
|
144626
|
+
}
|
|
144627
|
+
function claimTag(name, usedTags) {
|
|
144628
|
+
if (name === undefined)
|
|
144629
|
+
return;
|
|
144630
|
+
const sanitized = sanitizeId(name);
|
|
144631
|
+
if (usedTags.has(sanitized))
|
|
144632
|
+
return;
|
|
144633
|
+
usedTags.add(sanitized);
|
|
144634
|
+
return sanitized;
|
|
144635
|
+
}
|
|
144636
|
+
function deepForkPoints(topology, baseRow, windowLength, hideContained) {
|
|
144637
|
+
const behindBySha = new Map;
|
|
144638
|
+
for (const row of topology.branches) {
|
|
144639
|
+
if (row === baseRow)
|
|
144640
|
+
continue;
|
|
144641
|
+
if (row.contained && hideContained)
|
|
144642
|
+
continue;
|
|
144643
|
+
if (row.forkPoint.length === 0 || row.forkPoint === row.tip)
|
|
144644
|
+
continue;
|
|
144645
|
+
if (row.behind < windowLength)
|
|
144646
|
+
continue;
|
|
144647
|
+
if (!behindBySha.has(row.forkPoint))
|
|
144648
|
+
behindBySha.set(row.forkPoint, row.behind);
|
|
144649
|
+
}
|
|
144650
|
+
return [...behindBySha].map(([sha, behind]) => ({ sha, behind })).sort((a, b) => b.behind - a.behind || (a.sha < b.sha ? -1 : 1));
|
|
144651
|
+
}
|
|
144652
|
+
function commitType(commit) {
|
|
144653
|
+
return commit.subject.startsWith('Revert "') ? "REVERSE" : undefined;
|
|
144654
|
+
}
|
|
144655
|
+
function collectTags(branches) {
|
|
144656
|
+
const tags = new Map;
|
|
144657
|
+
for (const row of branches) {
|
|
144658
|
+
for (const commit of row.commits)
|
|
144659
|
+
tags.set(commit.sha, commit.tags);
|
|
144660
|
+
}
|
|
144661
|
+
return tags;
|
|
144662
|
+
}
|
|
144663
|
+
function isBaseRow(base, row) {
|
|
144664
|
+
if (row.name === base)
|
|
144665
|
+
return true;
|
|
144666
|
+
if (row.kind !== "local")
|
|
144667
|
+
return false;
|
|
144668
|
+
return row.name === base.replace(/^[^/]+\//, "");
|
|
144669
|
+
}
|
|
144670
|
+
function short(sha) {
|
|
144671
|
+
return sha.slice(0, 7);
|
|
144672
|
+
}
|
|
144673
|
+
function refToken(value, usedRefs) {
|
|
144674
|
+
const id = uniqueId(sanitizeRef(value), usedRefs);
|
|
144675
|
+
return /^[A-Za-z_]/.test(id) ? id : `"${id}"`;
|
|
144676
|
+
}
|
|
144677
|
+
function sanitizeId(value) {
|
|
144678
|
+
return sanitizeRef(value).slice(0, 40);
|
|
144679
|
+
}
|
|
144680
|
+
function sanitizeRef(value) {
|
|
144681
|
+
return value.replace(/[^A-Za-z0-9._/-]/g, "-");
|
|
144682
|
+
}
|
|
144683
|
+
function baseRowOf(topology) {
|
|
144684
|
+
return topology.branches.find((row) => row.name === topology.base) ?? topology.branches.find((row) => isBaseRow(topology.base, row)) ?? null;
|
|
144685
|
+
}
|
|
144686
|
+
function uniqueId(id, usedIds) {
|
|
144687
|
+
let candidate = id;
|
|
144688
|
+
let suffix = 2;
|
|
144689
|
+
while (usedIds.has(candidate)) {
|
|
144690
|
+
candidate = `${id}-${suffix}`;
|
|
144691
|
+
suffix += 1;
|
|
144692
|
+
}
|
|
144693
|
+
usedIds.add(candidate);
|
|
144694
|
+
return candidate;
|
|
144695
|
+
}
|
|
144696
|
+
|
|
144697
|
+
// ../../packages/app/src/services/branch-topology/index.ts
|
|
144698
|
+
var GRAPH_COMMIT_WINDOW = 10;
|
|
144699
|
+
var FIELD_SEP = "\x00";
|
|
144700
|
+
|
|
144701
|
+
class BranchTopologyUsageError extends Error {
|
|
144702
|
+
}
|
|
144703
|
+
|
|
144704
|
+
class BranchTopologyGitError extends Error {
|
|
144705
|
+
}
|
|
144706
|
+
function collectBranchTopology(runner, opts) {
|
|
144707
|
+
const warnings = [];
|
|
144708
|
+
const base = resolveBase(runner, opts.base);
|
|
144709
|
+
const refs = listRefs(runner, opts.scope);
|
|
144710
|
+
const worktrees = readWorktrees(runner, warnings);
|
|
144711
|
+
const tagIndex = readTagIndex(runner, warnings);
|
|
144712
|
+
const rows = refs.map((ref) => buildRow(runner, base.sha, ref));
|
|
144713
|
+
const detached = detachedHead(runner, base.sha);
|
|
144714
|
+
if (detached !== null && opts.scope !== "remote")
|
|
144715
|
+
rows.push(detached);
|
|
144716
|
+
for (const row of rows) {
|
|
144717
|
+
row.worktree = row.kind === "detached" ? worktrees.bySha.get(row.tip) ?? null : worktrees.byBranch.get(row.name) ?? null;
|
|
144718
|
+
}
|
|
144719
|
+
if (opts.pr)
|
|
144720
|
+
attachPullRequests(rows, readOpenPullRequests(runner, warnings));
|
|
144721
|
+
const sorted = sortRows(rows);
|
|
144722
|
+
const branches = sorted.slice(0, Math.max(opts.limit, 0));
|
|
144723
|
+
const omitted = sorted.length - branches.length;
|
|
144724
|
+
for (const row of branches) {
|
|
144725
|
+
if (row.contained && !isBaseRow(base.name, row))
|
|
144726
|
+
continue;
|
|
144727
|
+
row.commits = readCommits(runner, row.tip, tagIndex, warnings);
|
|
144728
|
+
}
|
|
144729
|
+
return { base: base.name, generatedAt: new Date().toISOString(), branches, omitted, warnings };
|
|
144730
|
+
}
|
|
144731
|
+
function resolveBase(runner, explicit) {
|
|
144732
|
+
const trimmed = explicit?.trim();
|
|
144733
|
+
const requested = trimmed !== undefined && trimmed.length > 0 ? trimmed : undefined;
|
|
144734
|
+
let candidate = requested ?? "";
|
|
144735
|
+
if (candidate.length === 0) {
|
|
144736
|
+
const originHead = runner(["git", "symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
|
|
144737
|
+
const target = originHead.code === 0 ? originHead.stdout.trim() : "";
|
|
144738
|
+
candidate = target.length > 0 ? target : "main";
|
|
144739
|
+
}
|
|
144740
|
+
const resolved = runner(["git", "rev-parse", "--verify", "--quiet", `${candidate}^{commit}`]);
|
|
144741
|
+
if (resolved.code !== 0) {
|
|
144742
|
+
if ((resolved.error ?? "").length > 0) {
|
|
144743
|
+
throw new BranchTopologyGitError(`git is not available: ${resolved.error}`);
|
|
144744
|
+
}
|
|
144745
|
+
if (/not a git repository/i.test(resolved.stderr)) {
|
|
144746
|
+
throw new BranchTopologyGitError(`not a git repository: ${failureDetail(resolved)}`);
|
|
144747
|
+
}
|
|
144748
|
+
throw new BranchTopologyUsageError(requested === undefined ? `unknown base ref '${candidate}' (no --base given; tried origin/HEAD, then main)` : `unknown base ref '${candidate}'`);
|
|
144749
|
+
}
|
|
144750
|
+
return { name: candidate, sha: resolved.stdout.trim() };
|
|
144751
|
+
}
|
|
144752
|
+
function listRefs(runner, scope) {
|
|
144753
|
+
const namespaces = scope === "local" ? ["refs/heads"] : scope === "remote" ? ["refs/remotes"] : ["refs/heads", "refs/remotes"];
|
|
144754
|
+
const res = runner(["git", "for-each-ref", "--format=%(refname)%00%(objectname)", ...namespaces]);
|
|
144755
|
+
if (res.code !== 0)
|
|
144756
|
+
throw gitFailure(res, "git for-each-ref");
|
|
144757
|
+
const refs = [];
|
|
144758
|
+
for (const line of res.stdout.split(`
|
|
144759
|
+
`)) {
|
|
144760
|
+
if (line.length === 0)
|
|
144761
|
+
continue;
|
|
144762
|
+
const [refname, sha] = line.split(FIELD_SEP);
|
|
144763
|
+
if (refname === undefined || sha === undefined || sha.length === 0)
|
|
144764
|
+
continue;
|
|
144765
|
+
if (refname.endsWith("/HEAD"))
|
|
144766
|
+
continue;
|
|
144767
|
+
if (refname.startsWith("refs/heads/")) {
|
|
144768
|
+
refs.push({ name: refname.slice("refs/heads/".length), kind: "local", sha });
|
|
144769
|
+
} else if (refname.startsWith("refs/remotes/")) {
|
|
144770
|
+
refs.push({ name: refname.slice("refs/remotes/".length), kind: "remote", sha });
|
|
144771
|
+
}
|
|
144772
|
+
}
|
|
144773
|
+
if (scope !== "all")
|
|
144774
|
+
return refs;
|
|
144775
|
+
const localTips = new Map(refs.filter((ref) => ref.kind === "local").map((ref) => [ref.name, ref.sha]));
|
|
144776
|
+
return refs.filter((ref) => {
|
|
144777
|
+
if (ref.kind !== "remote")
|
|
144778
|
+
return true;
|
|
144779
|
+
const twin = ref.name.slice(ref.name.indexOf("/") + 1);
|
|
144780
|
+
return localTips.get(twin) !== ref.sha;
|
|
144781
|
+
});
|
|
144782
|
+
}
|
|
144783
|
+
function buildRow(runner, baseSha, ref) {
|
|
144784
|
+
const mergeBase = runner(["git", "merge-base", baseSha, ref.sha]);
|
|
144785
|
+
const forkPoint = mergeBase.code === 0 ? mergeBase.stdout.trim() : "";
|
|
144786
|
+
const counts = runner(["git", "rev-list", "--left-right", "--count", `${baseSha}...${ref.sha}`]);
|
|
144787
|
+
if (counts.code !== 0)
|
|
144788
|
+
throw gitFailure(counts, `git rev-list for ${ref.name}`);
|
|
144789
|
+
const [behindRaw, aheadRaw] = counts.stdout.trim().split(/\s+/);
|
|
144790
|
+
const behind = parseCount2(behindRaw, ref.name);
|
|
144791
|
+
const ahead = parseCount2(aheadRaw, ref.name);
|
|
144792
|
+
const log = runner(["git", "log", "-1", "--format=%H%x00%cI%x00%s", ref.sha]);
|
|
144793
|
+
if (log.code !== 0)
|
|
144794
|
+
throw gitFailure(log, `git log for ${ref.name}`);
|
|
144795
|
+
const [tip, lastCommitAt, ...subjectParts] = log.stdout.replace(/\n$/, "").split(FIELD_SEP);
|
|
144796
|
+
return {
|
|
144797
|
+
name: ref.name,
|
|
144798
|
+
kind: ref.kind,
|
|
144799
|
+
tip: tip ?? ref.sha,
|
|
144800
|
+
forkPoint,
|
|
144801
|
+
ahead,
|
|
144802
|
+
behind,
|
|
144803
|
+
contained: ahead === 0,
|
|
144804
|
+
lastCommitAt: lastCommitAt ?? "",
|
|
144805
|
+
subject: subjectParts.join(FIELD_SEP),
|
|
144806
|
+
worktree: null,
|
|
144807
|
+
pr: null,
|
|
144808
|
+
commits: []
|
|
144809
|
+
};
|
|
144810
|
+
}
|
|
144811
|
+
function parseCount2(raw, ref) {
|
|
144812
|
+
const parsed = Number.parseInt(raw ?? "", 10);
|
|
144813
|
+
if (!Number.isInteger(parsed)) {
|
|
144814
|
+
throw new BranchTopologyGitError(`git rev-list for ${ref} returned '${raw ?? ""}'`);
|
|
144815
|
+
}
|
|
144816
|
+
return parsed;
|
|
144817
|
+
}
|
|
144818
|
+
function detachedHead(runner, baseSha) {
|
|
144819
|
+
const symbolic = runner(["git", "symbolic-ref", "--short", "-q", "HEAD"]);
|
|
144820
|
+
if (symbolic.code === 0)
|
|
144821
|
+
return null;
|
|
144822
|
+
const head = runner(["git", "rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
|
|
144823
|
+
if (head.code !== 0)
|
|
144824
|
+
return null;
|
|
144825
|
+
const tip = head.stdout.trim();
|
|
144826
|
+
return buildRow(runner, baseSha, { name: shortSha(tip), kind: "detached", sha: tip });
|
|
144827
|
+
}
|
|
144828
|
+
function sortRows(rows) {
|
|
144829
|
+
const time = (row) => {
|
|
144830
|
+
const parsed = Date.parse(row.lastCommitAt);
|
|
144831
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
144832
|
+
};
|
|
144833
|
+
return [...rows].sort((a, b) => {
|
|
144834
|
+
const byTime = time(b) - time(a);
|
|
144835
|
+
if (byTime !== 0)
|
|
144836
|
+
return byTime;
|
|
144837
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
144838
|
+
});
|
|
144839
|
+
}
|
|
144840
|
+
function readWorktrees(runner, warnings) {
|
|
144841
|
+
const index = { byBranch: new Map, bySha: new Map };
|
|
144842
|
+
const res = runner(["git", "worktree", "list", "--porcelain"]);
|
|
144843
|
+
if (res.code !== 0) {
|
|
144844
|
+
warnings.push(`git worktree list failed: ${failureDetail(res)}`);
|
|
144845
|
+
return index;
|
|
144846
|
+
}
|
|
144847
|
+
let path = "";
|
|
144848
|
+
let head = "";
|
|
144849
|
+
let detached = false;
|
|
144850
|
+
const flush = () => {
|
|
144851
|
+
if (detached && head.length > 0 && path.length > 0)
|
|
144852
|
+
index.bySha.set(head, path);
|
|
144853
|
+
path = "";
|
|
144854
|
+
head = "";
|
|
144855
|
+
detached = false;
|
|
144856
|
+
};
|
|
144857
|
+
for (const line of res.stdout.split(`
|
|
144858
|
+
`)) {
|
|
144859
|
+
if (line.length === 0) {
|
|
144860
|
+
flush();
|
|
144861
|
+
} else if (line.startsWith("worktree ")) {
|
|
144862
|
+
flush();
|
|
144863
|
+
path = line.slice("worktree ".length).trim();
|
|
144864
|
+
} else if (line.startsWith("HEAD ")) {
|
|
144865
|
+
head = line.slice("HEAD ".length).trim();
|
|
144866
|
+
} else if (line === "detached") {
|
|
144867
|
+
detached = true;
|
|
144868
|
+
} else if (line.startsWith("branch refs/heads/")) {
|
|
144869
|
+
index.byBranch.set(line.slice("branch refs/heads/".length).trim(), path);
|
|
144870
|
+
}
|
|
144871
|
+
}
|
|
144872
|
+
flush();
|
|
144873
|
+
return index;
|
|
144874
|
+
}
|
|
144875
|
+
function readTagIndex(runner, warnings) {
|
|
144876
|
+
const index = new Map;
|
|
144877
|
+
const res = runner(["git", "for-each-ref", "--format=%(objectname) %(*objectname) %(refname:short)", "refs/tags"]);
|
|
144878
|
+
if (res.code !== 0) {
|
|
144879
|
+
warnings.push(`git for-each-ref refs/tags failed: ${failureDetail(res)}`);
|
|
144880
|
+
return index;
|
|
144881
|
+
}
|
|
144882
|
+
for (const line of res.stdout.split(`
|
|
144883
|
+
`)) {
|
|
144884
|
+
if (line.length === 0)
|
|
144885
|
+
continue;
|
|
144886
|
+
const [object, peeled, ...nameParts] = line.split(" ");
|
|
144887
|
+
const name = nameParts.join(" ");
|
|
144888
|
+
if (object === undefined || name.length === 0)
|
|
144889
|
+
continue;
|
|
144890
|
+
const target = (peeled ?? "").length > 0 ? peeled : object;
|
|
144891
|
+
index.set(target, [...index.get(target) ?? [], name]);
|
|
144892
|
+
}
|
|
144893
|
+
return index;
|
|
144894
|
+
}
|
|
144895
|
+
function readCommits(runner, sha, tagIndex, warnings) {
|
|
144896
|
+
const res = runner(["git", "log", "-n", String(GRAPH_COMMIT_WINDOW), "--format=%H%x00%s", sha]);
|
|
144897
|
+
if (res.code !== 0) {
|
|
144898
|
+
warnings.push(`git log for ${shortSha(sha)} failed: ${failureDetail(res)}`);
|
|
144899
|
+
return [];
|
|
144900
|
+
}
|
|
144901
|
+
const commits = [];
|
|
144902
|
+
for (const line of res.stdout.split(`
|
|
144903
|
+
`)) {
|
|
144904
|
+
if (line.length === 0)
|
|
144905
|
+
continue;
|
|
144906
|
+
const [commitSha, ...subjectParts] = line.split(FIELD_SEP);
|
|
144907
|
+
if (commitSha === undefined || commitSha.length === 0)
|
|
144908
|
+
continue;
|
|
144909
|
+
commits.push({
|
|
144910
|
+
sha: commitSha,
|
|
144911
|
+
subject: subjectParts.join(FIELD_SEP),
|
|
144912
|
+
tags: tagIndex.get(commitSha) ?? []
|
|
144913
|
+
});
|
|
144914
|
+
}
|
|
144915
|
+
return commits;
|
|
144916
|
+
}
|
|
144917
|
+
function readOpenPullRequests(runner, warnings) {
|
|
144918
|
+
const index = new Map;
|
|
144919
|
+
const res = runner([
|
|
144920
|
+
"gh",
|
|
144921
|
+
"pr",
|
|
144922
|
+
"list",
|
|
144923
|
+
"--state",
|
|
144924
|
+
"open",
|
|
144925
|
+
"--json",
|
|
144926
|
+
"number,headRefName,state,reviewDecision",
|
|
144927
|
+
"--limit",
|
|
144928
|
+
"200"
|
|
144929
|
+
]);
|
|
144930
|
+
if (res.code !== 0 || res.stdout.trim().length === 0) {
|
|
144931
|
+
warnings.push(`--pr: gh pr list failed: ${failureDetail(res)}`);
|
|
144932
|
+
return index;
|
|
144933
|
+
}
|
|
144934
|
+
let parsed;
|
|
144935
|
+
try {
|
|
144936
|
+
parsed = JSON.parse(res.stdout);
|
|
144937
|
+
} catch {
|
|
144938
|
+
warnings.push("--pr: gh pr list returned unparseable JSON");
|
|
144939
|
+
return index;
|
|
144940
|
+
}
|
|
144941
|
+
if (!Array.isArray(parsed)) {
|
|
144942
|
+
warnings.push("--pr: gh pr list returned unparseable JSON");
|
|
144943
|
+
return index;
|
|
144944
|
+
}
|
|
144945
|
+
for (const entry of parsed) {
|
|
144946
|
+
const { number, headRefName, state, reviewDecision } = entry;
|
|
144947
|
+
if (typeof number !== "number" || typeof headRefName !== "string")
|
|
144948
|
+
continue;
|
|
144949
|
+
const pr = {
|
|
144950
|
+
number,
|
|
144951
|
+
state: typeof state === "string" ? state : "",
|
|
144952
|
+
reviewDecision: typeof reviewDecision === "string" && reviewDecision.length > 0 ? reviewDecision : null
|
|
144953
|
+
};
|
|
144954
|
+
const existing = index.get(headRefName);
|
|
144955
|
+
if (existing === undefined || pr.number < existing.number)
|
|
144956
|
+
index.set(headRefName, pr);
|
|
144957
|
+
}
|
|
144958
|
+
return index;
|
|
144959
|
+
}
|
|
144960
|
+
function attachPullRequests(rows, index) {
|
|
144961
|
+
for (const row of rows) {
|
|
144962
|
+
if (row.kind === "detached")
|
|
144963
|
+
continue;
|
|
144964
|
+
const keys = row.kind === "remote" ? [row.name, row.name.replace(/^[^/]+\//, "")] : [row.name];
|
|
144965
|
+
row.pr = keys.map((key) => index.get(key)).find((pr) => pr !== undefined) ?? null;
|
|
144966
|
+
}
|
|
144967
|
+
}
|
|
144968
|
+
function shortSha(sha) {
|
|
144969
|
+
return sha.slice(0, 7);
|
|
144970
|
+
}
|
|
144971
|
+
function gitFailure(res, what) {
|
|
144972
|
+
return new BranchTopologyGitError(`${what} failed: ${failureDetail(res)}`);
|
|
144973
|
+
}
|
|
144974
|
+
function failureDetail(res) {
|
|
144975
|
+
const first = (res.stderr.trim() || res.stdout.trim()).split(`
|
|
144976
|
+
`)[0] ?? "";
|
|
144977
|
+
if (first.length > 0)
|
|
144978
|
+
return first;
|
|
144979
|
+
return (res.error ?? "").length > 0 ? res.error : `exit ${res.code}`;
|
|
144980
|
+
}
|
|
144981
|
+
|
|
144982
|
+
// src/commands/builder/show-branches.ts
|
|
144983
|
+
var FORMATS = ["markdown", "json", "text"];
|
|
144984
|
+
function registerBuilderShowBranchesCommand(builder, context) {
|
|
144985
|
+
const command = builder.command("show-branches").summary("read-only branch topology report against a base (markdown | json | text)").description([
|
|
144986
|
+
"Report every branch against a base ref, read-only: fork point, commits ahead/behind,",
|
|
144987
|
+
"contained flag, last commit, worktree binding, and (with --pr) open pull-request state.",
|
|
144988
|
+
"Base resolution: --base, else the origin/HEAD target, else main.",
|
|
144989
|
+
"markdown (default) prints the summary table and one condensed mermaid gitGraph;",
|
|
144990
|
+
"json prints the raw BranchTopology object; text prints the aligned table only.",
|
|
144991
|
+
"Exit codes: 0 ok \xB7 1 usage (bad --format/--limit/--max-commits, unknown base ref,",
|
|
144992
|
+
"--local with --remote) \xB7 2 git failure (not a repository, git missing)."
|
|
144993
|
+
].join(`
|
|
144994
|
+
`)).option("--format <format>", "output format: markdown | json | text (default markdown)", "markdown").option("--json", "alias of --format json (prints the raw BranchTopology object)").option("--base <ref>", "base ref to measure every branch against (default: origin/HEAD, else main)").option("--local", "only local branches").option("--remote", "only remote branches").option("--pr", "enrich rows with open pull-request state (one gh call; degrades with a warning)").option("--limit <n>", "maximum branch rows kept, most recent last-commit first (default 30)", "30").option("--max-commits <n>", "maximum commit nodes per branch line in the graph (default 3)", "3").option("--hide-contained", "drop contained branches from the graph instead of drawing their join").option("--theme <name>", "mermaid theme name for the graph init directive").action((options) => {
|
|
144995
|
+
const format = resolveFormat(options.format, command.getOptionValueSource("format") === "cli", options.json === true, context);
|
|
144996
|
+
if (format === null)
|
|
144997
|
+
return;
|
|
144998
|
+
const limit = positiveInteger(options.limit, "--limit", context);
|
|
144999
|
+
if (limit === null)
|
|
145000
|
+
return;
|
|
145001
|
+
const maxCommits = positiveInteger(options.maxCommits, "--max-commits", context);
|
|
145002
|
+
if (maxCommits === null)
|
|
145003
|
+
return;
|
|
145004
|
+
if (options.local === true && options.remote === true) {
|
|
145005
|
+
usageError("--local and --remote are mutually exclusive", context);
|
|
145006
|
+
return;
|
|
145007
|
+
}
|
|
145008
|
+
try {
|
|
145009
|
+
const topology = collectBranchTopology(createSpawnRunner({ cwd: context.cwd, env: context.env }), {
|
|
145010
|
+
...options.base !== undefined ? { base: options.base } : {},
|
|
145011
|
+
scope: options.local === true ? "local" : options.remote === true ? "remote" : "all",
|
|
145012
|
+
pr: options.pr === true,
|
|
145013
|
+
limit
|
|
145014
|
+
});
|
|
145015
|
+
if (format === "json") {
|
|
145016
|
+
context.output.write(toJson2(topology));
|
|
145017
|
+
} else if (format === "text") {
|
|
145018
|
+
context.output.write(renderText(topology));
|
|
145019
|
+
} else {
|
|
145020
|
+
context.output.write(renderMarkdown2(topology, {
|
|
145021
|
+
maxCommits,
|
|
145022
|
+
hideContained: options.hideContained === true,
|
|
145023
|
+
...options.theme !== undefined ? { theme: options.theme } : {}
|
|
145024
|
+
}));
|
|
145025
|
+
}
|
|
145026
|
+
context.setExitCode(0);
|
|
145027
|
+
} catch (err) {
|
|
145028
|
+
if (err instanceof BranchTopologyUsageError) {
|
|
145029
|
+
usageError(err.message, context);
|
|
145030
|
+
return;
|
|
145031
|
+
}
|
|
145032
|
+
if (err instanceof BranchTopologyGitError) {
|
|
145033
|
+
context.output.error(err.message);
|
|
145034
|
+
context.setExitCode(2);
|
|
145035
|
+
return;
|
|
145036
|
+
}
|
|
145037
|
+
context.output.error(err instanceof Error ? err.message : String(err));
|
|
145038
|
+
context.setExitCode(2);
|
|
145039
|
+
}
|
|
145040
|
+
});
|
|
145041
|
+
}
|
|
145042
|
+
function resolveFormat(requested, explicitFormat, json, context) {
|
|
145043
|
+
if (!FORMATS.includes(requested)) {
|
|
145044
|
+
usageError(`unknown --format '${requested}': expected ${FORMATS.join(" | ")}`, context);
|
|
145045
|
+
return null;
|
|
145046
|
+
}
|
|
145047
|
+
if (!json)
|
|
145048
|
+
return requested;
|
|
145049
|
+
if (explicitFormat && requested !== "json") {
|
|
145050
|
+
usageError(`--json conflicts with --format ${requested}`, context);
|
|
145051
|
+
return null;
|
|
145052
|
+
}
|
|
145053
|
+
return "json";
|
|
145054
|
+
}
|
|
145055
|
+
function positiveInteger(value, flag, context) {
|
|
145056
|
+
const trimmed = value.trim();
|
|
145057
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
145058
|
+
if (!/^\d+$/.test(trimmed) || parsed < 1) {
|
|
145059
|
+
usageError(`${flag} expects a positive integer, got '${value}'`, context);
|
|
145060
|
+
return null;
|
|
145061
|
+
}
|
|
145062
|
+
return parsed;
|
|
145063
|
+
}
|
|
145064
|
+
function usageError(message, context) {
|
|
145065
|
+
context.output.error(message);
|
|
145066
|
+
context.setExitCode(1);
|
|
145067
|
+
}
|
|
145068
|
+
|
|
144229
145069
|
// src/commands/builder.ts
|
|
144230
145070
|
function registerBuilderCommand(program, context) {
|
|
144231
|
-
const noun = program.command("builder").summary("release plumbing: version bumps, tags, and PR-review ops");
|
|
145071
|
+
const noun = program.command("builder").summary("release plumbing: version bumps, tags, branch topology, and PR-review ops");
|
|
144232
145072
|
noun.command("bump-ver").summary("bump a package version, commit, tag, optionally push").description([
|
|
144233
145073
|
"Bump one workspace package (or every released package with --all),",
|
|
144234
145074
|
"rewrite workspace pins, commit, and tag. A bare version bumps all:",
|
|
@@ -144330,7 +145170,9 @@ function registerBuilderCommand(program, context) {
|
|
|
144330
145170
|
context.setExitCode(1);
|
|
144331
145171
|
}
|
|
144332
145172
|
});
|
|
144333
|
-
|
|
145173
|
+
registerBuilderShowBranchesCommand(noun, context);
|
|
145174
|
+
registerBuilderPrCommands(noun, context);
|
|
145175
|
+
registerBuilderPrAliasCommand(noun, context);
|
|
144334
145176
|
}
|
|
144335
145177
|
|
|
144336
145178
|
// src/commands/decision.ts
|
|
@@ -144717,9 +145559,9 @@ function registerFeatureVerifyCommand(feature, context) {
|
|
|
144717
145559
|
"Runs the selected feature-verification definition\u2019s command once (or --cmd) and writes the",
|
|
144718
145560
|
"run-scoped + feature-latest receipts and .spur/run/<runId>-feature-verification.status.",
|
|
144719
145561
|
"Exit: 0 after a completed pass (PASS or FAIL); 1 when the pass could not run.",
|
|
144720
|
-
"Flags fall back to the workflow env names: featureId, __runId, verificationCmd
|
|
145562
|
+
"Flags fall back to the workflow env names: featureId, __runId, verificationCmd."
|
|
144721
145563
|
].join(`
|
|
144722
|
-
`)).argument("[id]", "Feature ID (env: featureId)").option("--run-id <id>", "workflow run id the receipt binds to (env: __runId)").option("--cmd <command>", "verification command override (env: verificationCmd)").
|
|
145564
|
+
`)).argument("[id]", "Feature ID (env: featureId)").option("--run-id <id>", "workflow run id the receipt binds to (env: __runId)").option("--cmd <command>", "verification command override (env: verificationCmd)").action(async (id, options) => {
|
|
144723
145565
|
await Promise.resolve().then(() => init_src3());
|
|
144724
145566
|
const env = context.env;
|
|
144725
145567
|
const featureId = id || env.featureId || "";
|
|
@@ -144728,9 +145570,7 @@ function registerFeatureVerifyCommand(feature, context) {
|
|
|
144728
145570
|
workdir: context.cwd,
|
|
144729
145571
|
featureId,
|
|
144730
145572
|
runId: options.runId || env.__runId || "",
|
|
144731
|
-
cmdOverride: options.cmd || env.verificationCmd || ""
|
|
144732
|
-
confidenceOverride: options.confidence || env.confidence || "",
|
|
144733
|
-
ackLowConfidence: options.ackLowConfidence === true || env.ackLowConfidence === "true"
|
|
145573
|
+
cmdOverride: options.cmd || env.verificationCmd || ""
|
|
144734
145574
|
});
|
|
144735
145575
|
context.output.write(`feature verify: ${featureId} ${result.message}`);
|
|
144736
145576
|
} catch (err) {
|
|
@@ -145177,7 +146017,7 @@ import { createRequire as createRequire3 } from "module";
|
|
|
145177
146017
|
var CLI_CONFIG = {
|
|
145178
146018
|
binaryName: "spur",
|
|
145179
146019
|
binaryLabel: "spur",
|
|
145180
|
-
binaryVersion: "0.4.
|
|
146020
|
+
binaryVersion: "0.4.11",
|
|
145181
146021
|
configDir: ".spur",
|
|
145182
146022
|
configFile: ".spur/config.yaml",
|
|
145183
146023
|
databaseFile: ".spur/spur.db"
|
|
@@ -145721,7 +146561,7 @@ init_loader2();
|
|
|
145721
146561
|
init_src2();
|
|
145722
146562
|
init_dist11();
|
|
145723
146563
|
import { homedir as homedir15 } from "os";
|
|
145724
|
-
import { join as
|
|
146564
|
+
import { join as join85, resolve as resolve31 } from "path";
|
|
145725
146565
|
|
|
145726
146566
|
// src/config/scaffold-manifest.ts
|
|
145727
146567
|
var SCAFFOLD_MANIFEST = [
|
|
@@ -145750,8 +146590,8 @@ var SCAFFOLD_MANIFEST = [
|
|
|
145750
146590
|
];
|
|
145751
146591
|
|
|
145752
146592
|
// src/commands/init.ts
|
|
145753
|
-
var GLOBAL_CONFIG_DIR =
|
|
145754
|
-
var GLOBAL_RULES_DIR2 =
|
|
146593
|
+
var GLOBAL_CONFIG_DIR = join85(".config", "spur");
|
|
146594
|
+
var GLOBAL_RULES_DIR2 = join85(GLOBAL_CONFIG_DIR, "rules");
|
|
145755
146595
|
var GLOBAL_CONFIG_EXAMPLE = BUNDLED_GLOBAL_CONFIG;
|
|
145756
146596
|
var GLOBAL_CONFIG_FILE2 = "config.yaml";
|
|
145757
146597
|
var INDEXED_CONTEXT_MARKER = "## Indexed context";
|
|
@@ -145782,7 +146622,7 @@ If \`.spur/context/\` is absent, proceed normally. Never block work on its absen
|
|
|
145782
146622
|
`;
|
|
145783
146623
|
function globalRulesRoot(context) {
|
|
145784
146624
|
const override = context.env.SPUR_GLOBAL_RULES_DIR;
|
|
145785
|
-
return override !== undefined && override.length > 0 ? resolve31(context.cwd, override) :
|
|
146625
|
+
return override !== undefined && override.length > 0 ? resolve31(context.cwd, override) : join85(homedir15(), GLOBAL_RULES_DIR2);
|
|
145786
146626
|
}
|
|
145787
146627
|
async function writeIfNew(context, path, content, force, result) {
|
|
145788
146628
|
if (!force && await context.fs.exists(path)) {
|
|
@@ -145799,18 +146639,18 @@ async function seedGlobalRules(context) {
|
|
|
145799
146639
|
const target = globalRulesRoot(context);
|
|
145800
146640
|
let written = 0;
|
|
145801
146641
|
for (const relPath of await listBundledRuleFiles()) {
|
|
145802
|
-
const destination =
|
|
146642
|
+
const destination = join85(target, relPath);
|
|
145803
146643
|
if (await context.fs.exists(destination))
|
|
145804
146644
|
continue;
|
|
145805
|
-
await context.fs.ensureDir(
|
|
145806
|
-
await context.fs.writeFile(destination, await context.fs.readFile(
|
|
146645
|
+
await context.fs.ensureDir(join85(target, ...relPath.split("/").slice(0, -1)));
|
|
146646
|
+
await context.fs.writeFile(destination, await context.fs.readFile(join85(source, relPath)));
|
|
145807
146647
|
written += 1;
|
|
145808
146648
|
}
|
|
145809
146649
|
return written;
|
|
145810
146650
|
}
|
|
145811
146651
|
function resolveGlobalConfigDir(context) {
|
|
145812
146652
|
const globalOverride = context.env.SPUR_GLOBAL_RULES_DIR;
|
|
145813
|
-
return globalOverride !== undefined && globalOverride.length > 0 ? resolve31(context.cwd, globalOverride) :
|
|
146653
|
+
return globalOverride !== undefined && globalOverride.length > 0 ? resolve31(context.cwd, globalOverride) : join85(homedir15(), GLOBAL_CONFIG_DIR);
|
|
145814
146654
|
}
|
|
145815
146655
|
async function seedGlobalConfig(context) {
|
|
145816
146656
|
const source = bundledConfigRoot();
|
|
@@ -145821,15 +146661,15 @@ async function seedGlobalConfig(context) {
|
|
|
145821
146661
|
for (const relPath of listBundledConfigFiles()) {
|
|
145822
146662
|
if (relPath === GLOBAL_CONFIG_EXAMPLE)
|
|
145823
146663
|
continue;
|
|
145824
|
-
const destination =
|
|
146664
|
+
const destination = join85(target, relPath);
|
|
145825
146665
|
if (await context.fs.exists(destination))
|
|
145826
146666
|
continue;
|
|
145827
|
-
await context.fs.ensureDir(
|
|
145828
|
-
await context.fs.writeFile(destination, await context.fs.readFile(
|
|
146667
|
+
await context.fs.ensureDir(join85(target, ...relPath.split("/").slice(0, -1)));
|
|
146668
|
+
await context.fs.writeFile(destination, await context.fs.readFile(join85(source, relPath)));
|
|
145829
146669
|
written += 1;
|
|
145830
146670
|
}
|
|
145831
|
-
const examplePath =
|
|
145832
|
-
const globalConfigPath =
|
|
146671
|
+
const examplePath = join85(source, GLOBAL_CONFIG_EXAMPLE);
|
|
146672
|
+
const globalConfigPath = join85(target, GLOBAL_CONFIG_FILE2);
|
|
145833
146673
|
if (await context.fs.exists(examplePath) && !await context.fs.exists(globalConfigPath)) {
|
|
145834
146674
|
await context.fs.ensureDir(target);
|
|
145835
146675
|
await context.fs.writeFile(globalConfigPath, await context.fs.readFile(examplePath));
|
|
@@ -145844,10 +146684,10 @@ function registerInitCommand(program, context, options = {}) {
|
|
|
145844
146684
|
const minimal = options.minimal === true;
|
|
145845
146685
|
const adoptGlobal = options.adoptGlobalConfig === true;
|
|
145846
146686
|
const projectName = options.name ?? "default";
|
|
145847
|
-
const configPath =
|
|
146687
|
+
const configPath = join85(context.cwd, CLI_CONFIG.configFile);
|
|
145848
146688
|
const result = { created: [], skipped: [] };
|
|
145849
146689
|
const globalConfigDir = resolveGlobalConfigDir(context);
|
|
145850
|
-
const globalConfigPath =
|
|
146690
|
+
const globalConfigPath = join85(globalConfigDir, GLOBAL_CONFIG_FILE2);
|
|
145851
146691
|
const globalConfigExists = await context.fs.exists(globalConfigPath);
|
|
145852
146692
|
let misplacedKeys = [];
|
|
145853
146693
|
if (globalConfigExists) {
|
|
@@ -145860,20 +146700,20 @@ function registerInitCommand(program, context, options = {}) {
|
|
|
145860
146700
|
const configRoot = bundledConfigRoot();
|
|
145861
146701
|
if (configRoot !== null && !minimal) {
|
|
145862
146702
|
for (const relPath of listBundledProjectSeedFiles()) {
|
|
145863
|
-
const sourcePath =
|
|
146703
|
+
const sourcePath = join85(configRoot, relPath);
|
|
145864
146704
|
if (!await context.fs.exists(sourcePath))
|
|
145865
146705
|
continue;
|
|
145866
|
-
const targetPath =
|
|
145867
|
-
await context.fs.ensureDir(
|
|
146706
|
+
const targetPath = join85(context.cwd, CLI_CONFIG.configDir, relPath);
|
|
146707
|
+
await context.fs.ensureDir(join85(targetPath, ".."));
|
|
145868
146708
|
await writeIfNew(context, targetPath, await context.fs.readFile(sourcePath), false, result);
|
|
145869
146709
|
}
|
|
145870
146710
|
for (const entry of SCAFFOLD_MANIFEST) {
|
|
145871
|
-
const sourcePath =
|
|
146711
|
+
const sourcePath = join85(configRoot, entry.source);
|
|
145872
146712
|
if (!await context.fs.exists(sourcePath))
|
|
145873
146713
|
continue;
|
|
145874
|
-
const baseDir = entry.root === true ? context.cwd :
|
|
145875
|
-
const targetPath =
|
|
145876
|
-
await context.fs.ensureDir(
|
|
146714
|
+
const baseDir = entry.root === true ? context.cwd : join85(context.cwd, CLI_CONFIG.configDir);
|
|
146715
|
+
const targetPath = join85(baseDir, entry.target);
|
|
146716
|
+
await context.fs.ensureDir(join85(targetPath, ".."));
|
|
145877
146717
|
const entryForce = false;
|
|
145878
146718
|
let body = await context.fs.readFile(sourcePath);
|
|
145879
146719
|
if (entry.target === "AGENTS.md") {
|
|
@@ -145890,7 +146730,7 @@ function registerInitCommand(program, context, options = {}) {
|
|
|
145890
146730
|
const backupPath = `${globalConfigPath}.bak-${new Date().toISOString().replace(/[:.]/g, "-")}`;
|
|
145891
146731
|
await context.fs.writeFile(backupPath, await context.fs.readFile(globalConfigPath));
|
|
145892
146732
|
const bundledRoot = bundledConfigRoot();
|
|
145893
|
-
const examplePath = bundledRoot !== null ?
|
|
146733
|
+
const examplePath = bundledRoot !== null ? join85(bundledRoot, GLOBAL_CONFIG_EXAMPLE) : null;
|
|
145894
146734
|
if (examplePath !== null && await context.fs.exists(examplePath)) {
|
|
145895
146735
|
await context.fs.writeFile(globalConfigPath, await context.fs.readFile(examplePath));
|
|
145896
146736
|
adoptedGlobal = true;
|
|
@@ -145963,13 +146803,13 @@ function registerInitCommand(program, context, options = {}) {
|
|
|
145963
146803
|
].join(`
|
|
145964
146804
|
`)}
|
|
145965
146805
|
`;
|
|
145966
|
-
await context.fs.ensureDir(
|
|
146806
|
+
await context.fs.ensureDir(join85(context.cwd, CLI_CONFIG.configDir));
|
|
145967
146807
|
await context.fs.writeFile(configPath, configYaml);
|
|
145968
146808
|
result.created.push(configPath);
|
|
145969
|
-
const agentsDir =
|
|
146809
|
+
const agentsDir = join85(context.cwd, CLI_CONFIG.configDir, "agents");
|
|
145970
146810
|
await context.fs.ensureDir(agentsDir);
|
|
145971
|
-
await writeIfNew(context,
|
|
145972
|
-
const gitignorePath =
|
|
146811
|
+
await writeIfNew(context, join85(agentsDir, ".gitkeep"), "", force, result);
|
|
146812
|
+
const gitignorePath = join85(context.cwd, ".gitignore");
|
|
145973
146813
|
const contextEntry = ".spur/context/";
|
|
145974
146814
|
if (await context.fs.exists(gitignorePath)) {
|
|
145975
146815
|
const existing = await context.fs.readFile(gitignorePath);
|
|
@@ -145989,20 +146829,20 @@ ${contextEntry}
|
|
|
145989
146829
|
const configRoot = bundledConfigRoot();
|
|
145990
146830
|
if (configRoot !== null) {
|
|
145991
146831
|
for (const relPath of listBundledProjectSeedFiles()) {
|
|
145992
|
-
const sourcePath =
|
|
146832
|
+
const sourcePath = join85(configRoot, relPath);
|
|
145993
146833
|
if (!await context.fs.exists(sourcePath))
|
|
145994
146834
|
continue;
|
|
145995
|
-
const targetPath =
|
|
145996
|
-
await context.fs.ensureDir(
|
|
146835
|
+
const targetPath = join85(context.cwd, CLI_CONFIG.configDir, relPath);
|
|
146836
|
+
await context.fs.ensureDir(join85(targetPath, ".."));
|
|
145997
146837
|
await writeIfNew(context, targetPath, await context.fs.readFile(sourcePath), force, result);
|
|
145998
146838
|
}
|
|
145999
146839
|
for (const entry of SCAFFOLD_MANIFEST) {
|
|
146000
|
-
const sourcePath =
|
|
146840
|
+
const sourcePath = join85(configRoot, entry.source);
|
|
146001
146841
|
if (!await context.fs.exists(sourcePath))
|
|
146002
146842
|
continue;
|
|
146003
|
-
const baseDir = entry.root === true ? context.cwd :
|
|
146004
|
-
const targetPath =
|
|
146005
|
-
await context.fs.ensureDir(
|
|
146843
|
+
const baseDir = entry.root === true ? context.cwd : join85(context.cwd, CLI_CONFIG.configDir);
|
|
146844
|
+
const targetPath = join85(baseDir, entry.target);
|
|
146845
|
+
await context.fs.ensureDir(join85(targetPath, ".."));
|
|
146006
146846
|
const entryForce = entry.preserve === true ? false : force;
|
|
146007
146847
|
let body = await context.fs.readFile(sourcePath);
|
|
146008
146848
|
if (entry.target === "AGENTS.md") {
|
|
@@ -146015,7 +146855,7 @@ ${contextEntry}
|
|
|
146015
146855
|
}
|
|
146016
146856
|
}
|
|
146017
146857
|
}
|
|
146018
|
-
const agentsMdPath =
|
|
146858
|
+
const agentsMdPath = join85(context.cwd, "AGENTS.md");
|
|
146019
146859
|
if (await context.fs.exists(agentsMdPath)) {
|
|
146020
146860
|
const existing = await context.fs.readFile(agentsMdPath);
|
|
146021
146861
|
if (!existing.includes(INDEXED_CONTEXT_MARKER)) {
|
|
@@ -146056,11 +146896,11 @@ ${contextEntry}
|
|
|
146056
146896
|
// src/commands/maintain.ts
|
|
146057
146897
|
init_src();
|
|
146058
146898
|
init_src2();
|
|
146059
|
-
import { join as
|
|
146899
|
+
import { join as join86 } from "path";
|
|
146060
146900
|
function registerMaintainCommand(program, context, options = {}) {
|
|
146061
146901
|
program.command("maintain", { hidden: options.hidden === true }).summary("run database maintenance (optimize, WAL checkpoint truncation, optional VACUUM)").description("Run database maintenance: PRAGMA optimize, WAL checkpoint truncation; optionally VACUUM compaction.").option("--vacuum", "Run VACUUM defragmentation and page compaction", false).option(...SHARED_OPTIONS.jsonSupported).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options) => {
|
|
146062
146902
|
const db = await context.getDb();
|
|
146063
|
-
const dbPath = context.env.DATABASE_URL ??
|
|
146903
|
+
const dbPath = context.env.DATABASE_URL ?? join86(context.cwd, DEFAULT_DATABASE_URL);
|
|
146064
146904
|
const result = await maintainDatabase(db, {
|
|
146065
146905
|
vacuum: options.vacuum === true,
|
|
146066
146906
|
dbPath: dbPath.startsWith(":memory:") ? undefined : dbPath
|
|
@@ -146484,14 +147324,14 @@ async function waitForPendingDrain(agentService, coordination, pin, timeoutMs, s
|
|
|
146484
147324
|
|
|
146485
147325
|
// src/commands/migrate.ts
|
|
146486
147326
|
init_src2();
|
|
146487
|
-
import { join as
|
|
147327
|
+
import { join as join88, relative as relative18 } from "path";
|
|
146488
147328
|
|
|
146489
147329
|
// src/context.ts
|
|
146490
147330
|
init_src3();
|
|
146491
147331
|
init_src();
|
|
146492
147332
|
init_src2();
|
|
146493
147333
|
init_dist5();
|
|
146494
|
-
import { dirname as dirname41, join as
|
|
147334
|
+
import { dirname as dirname41, join as join87, resolve as resolve32 } from "path";
|
|
146495
147335
|
import { isatty as isatty4 } from "tty";
|
|
146496
147336
|
|
|
146497
147337
|
// ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
|
|
@@ -147314,7 +148154,7 @@ ${l}
|
|
|
147314
148154
|
}).prompt();
|
|
147315
148155
|
};
|
|
147316
148156
|
var i2 = `${styleText2("gray", S_BAR)} `;
|
|
147317
|
-
var
|
|
148157
|
+
var text5 = (t) => new n3({
|
|
147318
148158
|
validate: t.validate,
|
|
147319
148159
|
placeholder: t.placeholder,
|
|
147320
148160
|
defaultValue: t.defaultValue,
|
|
@@ -147384,7 +148224,7 @@ class ClackHitlResponder {
|
|
|
147384
148224
|
return { value: result };
|
|
147385
148225
|
}
|
|
147386
148226
|
case "input": {
|
|
147387
|
-
const result = await
|
|
148227
|
+
const result = await text5({
|
|
147388
148228
|
message: request.prompt
|
|
147389
148229
|
});
|
|
147390
148230
|
if (isCancel(result)) {
|
|
@@ -147476,7 +148316,7 @@ function createCliContext(options) {
|
|
|
147476
148316
|
}
|
|
147477
148317
|
function noopSetExitCode(_code) {}
|
|
147478
148318
|
function resolveDatabaseUrl(cwd, env) {
|
|
147479
|
-
return env.DATABASE_URL === undefined ?
|
|
148319
|
+
return env.DATABASE_URL === undefined ? join87(cwd, DEFAULT_DATABASE_URL) : buildConfigFromEnv(env).database.url;
|
|
147480
148320
|
}
|
|
147481
148321
|
async function createMigratedDbAdapter(cwd = process.cwd(), env = getEnvVars(), dbUrl) {
|
|
147482
148322
|
const url = dbUrl ?? resolveDatabaseUrl(cwd, env);
|
|
@@ -147539,7 +148379,7 @@ function registerMigrateCommand(program, context, options = {}) {
|
|
|
147539
148379
|
context.setExitCode(1);
|
|
147540
148380
|
return;
|
|
147541
148381
|
}
|
|
147542
|
-
const migrations = await loadSqlMigrations(
|
|
148382
|
+
const migrations = await loadSqlMigrations(join88(context.cwd, "drizzle")).catch(() => {
|
|
147543
148383
|
return;
|
|
147544
148384
|
});
|
|
147545
148385
|
const db = await context.getDb();
|
|
@@ -147554,22 +148394,28 @@ function registerMigrateCommand(program, context, options = {}) {
|
|
|
147554
148394
|
init_src3();
|
|
147555
148395
|
init_src2();
|
|
147556
148396
|
init_dist5();
|
|
147557
|
-
import { basename as basename19, dirname as dirname42, join as
|
|
148397
|
+
import { basename as basename19, dirname as dirname42, join as join89, resolve as resolve33 } from "path";
|
|
147558
148398
|
function registerProjectsCommand(program, context) {
|
|
147559
148399
|
const projectsCmd = program.command("projects").summary("manage the Spur multi-project registry");
|
|
147560
|
-
projectsCmd.command("add").argument("<path>", "Project root directory path").option(...SHARED_OPTIONS.nameProjectDisplay).option(...SHARED_OPTIONS.jsonProjectsResponse).option(...SHARED_OPTIONS.jsonEnvelope).action(async (pathArg, options) => {
|
|
148400
|
+
projectsCmd.command("add").argument("<path>", "Project root directory path").option("--host <host>", 'Host for local or remote project (default: "localhost")').option(...SHARED_OPTIONS.nameProjectDisplay).option(...SHARED_OPTIONS.jsonProjectsResponse).option(...SHARED_OPTIONS.jsonEnvelope).action(async (pathArg, options) => {
|
|
147561
148401
|
try {
|
|
147562
|
-
const
|
|
147563
|
-
|
|
147564
|
-
|
|
148402
|
+
const host = options.host ?? "localhost";
|
|
148403
|
+
const isLocal = isLocalHost(host);
|
|
148404
|
+
let absolutePath = pathArg;
|
|
148405
|
+
if (isLocal) {
|
|
148406
|
+
absolutePath = resolve33(context.cwd, pathArg);
|
|
148407
|
+
if (!await context.fs.exists(absolutePath)) {
|
|
148408
|
+
throw new Error(`Directory does not exist: ${absolutePath}`);
|
|
148409
|
+
}
|
|
147565
148410
|
}
|
|
147566
148411
|
const name = options.name ?? basename19(absolutePath);
|
|
147567
148412
|
const registry = new ProjectRegistry;
|
|
147568
|
-
const entry = await registry.upsert({ path: absolutePath, name, port: 0 });
|
|
148413
|
+
const entry = await registry.upsert({ path: absolutePath, name, host, port: 0 });
|
|
147569
148414
|
if (options.json) {
|
|
147570
148415
|
context.output.write(toEnvelopeJson({ ok: true, project: entry }, { enveloped: options.jsonEnvelope }));
|
|
147571
148416
|
} else {
|
|
147572
|
-
|
|
148417
|
+
const hostLabel = !isLocalHost(entry.host) ? ` on host ${entry.host}` : "";
|
|
148418
|
+
context.output.write(`Registered project "${entry.name}" at ${entry.path}${hostLabel}`);
|
|
147573
148419
|
}
|
|
147574
148420
|
} catch (err) {
|
|
147575
148421
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -147614,14 +148460,17 @@ function registerProjectsCommand(program, context) {
|
|
|
147614
148460
|
ok: true,
|
|
147615
148461
|
purgedCount: result.purgedProjects.length,
|
|
147616
148462
|
purgedProjects: result.purgedProjects,
|
|
147617
|
-
terminatedProcesses: result.terminatedProcesses
|
|
148463
|
+
terminatedProcesses: result.terminatedProcesses,
|
|
148464
|
+
...result.resetPorts && result.resetPorts.length > 0 ? { resetPorts: result.resetPorts } : {}
|
|
147618
148465
|
}, { enveloped: options.jsonEnvelope }));
|
|
147619
|
-
} else if (result.purgedProjects.length === 0) {
|
|
148466
|
+
} else if (result.purgedProjects.length === 0 && (!result.resetPorts || result.resetPorts.length === 0)) {
|
|
147620
148467
|
context.output.write("Registry is clean. No stale project entries found.");
|
|
147621
148468
|
} else {
|
|
147622
|
-
|
|
147623
|
-
|
|
147624
|
-
|
|
148469
|
+
if (result.purgedProjects.length > 0) {
|
|
148470
|
+
context.output.write(`Purged ${result.purgedProjects.length} stale project(s):`);
|
|
148471
|
+
for (const project of result.purgedProjects) {
|
|
148472
|
+
context.output.write(`- ${project.name} (${project.path})`);
|
|
148473
|
+
}
|
|
147625
148474
|
}
|
|
147626
148475
|
if (result.terminatedProcesses.length > 0) {
|
|
147627
148476
|
context.output.write(`Terminated ${result.terminatedProcesses.length} lingering process(es):`);
|
|
@@ -147629,6 +148478,13 @@ function registerProjectsCommand(program, context) {
|
|
|
147629
148478
|
context.output.write(`- PID ${proc.pid} on port ${proc.port} (${proc.signal})`);
|
|
147630
148479
|
}
|
|
147631
148480
|
}
|
|
148481
|
+
if (result.resetPorts && result.resetPorts.length > 0) {
|
|
148482
|
+
context.output.write(`Reset ${result.resetPorts.length} closed port(s):`);
|
|
148483
|
+
for (const project of result.resetPorts) {
|
|
148484
|
+
const hostLabel = !isLocalHost(project.host) ? `${project.host}:` : "";
|
|
148485
|
+
context.output.write(`- ${project.name} (${hostLabel}${project.port} -> 0)`);
|
|
148486
|
+
}
|
|
148487
|
+
}
|
|
147632
148488
|
}
|
|
147633
148489
|
} catch (err) {
|
|
147634
148490
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -147646,10 +148502,10 @@ function registerProjectsCommand(program, context) {
|
|
|
147646
148502
|
const rawProjects = await registry.list();
|
|
147647
148503
|
const projects = await Promise.all(rawProjects.map(async (p) => ({
|
|
147648
148504
|
...p,
|
|
147649
|
-
running: p.port > 0 ? await isPortLive(p.port) : false
|
|
148505
|
+
running: p.port > 0 ? await isPortLive(p.port, undefined, p.host) : false
|
|
147650
148506
|
})));
|
|
147651
148507
|
const openProjectDb = async (projectPath) => {
|
|
147652
|
-
const url =
|
|
148508
|
+
const url = join89(projectPath, ".spur", "spur.db");
|
|
147653
148509
|
await context.fs.ensureDir(dirname42(url));
|
|
147654
148510
|
return createMigratedDb({ url });
|
|
147655
148511
|
};
|
|
@@ -147715,8 +148571,9 @@ function registerProjectsCommand(program, context) {
|
|
|
147715
148571
|
context.output.write(`Registered Projects:
|
|
147716
148572
|
`);
|
|
147717
148573
|
for (const [i, p] of projects.entries()) {
|
|
148574
|
+
const hostLabel = !isLocalHost(p.host) ? ` (${p.host})` : "";
|
|
147718
148575
|
const status = p.running ? `[RUNNING: ${p.port}]` : "[STOPPED]";
|
|
147719
|
-
context.output.write(`- ${p.name} ${status} (${p.path})`);
|
|
148576
|
+
context.output.write(`- ${p.name}${hostLabel} ${status} (${p.path})`);
|
|
147720
148577
|
const f = fleets?.[i];
|
|
147721
148578
|
if (f === undefined)
|
|
147722
148579
|
continue;
|
|
@@ -148078,7 +148935,7 @@ function formatTraceDetail(detail) {
|
|
|
148078
148935
|
// src/commands/serve.ts
|
|
148079
148936
|
init_src();
|
|
148080
148937
|
import { statSync as statSync18 } from "fs";
|
|
148081
|
-
import { join as
|
|
148938
|
+
import { join as join94, resolve as resolve36 } from "path";
|
|
148082
148939
|
|
|
148083
148940
|
// ../server/src/index.ts
|
|
148084
148941
|
init_src();
|
|
@@ -148089,11 +148946,11 @@ init_src();
|
|
|
148089
148946
|
init_loader2();
|
|
148090
148947
|
init_src2();
|
|
148091
148948
|
import { homedir as homedir17 } from "os";
|
|
148092
|
-
import { basename as basename21, dirname as dirname44, isAbsolute as isAbsolute15, join as
|
|
148949
|
+
import { basename as basename21, dirname as dirname44, isAbsolute as isAbsolute15, join as join93 } from "path";
|
|
148093
148950
|
init_dist5();
|
|
148094
148951
|
|
|
148095
148952
|
// ../server/src/bootstrap.ts
|
|
148096
|
-
import { join as
|
|
148953
|
+
import { join as join91 } from "path";
|
|
148097
148954
|
|
|
148098
148955
|
// ../../node_modules/.bun/@orpc+shared@1.14.4+e40b0dfdd726a224/node_modules/@orpc/shared/dist/index.mjs
|
|
148099
148956
|
function resolveMaybeOptionalOptions(rest) {
|
|
@@ -154946,9 +155803,9 @@ init_loader2();
|
|
|
154946
155803
|
init_src2();
|
|
154947
155804
|
init_dist5();
|
|
154948
155805
|
import { homedir as homedir16 } from "os";
|
|
154949
|
-
import { basename as basename20, join as
|
|
155806
|
+
import { basename as basename20, join as join90, relative as relative19, resolve as resolve35 } from "path";
|
|
154950
155807
|
// ../server/package.json
|
|
154951
|
-
var version3 = "0.4.
|
|
155808
|
+
var version3 = "0.4.11";
|
|
154952
155809
|
|
|
154953
155810
|
// ../server/src/modules/health/index.ts
|
|
154954
155811
|
var startedAt = Date.now();
|
|
@@ -155421,8 +156278,8 @@ var healthModule = {
|
|
|
155421
156278
|
}
|
|
155422
156279
|
});
|
|
155423
156280
|
}
|
|
155424
|
-
const projectPath =
|
|
155425
|
-
const globalPath =
|
|
156281
|
+
const projectPath = join90(ctx.cwd, ".spur", "config.yaml");
|
|
156282
|
+
const globalPath = join90(homedir16(), ".config", "spur", "config.yaml");
|
|
155426
156283
|
let projectExists = false;
|
|
155427
156284
|
let projectContent = "";
|
|
155428
156285
|
let projectSizeBytes = 0;
|
|
@@ -155539,7 +156396,7 @@ var healthModule = {
|
|
|
155539
156396
|
const projectRoot = ctx.cwd ?? process.cwd();
|
|
155540
156397
|
const fs = ctx.fs ?? createNodeFileSystem2(projectRoot);
|
|
155541
156398
|
const files = [];
|
|
155542
|
-
const rootDesignPath =
|
|
156399
|
+
const rootDesignPath = join90(projectRoot, "DESIGN.md");
|
|
155543
156400
|
if (await fs.exists(rootDesignPath)) {
|
|
155544
156401
|
let title = "UI/UX Design System";
|
|
155545
156402
|
try {
|
|
@@ -155556,7 +156413,7 @@ var healthModule = {
|
|
|
155556
156413
|
category: "root"
|
|
155557
156414
|
});
|
|
155558
156415
|
}
|
|
155559
|
-
const doc04Path =
|
|
156416
|
+
const doc04Path = join90(projectRoot, "docs", "04_DESIGN.md");
|
|
155560
156417
|
if (await fs.exists(doc04Path)) {
|
|
155561
156418
|
let title = "Concrete Surfaces & Contracts";
|
|
155562
156419
|
try {
|
|
@@ -155573,7 +156430,7 @@ var healthModule = {
|
|
|
155573
156430
|
category: "architecture"
|
|
155574
156431
|
});
|
|
155575
156432
|
}
|
|
155576
|
-
const designDir =
|
|
156433
|
+
const designDir = join90(projectRoot, "docs", "design");
|
|
155577
156434
|
if (await fs.exists(designDir)) {
|
|
155578
156435
|
let mdFiles = [];
|
|
155579
156436
|
try {
|
|
@@ -155582,7 +156439,7 @@ var healthModule = {
|
|
|
155582
156439
|
} catch {}
|
|
155583
156440
|
mdFiles.sort((a, b) => a.localeCompare(b));
|
|
155584
156441
|
for (const filename of mdFiles) {
|
|
155585
|
-
const fullPath =
|
|
156442
|
+
const fullPath = join90(designDir, filename);
|
|
155586
156443
|
const relPath = `docs/design/${filename}`;
|
|
155587
156444
|
let title = filename.replace(/\.md$/, "").replace(/-/g, " ");
|
|
155588
156445
|
try {
|
|
@@ -155620,7 +156477,7 @@ var healthModule = {
|
|
|
155620
156477
|
try {
|
|
155621
156478
|
const projectRoot = ctx.cwd ?? process.cwd();
|
|
155622
156479
|
const fs = ctx.fs ?? createNodeFileSystem2(projectRoot);
|
|
155623
|
-
const fullPath =
|
|
156480
|
+
const fullPath = join90(projectRoot, reqPath);
|
|
155624
156481
|
if (!await fs.exists(fullPath)) {
|
|
155625
156482
|
return c.json({ ok: false, error: "File not found" }, 404);
|
|
155626
156483
|
}
|
|
@@ -155646,7 +156503,7 @@ var healthModule = {
|
|
|
155646
156503
|
const projectRoot = ctx.cwd ?? process.cwd();
|
|
155647
156504
|
const fs = ctx.fs ?? createNodeFileSystem2(projectRoot);
|
|
155648
156505
|
const files = [];
|
|
155649
|
-
const roadmapPath =
|
|
156506
|
+
const roadmapPath = join90(projectRoot, "docs", "02_ROADMAP.md");
|
|
155650
156507
|
if (await fs.exists(roadmapPath)) {
|
|
155651
156508
|
let title = "02 Roadmap \u2014 Spur";
|
|
155652
156509
|
try {
|
|
@@ -155663,7 +156520,7 @@ var healthModule = {
|
|
|
155663
156520
|
category: "roadmap"
|
|
155664
156521
|
});
|
|
155665
156522
|
}
|
|
155666
|
-
const plansDir =
|
|
156523
|
+
const plansDir = join90(projectRoot, "docs", "plans");
|
|
155667
156524
|
if (await fs.exists(plansDir)) {
|
|
155668
156525
|
let mdFiles = [];
|
|
155669
156526
|
try {
|
|
@@ -155672,7 +156529,7 @@ var healthModule = {
|
|
|
155672
156529
|
} catch {}
|
|
155673
156530
|
mdFiles.sort((a, b) => b.localeCompare(a));
|
|
155674
156531
|
for (const filename of mdFiles) {
|
|
155675
|
-
const fullPath =
|
|
156532
|
+
const fullPath = join90(plansDir, filename);
|
|
155676
156533
|
const relPath = `docs/plans/${filename}`;
|
|
155677
156534
|
let title = filename.replace(/\.md$/, "").replace(/-/g, " ");
|
|
155678
156535
|
try {
|
|
@@ -155710,7 +156567,7 @@ var healthModule = {
|
|
|
155710
156567
|
try {
|
|
155711
156568
|
const projectRoot = ctx.cwd ?? process.cwd();
|
|
155712
156569
|
const fs = ctx.fs ?? createNodeFileSystem2(projectRoot);
|
|
155713
|
-
const fullPath =
|
|
156570
|
+
const fullPath = join90(projectRoot, reqPath);
|
|
155714
156571
|
if (!await fs.exists(fullPath)) {
|
|
155715
156572
|
return c.json({ ok: false, error: "File not found" }, 404);
|
|
155716
156573
|
}
|
|
@@ -158224,7 +159081,7 @@ function createApp(appRt, opts) {
|
|
|
158224
159081
|
app.use("*", async (c, next) => {
|
|
158225
159082
|
const pathname = c.req.path === "/" ? "/index.html" : c.req.path;
|
|
158226
159083
|
try {
|
|
158227
|
-
const file = Bun.file(
|
|
159084
|
+
const file = Bun.file(join91(webDistPath, pathname));
|
|
158228
159085
|
if (await file.exists()) {
|
|
158229
159086
|
const headers = new Headers({ "content-type": file.type });
|
|
158230
159087
|
return new Response(file.stream(), { headers });
|
|
@@ -158237,7 +159094,7 @@ function createApp(appRt, opts) {
|
|
|
158237
159094
|
return c.json({ error: "Not Found" }, 404);
|
|
158238
159095
|
}
|
|
158239
159096
|
try {
|
|
158240
|
-
const indexFile = Bun.file(
|
|
159097
|
+
const indexFile = Bun.file(join91(webDistPath, "index.html"));
|
|
158241
159098
|
if (await indexFile.exists()) {
|
|
158242
159099
|
return new Response(indexFile.stream(), {
|
|
158243
159100
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
@@ -158259,7 +159116,7 @@ init_src();
|
|
|
158259
159116
|
init_loader2();
|
|
158260
159117
|
init_src2();
|
|
158261
159118
|
init_dist5();
|
|
158262
|
-
import { dirname as dirname43, join as
|
|
159119
|
+
import { dirname as dirname43, join as join92 } from "path";
|
|
158263
159120
|
var NOOP_OUTPUT = { write: (_s) => {}, error: (_s) => {} };
|
|
158264
159121
|
|
|
158265
159122
|
class LazyPlanningEventEmitter {
|
|
@@ -158295,7 +159152,7 @@ class NotConfiguredError extends Error {
|
|
|
158295
159152
|
function createServerContext(appRt, options) {
|
|
158296
159153
|
const cwd = options.cwd;
|
|
158297
159154
|
const fs = options.fs;
|
|
158298
|
-
const dbUrl = options.dbUrl ??
|
|
159155
|
+
const dbUrl = options.dbUrl ?? join92(cwd, DEFAULT_DATABASE_URL);
|
|
158299
159156
|
const eventsBus = options.eventsBus ?? appRt.events;
|
|
158300
159157
|
const jobQueueEnabled = options.jobQueueEnabled ?? false;
|
|
158301
159158
|
const eventProjectContext = systemEventProjectContext(cwd);
|
|
@@ -158331,7 +159188,7 @@ function createServerContext(appRt, options) {
|
|
|
158331
159188
|
return {
|
|
158332
159189
|
cwd,
|
|
158333
159190
|
fs,
|
|
158334
|
-
runDir:
|
|
159191
|
+
runDir: join92(cwd, ".spur", "run"),
|
|
158335
159192
|
webDistPath: options.webDistPath,
|
|
158336
159193
|
boardModules: options.boardModules,
|
|
158337
159194
|
logger: appRt.logger,
|
|
@@ -158695,7 +159552,7 @@ var BOARD_RUNTIME_MANIFEST_FILE = "board-runtime.json";
|
|
|
158695
159552
|
async function readBoardHostRuntime(webDistPath, hasContributions) {
|
|
158696
159553
|
if (!webDistPath)
|
|
158697
159554
|
return null;
|
|
158698
|
-
const manifestPath =
|
|
159555
|
+
const manifestPath = join93(webDistPath, BOARD_RUNTIME_MANIFEST_FILE);
|
|
158699
159556
|
try {
|
|
158700
159557
|
const file = Bun.file(manifestPath);
|
|
158701
159558
|
if (!await file.exists())
|
|
@@ -158732,7 +159589,7 @@ function defaultUsageSnapshotPath(env) {
|
|
|
158732
159589
|
const override = env.SPUR_AGENT_USAGE_SNAPSHOT;
|
|
158733
159590
|
if (override !== undefined && override.length > 0)
|
|
158734
159591
|
return override;
|
|
158735
|
-
return
|
|
159592
|
+
return join93(env.HOME ?? homedir17(), ".config", "spur", "agent-usage.json");
|
|
158736
159593
|
}
|
|
158737
159594
|
var JOB_WORKER_START_DELAY_MS = 30000;
|
|
158738
159595
|
var defaultDeps = {
|
|
@@ -158996,15 +159853,15 @@ async function handleFeatureActionJob(ctx, env, payload) {
|
|
|
158996
159853
|
await runFeatureActionJob(ctx, env, payload);
|
|
158997
159854
|
}
|
|
158998
159855
|
async function resolveWebDistPath(configuredPath, projectRoot = process.cwd()) {
|
|
158999
|
-
const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute15(configuredPath) ? configuredPath :
|
|
159000
|
-
|
|
159001
|
-
|
|
159002
|
-
|
|
159003
|
-
|
|
159004
|
-
|
|
159856
|
+
const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute15(configuredPath) ? configuredPath : join93(projectRoot, configuredPath)] : [
|
|
159857
|
+
join93(projectRoot, "dist/web"),
|
|
159858
|
+
join93(import.meta.dir, "web"),
|
|
159859
|
+
join93(dirname44(process.execPath), "web"),
|
|
159860
|
+
join93(dirname44(process.execPath), "../web"),
|
|
159861
|
+
join93(import.meta.dir, "../../../dist/web")
|
|
159005
159862
|
];
|
|
159006
159863
|
for (const candidate of candidates) {
|
|
159007
|
-
if (await Bun.file(
|
|
159864
|
+
if (await Bun.file(join93(candidate, "index.html")).exists()) {
|
|
159008
159865
|
return candidate;
|
|
159009
159866
|
}
|
|
159010
159867
|
}
|
|
@@ -159479,7 +160336,7 @@ var SQLITE_BUSY_MESSAGE_CONSTANTS = Object.freeze({
|
|
|
159479
160336
|
|
|
159480
160337
|
// src/commands/serve.ts
|
|
159481
160338
|
function resolveServeDbUrl(cwd, env, configuredUrl) {
|
|
159482
|
-
return env.DATABASE_URL === undefined ?
|
|
160339
|
+
return env.DATABASE_URL === undefined ? join94(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
|
|
159483
160340
|
}
|
|
159484
160341
|
function resolveServeCwd(cwd) {
|
|
159485
160342
|
const resolved = resolve36(cwd);
|
|
@@ -159543,7 +160400,7 @@ function registerServeCommand(program, context, options = {}) {
|
|
|
159543
160400
|
}
|
|
159544
160401
|
|
|
159545
160402
|
// src/commands/status.ts
|
|
159546
|
-
import { join as
|
|
160403
|
+
import { join as join95 } from "path";
|
|
159547
160404
|
|
|
159548
160405
|
// src/git-context.ts
|
|
159549
160406
|
init_dist5();
|
|
@@ -159591,8 +160448,8 @@ function registerStatusCommand(program, context, options = {}) {
|
|
|
159591
160448
|
}
|
|
159592
160449
|
async function runStatusCore(path, options, context) {
|
|
159593
160450
|
const [packageJsonExists, spurConfigExists, git, agentSpecs] = await Promise.all([
|
|
159594
|
-
context.fs.exists(
|
|
159595
|
-
context.fs.exists(
|
|
160451
|
+
context.fs.exists(join95(context.cwd, "package.json")),
|
|
160452
|
+
context.fs.exists(join95(context.cwd, ".spur", "config.yaml")),
|
|
159596
160453
|
gitContext(context.cwd),
|
|
159597
160454
|
listAgentSpecIds(context)
|
|
159598
160455
|
]);
|
|
@@ -159623,14 +160480,14 @@ async function runStatusCore(path, options, context) {
|
|
|
159623
160480
|
return status.ok ? 0 : 1;
|
|
159624
160481
|
}
|
|
159625
160482
|
async function listAgentSpecIds(context) {
|
|
159626
|
-
const dir =
|
|
160483
|
+
const dir = join95(context.cwd, ".spur", "agents");
|
|
159627
160484
|
if (!await context.fs.exists(dir))
|
|
159628
160485
|
return [];
|
|
159629
160486
|
const entries = await context.fs.readDir(dir);
|
|
159630
160487
|
return entries.filter((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml")).map((entry) => entry.replace(/\.ya?ml$/, "")).sort();
|
|
159631
160488
|
}
|
|
159632
160489
|
async function readTargetStatus(context, targetPath) {
|
|
159633
|
-
const resolved =
|
|
160490
|
+
const resolved = join95(context.cwd, targetPath);
|
|
159634
160491
|
const stat = await context.fs.stat(resolved);
|
|
159635
160492
|
if (stat === null)
|
|
159636
160493
|
throw new CommandError(`status failed: path does not exist at ${resolved}`);
|
|
@@ -159881,11 +160738,11 @@ function registerWorkflowCleanCommand(workflow, context) {
|
|
|
159881
160738
|
// ../../packages/app/src/workflow/commit-guard.ts
|
|
159882
160739
|
init_dist5();
|
|
159883
160740
|
import { mkdirSync as mkdirSync23, readFileSync as readFileSync28, renameSync as renameSync11, writeFileSync as writeFileSync20 } from "fs";
|
|
159884
|
-
import { join as
|
|
160741
|
+
import { join as join96 } from "path";
|
|
159885
160742
|
var COMMIT_GUARD_USAGE = "usage: spur workflow commit-guard start --run <id> | stage --run <id> [--base <sha>] -- <paths\u2026> | check --run <id> [--base <sha>]";
|
|
159886
160743
|
var isBookkeeping = (path) => path === ".spur" || path.startsWith(".spur/");
|
|
159887
|
-
var at2 = (options, rel) => options.cwd ?
|
|
159888
|
-
var artifactPath = (run) =>
|
|
160744
|
+
var at2 = (options, rel) => options.cwd ? join96(options.cwd, rel) : rel;
|
|
160745
|
+
var artifactPath = (run) => join96(".spur", "run", `${run}-tree-start.json`);
|
|
159889
160746
|
function gitOf(options) {
|
|
159890
160747
|
if (options.git)
|
|
159891
160748
|
return options.git;
|
|
@@ -159954,7 +160811,7 @@ function runContext(run, options) {
|
|
|
159954
160811
|
let base = options.base ?? "";
|
|
159955
160812
|
if (!base) {
|
|
159956
160813
|
try {
|
|
159957
|
-
const anchored = readFileSync28(at2(options,
|
|
160814
|
+
const anchored = readFileSync28(at2(options, join96(".spur", "run", `${run}-base.sha`)), "utf8").trim();
|
|
159958
160815
|
if (/^[0-9a-f]{7,40}$/i.test(anchored))
|
|
159959
160816
|
base = anchored;
|
|
159960
160817
|
} catch {}
|
|
@@ -159994,7 +160851,7 @@ function commitGuardStart(run, options = {}) {
|
|
|
159994
160851
|
dirty: parsePorcelainZ(status.stdout).map((path) => ({ path, sha: hashPath(path, git) }))
|
|
159995
160852
|
};
|
|
159996
160853
|
const abs = at2(options, artifactPath(run));
|
|
159997
|
-
mkdirSync23(
|
|
160854
|
+
mkdirSync23(join96(abs, ".."), { recursive: true });
|
|
159998
160855
|
writeFileSync20(`${abs}.tmp`, `${JSON.stringify(snapshot, null, 2)}
|
|
159999
160856
|
`);
|
|
160000
160857
|
renameSync11(`${abs}.tmp`, abs);
|
|
@@ -160266,14 +161123,14 @@ function registerWorkflowContinueCommand(workflow, context) {
|
|
|
160266
161123
|
}
|
|
160267
161124
|
|
|
160268
161125
|
// src/commands/workflow/cost.ts
|
|
160269
|
-
import { join as
|
|
161126
|
+
import { join as join98 } from "path";
|
|
160270
161127
|
|
|
160271
161128
|
// ../../packages/app/src/workflow/run-cost.ts
|
|
160272
161129
|
init_src2();
|
|
160273
161130
|
init_dist5();
|
|
160274
161131
|
init_dist2();
|
|
160275
161132
|
init_composition_baseline();
|
|
160276
|
-
import { join as
|
|
161133
|
+
import { join as join97 } from "path";
|
|
160277
161134
|
|
|
160278
161135
|
// ../../packages/app/src/workflow/run-measurements.ts
|
|
160279
161136
|
init_src2();
|
|
@@ -160507,7 +161364,7 @@ async function loadQueryCounts(workflowsDir, fs = createNodeFileSystem2()) {
|
|
|
160507
161364
|
const name = /^(.+)\.ya?ml$/.exec(entry)?.[1];
|
|
160508
161365
|
if (!name)
|
|
160509
161366
|
continue;
|
|
160510
|
-
const def = $parse(await fs.readFile(
|
|
161367
|
+
const def = $parse(await fs.readFile(join97(workflowsDir, entry)));
|
|
160511
161368
|
out[name] = extractResolvedWorkflowFacts(def).modelQueries.length;
|
|
160512
161369
|
}
|
|
160513
161370
|
return out;
|
|
@@ -160533,7 +161390,7 @@ async function runBudgetGate(opts) {
|
|
|
160533
161390
|
const fs = opts.fs ?? createNodeFileSystem2();
|
|
160534
161391
|
const gitShow = opts.gitShow ?? defaultGitShow;
|
|
160535
161392
|
const openDb = opts.openDb ?? defaultDbOpener;
|
|
160536
|
-
const absBudget = opts.budgetPath.startsWith("/") ? opts.budgetPath :
|
|
161393
|
+
const absBudget = opts.budgetPath.startsWith("/") ? opts.budgetPath : join97(opts.cwd, opts.budgetPath);
|
|
160537
161394
|
const relBudget = absBudget.startsWith(`${opts.cwd}/`) ? absBudget.slice(opts.cwd.length + 1) : opts.budgetPath;
|
|
160538
161395
|
const config = await loadPipelineBudgets(absBudget, fs);
|
|
160539
161396
|
let head = null;
|
|
@@ -160549,7 +161406,7 @@ async function runBudgetGate(opts) {
|
|
|
160549
161406
|
const queryCounts = await loadQueryCounts(opts.workflowsDir, fs);
|
|
160550
161407
|
let measured;
|
|
160551
161408
|
if (opts.measuredFile !== undefined) {
|
|
160552
|
-
const measuredPath = opts.measuredFile.startsWith("/") ? opts.measuredFile :
|
|
161409
|
+
const measuredPath = opts.measuredFile.startsWith("/") ? opts.measuredFile : join97(opts.cwd, opts.measuredFile);
|
|
160553
161410
|
measured = JSON.parse(await fs.readFile(measuredPath));
|
|
160554
161411
|
} else if (!await fs.exists(opts.dbPath)) {
|
|
160555
161412
|
measured = {};
|
|
@@ -160598,8 +161455,8 @@ function registerWorkflowCostCommand(workflow, context, deps = {}) {
|
|
|
160598
161455
|
].join(`
|
|
160599
161456
|
`)).option("--workflow <name>", "restrict to a workflow (repeatable)", collect3, []).option("--by-state", "report per-state rows instead of per-workflow rows").option("--include-bookkeeping", "keep bookkeeping workflows in the default cohort").option("--since <YYYY-MM-DD>", "only runs created on/after this UTC date").option("--budget <file>", "run the pipeline budget gate against this budget file").option("--measured-file <json>", "with --budget: read measured values from this file instead of the DB").option(...SHARED_OPTIONS.json).action(async (options) => {
|
|
160600
161457
|
const cwd = context.cwd;
|
|
160601
|
-
const dbPath =
|
|
160602
|
-
const workflowsDir =
|
|
161458
|
+
const dbPath = join98(cwd, ".spur/spur.db");
|
|
161459
|
+
const workflowsDir = join98(cwd, ".spur", "workflows");
|
|
160603
161460
|
const openDb = deps.openDb ?? defaultDbOpener;
|
|
160604
161461
|
try {
|
|
160605
161462
|
if (options.budget !== undefined) {
|
|
@@ -160663,9 +161520,9 @@ init_run_summary_render();
|
|
|
160663
161520
|
// ../../packages/app/src/workflow/persist-check.ts
|
|
160664
161521
|
init_dist5();
|
|
160665
161522
|
import { existsSync as existsSync33, readdirSync as readdirSync15, readFileSync as readFileSync30, statSync as statSync19 } from "fs";
|
|
160666
|
-
import { basename as basename22, dirname as dirname45, isAbsolute as isAbsolute16, join as
|
|
160667
|
-
var EVIDENCE_DIR =
|
|
160668
|
-
var RUN_DIR =
|
|
161523
|
+
import { basename as basename22, dirname as dirname45, isAbsolute as isAbsolute16, join as join100, relative as relative20, resolve as resolve37 } from "path";
|
|
161524
|
+
var EVIDENCE_DIR = join100(".spur", "memory", "evidence");
|
|
161525
|
+
var RUN_DIR = join100(".spur", "run");
|
|
160669
161526
|
var NAMED_CAP = 32;
|
|
160670
161527
|
var PREFIX_CAP = 64;
|
|
160671
161528
|
var DURABLE_FAMILY_RE = /^\d{4}-(?:verdict|feature-verification)\.json$/;
|
|
@@ -160680,7 +161537,7 @@ function walkFiles(dir, root) {
|
|
|
160680
161537
|
const out = [];
|
|
160681
161538
|
const walk = (cur) => {
|
|
160682
161539
|
for (const e of readdirSync15(cur, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
160683
|
-
const p =
|
|
161540
|
+
const p = join100(cur, e.name);
|
|
160684
161541
|
if (e.isDirectory())
|
|
160685
161542
|
walk(p);
|
|
160686
161543
|
else if (e.isFile())
|
|
@@ -160707,8 +161564,8 @@ function listObligations(wtRoot, wbsList, runIds) {
|
|
|
160707
161564
|
const prefixes = [...new Set([...wbsList, ...runIds].filter(Boolean))];
|
|
160708
161565
|
if (prefixes.length === 0)
|
|
160709
161566
|
return null;
|
|
160710
|
-
const evFiles = walkFiles(
|
|
160711
|
-
const runFiles = walkFiles(
|
|
161567
|
+
const evFiles = walkFiles(join100(wtRoot, EVIDENCE_DIR), wtRoot);
|
|
161568
|
+
const runFiles = walkFiles(join100(wtRoot, RUN_DIR), wtRoot);
|
|
160712
161569
|
const files = [];
|
|
160713
161570
|
const counts = new Map;
|
|
160714
161571
|
if (!collectOwned(evFiles, prefixes, counts, files))
|
|
@@ -160725,13 +161582,13 @@ function compareTrees(wtRoot, invokeRoot, files, foreignDivergent = new Set) {
|
|
|
160725
161582
|
for (const rel of files) {
|
|
160726
161583
|
if (foreignDivergent.has(rel) || foreignDivergent.has(basename22(rel)))
|
|
160727
161584
|
continue;
|
|
160728
|
-
const invPath =
|
|
161585
|
+
const invPath = join100(invokeRoot, rel);
|
|
160729
161586
|
if (!existsSync33(invPath)) {
|
|
160730
161587
|
missing.push(rel);
|
|
160731
161588
|
continue;
|
|
160732
161589
|
}
|
|
160733
161590
|
try {
|
|
160734
|
-
if (readFileSync30(
|
|
161591
|
+
if (readFileSync30(join100(wtRoot, rel)).equals(readFileSync30(invPath)))
|
|
160735
161592
|
ok++;
|
|
160736
161593
|
else
|
|
160737
161594
|
divergent.push(rel);
|
|
@@ -160748,7 +161605,7 @@ function defaultInvokeRoot(cwd) {
|
|
|
160748
161605
|
});
|
|
160749
161606
|
const common = run.stdout.trim();
|
|
160750
161607
|
if (run.exitCode === 0 && common)
|
|
160751
|
-
return dirname45(isAbsolute16(common) ? common :
|
|
161608
|
+
return dirname45(isAbsolute16(common) ? common : join100(cwd, common));
|
|
160752
161609
|
return cwd;
|
|
160753
161610
|
}
|
|
160754
161611
|
function readPersistOutSkips(candidates) {
|
|
@@ -160763,7 +161620,7 @@ function readPersistOutSkips(candidates) {
|
|
|
160763
161620
|
for (const item of parsed.evidenceSkipped) {
|
|
160764
161621
|
if (item.reason === "foreign-divergent" && typeof item.name === "string") {
|
|
160765
161622
|
foreignDivergent.add(item.name);
|
|
160766
|
-
foreignDivergent.add(
|
|
161623
|
+
foreignDivergent.add(join100(EVIDENCE_DIR, item.name));
|
|
160767
161624
|
}
|
|
160768
161625
|
}
|
|
160769
161626
|
}
|
|
@@ -160796,8 +161653,8 @@ function runPersistCheck(input) {
|
|
|
160796
161653
|
const invokeRoot = input.root === undefined ? defaultInvokeRoot(input.cwd) : resolve37(input.cwd, input.root);
|
|
160797
161654
|
const { foreignDivergent, skips } = readPersistOutSkips([
|
|
160798
161655
|
input.successJson === undefined || input.successJson === "" ? "" : resolve37(input.cwd, input.successJson),
|
|
160799
|
-
|
|
160800
|
-
|
|
161656
|
+
join100(invokeRoot, ".spur", "run", "persist-out.json"),
|
|
161657
|
+
join100(wtRoot, ".spur", "run", "persist-out.json")
|
|
160801
161658
|
].filter(Boolean));
|
|
160802
161659
|
const obligations = listObligations(wtRoot, wbsList, runIds);
|
|
160803
161660
|
if (obligations === null) {
|
|
@@ -161166,7 +162023,7 @@ init_src3();
|
|
|
161166
162023
|
init_capability_attestation();
|
|
161167
162024
|
init_src();
|
|
161168
162025
|
init_dist7();
|
|
161169
|
-
import { join as
|
|
162026
|
+
import { join as join101 } from "path";
|
|
161170
162027
|
import { createInterface } from "readline";
|
|
161171
162028
|
import { setTimeout as sleep6 } from "timers/promises";
|
|
161172
162029
|
var WORKFLOW_RUN_ACTIVE_ENV = "SPUR_WORKFLOW_RUN_ACTIVE";
|
|
@@ -161270,9 +162127,9 @@ async function writeWorkflowPlanArtifact(cwd, runId, resolved, secretValues, sta
|
|
|
161270
162127
|
}))
|
|
161271
162128
|
});
|
|
161272
162129
|
const { mkdir, writeFile } = await import("fs/promises");
|
|
161273
|
-
const artifactsDir =
|
|
162130
|
+
const artifactsDir = join101(cwd, ".spur", "run");
|
|
161274
162131
|
await mkdir(artifactsDir, { recursive: true });
|
|
161275
|
-
const artifactPath =
|
|
162132
|
+
const artifactPath = join101(artifactsDir, `${runId}-workflow-plan.json`);
|
|
161276
162133
|
await writeFile(artifactPath, payload);
|
|
161277
162134
|
return artifactPath;
|
|
161278
162135
|
}
|
|
@@ -161743,7 +162600,7 @@ function registerWorkflowSummaryCommand(workflow, context) {
|
|
|
161743
162600
|
// src/commands/workflow/trace.ts
|
|
161744
162601
|
init_src3();
|
|
161745
162602
|
import { closeSync as closeSync11, fstatSync as fstatSync5, openSync as openSync11, readFileSync as readFileSync32, readSync as readSync3 } from "fs";
|
|
161746
|
-
import { join as
|
|
162603
|
+
import { join as join102 } from "path";
|
|
161747
162604
|
import { setTimeout as sleep7 } from "timers/promises";
|
|
161748
162605
|
function formatTraceList2(result) {
|
|
161749
162606
|
if (result.entries.length === 0) {
|
|
@@ -161912,8 +162769,8 @@ function readRunLogChunk(logPath, offset) {
|
|
|
161912
162769
|
async function followRunLog(service, runId, dir, pollMs, write, wait = (ms) => sleep7(ms), now = Date.now, deadlineMs) {
|
|
161913
162770
|
const start = now();
|
|
161914
162771
|
const deadline = deadlineMs === undefined ? Number.POSITIVE_INFINITY : start + deadlineMs;
|
|
161915
|
-
const runDir =
|
|
161916
|
-
const pendingMdPath =
|
|
162772
|
+
const runDir = join102(dir, ".spur", "run");
|
|
162773
|
+
const pendingMdPath = join102(runDir, `${runId}.md`);
|
|
161917
162774
|
let logPath;
|
|
161918
162775
|
let record = { kind: "missing" };
|
|
161919
162776
|
let offset = 0;
|
|
@@ -162328,8 +163185,14 @@ function shouldRenderBanner(argv) {
|
|
|
162328
163185
|
return false;
|
|
162329
163186
|
if (argv[0] === "history" && argv[1] === "timeline")
|
|
162330
163187
|
return false;
|
|
163188
|
+
if (argv[0] === "builder" && argv[1] === "show-branches" && requestsJsonFormat(argv))
|
|
163189
|
+
return false;
|
|
162331
163190
|
return !argv.some((arg) => arg === "--no-logo" || arg === "--json" || arg === "--quiet" || arg === "--silent");
|
|
162332
163191
|
}
|
|
163192
|
+
function requestsJsonFormat(argv) {
|
|
163193
|
+
const index = argv.indexOf("--format");
|
|
163194
|
+
return argv.includes("--format=json") || index !== -1 && argv[index + 1] === "json";
|
|
163195
|
+
}
|
|
162333
163196
|
async function runCli() {
|
|
162334
163197
|
const argv = process.argv.slice(2);
|
|
162335
163198
|
if (shouldRenderBanner(argv)) {
|