@mutmutco/cli 4.5.41 → 4.5.43
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/dist/main.cjs +413 -189
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5496,8 +5496,8 @@ function resolveVerbAliasShim(argv) {
|
|
|
5496
5496
|
}
|
|
5497
5497
|
|
|
5498
5498
|
// src/command-composition.ts
|
|
5499
|
-
var
|
|
5500
|
-
var
|
|
5499
|
+
var import_node_os23 = require("node:os");
|
|
5500
|
+
var import_node_path52 = require("node:path");
|
|
5501
5501
|
|
|
5502
5502
|
// src/board-read.ts
|
|
5503
5503
|
var import_node_fs13 = require("node:fs");
|
|
@@ -16871,10 +16871,10 @@ var rollout_plan_default = {
|
|
|
16871
16871
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
16872
16872
|
},
|
|
16873
16873
|
baseline: {
|
|
16874
|
-
version: "4.5.
|
|
16875
|
-
tag: "v4.5.
|
|
16876
|
-
commit: "
|
|
16877
|
-
npm: "@mutmutco/cli@4.5.
|
|
16874
|
+
version: "4.5.43",
|
|
16875
|
+
tag: "v4.5.43",
|
|
16876
|
+
commit: "daaa2fa607cd",
|
|
16877
|
+
npm: "@mutmutco/cli@4.5.43"
|
|
16878
16878
|
},
|
|
16879
16879
|
exitCriterion: "fleet-n-of-n",
|
|
16880
16880
|
hubOnlyShortcut: "forbidden",
|
|
@@ -16891,14 +16891,14 @@ var rollout_plan_default = {
|
|
|
16891
16891
|
repo: "mutmutco/mmi-hub",
|
|
16892
16892
|
role: "canary",
|
|
16893
16893
|
schedule: "train",
|
|
16894
|
-
v3Target: "v4.5.
|
|
16894
|
+
v3Target: "v4.5.43"
|
|
16895
16895
|
}
|
|
16896
16896
|
],
|
|
16897
16897
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
16898
16898
|
rollback: {
|
|
16899
16899
|
independent: true,
|
|
16900
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
16901
|
-
v3Target: "v4.5.
|
|
16900
|
+
mechanism: "npm dist-tag latest -> 4.5.43 and redeploy the Hub Lambda from tag v4.5.43 (daaa2fa607cd); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
16901
|
+
v3Target: "v4.5.43 (@mutmutco/cli@4.5.43, tag commit daaa2fa607cd \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
16902
16902
|
}
|
|
16903
16903
|
},
|
|
16904
16904
|
{
|
|
@@ -29577,8 +29577,8 @@ function registerBoardCommands(program3) {
|
|
|
29577
29577
|
|
|
29578
29578
|
// src/bootstrap-commands.ts
|
|
29579
29579
|
var import_node_fs28 = require("node:fs");
|
|
29580
|
-
var
|
|
29581
|
-
var
|
|
29580
|
+
var import_node_os17 = require("node:os");
|
|
29581
|
+
var import_node_path28 = require("node:path");
|
|
29582
29582
|
init_cli_shared();
|
|
29583
29583
|
init_clean_exit();
|
|
29584
29584
|
|
|
@@ -29890,6 +29890,122 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
|
|
|
29890
29890
|
|
|
29891
29891
|
// src/box-commands.ts
|
|
29892
29892
|
var import_node_fs27 = require("node:fs");
|
|
29893
|
+
var import_node_child_process10 = require("node:child_process");
|
|
29894
|
+
var import_node_os16 = require("node:os");
|
|
29895
|
+
var import_node_path27 = require("node:path");
|
|
29896
|
+
|
|
29897
|
+
// src/box-control.ts
|
|
29898
|
+
var import_node_crypto8 = require("node:crypto");
|
|
29899
|
+
var MANIFEST_PATH = "infra/box-control/manifest.json";
|
|
29900
|
+
var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
|
|
29901
|
+
function parseManifest(text) {
|
|
29902
|
+
const raw = JSON.parse(text);
|
|
29903
|
+
if (!Array.isArray(raw.files)) throw new Error('box control manifest: "files" must be an array');
|
|
29904
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29905
|
+
return raw.files.map((f, i) => {
|
|
29906
|
+
const where = `box control manifest: files[${i}]`;
|
|
29907
|
+
if (typeof f.src !== "string" || !f.src || f.src.startsWith("/") || f.src.includes("..")) throw new Error(`${where}: src must be a repo-relative path`);
|
|
29908
|
+
if (f.mode !== "0644" && f.mode !== "0755") throw new Error(`${where} (${f.src}): mode must be "0644" or "0755"`);
|
|
29909
|
+
if (f.post !== void 0 && (typeof f.post !== "string" || !ALLOWED_POST.has(f.post))) {
|
|
29910
|
+
throw new Error(`${where} (${f.src}): post must be one of ${[...ALLOWED_POST].join(", ")}`);
|
|
29911
|
+
}
|
|
29912
|
+
const targets = [];
|
|
29913
|
+
if (Array.isArray(f.targets)) targets.push(...f.targets);
|
|
29914
|
+
if (f.lanes !== void 0) targets.push(...expandLanes(f.lanes, where));
|
|
29915
|
+
if (!targets.length) throw new Error(`${where} (${f.src}): needs targets or lanes`);
|
|
29916
|
+
for (const t of targets) {
|
|
29917
|
+
if (typeof t.box !== "string" || !/^[a-z0-9-]+$/.test(t.box)) throw new Error(`${where} (${f.src}): bad box name`);
|
|
29918
|
+
if (typeof t.dest !== "string" || !/^\/[A-Za-z0-9._\/-]+$/.test(t.dest) || t.dest.includes("..")) {
|
|
29919
|
+
throw new Error(`${where} (${f.src}): dest must be an absolute path of plain characters`);
|
|
29920
|
+
}
|
|
29921
|
+
if (t.dest.startsWith("/opt/mmi-control/secrets") || t.dest.endsWith(".env")) {
|
|
29922
|
+
throw new Error(`${where} (${f.src}): ${t.dest} is vault-owned and cannot be a manifest destination`);
|
|
29923
|
+
}
|
|
29924
|
+
const key = `${t.box}:${t.dest}`;
|
|
29925
|
+
if (seen.has(key)) throw new Error(`${where} (${f.src}): duplicate destination ${key}`);
|
|
29926
|
+
seen.add(key);
|
|
29927
|
+
}
|
|
29928
|
+
return { src: f.src, mode: f.mode, ...f.post ? { post: f.post } : {}, targets };
|
|
29929
|
+
});
|
|
29930
|
+
}
|
|
29931
|
+
function expandLanes(l, where) {
|
|
29932
|
+
if (!l || typeof l.box !== "string" || !Number.isInteger(l.from) || !Number.isInteger(l.to) || l.from < 1 || l.to < l.from || typeof l.dest !== "string" || !l.dest.includes("{N}")) {
|
|
29933
|
+
throw new Error(`${where}: lanes needs box, integer from <= to, and a dest containing {N}`);
|
|
29934
|
+
}
|
|
29935
|
+
const out = [];
|
|
29936
|
+
for (let n = l.from; n <= l.to; n += 1) out.push({ box: l.box, dest: l.dest.replace("{N}", String(n)) });
|
|
29937
|
+
return out;
|
|
29938
|
+
}
|
|
29939
|
+
function filesForBox(files, box) {
|
|
29940
|
+
return files.flatMap((file) => file.targets.filter((t) => t.box === box).map((t) => ({ file, dest: t.dest })));
|
|
29941
|
+
}
|
|
29942
|
+
function md5(content) {
|
|
29943
|
+
return (0, import_node_crypto8.createHash)("md5").update(content).digest("hex");
|
|
29944
|
+
}
|
|
29945
|
+
function planRemoteScript(dests) {
|
|
29946
|
+
const lines2 = ["set -u"];
|
|
29947
|
+
for (const d of dests) {
|
|
29948
|
+
lines2.push(`if [ -f '${d}' ]; then printf '%s %s %s\\n' "$(md5sum < '${d}' | cut -d' ' -f1)" "$(stat -c %a '${d}')" '${d}'; else printf 'MISSING - %s\\n' '${d}'; fi`);
|
|
29949
|
+
}
|
|
29950
|
+
return `${lines2.join("\n")}
|
|
29951
|
+
`;
|
|
29952
|
+
}
|
|
29953
|
+
function parsePlanOutput(output, rows) {
|
|
29954
|
+
const seen = /* @__PURE__ */ new Map();
|
|
29955
|
+
for (const line of output.split("\n")) {
|
|
29956
|
+
const m = /^([0-9a-f]{32}|MISSING) (\d{3,4}|-) (\/\S+)$/.exec(line.trim());
|
|
29957
|
+
if (m) seen.set(m[3], { md5: m[1], mode: m[2] });
|
|
29958
|
+
}
|
|
29959
|
+
return rows.map(({ file, dest, expectedMd5 }) => {
|
|
29960
|
+
const expectedMode = file.mode.replace(/^0/, "");
|
|
29961
|
+
const got = seen.get(dest);
|
|
29962
|
+
const base = { src: file.src, dest, expectedMd5, expectedMode: file.mode };
|
|
29963
|
+
if (!got) return { ...base, status: "unread", installedMd5: null, installedMode: null };
|
|
29964
|
+
if (got.md5 === "MISSING") return { ...base, status: "missing", installedMd5: null, installedMode: null };
|
|
29965
|
+
const status = got.md5 !== expectedMd5 ? "drift" : got.mode !== expectedMode ? "mode-drift" : "in-sync";
|
|
29966
|
+
return { ...base, status, installedMd5: got.md5, installedMode: `0${got.mode}` };
|
|
29967
|
+
});
|
|
29968
|
+
}
|
|
29969
|
+
function selectApplyRows(rows, sel) {
|
|
29970
|
+
const unknown = sel.only.filter((s) => !rows.some((r) => r.file.src === s));
|
|
29971
|
+
if (unknown.length) return `not a manifest src for this box: ${unknown.join(", ")}`;
|
|
29972
|
+
const chosen = rows.filter((r) => sel.only.includes(r.file.src));
|
|
29973
|
+
const strayDest = sel.dest.filter((d) => !chosen.some((r) => r.dest === d));
|
|
29974
|
+
if (strayDest.length) return `--dest is not a destination of the chosen --only source(s): ${strayDest.join(", ")}`;
|
|
29975
|
+
const out = [];
|
|
29976
|
+
for (const src of sel.only) {
|
|
29977
|
+
const targets = chosen.filter((r) => r.file.src === src);
|
|
29978
|
+
if (targets.length === 1 || sel.allTargets) {
|
|
29979
|
+
out.push(...targets);
|
|
29980
|
+
continue;
|
|
29981
|
+
}
|
|
29982
|
+
const named = targets.filter((r) => sel.dest.includes(r.dest));
|
|
29983
|
+
if (!named.length) {
|
|
29984
|
+
return `${src} has ${targets.length} destinations on this box; name each with --dest <path>, or pass --all-targets`;
|
|
29985
|
+
}
|
|
29986
|
+
out.push(...named);
|
|
29987
|
+
}
|
|
29988
|
+
return out;
|
|
29989
|
+
}
|
|
29990
|
+
function backupPath(dest, date) {
|
|
29991
|
+
if (dest.startsWith("/etc/cron.d/")) return `/root/${dest.slice("/etc/cron.d/".length)}.cron.bak-${date}`;
|
|
29992
|
+
return `${dest}.bak-${date}`;
|
|
29993
|
+
}
|
|
29994
|
+
function applyRemoteScript(items, date) {
|
|
29995
|
+
const lines2 = ["set -eu", "tmp=$(mktemp)", `trap 'rm -f "$tmp"' EXIT`];
|
|
29996
|
+
for (const it of items) {
|
|
29997
|
+
const b64 = Buffer.from(it.content).toString("base64");
|
|
29998
|
+
lines2.push(`if [ -f '${it.dest}' ]; then cp -p '${it.dest}' '${backupPath(it.dest, date)}'; fi`);
|
|
29999
|
+
lines2.push(`printf '%s' '${b64}' | base64 -d > "$tmp"`);
|
|
30000
|
+
lines2.push(`install -D -m ${it.mode} "$tmp" '${it.dest}'`);
|
|
30001
|
+
}
|
|
30002
|
+
for (const post of [...new Set(items.map((i) => i.post).filter((p) => !!p))]) lines2.push(post);
|
|
30003
|
+
lines2.push(planRemoteScript(items.map((i) => i.dest)).replace(/^set -u\n/, ""));
|
|
30004
|
+
return `${lines2.join("\n")}
|
|
30005
|
+
`;
|
|
30006
|
+
}
|
|
30007
|
+
|
|
30008
|
+
// src/box-commands.ts
|
|
29893
30009
|
init_clean_exit();
|
|
29894
30010
|
|
|
29895
30011
|
// src/secrets-commands.ts
|
|
@@ -31479,6 +31595,80 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
31479
31595
|
await failGraceful(e.message);
|
|
31480
31596
|
}
|
|
31481
31597
|
});
|
|
31598
|
+
box.command("control <name>").description("box-side control files (#7402): --plan (default, read-only) reports drift from the repo; --apply --only <src> installs with backups").option("--plan", "read-only drift report (the default)").option("--apply", "install the files named by --only on this box, backing up each previous copy").option("--only <src>", "with --apply: a manifest src to install (repeatable; approval is file by file)", (v, acc) => [...acc, v], []).option("--dest <path>", "with --apply: a destination of a multi-target --only source to install (repeatable, #7409)", (v, acc) => [...acc, v], []).option("--all-targets", "with --apply: install a multi-target --only source to every one of its destinations on this box").option("--ref <ref>", "git ref the repo content is read from", "origin/development").option("--json", "machine-readable output").action(async (name, o) => {
|
|
31599
|
+
try {
|
|
31600
|
+
if (o.apply && o.plan) throw new UsageError("box control: --plan and --apply are exclusive");
|
|
31601
|
+
if (o.apply && !o.only.length) throw new UsageError("box control: --apply needs --only <src> for each file to install; approval is file by file");
|
|
31602
|
+
if (!o.apply && (o.only.length || o.dest.length || o.allTargets)) throw new UsageError("box control: --only, --dest and --all-targets apply to --apply");
|
|
31603
|
+
const show = hubGitShow(o.ref);
|
|
31604
|
+
const files = parseManifest(show(MANIFEST_PATH).toString("utf8"));
|
|
31605
|
+
const { boxes, incomplete } = await fetchInventory();
|
|
31606
|
+
const found = lookupBox(name, boxes, incomplete);
|
|
31607
|
+
if (found.kind !== "found") {
|
|
31608
|
+
const why = found.kind === "unknown" ? `the inventory is incomplete (${found.reasons.join("; ")})` : `no box named '${name}'`;
|
|
31609
|
+
throw new UnreadableError(`box control: ${why}`);
|
|
31610
|
+
}
|
|
31611
|
+
const rows = filesForBox(files, found.box.name).map(({ file, dest }) => {
|
|
31612
|
+
const content = show(file.src);
|
|
31613
|
+
return { file, dest, content, expectedMd5: md5(content) };
|
|
31614
|
+
});
|
|
31615
|
+
if (!rows.length) throw new UsageError(`box control: ${MANIFEST_PATH} lists no files for ${found.box.name}`);
|
|
31616
|
+
let probeRows = rows;
|
|
31617
|
+
if (o.apply) {
|
|
31618
|
+
const picked = selectApplyRows(rows, { only: o.only, dest: o.dest, allTargets: !!o.allTargets });
|
|
31619
|
+
if (typeof picked === "string") throw new UsageError(`box control: ${picked}`);
|
|
31620
|
+
probeRows = picked;
|
|
31621
|
+
}
|
|
31622
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
|
|
31623
|
+
const script = o.apply ? applyRemoteScript(probeRows.map((r) => ({ dest: r.dest, mode: r.file.mode, content: r.content, post: r.file.post })), date) : planRemoteScript(probeRows.map((r) => r.dest));
|
|
31624
|
+
const output = runOnBox(found.box, script);
|
|
31625
|
+
const results = parsePlanOutput(output, probeRows);
|
|
31626
|
+
const drift = results.filter((r) => r.status !== "in-sync").length;
|
|
31627
|
+
const unread = results.some((r) => r.status === "unread");
|
|
31628
|
+
const payload = { box: found.box.name, ref: o.ref, mode: o.apply ? "apply" : "plan", files: results, drift, ok: drift === 0 };
|
|
31629
|
+
if (o.json) console.log(JSON.stringify(payload, null, 2));
|
|
31630
|
+
else console.log(formatControl(payload.mode, found.box.name, results));
|
|
31631
|
+
process.exitCode = unread ? 2 : drift ? 1 : 0;
|
|
31632
|
+
} catch (e) {
|
|
31633
|
+
if (o.json) console.log(JSON.stringify({ box: name, ref: o.ref, ok: false, error: e.message }, null, 2));
|
|
31634
|
+
else console.error(e.message);
|
|
31635
|
+
process.exitCode = 2;
|
|
31636
|
+
}
|
|
31637
|
+
});
|
|
31638
|
+
}
|
|
31639
|
+
var UsageError = class extends Error {
|
|
31640
|
+
};
|
|
31641
|
+
var UnreadableError = class extends Error {
|
|
31642
|
+
};
|
|
31643
|
+
function hubGitShow(ref) {
|
|
31644
|
+
const git3 = (args) => (0, import_node_child_process10.execFileSync)("git", args, { maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
31645
|
+
try {
|
|
31646
|
+
git3(["cat-file", "-e", `HEAD:${MANIFEST_PATH}`]);
|
|
31647
|
+
} catch {
|
|
31648
|
+
throw new UsageError(`box control: run this from an MMI-Hub checkout (no ${MANIFEST_PATH} here)`);
|
|
31649
|
+
}
|
|
31650
|
+
if (ref.startsWith("origin/")) git3(["fetch", "--quiet", "origin"]);
|
|
31651
|
+
return (path2) => git3(["show", `${ref}:${path2}`]);
|
|
31652
|
+
}
|
|
31653
|
+
function runOnBox(found, script) {
|
|
31654
|
+
const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path27.join)((0, import_node_os16.tmpdir)(), "mmi-box-control-"));
|
|
31655
|
+
try {
|
|
31656
|
+
const recipe = (0, import_node_path27.join)(dir, "recipe.sh");
|
|
31657
|
+
(0, import_node_fs27.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
|
|
31658
|
+
const r = (0, import_node_child_process10.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
|
|
31659
|
+
if (r.status !== 0) {
|
|
31660
|
+
throw new UnreadableError(`box control: the ssh session to ${found.name} failed (exit ${r.status ?? r.signal}): ${(r.stderr || "").trim().split("\n").slice(-3).join(" | ")}`);
|
|
31661
|
+
}
|
|
31662
|
+
return r.stdout;
|
|
31663
|
+
} finally {
|
|
31664
|
+
(0, import_node_fs27.rmSync)(dir, { recursive: true, force: true });
|
|
31665
|
+
}
|
|
31666
|
+
}
|
|
31667
|
+
function formatControl(mode, box, rows) {
|
|
31668
|
+
const lines2 = rows.map((r) => `${r.status.padEnd(10)} ${r.dest} <- ${r.src}`);
|
|
31669
|
+
const bad = rows.filter((r) => r.status !== "in-sync").length;
|
|
31670
|
+
lines2.push(`${box}: ${mode} \u2014 ${rows.length} file(s), ${bad} not in sync`);
|
|
31671
|
+
return lines2.join("\n");
|
|
31482
31672
|
}
|
|
31483
31673
|
|
|
31484
31674
|
// src/bootstrap-box-ports.ts
|
|
@@ -31599,7 +31789,7 @@ function renderDeployPortDoctor(report) {
|
|
|
31599
31789
|
}
|
|
31600
31790
|
|
|
31601
31791
|
// src/bootstrap-drift.ts
|
|
31602
|
-
var
|
|
31792
|
+
var import_node_crypto9 = require("node:crypto");
|
|
31603
31793
|
function byteComparableSeeds(manifest, cls) {
|
|
31604
31794
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
31605
31795
|
}
|
|
@@ -31619,7 +31809,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
31619
31809
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
31620
31810
|
}
|
|
31621
31811
|
function seedContentHash(content) {
|
|
31622
|
-
return (0,
|
|
31812
|
+
return (0, import_node_crypto9.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
31623
31813
|
}
|
|
31624
31814
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
31625
31815
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -31781,7 +31971,7 @@ function renderPropagationReport(plan) {
|
|
|
31781
31971
|
}
|
|
31782
31972
|
|
|
31783
31973
|
// src/bootstrap-propagation-identity.ts
|
|
31784
|
-
var
|
|
31974
|
+
var import_node_crypto10 = require("node:crypto");
|
|
31785
31975
|
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
31786
31976
|
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
31787
31977
|
function safeBranchPart(value, maxLength, fallback) {
|
|
@@ -31794,7 +31984,7 @@ function repoSlug2(repo) {
|
|
|
31794
31984
|
function propagationBranch(repo, target) {
|
|
31795
31985
|
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
31796
31986
|
const targetPart = safeBranchPart(target, 48, "target");
|
|
31797
|
-
const hash = (0,
|
|
31987
|
+
const hash = (0, import_node_crypto10.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
31798
31988
|
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
31799
31989
|
}
|
|
31800
31990
|
function legacyPropagationBranch(repo) {
|
|
@@ -32950,7 +33140,7 @@ function renderBootstrapVerifyReport(report) {
|
|
|
32950
33140
|
}
|
|
32951
33141
|
|
|
32952
33142
|
// src/bootstrap-seed-delivery.ts
|
|
32953
|
-
var
|
|
33143
|
+
var import_node_crypto11 = require("node:crypto");
|
|
32954
33144
|
init_github_client();
|
|
32955
33145
|
var SHA = /^[a-f0-9]{40}$/;
|
|
32956
33146
|
async function readSeedFile(repo, target, ref, client) {
|
|
@@ -32983,7 +33173,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
|
|
|
32983
33173
|
}
|
|
32984
33174
|
const baseSha = base?.object?.sha;
|
|
32985
33175
|
if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
|
|
32986
|
-
const identity = (0,
|
|
33176
|
+
const identity = (0, import_node_crypto11.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
|
|
32987
33177
|
return { ...planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`), baseSha, baseBranch };
|
|
32988
33178
|
}
|
|
32989
33179
|
async function verifyTargetedSeedBranch(repo, plan, target, client, allowMissing = false) {
|
|
@@ -33378,7 +33568,7 @@ function registerBootstrapCommands(program3) {
|
|
|
33378
33568
|
const readFile9 = (p) => (0, import_node_fs28.existsSync)(p) ? (0, import_node_fs28.readFileSync)(p, "utf8") : null;
|
|
33379
33569
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
33380
33570
|
const putSeed = async (target, content, ref, sha) => {
|
|
33381
|
-
const tmp = (0,
|
|
33571
|
+
const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
33382
33572
|
(0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
33383
33573
|
try {
|
|
33384
33574
|
await gh2(contentPutInputArgs(repo, target, tmp));
|
|
@@ -34125,7 +34315,7 @@ LIVE apply to ${repo}:
|
|
|
34125
34315
|
} catch {
|
|
34126
34316
|
existingSha = void 0;
|
|
34127
34317
|
}
|
|
34128
|
-
const tmp = (0,
|
|
34318
|
+
const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
34129
34319
|
const desiredContent = desiredByRepo.get(rec.repo);
|
|
34130
34320
|
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
34131
34321
|
(0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
|
|
@@ -34300,7 +34490,7 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
34300
34490
|
} catch {
|
|
34301
34491
|
existingSha = void 0;
|
|
34302
34492
|
}
|
|
34303
|
-
const tmp = (0,
|
|
34493
|
+
const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
34304
34494
|
(0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
|
|
34305
34495
|
try {
|
|
34306
34496
|
await gh2(contentPutInputArgs(repo, seed.target, tmp));
|
|
@@ -34586,8 +34776,8 @@ function formatSuiteStatus(report) {
|
|
|
34586
34776
|
|
|
34587
34777
|
// src/suite-train.ts
|
|
34588
34778
|
var import_node_fs29 = require("node:fs");
|
|
34589
|
-
var
|
|
34590
|
-
var
|
|
34779
|
+
var import_node_path29 = require("node:path");
|
|
34780
|
+
var import_node_crypto12 = require("node:crypto");
|
|
34591
34781
|
var SCHEMA = "mmi-suite-cut/v1";
|
|
34592
34782
|
var SHA2 = /^[a-f0-9]{40}$/i;
|
|
34593
34783
|
var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
|
|
@@ -34622,8 +34812,8 @@ function readSuiteCut(path2) {
|
|
|
34622
34812
|
return record;
|
|
34623
34813
|
}
|
|
34624
34814
|
function writeSuiteCut(path2, record) {
|
|
34625
|
-
(0, import_node_fs29.mkdirSync)((0,
|
|
34626
|
-
const temp = `${path2}.${(0,
|
|
34815
|
+
(0, import_node_fs29.mkdirSync)((0, import_node_path29.dirname)(path2), { recursive: true });
|
|
34816
|
+
const temp = `${path2}.${(0, import_node_crypto12.randomBytes)(8).toString("hex")}.tmp`;
|
|
34627
34817
|
try {
|
|
34628
34818
|
(0, import_node_fs29.writeFileSync)(temp, JSON.stringify(record, null, 2), { flag: "wx" });
|
|
34629
34819
|
(0, import_node_fs29.renameSync)(temp, path2);
|
|
@@ -34652,6 +34842,17 @@ async function carriedPins(deps, member, bundles) {
|
|
|
34652
34842
|
}
|
|
34653
34843
|
throw new Error(`${member.repo}: nothing new since rc, but no pinned MM-Work bundle carries development ${member.development.slice(0, 12)}; pin it before the suite cut`);
|
|
34654
34844
|
}
|
|
34845
|
+
function suiteAbandon(recordPath) {
|
|
34846
|
+
const record = readSuiteCut(recordPath);
|
|
34847
|
+
if (!record) throw new Error("no suite cut record to abandon");
|
|
34848
|
+
if (record.pinPr) {
|
|
34849
|
+
throw new Error(`the cut already opened pin PR ${record.pinPr}; its pins may be visible, so resume it (suite rcand --owner-go) or release it (suite release) instead`);
|
|
34850
|
+
}
|
|
34851
|
+
const archive = `${recordPath}.abandoned-${record.leadDevelopment.slice(0, 12)}.json`;
|
|
34852
|
+
if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
|
|
34853
|
+
(0, import_node_fs29.renameSync)(recordPath, archive);
|
|
34854
|
+
return { archive, orphanTags: record.members.filter((m) => m.tag).map((m) => `${m.repo}@${m.tag}`) };
|
|
34855
|
+
}
|
|
34655
34856
|
async function suiteRcand(deps, opts) {
|
|
34656
34857
|
if (!opts.ownerGo) throw new Error("suite rcand requires --owner-go (rc uses live data); nothing was written");
|
|
34657
34858
|
const members = suiteMembers(await deps.registry());
|
|
@@ -34669,6 +34870,10 @@ async function suiteRcand(deps, opts) {
|
|
|
34669
34870
|
(0, import_node_fs29.renameSync)(deps.recordPath, archive);
|
|
34670
34871
|
record = void 0;
|
|
34671
34872
|
}
|
|
34873
|
+
if (record && !record.pinPr && record.members.every((m) => !m.tag)) {
|
|
34874
|
+
(0, import_node_fs29.rmSync)(deps.recordPath, { force: true });
|
|
34875
|
+
record = void 0;
|
|
34876
|
+
}
|
|
34672
34877
|
if (!record) {
|
|
34673
34878
|
const [leadDevelopment, leadPreviousRc, snapshots] = await Promise.all([
|
|
34674
34879
|
deps.head(SUITE_REPO, "development"),
|
|
@@ -34813,7 +35018,7 @@ async function suiteRelease(deps, ownerGo) {
|
|
|
34813
35018
|
|
|
34814
35019
|
// src/suite-train-io.ts
|
|
34815
35020
|
var import_node_fs43 = require("node:fs");
|
|
34816
|
-
var
|
|
35021
|
+
var import_node_path43 = require("node:path");
|
|
34817
35022
|
init_cli_shared();
|
|
34818
35023
|
|
|
34819
35024
|
// src/command-register-train-operations.ts
|
|
@@ -35093,9 +35298,9 @@ A hotfix branch is cut from origin/main and carries ONLY the fixes --from names,
|
|
|
35093
35298
|
async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
|
|
35094
35299
|
if (sources.length === 0) return { failures: [], skipped: [] };
|
|
35095
35300
|
const { mkdtemp: mkdtemp2, rm: rm2 } = await import("node:fs/promises");
|
|
35096
|
-
const { tmpdir:
|
|
35097
|
-
const { join:
|
|
35098
|
-
const dir = await mkdtemp2(
|
|
35301
|
+
const { tmpdir: tmpdir11 } = await import("node:os");
|
|
35302
|
+
const { join: join45 } = await import("node:path");
|
|
35303
|
+
const dir = await mkdtemp2(join45(tmpdir11(), "mmi-hotfix-preflight-"));
|
|
35099
35304
|
const wt = { run: (file, args) => deps.run(file, file === "git" ? ["-C", dir, ...args] : args) };
|
|
35100
35305
|
const failures = [];
|
|
35101
35306
|
const skipped = [];
|
|
@@ -36294,13 +36499,13 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag, workflows = H
|
|
|
36294
36499
|
}
|
|
36295
36500
|
|
|
36296
36501
|
// src/hotfix-coverage.ts
|
|
36297
|
-
var
|
|
36502
|
+
var import_node_child_process11 = require("node:child_process");
|
|
36298
36503
|
var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
36299
36504
|
var GIT_MAX_BUFFER = 512 * 1024 * 1024;
|
|
36300
36505
|
function checkHotfixCoverage(options = {}) {
|
|
36301
36506
|
const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
|
|
36302
36507
|
const ack = (options.ack ?? []).filter(Boolean);
|
|
36303
|
-
const git3 = options.git ?? ((args, opts) => (0,
|
|
36508
|
+
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process11.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER }));
|
|
36304
36509
|
const revList = (range) => {
|
|
36305
36510
|
const out = git3(["rev-list", "--no-merges", range]).trim();
|
|
36306
36511
|
return out ? out.split("\n") : [];
|
|
@@ -36368,7 +36573,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
36368
36573
|
}
|
|
36369
36574
|
function checkHotfixCarries(options) {
|
|
36370
36575
|
const { cwd = process.cwd(), branch, baseRef, targets } = options;
|
|
36371
|
-
const git3 = options.git ?? ((args, opts) => (0,
|
|
36576
|
+
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process11.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER }));
|
|
36372
36577
|
const isAncestor = (sha, ref) => {
|
|
36373
36578
|
try {
|
|
36374
36579
|
git3(["merge-base", "--is-ancestor", sha, ref]);
|
|
@@ -36401,8 +36606,8 @@ function checkHotfixCarries(options) {
|
|
|
36401
36606
|
|
|
36402
36607
|
// src/merge-cleanup.ts
|
|
36403
36608
|
var import_node_fs31 = require("node:fs");
|
|
36404
|
-
var
|
|
36405
|
-
var
|
|
36609
|
+
var import_node_path31 = require("node:path");
|
|
36610
|
+
var import_node_os18 = require("node:os");
|
|
36406
36611
|
init_cli_shared();
|
|
36407
36612
|
init_github_client();
|
|
36408
36613
|
|
|
@@ -36691,12 +36896,12 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
36691
36896
|
}
|
|
36692
36897
|
|
|
36693
36898
|
// src/test-policy-core.ts
|
|
36694
|
-
var
|
|
36899
|
+
var import_node_child_process13 = require("node:child_process");
|
|
36695
36900
|
var import_node_fs30 = require("node:fs");
|
|
36696
|
-
var
|
|
36901
|
+
var import_node_path30 = require("node:path");
|
|
36697
36902
|
|
|
36698
36903
|
// src/test-command-policy-shared.mjs
|
|
36699
|
-
var
|
|
36904
|
+
var import_node_child_process12 = require("node:child_process");
|
|
36700
36905
|
var TEST_COMMAND_CLASS = "test";
|
|
36701
36906
|
var TRAILER_KEY = "Test-Policy-Override";
|
|
36702
36907
|
var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
|
|
@@ -36813,7 +37018,7 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, overrid
|
|
|
36813
37018
|
};
|
|
36814
37019
|
}
|
|
36815
37020
|
function git(args, cwd) {
|
|
36816
|
-
return (0,
|
|
37021
|
+
return (0, import_node_child_process12.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
36817
37022
|
}
|
|
36818
37023
|
function parseScope(value) {
|
|
36819
37024
|
const scoped = /^\[([^\]]*)\]\s*([\s\S]*)$/.exec(value);
|
|
@@ -37169,7 +37374,7 @@ function isMeaningfulRow(file) {
|
|
|
37169
37374
|
return file.meaningful !== false;
|
|
37170
37375
|
}
|
|
37171
37376
|
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
37172
|
-
const raw = readFile9((0,
|
|
37377
|
+
const raw = readFile9((0, import_node_path30.join)(root, POLICY_FILE));
|
|
37173
37378
|
if (raw == null) return { mandatory: [], declared: false };
|
|
37174
37379
|
return parsePolicy(raw, POLICY_FILE);
|
|
37175
37380
|
}
|
|
@@ -37275,11 +37480,11 @@ function classify(changed, policy, present = () => false) {
|
|
|
37275
37480
|
return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
|
|
37276
37481
|
}
|
|
37277
37482
|
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
|
|
37278
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
37483
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
37279
37484
|
}
|
|
37280
37485
|
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
|
|
37281
37486
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
37282
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
37487
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
37283
37488
|
}
|
|
37284
37489
|
function evaluate(changed, policy, present = () => false) {
|
|
37285
37490
|
const { mandatoryHits, untestedHits, meaningfulTestChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -37312,14 +37517,14 @@ function evaluate(changed, policy, present = () => false) {
|
|
|
37312
37517
|
return findings;
|
|
37313
37518
|
}
|
|
37314
37519
|
function git2(args, cwd) {
|
|
37315
|
-
return (0,
|
|
37520
|
+
return (0, import_node_child_process13.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
37316
37521
|
}
|
|
37317
37522
|
var COAUTHOR_KEY = "Co-authored-by";
|
|
37318
37523
|
var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
|
|
37319
37524
|
var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
|
|
37320
37525
|
function parseTrailers(message2, cwd) {
|
|
37321
37526
|
try {
|
|
37322
|
-
return (0,
|
|
37527
|
+
return (0, import_node_child_process13.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
37323
37528
|
windowsHide: true,
|
|
37324
37529
|
cwd,
|
|
37325
37530
|
input: message2,
|
|
@@ -37425,7 +37630,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
37425
37630
|
const raw = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
37426
37631
|
const changed = deps.changed ? raw : annotateChangeMeaning(raw, policy, (path2) => ({
|
|
37427
37632
|
before: blobAt(base, path2, root),
|
|
37428
|
-
after: readFileOrNull2((0,
|
|
37633
|
+
after: readFileOrNull2((0, import_node_path30.join)(root, path2))
|
|
37429
37634
|
}));
|
|
37430
37635
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root) : { override: null, refusals: [] };
|
|
37431
37636
|
const policyRefusals = policy.declared === false ? [{
|
|
@@ -37435,8 +37640,8 @@ function runTestPolicy(root, deps = {}) {
|
|
|
37435
37640
|
A gate with no policy cannot decide which test commands are required or permitted.
|
|
37436
37641
|
` + (deps.base === HOTFIX_DIFF_BASE ? ` Hotfix lane (--base ${HOTFIX_DIFF_BASE}): pass --policy-ref <exact-${HOTFIX_POLICY_REF}-commit> so the current policy is read from ${HOTFIX_POLICY_REF}.` : ` Restore ${POLICY_FILE} on this branch; the policy file belongs on every non-hotfix lane.`)
|
|
37437
37642
|
}] : [];
|
|
37438
|
-
const present = (path2) => exists((0,
|
|
37439
|
-
const policyTree = policySource.sha ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", policySource.sha], root).split("\0").filter(Boolean).map((p) => (0,
|
|
37643
|
+
const present = (path2) => exists((0, import_node_path30.join)(root, path2));
|
|
37644
|
+
const policyTree = policySource.sha ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", policySource.sha], root).split("\0").filter(Boolean).map((p) => (0, import_node_path30.join)(root, p))) : null;
|
|
37440
37645
|
const policyTreeExists = policyTree ? (abs) => policyTree.has(abs) : exists;
|
|
37441
37646
|
const where = policySource.sha ? `${policySource.ref}@${policySource.sha}` : "this worktree";
|
|
37442
37647
|
const removedByThisDiff = removedPaths(changed);
|
|
@@ -37681,8 +37886,8 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
|
|
|
37681
37886
|
return rewritten === base ? null : rewritten;
|
|
37682
37887
|
}
|
|
37683
37888
|
function writeSquashBodyFile(body) {
|
|
37684
|
-
const dir = (0, import_node_fs31.mkdtempSync)((0,
|
|
37685
|
-
const path2 = (0,
|
|
37889
|
+
const dir = (0, import_node_fs31.mkdtempSync)((0, import_node_path31.join)((0, import_node_os18.tmpdir)(), "mmi-squash-body-"));
|
|
37890
|
+
const path2 = (0, import_node_path31.join)(dir, "body.txt");
|
|
37686
37891
|
(0, import_node_fs31.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
|
|
37687
37892
|
`, "utf8");
|
|
37688
37893
|
return { path: path2, cleanup: () => {
|
|
@@ -37895,7 +38100,7 @@ async function deleteMergedRemoteBranch(options) {
|
|
|
37895
38100
|
|
|
37896
38101
|
// src/project-info-sync.ts
|
|
37897
38102
|
var import_node_fs32 = require("node:fs");
|
|
37898
|
-
var
|
|
38103
|
+
var import_node_path32 = require("node:path");
|
|
37899
38104
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
37900
38105
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
37901
38106
|
projectV2 { id }
|
|
@@ -37942,7 +38147,7 @@ function sharedName(entries, fallback) {
|
|
|
37942
38147
|
}
|
|
37943
38148
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
37944
38149
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
37945
|
-
const readmePath = (0,
|
|
38150
|
+
const readmePath = (0, import_node_path32.join)(repoRoot2, "README.md");
|
|
37946
38151
|
if (!(0, import_node_fs32.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
37947
38152
|
const entries = entriesFor(project2, projects);
|
|
37948
38153
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
@@ -37968,8 +38173,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
37968
38173
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
37969
38174
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
37970
38175
|
const orgDocs = [
|
|
37971
|
-
(0, import_node_fs32.existsSync)((0,
|
|
37972
|
-
(0, import_node_fs32.existsSync)((0,
|
|
38176
|
+
(0, import_node_fs32.existsSync)((0, import_node_path32.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
|
|
38177
|
+
(0, import_node_fs32.existsSync)((0, import_node_path32.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
|
|
37973
38178
|
].filter(Boolean);
|
|
37974
38179
|
if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
|
|
37975
38180
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
|
|
@@ -37992,7 +38197,7 @@ async function syncProjectInfo(plan, client, apply) {
|
|
|
37992
38197
|
|
|
37993
38198
|
// src/train-commands.ts
|
|
37994
38199
|
var import_node_fs33 = require("node:fs");
|
|
37995
|
-
var
|
|
38200
|
+
var import_node_path33 = require("node:path");
|
|
37996
38201
|
init_cli_shared();
|
|
37997
38202
|
init_clean_exit();
|
|
37998
38203
|
init_client_version();
|
|
@@ -38007,7 +38212,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
38007
38212
|
}
|
|
38008
38213
|
function readRepoVersion() {
|
|
38009
38214
|
try {
|
|
38010
|
-
return JSON.parse((0, import_node_fs33.readFileSync)((0,
|
|
38215
|
+
return JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path33.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
38011
38216
|
} catch {
|
|
38012
38217
|
return void 0;
|
|
38013
38218
|
}
|
|
@@ -38118,7 +38323,7 @@ function releaseProgressVerdict(status, subject = "Release") {
|
|
|
38118
38323
|
|
|
38119
38324
|
// src/suite-pin.ts
|
|
38120
38325
|
var import_promises5 = require("node:fs/promises");
|
|
38121
|
-
var
|
|
38326
|
+
var import_node_path34 = require("node:path");
|
|
38122
38327
|
async function openSuiteCutPinPr(deps, input) {
|
|
38123
38328
|
if (!/^[a-f0-9]{40}$/.test(input.cut) || !input.pins.length) throw new Error("suite pin PR needs an anchored cut and at least one module");
|
|
38124
38329
|
const ids = input.pins.map((p) => p.module);
|
|
@@ -38154,15 +38359,15 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
38154
38359
|
const row = rows.find((r) => r.id === p.module);
|
|
38155
38360
|
return row?.bundles.rc === p.rc && row?.bundles.main === p.main;
|
|
38156
38361
|
})) throw new Error("pins already match without a merged cut PR \u2014 provenance unverified");
|
|
38157
|
-
await (0, import_promises5.mkdir)((0,
|
|
38158
|
-
scratch = await (0, import_promises5.mkdtemp)((0,
|
|
38362
|
+
await (0, import_promises5.mkdir)((0, import_node_path34.join)(deps.root, "tmp"), { recursive: true });
|
|
38363
|
+
scratch = await (0, import_promises5.mkdtemp)((0, import_node_path34.join)(deps.root, "tmp", "suite-pin-"));
|
|
38159
38364
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
38160
38365
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
38161
38366
|
if ((await run("git", ["rev-parse", "HEAD"])).trim().toLowerCase() !== input.cut.toLowerCase())
|
|
38162
38367
|
throw new Error("MM-Work development moved after the suite snapshot; no pin PR opened");
|
|
38163
38368
|
await run("git", ["switch", "-c", branch]);
|
|
38164
38369
|
for (const stage of ["rc", "main"]) {
|
|
38165
|
-
const path2 = (0,
|
|
38370
|
+
const path2 = (0, import_node_path34.join)(scratch, `${stage}-pins.json`);
|
|
38166
38371
|
await (0, import_promises5.writeFile)(path2, JSON.stringify(input.pins.map((p) => ({ module: p.module, url: p[stage] }))));
|
|
38167
38372
|
await run("node", ["scripts/pin-modules.mjs", "--stage", stage, "--pins", path2]);
|
|
38168
38373
|
}
|
|
@@ -38208,8 +38413,8 @@ async function openSuitePinPr(deps, input) {
|
|
|
38208
38413
|
url.searchParams.set("v", tag);
|
|
38209
38414
|
const pin = url.toString();
|
|
38210
38415
|
const branch = `suite-pin/${stage}-${member.id}-${tag}`;
|
|
38211
|
-
await (0, import_promises5.mkdir)((0,
|
|
38212
|
-
scratch = await (0, import_promises5.mkdtemp)((0,
|
|
38416
|
+
await (0, import_promises5.mkdir)((0, import_node_path34.join)(deps.root, "tmp"), { recursive: true });
|
|
38417
|
+
scratch = await (0, import_promises5.mkdtemp)((0, import_node_path34.join)(deps.root, "tmp", "suite-pin-"));
|
|
38213
38418
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
38214
38419
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
38215
38420
|
const existing = JSON.parse(await run("gh", ["pr", "list", "--repo", SUITE_REPO, "--head", branch, "--state", "all", "--json", "url,state"]));
|
|
@@ -38252,7 +38457,7 @@ async function openSuitePinPr(deps, input) {
|
|
|
38252
38457
|
}
|
|
38253
38458
|
|
|
38254
38459
|
// src/command-register-collaboration.ts
|
|
38255
|
-
var
|
|
38460
|
+
var import_node_child_process17 = require("node:child_process");
|
|
38256
38461
|
var import_node_fs42 = require("node:fs");
|
|
38257
38462
|
var import_promises7 = require("node:fs/promises");
|
|
38258
38463
|
init_clean_exit();
|
|
@@ -38277,11 +38482,11 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
38277
38482
|
}
|
|
38278
38483
|
|
|
38279
38484
|
// src/command-register-collaboration.ts
|
|
38280
|
-
var
|
|
38485
|
+
var import_node_path42 = require("node:path");
|
|
38281
38486
|
init_house_map();
|
|
38282
38487
|
|
|
38283
38488
|
// src/issue-body.ts
|
|
38284
|
-
var
|
|
38489
|
+
var import_node_os19 = require("node:os");
|
|
38285
38490
|
init_error_codes();
|
|
38286
38491
|
var TextArgError = class extends Error {
|
|
38287
38492
|
constructor(message2, code, offendingFlag) {
|
|
@@ -38294,7 +38499,7 @@ var TextArgError = class extends Error {
|
|
|
38294
38499
|
offendingFlag;
|
|
38295
38500
|
};
|
|
38296
38501
|
function emptyStdinMessage(fileFlag) {
|
|
38297
|
-
if ((0,
|
|
38502
|
+
if ((0, import_node_os19.platform)() === "win32") {
|
|
38298
38503
|
return `${fileFlag} - read empty stdin (on Windows, ${fileFlag} - is unreliable through the npm .cmd shim \u2014 use ${fileFlag} <path>, or pipe to \`node cli/dist/index.cjs\` directly)`;
|
|
38299
38504
|
}
|
|
38300
38505
|
return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
|
|
@@ -38387,7 +38592,7 @@ init_github_client();
|
|
|
38387
38592
|
|
|
38388
38593
|
// src/issue-commands.ts
|
|
38389
38594
|
var import_node_fs34 = require("node:fs");
|
|
38390
|
-
var
|
|
38595
|
+
var import_node_crypto13 = require("node:crypto");
|
|
38391
38596
|
init_cli_shared();
|
|
38392
38597
|
init_clean_exit();
|
|
38393
38598
|
init_error_codes();
|
|
@@ -38860,7 +39065,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
38860
39065
|
const identity = `${spec.type}
|
|
38861
39066
|
${spec.title.trim()}
|
|
38862
39067
|
${spec.body ?? ""}`;
|
|
38863
|
-
const hash = (0,
|
|
39068
|
+
const hash = (0, import_node_crypto13.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
38864
39069
|
return `${batchKey}:${hash}`;
|
|
38865
39070
|
}
|
|
38866
39071
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -39320,7 +39525,7 @@ ${lines2}`, {
|
|
|
39320
39525
|
|
|
39321
39526
|
// src/post-merge-recon.ts
|
|
39322
39527
|
var import_node_fs35 = require("node:fs");
|
|
39323
|
-
var
|
|
39528
|
+
var import_node_path35 = require("node:path");
|
|
39324
39529
|
|
|
39325
39530
|
// src/cross-repo-filing-issue.ts
|
|
39326
39531
|
init_github_client();
|
|
@@ -39487,7 +39692,7 @@ function buildPostMergeReconRecovery(input) {
|
|
|
39487
39692
|
}
|
|
39488
39693
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
39489
39694
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
39490
|
-
(0, import_node_fs35.mkdirSync)((0,
|
|
39695
|
+
(0, import_node_fs35.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
|
|
39491
39696
|
(0, import_node_fs35.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
39492
39697
|
`, "utf8");
|
|
39493
39698
|
return path2;
|
|
@@ -39922,10 +40127,10 @@ function registerPrLifecycleCommands(program3) {
|
|
|
39922
40127
|
}
|
|
39923
40128
|
|
|
39924
40129
|
// src/review-verdict.ts
|
|
39925
|
-
var
|
|
40130
|
+
var import_node_child_process14 = require("node:child_process");
|
|
39926
40131
|
var import_node_fs36 = require("node:fs");
|
|
39927
|
-
var
|
|
39928
|
-
var
|
|
40132
|
+
var import_node_os20 = require("node:os");
|
|
40133
|
+
var import_node_path36 = require("node:path");
|
|
39929
40134
|
init_cli_shared();
|
|
39930
40135
|
var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
|
|
39931
40136
|
var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
|
|
@@ -40022,8 +40227,8 @@ async function checkPrReview(number, repo, head, deps = {
|
|
|
40022
40227
|
}
|
|
40023
40228
|
function computePrPatchId(number, repo) {
|
|
40024
40229
|
return new Promise((resolve7, reject) => {
|
|
40025
|
-
const gh2 = (0,
|
|
40026
|
-
const git3 = (0,
|
|
40230
|
+
const gh2 = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
40231
|
+
const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
40027
40232
|
let out = "";
|
|
40028
40233
|
let ghErr = "";
|
|
40029
40234
|
let gitErr = "";
|
|
@@ -40090,8 +40295,8 @@ async function readPrIssueComments(number, repo) {
|
|
|
40090
40295
|
return comments;
|
|
40091
40296
|
}
|
|
40092
40297
|
async function postPrCommentFromFile(number, repo, body) {
|
|
40093
|
-
const dir = (0, import_node_fs36.mkdtempSync)((0,
|
|
40094
|
-
const path2 = (0,
|
|
40298
|
+
const dir = (0, import_node_fs36.mkdtempSync)((0, import_node_path36.join)((0, import_node_os20.tmpdir)(), "mmi-review-verdict-"));
|
|
40299
|
+
const path2 = (0, import_node_path36.join)(dir, "body.md");
|
|
40095
40300
|
try {
|
|
40096
40301
|
(0, import_node_fs36.writeFileSync)(path2, body, "utf8");
|
|
40097
40302
|
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
@@ -40105,7 +40310,7 @@ async function postPrCommentFromFile(number, repo, body) {
|
|
|
40105
40310
|
}
|
|
40106
40311
|
|
|
40107
40312
|
// src/pr-create-docs-check.ts
|
|
40108
|
-
var
|
|
40313
|
+
var import_node_child_process15 = require("node:child_process");
|
|
40109
40314
|
init_cli_shared();
|
|
40110
40315
|
var GIT_TIMEOUT_MS2 = 15e3;
|
|
40111
40316
|
function catFileBatch(root, ref, paths) {
|
|
@@ -40113,7 +40318,7 @@ function catFileBatch(root, ref, paths) {
|
|
|
40113
40318
|
return new Promise((resolve7) => {
|
|
40114
40319
|
const chunks = [];
|
|
40115
40320
|
let settled = false;
|
|
40116
|
-
const child2 = (0,
|
|
40321
|
+
const child2 = (0, import_node_child_process15.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
|
|
40117
40322
|
const finish = () => {
|
|
40118
40323
|
if (settled) return;
|
|
40119
40324
|
settled = true;
|
|
@@ -40325,17 +40530,17 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
40325
40530
|
|
|
40326
40531
|
// src/worktree-residue-sweep.ts
|
|
40327
40532
|
var import_node_fs41 = require("node:fs");
|
|
40328
|
-
var
|
|
40533
|
+
var import_node_path41 = require("node:path");
|
|
40329
40534
|
|
|
40330
40535
|
// src/worktree-merge-cleanup.ts
|
|
40331
40536
|
var import_node_fs40 = require("node:fs");
|
|
40332
|
-
var
|
|
40537
|
+
var import_node_path40 = require("node:path");
|
|
40333
40538
|
init_cli_shared();
|
|
40334
40539
|
|
|
40335
40540
|
// src/stage-runner.ts
|
|
40336
|
-
var
|
|
40541
|
+
var import_node_child_process16 = require("node:child_process");
|
|
40337
40542
|
var import_node_fs38 = require("node:fs");
|
|
40338
|
-
var
|
|
40543
|
+
var import_node_path38 = require("node:path");
|
|
40339
40544
|
var import_node_net = require("node:net");
|
|
40340
40545
|
var import_node_util5 = require("node:util");
|
|
40341
40546
|
|
|
@@ -40346,7 +40551,7 @@ function normalizeEol2(s) {
|
|
|
40346
40551
|
|
|
40347
40552
|
// src/env-detection.ts
|
|
40348
40553
|
var import_node_fs37 = require("node:fs");
|
|
40349
|
-
var
|
|
40554
|
+
var import_node_path37 = require("node:path");
|
|
40350
40555
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
|
|
40351
40556
|
function isEnvBaseName(name) {
|
|
40352
40557
|
return name.toLowerCase() === ".env";
|
|
@@ -40364,13 +40569,13 @@ function findEnvFiles(root) {
|
|
|
40364
40569
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
40365
40570
|
if (entry.isDirectory()) {
|
|
40366
40571
|
if (SKIPPED_DIRS.has(entry.name)) continue;
|
|
40367
|
-
walk2((0,
|
|
40572
|
+
walk2((0, import_node_path37.join)(dir, entry.name), relPath);
|
|
40368
40573
|
continue;
|
|
40369
40574
|
}
|
|
40370
40575
|
if (isEnvBaseName(entry.name)) {
|
|
40371
40576
|
let isDir = false;
|
|
40372
40577
|
try {
|
|
40373
|
-
isDir = (0, import_node_fs37.lstatSync)((0,
|
|
40578
|
+
isDir = (0, import_node_fs37.lstatSync)((0, import_node_path37.join)(dir, entry.name)).isDirectory();
|
|
40374
40579
|
} catch {
|
|
40375
40580
|
}
|
|
40376
40581
|
if (!isDir) found.push(relPath);
|
|
@@ -40427,15 +40632,15 @@ function stageComposeFileEnv(files) {
|
|
|
40427
40632
|
}
|
|
40428
40633
|
|
|
40429
40634
|
// src/stage-runner.ts
|
|
40430
|
-
var execFileP3 = (0, import_node_util5.promisify)(
|
|
40635
|
+
var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process16.execFile);
|
|
40431
40636
|
var DOCKER_TIMEOUT_MS = 15e3;
|
|
40432
40637
|
function stageBash() {
|
|
40433
40638
|
if (process.platform !== "win32") return "bash";
|
|
40434
40639
|
const configured = process.env.SHELL;
|
|
40435
|
-
if (configured && (0,
|
|
40640
|
+
if (configured && (0, import_node_path38.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs38.existsSync)(configured)) return configured;
|
|
40436
40641
|
const candidates = [
|
|
40437
|
-
(0,
|
|
40438
|
-
...process.env.LOCALAPPDATA ? [(0,
|
|
40642
|
+
(0, import_node_path38.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
|
|
40643
|
+
...process.env.LOCALAPPDATA ? [(0, import_node_path38.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
|
|
40439
40644
|
];
|
|
40440
40645
|
const bash = candidates.find((path2) => (0, import_node_fs38.existsSync)(path2));
|
|
40441
40646
|
if (!bash) throw new Error("Local stages require Git Bash on Windows. Install Git for Windows or set SHELL to its absolute bash.exe path.");
|
|
@@ -40568,7 +40773,7 @@ function appendForceRecreate(up) {
|
|
|
40568
40773
|
return `${up.trimEnd()} --force-recreate`;
|
|
40569
40774
|
}
|
|
40570
40775
|
function stageStatePath(cwd = process.cwd()) {
|
|
40571
|
-
return (0,
|
|
40776
|
+
return (0, import_node_path38.join)(cwd, "tmp", "stage", "state.json");
|
|
40572
40777
|
}
|
|
40573
40778
|
function normPath(path2) {
|
|
40574
40779
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -40785,7 +40990,7 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
40785
40990
|
}
|
|
40786
40991
|
function composeResolvesPort(cwd) {
|
|
40787
40992
|
if (process.env.PORT) return true;
|
|
40788
|
-
const envFile = (0,
|
|
40993
|
+
const envFile = (0, import_node_path38.join)(cwd, ".env");
|
|
40789
40994
|
return (0, import_node_fs38.existsSync)(envFile) && envFileKeys((0, import_node_fs38.readFileSync)(envFile, "utf8")).has("PORT");
|
|
40790
40995
|
}
|
|
40791
40996
|
function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
@@ -40799,13 +41004,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
|
40799
41004
|
function stageComposeFiles(cwd) {
|
|
40800
41005
|
const files = ["docker-compose.yml"];
|
|
40801
41006
|
for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
|
|
40802
|
-
if ((0, import_node_fs38.existsSync)((0,
|
|
41007
|
+
if ((0, import_node_fs38.existsSync)((0, import_node_path38.join)(cwd, conventional))) files.push(conventional);
|
|
40803
41008
|
}
|
|
40804
41009
|
return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
|
|
40805
41010
|
}
|
|
40806
41011
|
async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
40807
41012
|
const keys = Object.keys(vaultEnvMerge ?? {});
|
|
40808
|
-
if (!keys.length || !(0, import_node_fs38.existsSync)((0,
|
|
41013
|
+
if (!keys.length || !(0, import_node_fs38.existsSync)((0, import_node_path38.join)(cwd, "docker-compose.yml"))) return {};
|
|
40809
41014
|
let configJson;
|
|
40810
41015
|
try {
|
|
40811
41016
|
const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
|
|
@@ -40821,15 +41026,15 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
|
40821
41026
|
}
|
|
40822
41027
|
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys);
|
|
40823
41028
|
if (!body) return {};
|
|
40824
|
-
const overridePath = (0,
|
|
40825
|
-
(0, import_node_fs38.mkdirSync)((0,
|
|
41029
|
+
const overridePath = (0, import_node_path38.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
|
|
41030
|
+
(0, import_node_fs38.mkdirSync)((0, import_node_path38.join)(cwd, "tmp", "stage"), { recursive: true });
|
|
40826
41031
|
(0, import_node_fs38.writeFileSync)(overridePath, body, "utf8");
|
|
40827
41032
|
return stageComposeFileEnv(stageComposeFiles(cwd));
|
|
40828
41033
|
}
|
|
40829
41034
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
40830
41035
|
if (!config.ensureEnv) return;
|
|
40831
|
-
const target = (0,
|
|
40832
|
-
const example = (0,
|
|
41036
|
+
const target = (0, import_node_path38.join)(cwd, config.ensureEnv.target);
|
|
41037
|
+
const example = (0, import_node_path38.join)(cwd, config.ensureEnv.example);
|
|
40833
41038
|
if (!(0, import_node_fs38.existsSync)(target) && (0, import_node_fs38.existsSync)(example)) {
|
|
40834
41039
|
(0, import_node_fs38.copyFileSync)(example, target);
|
|
40835
41040
|
} else if ((0, import_node_fs38.existsSync)(target) && (0, import_node_fs38.existsSync)(example)) {
|
|
@@ -41010,7 +41215,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
41010
41215
|
let up = sub(config.up.trim());
|
|
41011
41216
|
if (opts.forceRecreate) up = appendForceRecreate(up);
|
|
41012
41217
|
const identity = await resolveStageIdentity(cwd);
|
|
41013
|
-
const child2 = (0,
|
|
41218
|
+
const child2 = (0, import_node_child_process16.spawn)(bash, ["-c", up], {
|
|
41014
41219
|
cwd,
|
|
41015
41220
|
// POSIX-only: the process group exists for the group-kill in stopStage. On win32 teardown is
|
|
41016
41221
|
// `taskkill /T /F` (no group needed), and detached+shell defeats windowsHide — every spawn would
|
|
@@ -41120,7 +41325,7 @@ async function runStage(config = {}, opts = {}) {
|
|
|
41120
41325
|
|
|
41121
41326
|
// src/worktree-evidence-archive.ts
|
|
41122
41327
|
var import_node_fs39 = require("node:fs");
|
|
41123
|
-
var
|
|
41328
|
+
var import_node_path39 = require("node:path");
|
|
41124
41329
|
var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
|
|
41125
41330
|
function sanitizeArchiveSegment(value, max = 80) {
|
|
41126
41331
|
const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -41152,7 +41357,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
41152
41357
|
const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
|
|
41153
41358
|
if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
|
|
41154
41359
|
if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
|
|
41155
|
-
const source = (0,
|
|
41360
|
+
const source = (0, import_node_path39.join)(args.worktreePath, ".jerv");
|
|
41156
41361
|
if (!exists(source)) return { status: "absent" };
|
|
41157
41362
|
if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
|
|
41158
41363
|
const runScope = resolveArtifactRunScope(env);
|
|
@@ -41160,7 +41365,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
41160
41365
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
41161
41366
|
const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
|
|
41162
41367
|
try {
|
|
41163
|
-
mkdirp((0,
|
|
41368
|
+
mkdirp((0, import_node_path39.dirname)(dest));
|
|
41164
41369
|
copyDir(source, dest);
|
|
41165
41370
|
if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
|
|
41166
41371
|
return { status: "archived", path: dest, runScope };
|
|
@@ -41176,7 +41381,7 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
41176
41381
|
const exists = deps.exists ?? import_node_fs39.existsSync;
|
|
41177
41382
|
const stat4 = deps.stat ?? defaultStat;
|
|
41178
41383
|
const readdir2 = deps.readdir ?? import_node_fs39.readdirSync;
|
|
41179
|
-
const tmpRoot = (0,
|
|
41384
|
+
const tmpRoot = (0, import_node_path39.join)(worktreePath, "tmp");
|
|
41180
41385
|
if (!exists(tmpRoot)) return [];
|
|
41181
41386
|
const entries = [];
|
|
41182
41387
|
const walk2 = (dir) => {
|
|
@@ -41187,14 +41392,14 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
41187
41392
|
return;
|
|
41188
41393
|
}
|
|
41189
41394
|
for (const name of names) {
|
|
41190
|
-
const full = (0,
|
|
41395
|
+
const full = (0, import_node_path39.join)(dir, name);
|
|
41191
41396
|
let st;
|
|
41192
41397
|
try {
|
|
41193
41398
|
st = stat4(full);
|
|
41194
41399
|
} catch {
|
|
41195
41400
|
continue;
|
|
41196
41401
|
}
|
|
41197
|
-
const relPath = (0,
|
|
41402
|
+
const relPath = (0, import_node_path39.relative)(worktreePath, full).replace(/\\/g, "/");
|
|
41198
41403
|
if (st.isDirectory()) {
|
|
41199
41404
|
if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
|
|
41200
41405
|
walk2(full);
|
|
@@ -41219,7 +41424,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
|
41219
41424
|
if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
|
|
41220
41425
|
if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
|
|
41221
41426
|
const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
|
|
41222
|
-
const source = (0,
|
|
41427
|
+
const source = (0, import_node_path39.join)(args.worktreePath, "tmp");
|
|
41223
41428
|
if (!scanned.length) return { status: "absent" };
|
|
41224
41429
|
if (!exists(source)) return { status: "absent" };
|
|
41225
41430
|
const runScope = resolveArtifactRunScope(env);
|
|
@@ -41227,7 +41432,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
|
41227
41432
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
41228
41433
|
const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
|
|
41229
41434
|
try {
|
|
41230
|
-
mkdirp((0,
|
|
41435
|
+
mkdirp((0, import_node_path39.dirname)(dest));
|
|
41231
41436
|
copyDir(source, dest);
|
|
41232
41437
|
if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
|
|
41233
41438
|
const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
|
|
@@ -41296,7 +41501,7 @@ function normPath2(p) {
|
|
|
41296
41501
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
41297
41502
|
}
|
|
41298
41503
|
function unlinkNodeModulesJunction(wtPath) {
|
|
41299
|
-
const nm = (0,
|
|
41504
|
+
const nm = (0, import_node_path40.join)(wtPath, "node_modules");
|
|
41300
41505
|
try {
|
|
41301
41506
|
if ((0, import_node_fs40.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs40.rmdirSync)(nm);
|
|
41302
41507
|
return { ok: true };
|
|
@@ -41433,7 +41638,7 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
|
|
|
41433
41638
|
}
|
|
41434
41639
|
async function listNestedIgnoredNodeModules(wtPath, execGit) {
|
|
41435
41640
|
const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
|
|
41436
|
-
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0,
|
|
41641
|
+
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path40.join)(wtPath, line.slice(0, -1)));
|
|
41437
41642
|
}
|
|
41438
41643
|
function safeRemoveTree(path2) {
|
|
41439
41644
|
const stat4 = (0, import_node_fs40.lstatSync)(path2);
|
|
@@ -41446,7 +41651,7 @@ function safeRemoveTree(path2) {
|
|
|
41446
41651
|
return;
|
|
41447
41652
|
}
|
|
41448
41653
|
if (stat4.isDirectory()) {
|
|
41449
|
-
for (const entry of (0, import_node_fs40.readdirSync)(path2)) safeRemoveTree((0,
|
|
41654
|
+
for (const entry of (0, import_node_fs40.readdirSync)(path2)) safeRemoveTree((0, import_node_path40.join)(path2, entry));
|
|
41450
41655
|
(0, import_node_fs40.rmdirSync)(path2);
|
|
41451
41656
|
return;
|
|
41452
41657
|
}
|
|
@@ -41454,7 +41659,7 @@ function safeRemoveTree(path2) {
|
|
|
41454
41659
|
}
|
|
41455
41660
|
async function removeWorktreeNodeModules(wtPath) {
|
|
41456
41661
|
try {
|
|
41457
|
-
safeRemoveTree((0,
|
|
41662
|
+
safeRemoveTree((0, import_node_path40.join)(wtPath, "node_modules"));
|
|
41458
41663
|
return { ok: true };
|
|
41459
41664
|
} catch (e) {
|
|
41460
41665
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -41498,7 +41703,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
41498
41703
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
41499
41704
|
}
|
|
41500
41705
|
for (const entry of entries) {
|
|
41501
|
-
const child2 = (0,
|
|
41706
|
+
const child2 = (0, import_node_path40.join)(dir, entry.name);
|
|
41502
41707
|
if (entry.isSymbolicLink()) {
|
|
41503
41708
|
let target = "";
|
|
41504
41709
|
try {
|
|
@@ -41825,7 +42030,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
41825
42030
|
const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
|
|
41826
42031
|
if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
|
|
41827
42032
|
unlinkedReparsePoints.push(...preHelperGuard.unlinked);
|
|
41828
|
-
if (pathExists((0,
|
|
42033
|
+
if (pathExists((0, import_node_path40.join)(wtPath, "node_modules"))) {
|
|
41829
42034
|
const nmRemoved = await (options.removeRealNodeModules ?? removeWorktreeNodeModules)(wtPath);
|
|
41830
42035
|
if (!nmRemoved.ok) {
|
|
41831
42036
|
report.worktree = {
|
|
@@ -41965,7 +42170,7 @@ function norm(p) {
|
|
|
41965
42170
|
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
41966
42171
|
}
|
|
41967
42172
|
function residueLedgerPath(gitCommonDir) {
|
|
41968
|
-
return (0,
|
|
42173
|
+
return (0, import_node_path41.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
|
|
41969
42174
|
}
|
|
41970
42175
|
function readPaths(file, deps) {
|
|
41971
42176
|
try {
|
|
@@ -41978,7 +42183,7 @@ function readPaths(file, deps) {
|
|
|
41978
42183
|
function writePaths(file, paths, deps) {
|
|
41979
42184
|
try {
|
|
41980
42185
|
const write = deps.writeLedger ?? ((f, t) => {
|
|
41981
|
-
(0, import_node_fs41.mkdirSync)((0,
|
|
42186
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path41.dirname)(f), { recursive: true });
|
|
41982
42187
|
(0, import_node_fs41.writeFileSync)(f, t, "utf8");
|
|
41983
42188
|
});
|
|
41984
42189
|
write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
|
|
@@ -42029,7 +42234,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
|
42029
42234
|
}
|
|
42030
42235
|
if (recorded.length) writePaths(file, remaining, deps);
|
|
42031
42236
|
const roots = /* @__PURE__ */ new Set();
|
|
42032
|
-
for (const p of [...registered.slice(1), ...recorded]) roots.add((0,
|
|
42237
|
+
for (const p of [...registered.slice(1), ...recorded]) roots.add((0, import_node_path41.dirname)(p));
|
|
42033
42238
|
const keptKeys = new Set(remaining.map(norm));
|
|
42034
42239
|
for (const root of roots) {
|
|
42035
42240
|
if (!isDir(root) || live.has(norm(root)) || norm(root) === primary) continue;
|
|
@@ -42040,7 +42245,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
|
42040
42245
|
continue;
|
|
42041
42246
|
}
|
|
42042
42247
|
for (const name of children) {
|
|
42043
|
-
const child2 = (0,
|
|
42248
|
+
const child2 = (0, import_node_path41.join)(root, name);
|
|
42044
42249
|
const key = norm(child2);
|
|
42045
42250
|
if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
|
|
42046
42251
|
let entries;
|
|
@@ -42108,10 +42313,10 @@ function argvWantsJson2() {
|
|
|
42108
42313
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
42109
42314
|
}
|
|
42110
42315
|
function hubRoot() {
|
|
42111
|
-
const fromPkg = (0,
|
|
42316
|
+
const fromPkg = (0, import_node_path42.join)(__dirname, "..", "..");
|
|
42112
42317
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
42113
|
-
if ((0, import_node_fs42.existsSync)((0,
|
|
42114
|
-
if ((0, import_node_fs42.existsSync)((0,
|
|
42318
|
+
if ((0, import_node_fs42.existsSync)((0, import_node_path42.join)(fromPkg, marker))) return fromPkg;
|
|
42319
|
+
if ((0, import_node_fs42.existsSync)((0, import_node_path42.join)(process.cwd(), marker))) return process.cwd();
|
|
42115
42320
|
return null;
|
|
42116
42321
|
}
|
|
42117
42322
|
function ciAuditDeps() {
|
|
@@ -42123,7 +42328,7 @@ function ciAuditDeps() {
|
|
|
42123
42328
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
42124
42329
|
readSeedFile: (path2) => {
|
|
42125
42330
|
if (!root) return null;
|
|
42126
|
-
const fullPath = (0,
|
|
42331
|
+
const fullPath = (0, import_node_path42.join)(root, path2);
|
|
42127
42332
|
return (0, import_node_fs42.existsSync)(fullPath) ? (0, import_node_fs42.readFileSync)(fullPath, "utf8") : null;
|
|
42128
42333
|
}
|
|
42129
42334
|
};
|
|
@@ -42231,7 +42436,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
42231
42436
|
try {
|
|
42232
42437
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
42233
42438
|
if (o.repo) args.push("--repo", o.repo);
|
|
42234
|
-
spawnDetachedSelf(args, { spawn:
|
|
42439
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process17.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
42235
42440
|
} catch {
|
|
42236
42441
|
}
|
|
42237
42442
|
}
|
|
@@ -42882,11 +43087,11 @@ ${list}`);
|
|
|
42882
43087
|
}
|
|
42883
43088
|
});
|
|
42884
43089
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
42885
|
-
const wfDir = (0,
|
|
43090
|
+
const wfDir = (0, import_node_path42.join)(cwd, ".github", "workflows");
|
|
42886
43091
|
if (!(0, import_node_fs42.existsSync)(wfDir)) return [];
|
|
42887
43092
|
return (0, import_node_fs42.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
42888
43093
|
try {
|
|
42889
|
-
return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0,
|
|
43094
|
+
return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
|
|
42890
43095
|
} catch {
|
|
42891
43096
|
return true;
|
|
42892
43097
|
}
|
|
@@ -43391,7 +43596,7 @@ ${list}`);
|
|
|
43391
43596
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
43392
43597
|
let foreignCheckout;
|
|
43393
43598
|
if (foreignCwd) {
|
|
43394
|
-
const sibling = (0,
|
|
43599
|
+
const sibling = (0, import_node_path42.join)((0, import_node_path42.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
|
|
43395
43600
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
43396
43601
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
43397
43602
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -43600,7 +43805,7 @@ ${list}`);
|
|
|
43600
43805
|
let residueSweep;
|
|
43601
43806
|
if (!(foreignCwd && !foreignCheckout)) {
|
|
43602
43807
|
try {
|
|
43603
|
-
gitCommonDir = (0,
|
|
43808
|
+
gitCommonDir = (0, import_node_path42.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
|
|
43604
43809
|
residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
|
|
43605
43810
|
} catch {
|
|
43606
43811
|
}
|
|
@@ -44694,15 +44899,15 @@ function rcRunCauseFromLog(log) {
|
|
|
44694
44899
|
}
|
|
44695
44900
|
function suiteTrainIo(root, summaryFile) {
|
|
44696
44901
|
const recordPath = repoRuntimeStatePath(root, "suite", "cut.json");
|
|
44697
|
-
const base = (0,
|
|
44902
|
+
const base = (0, import_node_path43.join)(repoRuntimeStatePath(root, "suite"), "checkouts");
|
|
44698
44903
|
const checkout = async (repo) => {
|
|
44699
44904
|
if (repo === SUITE_REPO) return root;
|
|
44700
|
-
const target = (0,
|
|
44905
|
+
const target = (0, import_node_path43.join)(base, repo.split("/")[1]);
|
|
44701
44906
|
if (!(0, import_node_fs43.existsSync)(target)) {
|
|
44702
44907
|
(0, import_node_fs43.mkdirSync)(base, { recursive: true });
|
|
44703
44908
|
await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
|
|
44704
44909
|
}
|
|
44705
|
-
if (!(0, import_node_fs43.existsSync)((0,
|
|
44910
|
+
if (!(0, import_node_fs43.existsSync)((0, import_node_path43.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
|
|
44706
44911
|
const origin = await exec("git", ["remote", "get-url", "origin"], target);
|
|
44707
44912
|
if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
|
|
44708
44913
|
throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
|
|
@@ -44930,6 +45135,25 @@ function registerSuiteCommands(program3) {
|
|
|
44930
45135
|
return fail(`suite rcand: ${e.message}`);
|
|
44931
45136
|
}
|
|
44932
45137
|
});
|
|
45138
|
+
suite.command("abandon").description("retire an unfinished cut that opened no pin PR: archive the record; member rc tags stay (#7396)").option("--json", "structured receipt").action(async (o) => {
|
|
45139
|
+
try {
|
|
45140
|
+
const root = await leadRoot();
|
|
45141
|
+
const unlock = await acquireTrainWriterLock(root, "suite abandon");
|
|
45142
|
+
let result;
|
|
45143
|
+
try {
|
|
45144
|
+
result = suiteAbandon(suiteTrainIo(root).recordPath);
|
|
45145
|
+
} finally {
|
|
45146
|
+
await unlock();
|
|
45147
|
+
}
|
|
45148
|
+
console.log(o.json ? JSON.stringify(result, null, 2) : [
|
|
45149
|
+
`suite abandon: archived the cut record to ${result.archive}`,
|
|
45150
|
+
...result.orphanTags.length ? [" member rc tags left in place (immutable; the next rc supersedes them):", ...result.orphanTags.map((t) => ` - ${t}`)] : [" no member rc tag was minted"],
|
|
45151
|
+
" the next suite rcand --owner-go re-snapshots every member"
|
|
45152
|
+
].join("\n"));
|
|
45153
|
+
} catch (e) {
|
|
45154
|
+
return fail(`suite abandon: ${e.message}`);
|
|
45155
|
+
}
|
|
45156
|
+
});
|
|
44933
45157
|
suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train").option("--json", "structured receipt").action(async (o) => {
|
|
44934
45158
|
try {
|
|
44935
45159
|
if (!o.ownerGo) throw new Error("suite release requires --owner-go; nothing was written");
|
|
@@ -45072,8 +45296,8 @@ async function collectWaveStatus(deps) {
|
|
|
45072
45296
|
init_github_client();
|
|
45073
45297
|
init_cli_shared();
|
|
45074
45298
|
var import_node_fs46 = require("node:fs");
|
|
45075
|
-
var
|
|
45076
|
-
var
|
|
45299
|
+
var import_node_os21 = require("node:os");
|
|
45300
|
+
var import_node_path44 = require("node:path");
|
|
45077
45301
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
45078
45302
|
async function collectStatus() {
|
|
45079
45303
|
const repo = await resolveRepo();
|
|
@@ -45262,10 +45486,10 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
45262
45486
|
else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
|
|
45263
45487
|
else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
|
|
45264
45488
|
}
|
|
45265
|
-
const home = (0,
|
|
45489
|
+
const home = (0, import_node_os21.homedir)();
|
|
45266
45490
|
const plugin = onboardPluginGate({
|
|
45267
|
-
readKnown: () => readFileSyncSafe((0,
|
|
45268
|
-
readSettings: () => readFileSyncSafe((0,
|
|
45491
|
+
readKnown: () => readFileSyncSafe((0, import_node_path44.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs46.readFileSync),
|
|
45492
|
+
readSettings: () => readFileSyncSafe((0, import_node_path44.join)(home, ".claude", "settings.json"), import_node_fs46.readFileSync)
|
|
45269
45493
|
});
|
|
45270
45494
|
return {
|
|
45271
45495
|
guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
|
|
@@ -48379,7 +48603,7 @@ function registerSessionReport(program3) {
|
|
|
48379
48603
|
|
|
48380
48604
|
// src/stage-commands.ts
|
|
48381
48605
|
var import_node_fs47 = require("node:fs");
|
|
48382
|
-
var
|
|
48606
|
+
var import_node_path45 = require("node:path");
|
|
48383
48607
|
init_cli_shared();
|
|
48384
48608
|
init_clean_exit();
|
|
48385
48609
|
|
|
@@ -48625,8 +48849,8 @@ function registerStageCommands(program3) {
|
|
|
48625
48849
|
return {
|
|
48626
48850
|
resolution: decideStage({
|
|
48627
48851
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
48628
|
-
hasCompose: (0, import_node_fs47.existsSync)((0,
|
|
48629
|
-
hasEnvExample: (0, import_node_fs47.existsSync)((0,
|
|
48852
|
+
hasCompose: (0, import_node_fs47.existsSync)((0, import_node_path45.join)(process.cwd(), "docker-compose.yml")),
|
|
48853
|
+
hasEnvExample: (0, import_node_fs47.existsSync)((0, import_node_path45.join)(process.cwd(), ".env.example")),
|
|
48630
48854
|
repo
|
|
48631
48855
|
}),
|
|
48632
48856
|
project: project2,
|
|
@@ -48910,20 +49134,20 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
48910
49134
|
}
|
|
48911
49135
|
|
|
48912
49136
|
// src/tenant-artifact.ts
|
|
48913
|
-
var
|
|
49137
|
+
var import_node_crypto14 = require("node:crypto");
|
|
48914
49138
|
var import_node_fs48 = require("node:fs");
|
|
48915
49139
|
var import_promises10 = require("node:fs/promises");
|
|
48916
|
-
var
|
|
49140
|
+
var import_node_path46 = require("node:path");
|
|
48917
49141
|
var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
48918
49142
|
var MAX_BYTES = 5 * 1024 * 1024 * 1024;
|
|
48919
49143
|
async function sha256File(path2) {
|
|
48920
|
-
const hash = (0,
|
|
49144
|
+
const hash = (0, import_node_crypto14.createHash)("sha256");
|
|
48921
49145
|
for await (const chunk of (0, import_node_fs48.createReadStream)(path2)) hash.update(chunk);
|
|
48922
49146
|
return hash.digest("hex");
|
|
48923
49147
|
}
|
|
48924
49148
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
48925
49149
|
if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
|
|
48926
|
-
const path2 = (0,
|
|
49150
|
+
const path2 = (0, import_node_path46.resolve)(inputPath);
|
|
48927
49151
|
const info = await (0, import_promises10.stat)(path2);
|
|
48928
49152
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
48929
49153
|
if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
|
|
@@ -49113,7 +49337,7 @@ async function resolveWhoami(deps) {
|
|
|
49113
49337
|
}
|
|
49114
49338
|
|
|
49115
49339
|
// src/command-register-developer.ts
|
|
49116
|
-
var
|
|
49340
|
+
var import_node_path51 = require("node:path");
|
|
49117
49341
|
init_house_map();
|
|
49118
49342
|
init_hub_url();
|
|
49119
49343
|
|
|
@@ -49146,27 +49370,27 @@ async function executeWaveLand(plan, deps) {
|
|
|
49146
49370
|
}
|
|
49147
49371
|
|
|
49148
49372
|
// src/dist-drift.ts
|
|
49149
|
-
var
|
|
49150
|
-
var
|
|
49373
|
+
var import_node_child_process18 = require("node:child_process");
|
|
49374
|
+
var import_node_crypto16 = require("node:crypto");
|
|
49151
49375
|
var import_node_fs50 = require("node:fs");
|
|
49152
|
-
var
|
|
49153
|
-
var
|
|
49376
|
+
var import_node_os22 = require("node:os");
|
|
49377
|
+
var import_node_path48 = require("node:path");
|
|
49154
49378
|
|
|
49155
49379
|
// ../scripts/distribution-digest.mjs
|
|
49156
|
-
var
|
|
49380
|
+
var import_node_crypto15 = require("node:crypto");
|
|
49157
49381
|
var import_node_fs49 = require("node:fs");
|
|
49158
|
-
var
|
|
49382
|
+
var import_node_path47 = require("node:path");
|
|
49159
49383
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
49160
49384
|
function repoPath(root, declaredPath, label) {
|
|
49161
|
-
const absoluteRoot = (0,
|
|
49162
|
-
const target = (0,
|
|
49163
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
49385
|
+
const absoluteRoot = (0, import_node_path47.resolve)(root);
|
|
49386
|
+
const target = (0, import_node_path47.resolve)(root, declaredPath);
|
|
49387
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path47.sep}`)) {
|
|
49164
49388
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
49165
49389
|
}
|
|
49166
49390
|
return target;
|
|
49167
49391
|
}
|
|
49168
49392
|
function digestFiles(files) {
|
|
49169
|
-
const hash = (0,
|
|
49393
|
+
const hash = (0, import_node_crypto15.createHash)("sha256");
|
|
49170
49394
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
49171
49395
|
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs49.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs49.readFileSync)(file.absolute);
|
|
49172
49396
|
hash.update(file.relative, "utf8");
|
|
@@ -49196,7 +49420,7 @@ var DIST_ARTIFACTS = [
|
|
|
49196
49420
|
];
|
|
49197
49421
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
49198
49422
|
var ABSENT = "absent";
|
|
49199
|
-
var sha256 = (bytes) => `sha256:${(0,
|
|
49423
|
+
var sha256 = (bytes) => `sha256:${(0, import_node_crypto16.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
49200
49424
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
49201
49425
|
const committed = committedBytes ? sha256(committedBytes) : ABSENT;
|
|
49202
49426
|
const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
|
|
@@ -49274,7 +49498,7 @@ function walkFiles(root) {
|
|
|
49274
49498
|
const files = [];
|
|
49275
49499
|
const walk2 = (directory) => {
|
|
49276
49500
|
for (const entry of (0, import_node_fs50.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
49277
|
-
const child2 = (0,
|
|
49501
|
+
const child2 = (0, import_node_path48.join)(directory, entry.name);
|
|
49278
49502
|
if (entry.isDirectory()) walk2(child2);
|
|
49279
49503
|
else files.push(child2);
|
|
49280
49504
|
}
|
|
@@ -49284,14 +49508,14 @@ function walkFiles(root) {
|
|
|
49284
49508
|
}
|
|
49285
49509
|
function bomPathFor(root) {
|
|
49286
49510
|
try {
|
|
49287
|
-
const registry2 = JSON.parse((0, import_node_fs50.readFileSync)((0,
|
|
49288
|
-
return (0,
|
|
49511
|
+
const registry2 = JSON.parse((0, import_node_fs50.readFileSync)((0, import_node_path48.join)(root, "surfaces.json"), "utf8"));
|
|
49512
|
+
return (0, import_node_path48.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
49289
49513
|
} catch {
|
|
49290
|
-
return (0,
|
|
49514
|
+
return (0, import_node_path48.join)(root, "distribution-bom.json");
|
|
49291
49515
|
}
|
|
49292
49516
|
}
|
|
49293
49517
|
function rebuildTo(packageRoot, outDir) {
|
|
49294
|
-
(0,
|
|
49518
|
+
(0, import_node_child_process18.execFileSync)(process.execPath, ["build.mjs"], {
|
|
49295
49519
|
cwd: packageRoot,
|
|
49296
49520
|
env: { ...process.env, MMI_DIST_OUTDIR: outDir },
|
|
49297
49521
|
windowsHide: true,
|
|
@@ -49300,28 +49524,28 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
49300
49524
|
});
|
|
49301
49525
|
}
|
|
49302
49526
|
function runDistStatus(root) {
|
|
49303
|
-
const stage = (0, import_node_fs50.mkdtempSync)((0,
|
|
49527
|
+
const stage = (0, import_node_fs50.mkdtempSync)((0, import_node_path48.join)((0, import_node_os22.tmpdir)(), "mmi-dist-drift-"));
|
|
49304
49528
|
let overlayCount = 0;
|
|
49305
49529
|
try {
|
|
49306
|
-
const cliOut = (0,
|
|
49307
|
-
const hubOut = (0,
|
|
49308
|
-
rebuildTo((0,
|
|
49309
|
-
rebuildTo((0,
|
|
49530
|
+
const cliOut = (0, import_node_path48.join)(stage, "cli-dist");
|
|
49531
|
+
const hubOut = (0, import_node_path48.join)(stage, "hub-dist");
|
|
49532
|
+
rebuildTo((0, import_node_path48.join)(root, "cli"), cliOut);
|
|
49533
|
+
rebuildTo((0, import_node_path48.join)(root, "updater"), hubOut);
|
|
49310
49534
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
49311
49535
|
const rebuilt = (path2) => {
|
|
49312
49536
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
49313
|
-
return spec ? readOrNull((0,
|
|
49537
|
+
return spec ? readOrNull((0, import_node_path48.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
49314
49538
|
};
|
|
49315
|
-
const committed = (path2) => readOrNull((0,
|
|
49316
|
-
const tree = (path2) => readOrNull((0,
|
|
49317
|
-
const distRoot = (0,
|
|
49318
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
49539
|
+
const committed = (path2) => readOrNull((0, import_node_path48.join)(root, path2));
|
|
49540
|
+
const tree = (path2) => readOrNull((0, import_node_path48.join)(root, path2));
|
|
49541
|
+
const distRoot = (0, import_node_path48.join)(root, "cli", "dist");
|
|
49542
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path48.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
49319
49543
|
const bom = JSON.parse((0, import_node_fs50.readFileSync)(bomPathFor(root), "utf8"));
|
|
49320
49544
|
const digest = (entries) => {
|
|
49321
|
-
const overlay = (0,
|
|
49545
|
+
const overlay = (0, import_node_path48.join)(stage, `overlay-${overlayCount++}`);
|
|
49322
49546
|
for (const entry of entries) {
|
|
49323
|
-
const target = (0,
|
|
49324
|
-
(0, import_node_fs50.mkdirSync)((0,
|
|
49547
|
+
const target = (0, import_node_path48.join)(overlay, entry.path);
|
|
49548
|
+
(0, import_node_fs50.mkdirSync)((0, import_node_path48.dirname)(target), { recursive: true });
|
|
49325
49549
|
(0, import_node_fs50.writeFileSync)(target, entry.bytes);
|
|
49326
49550
|
}
|
|
49327
49551
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
@@ -49407,7 +49631,7 @@ init_hub_auth();
|
|
|
49407
49631
|
|
|
49408
49632
|
// src/schedules-lift-command.ts
|
|
49409
49633
|
var import_promises11 = require("node:fs/promises");
|
|
49410
|
-
var
|
|
49634
|
+
var import_node_path49 = require("node:path");
|
|
49411
49635
|
init_clean_exit();
|
|
49412
49636
|
init_cli_shared();
|
|
49413
49637
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -49436,7 +49660,7 @@ async function readWorkflowFiles(dir) {
|
|
|
49436
49660
|
const files = [];
|
|
49437
49661
|
for (const name of names.sort()) {
|
|
49438
49662
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
49439
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises11.readFile)((0,
|
|
49663
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises11.readFile)((0, import_node_path49.join)(dir, name), "utf8") });
|
|
49440
49664
|
}
|
|
49441
49665
|
return files;
|
|
49442
49666
|
}
|
|
@@ -49518,9 +49742,9 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
49518
49742
|
}
|
|
49519
49743
|
|
|
49520
49744
|
// src/spawn-policy-core.ts
|
|
49521
|
-
var
|
|
49745
|
+
var import_node_child_process19 = require("node:child_process");
|
|
49522
49746
|
var import_node_fs51 = require("node:fs");
|
|
49523
|
-
var
|
|
49747
|
+
var import_node_path50 = require("node:path");
|
|
49524
49748
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
49525
49749
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
49526
49750
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -49589,7 +49813,7 @@ function findViolationsInSource(raw) {
|
|
|
49589
49813
|
return found;
|
|
49590
49814
|
}
|
|
49591
49815
|
function policedFiles(root) {
|
|
49592
|
-
const r = (0,
|
|
49816
|
+
const r = (0, import_node_child_process19.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
49593
49817
|
cwd: root,
|
|
49594
49818
|
encoding: "utf8",
|
|
49595
49819
|
windowsHide: true,
|
|
@@ -49606,7 +49830,7 @@ function runSpawnPolicy(root) {
|
|
|
49606
49830
|
for (const file of files) {
|
|
49607
49831
|
let raw;
|
|
49608
49832
|
try {
|
|
49609
|
-
raw = (0, import_node_fs51.readFileSync)((0,
|
|
49833
|
+
raw = (0, import_node_fs51.readFileSync)((0, import_node_path50.join)(root, file), "utf8");
|
|
49610
49834
|
} catch {
|
|
49611
49835
|
continue;
|
|
49612
49836
|
}
|
|
@@ -49626,7 +49850,7 @@ function runSpawnPolicy(root) {
|
|
|
49626
49850
|
function registerDeveloperCommands(program3) {
|
|
49627
49851
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
49628
49852
|
rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
|
|
49629
|
-
const path2 = (0,
|
|
49853
|
+
const path2 = (0, import_node_path51.join)(process.cwd(), ".gitignore");
|
|
49630
49854
|
const current = (0, import_node_fs52.existsSync)(path2) ? (0, import_node_fs52.readFileSync)(path2, "utf8") : null;
|
|
49631
49855
|
const plan = planManagedGitignore(current);
|
|
49632
49856
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
@@ -49901,12 +50125,12 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
49901
50125
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
49902
50126
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
49903
50127
|
function envHealLockPath(home) {
|
|
49904
|
-
return (0,
|
|
50128
|
+
return (0, import_node_path52.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
49905
50129
|
}
|
|
49906
50130
|
async function withEnvHealLock(what, run) {
|
|
49907
50131
|
try {
|
|
49908
50132
|
return await withFileLock(
|
|
49909
|
-
envHealLockPath((0,
|
|
50133
|
+
envHealLockPath((0, import_node_os23.homedir)()),
|
|
49910
50134
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
49911
50135
|
run
|
|
49912
50136
|
);
|
|
@@ -50003,7 +50227,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
50003
50227
|
const configRoot = surfaceConfigRoot(surface);
|
|
50004
50228
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
50005
50229
|
const plan = buildPluginCachePlan(
|
|
50006
|
-
(0,
|
|
50230
|
+
(0, import_node_os23.homedir)(),
|
|
50007
50231
|
running,
|
|
50008
50232
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
50009
50233
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -50031,7 +50255,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
50031
50255
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
50032
50256
|
const installed = installedActivePluginVersion(surface);
|
|
50033
50257
|
const plan = buildPluginCachePlan(
|
|
50034
|
-
(0,
|
|
50258
|
+
(0, import_node_os23.homedir)(),
|
|
50035
50259
|
running,
|
|
50036
50260
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
50037
50261
|
{ configRoot, includeStaging: surface !== "codex", installedVersion: installed }
|
|
@@ -50073,7 +50297,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
50073
50297
|
// has no generated routing index would
|
|
50074
50298
|
// get a permanent — demanding an artifact it never asked for.
|
|
50075
50299
|
docsIndexState: (root) => {
|
|
50076
|
-
if (!(0, import_node_fs53.existsSync)((0,
|
|
50300
|
+
if (!(0, import_node_fs53.existsSync)((0, import_node_path52.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
50077
50301
|
const real = createDocsIndexDeps(root);
|
|
50078
50302
|
let docs;
|
|
50079
50303
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -50082,7 +50306,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
50082
50306
|
},
|
|
50083
50307
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
50084
50308
|
healDocsIndex: (root) => {
|
|
50085
|
-
if (!(0, import_node_fs53.existsSync)((0,
|
|
50309
|
+
if (!(0, import_node_fs53.existsSync)((0, import_node_path52.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
50086
50310
|
const real = createDocsIndexDeps(root);
|
|
50087
50311
|
let docs;
|
|
50088
50312
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -51169,16 +51393,16 @@ function ciAuditDeps2() {
|
|
|
51169
51393
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
51170
51394
|
readSeedFile: (path2) => {
|
|
51171
51395
|
if (!root) return null;
|
|
51172
|
-
const fullPath = (0,
|
|
51396
|
+
const fullPath = (0, import_node_path52.join)(root, path2);
|
|
51173
51397
|
return (0, import_node_fs53.existsSync)(fullPath) ? (0, import_node_fs53.readFileSync)(fullPath, "utf8") : null;
|
|
51174
51398
|
}
|
|
51175
51399
|
};
|
|
51176
51400
|
}
|
|
51177
51401
|
function hubRoot2() {
|
|
51178
|
-
const fromPkg = (0,
|
|
51402
|
+
const fromPkg = (0, import_node_path52.join)(__dirname, "..", "..");
|
|
51179
51403
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
51180
|
-
if ((0, import_node_fs53.existsSync)((0,
|
|
51181
|
-
if ((0, import_node_fs53.existsSync)((0,
|
|
51404
|
+
if ((0, import_node_fs53.existsSync)((0, import_node_path52.join)(fromPkg, marker))) return fromPkg;
|
|
51405
|
+
if ((0, import_node_fs53.existsSync)((0, import_node_path52.join)(process.cwd(), marker))) return process.cwd();
|
|
51182
51406
|
return null;
|
|
51183
51407
|
}
|
|
51184
51408
|
registerQueryCommands(program2);
|
|
@@ -51319,7 +51543,7 @@ function directoryBytes(path2) {
|
|
|
51319
51543
|
return 0;
|
|
51320
51544
|
}
|
|
51321
51545
|
for (const entry of entries) {
|
|
51322
|
-
const child2 = (0,
|
|
51546
|
+
const child2 = (0, import_node_path52.join)(path2, entry.name);
|
|
51323
51547
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
51324
51548
|
else {
|
|
51325
51549
|
try {
|
|
@@ -51349,7 +51573,7 @@ function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
|
51349
51573
|
dirBytes,
|
|
51350
51574
|
listStagingDirs: (root) => (0, import_node_fs53.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
51351
51575
|
try {
|
|
51352
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
51576
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path52.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs53.statSync)(p).mtimeMs) };
|
|
51353
51577
|
} catch {
|
|
51354
51578
|
return { name: d.name, mtimeMs: Date.now() };
|
|
51355
51579
|
}
|
|
@@ -51363,7 +51587,7 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
51363
51587
|
return {
|
|
51364
51588
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
51365
51589
|
mtimeMs: (name) => {
|
|
51366
|
-
const p = (0,
|
|
51590
|
+
const p = (0, import_node_path52.join)(stagingRoot, name);
|
|
51367
51591
|
if (!(0, import_node_fs53.existsSync)(p)) return null;
|
|
51368
51592
|
try {
|
|
51369
51593
|
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs53.statSync)(q).mtimeMs);
|
|
@@ -51386,7 +51610,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
51386
51610
|
return;
|
|
51387
51611
|
}
|
|
51388
51612
|
const plan = buildPluginCachePlan(
|
|
51389
|
-
(0,
|
|
51613
|
+
(0, import_node_os23.homedir)(),
|
|
51390
51614
|
running,
|
|
51391
51615
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
51392
51616
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|