@mutmutco/claude-plugin 4.6.22 → 4.6.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +1818 -424
- package/cli/package.json +1 -1
- package/package.json +1 -1
package/cli/dist/main.cjs
CHANGED
|
@@ -710,6 +710,10 @@ function createGitHubClient(options = {}) {
|
|
|
710
710
|
}
|
|
711
711
|
return read();
|
|
712
712
|
},
|
|
713
|
+
async restWithStatus(method, path2, init) {
|
|
714
|
+
const res = await request(method, joinUrl(baseUrl, path2), init);
|
|
715
|
+
return { status: res.status, body: await parseJson(res) };
|
|
716
|
+
},
|
|
713
717
|
async restPaginate(path2, init) {
|
|
714
718
|
const items = [];
|
|
715
719
|
let url = withPerPage(joinUrl(baseUrl, path2));
|
|
@@ -1237,6 +1241,10 @@ var init_house_map = __esm({
|
|
|
1237
1241
|
// allowlisted GitHub REST GET reads for automation (pull files, compare, contents)
|
|
1238
1242
|
run: "devops",
|
|
1239
1243
|
// #9479: one named workflow run — the bounded `run cancel <id>` and `run rerun <id>` writes
|
|
1244
|
+
vars: "devops",
|
|
1245
|
+
// #9780: repository Actions variables — typed get/list/create/update/delete receipts
|
|
1246
|
+
repo: "devops",
|
|
1247
|
+
// #9779: canonical repository typed reads (archive status)
|
|
1240
1248
|
ci: "devops",
|
|
1241
1249
|
// the CI/gate audit
|
|
1242
1250
|
jev: "devops",
|
|
@@ -1616,10 +1624,10 @@ var init_cli_shared = __esm({
|
|
|
1616
1624
|
);
|
|
1617
1625
|
const appEnv = options.env ? void 0 : await ghReadAppEnv(file, args) ?? await ghFilingAppEnv(file, args);
|
|
1618
1626
|
const reportsCredential = !options.env && /(^|[\\/])gh(\.exe)?$/i.test(file) && isAppFilingWrite(args);
|
|
1619
|
-
const tag = async (
|
|
1620
|
-
if (!reportsCredential) return
|
|
1627
|
+
const tag = async (call3, credential) => {
|
|
1628
|
+
if (!reportsCredential) return call3;
|
|
1621
1629
|
try {
|
|
1622
|
-
return { ...await
|
|
1630
|
+
return { ...await call3, credential };
|
|
1623
1631
|
} catch (e) {
|
|
1624
1632
|
if (e && typeof e === "object") e.credential = credential;
|
|
1625
1633
|
throw e;
|
|
@@ -13519,7 +13527,7 @@ function useColor() {
|
|
|
13519
13527
|
var program = new Command();
|
|
13520
13528
|
|
|
13521
13529
|
// src/command-composition.ts
|
|
13522
|
-
var
|
|
13530
|
+
var import_node_fs66 = require("node:fs");
|
|
13523
13531
|
init_clean_exit();
|
|
13524
13532
|
init_cli_shared();
|
|
13525
13533
|
|
|
@@ -15187,7 +15195,7 @@ function resolveVerbAliasShim(argv) {
|
|
|
15187
15195
|
|
|
15188
15196
|
// src/command-composition.ts
|
|
15189
15197
|
var import_node_os28 = require("node:os");
|
|
15190
|
-
var
|
|
15198
|
+
var import_node_path63 = require("node:path");
|
|
15191
15199
|
|
|
15192
15200
|
// src/board-read.ts
|
|
15193
15201
|
var import_node_fs18 = require("node:fs");
|
|
@@ -15354,12 +15362,19 @@ var APP_POLL_PERMISSIONS = {
|
|
|
15354
15362
|
var APP_JWT_BACKDATE_S = 60;
|
|
15355
15363
|
var APP_JWT_TTL_S = 9 * 60;
|
|
15356
15364
|
var AppActorError = class extends Error {
|
|
15357
|
-
constructor(message2, code = "mint-failed") {
|
|
15365
|
+
constructor(message2, code = "mint-failed", mintRefusal) {
|
|
15358
15366
|
super(message2);
|
|
15359
15367
|
this.code = code;
|
|
15368
|
+
this.mintRefusal = mintRefusal;
|
|
15360
15369
|
}
|
|
15361
15370
|
code;
|
|
15371
|
+
mintRefusal;
|
|
15362
15372
|
};
|
|
15373
|
+
function sanitizeMintRefusalReason(raw) {
|
|
15374
|
+
if (typeof raw !== "string") return void 0;
|
|
15375
|
+
const cleaned = raw.replace(/-----BEGIN [^-]+-----[\s\S]*?(?:-----END [^-]+-----|$)/g, "[redacted]").replace(/\bBearer\s+\S+/gi, "Bearer [redacted]").replace(/\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*\.?[A-Za-z0-9_-]*/g, "[redacted]").replace(/\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_]+/g, "[redacted]").replace(/\s+/g, " ").trim();
|
|
15376
|
+
return cleaned === "" ? void 0 : cleaned.slice(0, 200);
|
|
15377
|
+
}
|
|
15363
15378
|
function commandPathOf(cmd) {
|
|
15364
15379
|
const parts = [];
|
|
15365
15380
|
let cursor = cmd;
|
|
@@ -15415,6 +15430,18 @@ var APP_WRITE_COMMANDS = /* @__PURE__ */ new Map([
|
|
|
15415
15430
|
["run cancel", { metadata: "read", actions: "write" }],
|
|
15416
15431
|
// #9587: one named run rerun (all or failed jobs) — same actions:write profile, nothing broader.
|
|
15417
15432
|
["run rerun", { metadata: "read", actions: "write" }],
|
|
15433
|
+
// #9780: repository Actions variables — get/list mint only Variables:read; create/update/delete
|
|
15434
|
+
// Variables:write. Repository scope only; no secret store, no rename, no workflow dispatch.
|
|
15435
|
+
// #9790: the token key is `actions_variables` (as ci-reconcile.yml mints); `variables` was refused 422.
|
|
15436
|
+
["vars get", { metadata: "read", actions_variables: "read" }],
|
|
15437
|
+
["vars list", { metadata: "read", actions_variables: "read" }],
|
|
15438
|
+
["vars create", { metadata: "read", actions_variables: "write" }],
|
|
15439
|
+
["vars update", { metadata: "read", actions_variables: "write" }],
|
|
15440
|
+
["vars delete", { metadata: "read", actions_variables: "write" }],
|
|
15441
|
+
// #9779: the archive-status read is a metadata:read-only repository read — nothing broader.
|
|
15442
|
+
["repo archive-status", { metadata: "read" }],
|
|
15443
|
+
// #9788: historical merge-queue lifecycle — one fixed PullRequest.timelineItems GraphQL query; read-only.
|
|
15444
|
+
["read merge-queue lifecycle", { metadata: "read", pull_requests: "read" }],
|
|
15418
15445
|
["issue comment", { metadata: "read", issues: "write", pull_requests: "write" }],
|
|
15419
15446
|
// #9614: closing an issue is an issue-state write — it rides the org App (mmi-github-app), so the close
|
|
15420
15447
|
// is attributed to the App instead of the personal account. Nothing broader than issues.
|
|
@@ -15557,7 +15584,17 @@ async function mintFromMember(deps, permissions, keys) {
|
|
|
15557
15584
|
throw new AppActorError(`app actor: token mint transport failed (${e?.constructor?.name ?? "Error"}) \u2014 automation fails closed, no personal-token fallback`);
|
|
15558
15585
|
}
|
|
15559
15586
|
if (!res.ok) {
|
|
15560
|
-
|
|
15587
|
+
let reason;
|
|
15588
|
+
try {
|
|
15589
|
+
reason = sanitizeMintRefusalReason((await res.json())?.message);
|
|
15590
|
+
} catch {
|
|
15591
|
+
reason = void 0;
|
|
15592
|
+
}
|
|
15593
|
+
throw new AppActorError(
|
|
15594
|
+
`app actor: installation token mint failed (HTTP ${res.status}${reason ? `: ${reason}` : ""}) \u2014 automation fails closed, no personal-token fallback`,
|
|
15595
|
+
"mint-failed",
|
|
15596
|
+
{ status: res.status, ...reason ? { reason } : {} }
|
|
15597
|
+
);
|
|
15561
15598
|
}
|
|
15562
15599
|
let body;
|
|
15563
15600
|
try {
|
|
@@ -17934,7 +17971,45 @@ function jobReportsPullRequestCheck(job) {
|
|
|
17934
17971
|
});
|
|
17935
17972
|
if (!directIf) return true;
|
|
17936
17973
|
const expression = directIf.replace(/^\s*if:\s*/, "").trim().replace(/^\$\{\{\s*/, "").replace(/\s*\}\}$/, "").trim();
|
|
17937
|
-
|
|
17974
|
+
if (/pull_request/i.test(expression)) return true;
|
|
17975
|
+
let rest = expression;
|
|
17976
|
+
let valid = true;
|
|
17977
|
+
const take = (pattern) => {
|
|
17978
|
+
const match = rest.match(pattern);
|
|
17979
|
+
if (!match) return void 0;
|
|
17980
|
+
rest = rest.slice(match[0].length).trimStart();
|
|
17981
|
+
return match[0];
|
|
17982
|
+
};
|
|
17983
|
+
const atom = () => {
|
|
17984
|
+
if (take(/^\(/)) {
|
|
17985
|
+
const possible2 = disjunction();
|
|
17986
|
+
if (!take(/^\)/)) valid = false;
|
|
17987
|
+
return possible2;
|
|
17988
|
+
}
|
|
17989
|
+
if (take(/^(?:github\.event_name\s*==\s*'[^']*'|github\.event_name\s*==\s*"[^"]*"|'[^']*'\s*==\s*github\.event_name|"[^"]*"\s*==\s*github\.event_name)/)) return false;
|
|
17990
|
+
if (take(/^always\(\s*\)/)) return true;
|
|
17991
|
+
if (take(/^needs\.[A-Za-z0-9_-]+\.outputs\.[A-Za-z0-9_-]+\s*(?:==|!=)\s*(?:'[^']*'|"[^"]*")/)) return true;
|
|
17992
|
+
valid = false;
|
|
17993
|
+
return true;
|
|
17994
|
+
};
|
|
17995
|
+
const conjunction = () => {
|
|
17996
|
+
let possible2 = atom();
|
|
17997
|
+
while (take(/^&&/)) {
|
|
17998
|
+
const next = atom();
|
|
17999
|
+
possible2 = possible2 && next;
|
|
18000
|
+
}
|
|
18001
|
+
return possible2;
|
|
18002
|
+
};
|
|
18003
|
+
const disjunction = () => {
|
|
18004
|
+
let possible2 = conjunction();
|
|
18005
|
+
while (take(/^\|\|/)) {
|
|
18006
|
+
const next = conjunction();
|
|
18007
|
+
possible2 = possible2 || next;
|
|
18008
|
+
}
|
|
18009
|
+
return possible2;
|
|
18010
|
+
};
|
|
18011
|
+
const possible = disjunction();
|
|
18012
|
+
return !valid || rest.length > 0 || possible;
|
|
17938
18013
|
}
|
|
17939
18014
|
function workflowTriggersPullRequest(yaml) {
|
|
17940
18015
|
const onBlock = extractOnBlock(yaml);
|
|
@@ -25428,7 +25503,9 @@ function checkRunbookClaims(root, docs, deps) {
|
|
|
25428
25503
|
if (token === "--") break;
|
|
25429
25504
|
const flag = token.split("=")[0];
|
|
25430
25505
|
if (/^--[\w-]+$/.test(flag) && flag !== "--help" && !placeholder(flag) && !deps.commandOptions?.get(resolved)?.has(flag)) {
|
|
25431
|
-
|
|
25506
|
+
const known = deps.commandOptions?.get(resolved);
|
|
25507
|
+
const knownFlags = known?.size ? ` (known flags: ${[...known].sort().join(" ")})` : "";
|
|
25508
|
+
report("unknown-flag", `mmi-cli ${resolved}: ${flag}${knownFlags} (source: ${deps.commandSource ?? "unspecified"})`);
|
|
25432
25509
|
}
|
|
25433
25510
|
}
|
|
25434
25511
|
} else if (cmd === "npm") {
|
|
@@ -25667,7 +25744,7 @@ function runDocRefs(root, deps = {}) {
|
|
|
25667
25744
|
findings.push(...checkCommands(docs, commandPaths).findings);
|
|
25668
25745
|
}
|
|
25669
25746
|
const runbooks = Object.fromEntries(Object.entries(docs).filter(([doc, body]) => isRunbook(doc, body)));
|
|
25670
|
-
findings.push(...checkRunbookClaims(root, runbooks, { readFile: readFile10, exists, isIgnored, trackedFirstSegments, commandPaths, commandOptions: deps.commandOptions }));
|
|
25747
|
+
findings.push(...checkRunbookClaims(root, runbooks, { readFile: readFile10, exists, isIgnored, trackedFirstSegments, commandPaths, commandOptions: deps.commandOptions, commandSource: deps.commandSource }));
|
|
25671
25748
|
const warnings = [...refResult.warnings];
|
|
25672
25749
|
if (deps.runbooksOnly && !Object.keys(runbooks).length) {
|
|
25673
25750
|
warnings.push({ kind: "runbook-missing", doc: "docs/Guides/operator.md", line: 0, detail: "no operator runbook or <!-- runbook --> document exists" });
|
|
@@ -28089,10 +28166,10 @@ var rollout_plan_default = {
|
|
|
28089
28166
|
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)."
|
|
28090
28167
|
},
|
|
28091
28168
|
baseline: {
|
|
28092
|
-
version: "4.6.
|
|
28093
|
-
tag: "v4.6.
|
|
28094
|
-
commit: "
|
|
28095
|
-
npm: "@mutmutco/cli@4.6.
|
|
28169
|
+
version: "4.6.24",
|
|
28170
|
+
tag: "v4.6.24",
|
|
28171
|
+
commit: "3632dffdf8a5",
|
|
28172
|
+
npm: "@mutmutco/cli@4.6.24"
|
|
28096
28173
|
},
|
|
28097
28174
|
exitCriterion: "fleet-n-of-n",
|
|
28098
28175
|
hubOnlyShortcut: "forbidden",
|
|
@@ -28109,14 +28186,14 @@ var rollout_plan_default = {
|
|
|
28109
28186
|
repo: "mutmutco/mmi-hub",
|
|
28110
28187
|
role: "canary",
|
|
28111
28188
|
schedule: "train",
|
|
28112
|
-
v3Target: "v4.6.
|
|
28189
|
+
v3Target: "v4.6.24"
|
|
28113
28190
|
}
|
|
28114
28191
|
],
|
|
28115
28192
|
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.",
|
|
28116
28193
|
rollback: {
|
|
28117
28194
|
independent: true,
|
|
28118
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
28119
|
-
v3Target: "v4.6.
|
|
28195
|
+
mechanism: "npm dist-tag latest -> 4.6.24 and redeploy the Hub Lambda from tag v4.6.24 (3632dffdf8a5); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
28196
|
+
v3Target: "v4.6.24 (@mutmutco/cli@4.6.24, tag commit 3632dffdf8a5 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
28120
28197
|
}
|
|
28121
28198
|
},
|
|
28122
28199
|
{
|
|
@@ -41185,7 +41262,7 @@ var PRIMARY_GROUPS = [
|
|
|
41185
41262
|
["Setup and support", ["bootstrap", "secrets", "docs", "tests", "spawn", "dist"]],
|
|
41186
41263
|
["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
|
|
41187
41264
|
];
|
|
41188
|
-
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["oauth", "org", "runtime", "plugin", "packages", "github", "archive-prune", "intercom", "jev"]);
|
|
41265
|
+
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["oauth", "org", "runtime", "plugin", "packages", "github", "archive-prune", "intercom", "jev", "vars", "repo"]);
|
|
41189
41266
|
var CAMPAIGN_ORCHESTRATOR_TOP_LEVEL = /* @__PURE__ */ new Set(["pickup", "read", "run"]);
|
|
41190
41267
|
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
|
|
41191
41268
|
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
@@ -41234,6 +41311,10 @@ var COMMAND_OWNERSHIP = {
|
|
|
41234
41311
|
read: { module_owner: "cli/src/app-read-broker.ts", consumer: "agent-workflow" },
|
|
41235
41312
|
run: { module_owner: "cli/src/merge-group-orphans.ts", consumer: "agent-workflow" },
|
|
41236
41313
|
// #9479
|
|
41314
|
+
vars: { module_owner: "cli/src/devops-vars.ts", consumer: "agent-workflow" },
|
|
41315
|
+
// #9780 repository Actions variables
|
|
41316
|
+
repo: { module_owner: "cli/src/repo-archive-status.ts", consumer: "agent-workflow" },
|
|
41317
|
+
// #9779 archive-status read
|
|
41237
41318
|
ci: { module_owner: "cli/src/ci-audit.ts", consumer: "release-operator" },
|
|
41238
41319
|
rcand: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
41239
41320
|
release: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
@@ -44436,6 +44517,1208 @@ function registerRunRerun(run, writeToken, projects) {
|
|
|
44436
44517
|
});
|
|
44437
44518
|
}
|
|
44438
44519
|
|
|
44520
|
+
// src/devops-vars.ts
|
|
44521
|
+
var import_node_crypto13 = require("node:crypto");
|
|
44522
|
+
init_github_client();
|
|
44523
|
+
init_cli_shared();
|
|
44524
|
+
var VARS_RECEIPT_SCHEMA = "mmi-cli.devops-vars/1";
|
|
44525
|
+
function parseVarsName(raw) {
|
|
44526
|
+
const name = raw.trim();
|
|
44527
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || name.length > 256) {
|
|
44528
|
+
throw new Error(`<name> must match [A-Za-z_][A-Za-z0-9_]* (\u2264256 chars) \u2014 received '${raw.slice(0, 64)}'`);
|
|
44529
|
+
}
|
|
44530
|
+
return name;
|
|
44531
|
+
}
|
|
44532
|
+
function parseVarsValue(raw) {
|
|
44533
|
+
if (typeof raw !== "string") throw new Error(`--value must be a string \u2014 received ${raw === null ? "null" : typeof raw}; values are preserved exactly, never coerced`);
|
|
44534
|
+
if (raw.length > 49152) throw new Error(`--value exceeds GitHub's 48KB variable size limit (${raw.length} chars)`);
|
|
44535
|
+
return raw;
|
|
44536
|
+
}
|
|
44537
|
+
function resolveCanonicalRepo(raw, projects) {
|
|
44538
|
+
if (!Array.isArray(projects)) return { kind: "unavailable" };
|
|
44539
|
+
const input = raw.trim();
|
|
44540
|
+
if (!/^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)?$/.test(input) || input.split("/").some((part) => part === "." || part === "..")) {
|
|
44541
|
+
return { kind: "unregistered" };
|
|
44542
|
+
}
|
|
44543
|
+
const key = registryRepoKey(qualifyRegistryRepo(input));
|
|
44544
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
44545
|
+
const aliasRepos = [];
|
|
44546
|
+
const lower = input.toLowerCase();
|
|
44547
|
+
for (const project2 of projects) {
|
|
44548
|
+
if (!project2 || typeof project2 !== "object" || Array.isArray(project2)) continue;
|
|
44549
|
+
if (!input.includes("/")) {
|
|
44550
|
+
const alias = typeof project2.slug === "string" ? project2.slug : typeof project2.name === "string" ? project2.name : void 0;
|
|
44551
|
+
if (typeof alias === "string" && alias.trim() !== "" && alias.toLowerCase() === lower && Array.isArray(project2.repos)) {
|
|
44552
|
+
for (const r of project2.repos) if (typeof r === "string" && r.trim() !== "") aliasRepos.push(qualifyRegistryRepo(r.trim()));
|
|
44553
|
+
}
|
|
44554
|
+
}
|
|
44555
|
+
if (!Array.isArray(project2.repos)) continue;
|
|
44556
|
+
for (const r of project2.repos) {
|
|
44557
|
+
if (typeof r !== "string" || r.trim() === "") continue;
|
|
44558
|
+
const qualified = qualifyRegistryRepo(r.trim());
|
|
44559
|
+
if (registryRepoKey(qualified) === key) byKey.set(key, byKey.get(key) ?? qualified);
|
|
44560
|
+
}
|
|
44561
|
+
}
|
|
44562
|
+
const distinct = /* @__PURE__ */ new Map();
|
|
44563
|
+
if (byKey.has(key)) distinct.set(key, byKey.get(key));
|
|
44564
|
+
for (const r of aliasRepos) {
|
|
44565
|
+
const rk = registryRepoKey(r);
|
|
44566
|
+
if (!distinct.has(rk)) distinct.set(rk, r);
|
|
44567
|
+
}
|
|
44568
|
+
const hits = [...distinct.entries()];
|
|
44569
|
+
if (hits.length === 1) {
|
|
44570
|
+
const [hitKey, repo] = hits[0];
|
|
44571
|
+
return { kind: "resolved", repo, via: hitKey === key ? "repo-name" : "project-alias" };
|
|
44572
|
+
}
|
|
44573
|
+
if (hits.length > 1) return { kind: "conflict", candidates: hits.map(([, repo]) => repo) };
|
|
44574
|
+
return { kind: "unregistered" };
|
|
44575
|
+
}
|
|
44576
|
+
function classifyUpstreamError(e) {
|
|
44577
|
+
if (e instanceof AppActorError) {
|
|
44578
|
+
const status = e.mintRefusal?.status ?? 0;
|
|
44579
|
+
return { status, reason: "mint-refused", mint: { status, reason: e.mintRefusal?.reason ?? e.message } };
|
|
44580
|
+
}
|
|
44581
|
+
if (e instanceof GitHubApiError) {
|
|
44582
|
+
if (e.status === 404) return { status: 404, reason: "bare-404" };
|
|
44583
|
+
if (e.status === 403 || e.status === 401) return { status: e.status, reason: "denied" };
|
|
44584
|
+
return { status: e.status, reason: "transport-error" };
|
|
44585
|
+
}
|
|
44586
|
+
const name = e?.name ?? "";
|
|
44587
|
+
if (name === "TimeoutError" || name === "AbortError" || /timeout|abort/i.test(String(e?.message ?? ""))) {
|
|
44588
|
+
return { status: 0, reason: "timeout" };
|
|
44589
|
+
}
|
|
44590
|
+
return { status: 0, reason: "transport-error" };
|
|
44591
|
+
}
|
|
44592
|
+
async function call(deps, log, method, path2, body) {
|
|
44593
|
+
try {
|
|
44594
|
+
const res = await deps.transport.restWithStatus(method, path2, body !== void 0 ? { body } : void 0);
|
|
44595
|
+
log.push({ method, path: path2, status: res.status });
|
|
44596
|
+
return res;
|
|
44597
|
+
} catch (e) {
|
|
44598
|
+
if (!(e instanceof AppActorError)) log.push({ method, path: path2, status: classifyUpstreamError(e).status });
|
|
44599
|
+
throw e;
|
|
44600
|
+
}
|
|
44601
|
+
}
|
|
44602
|
+
function beginOp() {
|
|
44603
|
+
return { startedAt: /* @__PURE__ */ new Date(), log: [] };
|
|
44604
|
+
}
|
|
44605
|
+
function receiptBase(deps, operation, repo, ctx, now) {
|
|
44606
|
+
return {
|
|
44607
|
+
schema: VARS_RECEIPT_SCHEMA,
|
|
44608
|
+
operation,
|
|
44609
|
+
repo,
|
|
44610
|
+
window: { startedAt: ctx.startedAt.toISOString(), endedAt: now().toISOString(), atomicSnapshot: false },
|
|
44611
|
+
actor: deps.actor ?? { credential: "app_installation" },
|
|
44612
|
+
correlationId: (deps.correlationId ?? import_node_crypto13.randomUUID)(),
|
|
44613
|
+
upstream: ctx.log
|
|
44614
|
+
};
|
|
44615
|
+
}
|
|
44616
|
+
var VARS_PER_PAGE = 30;
|
|
44617
|
+
var VARS_MAX_PAGES = 100;
|
|
44618
|
+
function parseVariableEntry(raw) {
|
|
44619
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("variable entry is not an object");
|
|
44620
|
+
const e = raw;
|
|
44621
|
+
if (typeof e.name !== "string" || e.name === "") throw new Error("variable entry has no name");
|
|
44622
|
+
if (e.value !== void 0 && typeof e.value !== "string") throw new Error(`variable '${e.name}' value is ${typeof e.value}, not a string \u2014 refusing to coerce`);
|
|
44623
|
+
if (e.created_at !== void 0 && typeof e.created_at !== "string") throw new Error(`variable '${e.name}' created_at is not a string`);
|
|
44624
|
+
if (e.updated_at !== void 0 && typeof e.updated_at !== "string") throw new Error(`variable '${e.name}' updated_at is not a string`);
|
|
44625
|
+
return {
|
|
44626
|
+
name: e.name,
|
|
44627
|
+
...e.value !== void 0 ? { value: e.value } : {},
|
|
44628
|
+
...e.created_at !== void 0 ? { createdAt: e.created_at } : {},
|
|
44629
|
+
...e.updated_at !== void 0 ? { updatedAt: e.updated_at } : {}
|
|
44630
|
+
};
|
|
44631
|
+
}
|
|
44632
|
+
async function enumerateRepositoryVariables(deps, repo, log = []) {
|
|
44633
|
+
const entries = [];
|
|
44634
|
+
let total = -1;
|
|
44635
|
+
for (let page = 1; page <= VARS_MAX_PAGES; page++) {
|
|
44636
|
+
const { body } = await call(deps, log, "GET", `repos/${repo}/actions/variables?per_page=${VARS_PER_PAGE}&page=${page}`);
|
|
44637
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
44638
|
+
return { complete: false, reason: "malformed-page", entries, pages: page, detail: `page ${page} body is not a JSON object` };
|
|
44639
|
+
}
|
|
44640
|
+
const envelope = body;
|
|
44641
|
+
if (typeof envelope.total_count !== "number" || !Number.isInteger(envelope.total_count) || envelope.total_count < 0 || !Array.isArray(envelope.variables)) {
|
|
44642
|
+
return { complete: false, reason: "malformed-page", entries, pages: page, detail: `page ${page} envelope has no integer total_count / variables array` };
|
|
44643
|
+
}
|
|
44644
|
+
if (total >= 0 && envelope.total_count !== total) {
|
|
44645
|
+
return { complete: false, reason: "incomplete-pagination", entries, pages: page, detail: `total_count moved between pages (${total} \u2192 ${envelope.total_count}); the enumeration is not a snapshot` };
|
|
44646
|
+
}
|
|
44647
|
+
total = envelope.total_count;
|
|
44648
|
+
try {
|
|
44649
|
+
for (const raw of envelope.variables) entries.push(parseVariableEntry(raw));
|
|
44650
|
+
} catch (e) {
|
|
44651
|
+
return { complete: false, reason: "malformed-page", entries, pages: page, detail: `page ${page}: ${e.message}` };
|
|
44652
|
+
}
|
|
44653
|
+
const expectedPages = Math.max(1, Math.ceil(total / VARS_PER_PAGE));
|
|
44654
|
+
if (expectedPages > VARS_MAX_PAGES) {
|
|
44655
|
+
return { complete: false, reason: "page-bound-exceeded", entries, pages: page, detail: `total_count ${total} needs ${expectedPages} pages; the bounded read stops at ${VARS_MAX_PAGES}` };
|
|
44656
|
+
}
|
|
44657
|
+
if (page >= expectedPages) break;
|
|
44658
|
+
}
|
|
44659
|
+
if (entries.length !== total) {
|
|
44660
|
+
return { complete: false, reason: "incomplete-pagination", entries, pages: Math.max(1, Math.ceil(total / VARS_PER_PAGE)), detail: `accumulated ${entries.length} entries but total_count is ${total}` };
|
|
44661
|
+
}
|
|
44662
|
+
return { complete: true, entries, totalVariables: total, pages: Math.max(1, Math.ceil(total / VARS_PER_PAGE)) };
|
|
44663
|
+
}
|
|
44664
|
+
function mintCapability(c, permission) {
|
|
44665
|
+
return c.mint ? { permission, outcome: "mint-refused", status: c.mint.status, reason: c.mint.reason } : void 0;
|
|
44666
|
+
}
|
|
44667
|
+
function mintDetail(c) {
|
|
44668
|
+
return `App token mint refused${c.mint?.status ? ` (HTTP ${c.mint.status})` : ""}: ${c.mint?.reason ?? "unknown"}`;
|
|
44669
|
+
}
|
|
44670
|
+
function findEntry(entries, name) {
|
|
44671
|
+
const lower = name.toLowerCase();
|
|
44672
|
+
return entries.find((e) => e.name.toLowerCase() === lower);
|
|
44673
|
+
}
|
|
44674
|
+
async function readVariable(deps, repo, name, log) {
|
|
44675
|
+
let body;
|
|
44676
|
+
try {
|
|
44677
|
+
({ body } = await call(deps, log, "GET", `repos/${repo}/actions/variables/${name}`));
|
|
44678
|
+
} catch (e) {
|
|
44679
|
+
const classified = classifyUpstreamError(e);
|
|
44680
|
+
const { status, reason } = classified;
|
|
44681
|
+
if (reason === "mint-refused") return { presence: "unknown", unknownReason: "mint-refused", detail: mintDetail(classified), capability: mintCapability(classified, deps.mintPermission ?? "variables:read") };
|
|
44682
|
+
if (reason === "denied") return { presence: "unknown", unknownReason: "denied", detail: `HTTP ${status}: the App installation cannot read repository variables`, capability: { permission: "variables:read", outcome: "denied" } };
|
|
44683
|
+
if (reason !== "bare-404") return { presence: "unknown", unknownReason: reason, detail: String(e.message).slice(0, 200) };
|
|
44684
|
+
try {
|
|
44685
|
+
const en = await enumerateRepositoryVariables(deps, repo, log);
|
|
44686
|
+
if (!en.complete) return { presence: "unknown", unknownReason: en.reason, detail: `bare 404 on GET, and the absence proof failed: ${en.detail}` };
|
|
44687
|
+
const hit = findEntry(en.entries, name);
|
|
44688
|
+
if (hit) return { presence: "present", value: hit.value, observed: { ...hit.createdAt ? { createdAt: hit.createdAt } : {}, ...hit.updatedAt ? { updatedAt: hit.updatedAt } : {}, source: "enumeration" } };
|
|
44689
|
+
return { presence: "absent", absenceProof: { provenBy: "complete-enumeration", pages: en.pages, perPage: VARS_PER_PAGE, totalVariables: en.totalVariables, nameExcluded: true } };
|
|
44690
|
+
} catch (proofError) {
|
|
44691
|
+
const proof = classifyUpstreamError(proofError);
|
|
44692
|
+
if (proof.reason === "mint-refused") return { presence: "unknown", unknownReason: "mint-refused", detail: `bare 404 on GET, and the absence proof failed: ${mintDetail(proof)}`, capability: mintCapability(proof, deps.mintPermission ?? "variables:read") };
|
|
44693
|
+
return { presence: "unknown", unknownReason: proof.reason === "bare-404" ? "transport-error" : proof.reason, detail: `bare 404 on GET, and the absence proof failed: ${String(proofError.message).slice(0, 160)}` };
|
|
44694
|
+
}
|
|
44695
|
+
}
|
|
44696
|
+
try {
|
|
44697
|
+
const entry = parseVariableEntry(body);
|
|
44698
|
+
return { presence: "present", value: entry.value, observed: { ...entry.createdAt ? { createdAt: entry.createdAt } : {}, ...entry.updatedAt ? { updatedAt: entry.updatedAt } : {}, source: "get" } };
|
|
44699
|
+
} catch (e) {
|
|
44700
|
+
return { presence: "unknown", unknownReason: "parser-refused", detail: `HTTP 200 but the body could not be parsed as a variable: ${e.message}` };
|
|
44701
|
+
}
|
|
44702
|
+
}
|
|
44703
|
+
async function getVariableReceipt(deps, repo, name) {
|
|
44704
|
+
const ctx = beginOp();
|
|
44705
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
44706
|
+
const read = await readVariable(deps, repo, name, ctx.log);
|
|
44707
|
+
return {
|
|
44708
|
+
...receiptBase(deps, "get", repo, ctx, now),
|
|
44709
|
+
name,
|
|
44710
|
+
presence: read.presence,
|
|
44711
|
+
...read.value !== void 0 ? { value: read.value } : {},
|
|
44712
|
+
...read.observed ? { observed: read.observed } : {},
|
|
44713
|
+
...read.absenceProof ? { absenceProof: read.absenceProof } : {},
|
|
44714
|
+
...read.unknownReason ? { unknownReason: read.unknownReason } : {},
|
|
44715
|
+
...read.capability ? { capability: read.capability } : {}
|
|
44716
|
+
};
|
|
44717
|
+
}
|
|
44718
|
+
async function listVariablesReceipt(deps, repo) {
|
|
44719
|
+
const ctx = beginOp();
|
|
44720
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
44721
|
+
try {
|
|
44722
|
+
const en = await enumerateRepositoryVariables(deps, repo, ctx.log);
|
|
44723
|
+
if (en.complete) {
|
|
44724
|
+
return {
|
|
44725
|
+
...receiptBase(deps, "list", repo, ctx, now),
|
|
44726
|
+
pagination: { pages: en.pages, perPage: VARS_PER_PAGE, complete: true, totalVariables: en.totalVariables },
|
|
44727
|
+
variables: en.entries.map(({ name, createdAt, updatedAt }) => ({ name, ...createdAt ? { createdAt } : {}, ...updatedAt ? { updatedAt } : {} }))
|
|
44728
|
+
};
|
|
44729
|
+
}
|
|
44730
|
+
return {
|
|
44731
|
+
...receiptBase(deps, "list", repo, ctx, now),
|
|
44732
|
+
pagination: { pages: en.pages, perPage: VARS_PER_PAGE, complete: false, reason: en.reason, detail: en.detail },
|
|
44733
|
+
variables: en.entries.map(({ name, createdAt, updatedAt }) => ({ name, ...createdAt ? { createdAt } : {}, ...updatedAt ? { updatedAt } : {} })),
|
|
44734
|
+
unknownReason: en.reason
|
|
44735
|
+
};
|
|
44736
|
+
} catch (e) {
|
|
44737
|
+
const classified = classifyUpstreamError(e);
|
|
44738
|
+
const { status, reason } = classified;
|
|
44739
|
+
return {
|
|
44740
|
+
...receiptBase(deps, "list", repo, ctx, now),
|
|
44741
|
+
pagination: { pages: 0, perPage: VARS_PER_PAGE, complete: false, reason },
|
|
44742
|
+
variables: [],
|
|
44743
|
+
unknownReason: reason,
|
|
44744
|
+
...reason === "mint-refused" ? { capability: mintCapability(classified, deps.mintPermission ?? "variables:read"), pagination: { pages: 0, perPage: VARS_PER_PAGE, complete: false, reason, detail: mintDetail(classified) } } : {},
|
|
44745
|
+
...reason === "denied" ? { capability: { permission: "variables:read", outcome: "denied" }, pagination: { pages: 0, perPage: VARS_PER_PAGE, complete: false, reason, detail: `HTTP ${status}: the App installation cannot list repository variables` } } : {}
|
|
44746
|
+
};
|
|
44747
|
+
}
|
|
44748
|
+
}
|
|
44749
|
+
async function readBaseline(deps, repo, name, log) {
|
|
44750
|
+
const read = await readVariable(deps, repo, name, log);
|
|
44751
|
+
if (read.presence === "present") return { presence: "present", value: read.value };
|
|
44752
|
+
if (read.presence === "absent") return { presence: "absent" };
|
|
44753
|
+
return { presence: "unknown", unknownReason: read.unknownReason, ...read.capability ? { capability: read.capability } : {} };
|
|
44754
|
+
}
|
|
44755
|
+
function rejectedWrite(base, baseline, reason, capability) {
|
|
44756
|
+
return {
|
|
44757
|
+
...base,
|
|
44758
|
+
outcome: "rejected",
|
|
44759
|
+
casLimitation: "no-atomic-compare-and-swap",
|
|
44760
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44761
|
+
reason,
|
|
44762
|
+
...capability ? { capability } : {}
|
|
44763
|
+
};
|
|
44764
|
+
}
|
|
44765
|
+
function unknownWrite(base, baseline, reason, readback) {
|
|
44766
|
+
return {
|
|
44767
|
+
...base,
|
|
44768
|
+
outcome: "outcome-unknown",
|
|
44769
|
+
casLimitation: "no-atomic-compare-and-swap",
|
|
44770
|
+
baseline: baseline.presence === "present" ? { presence: "present", value: baseline.value, source: "get" } : baseline,
|
|
44771
|
+
...readback ? { readback } : {},
|
|
44772
|
+
reason
|
|
44773
|
+
};
|
|
44774
|
+
}
|
|
44775
|
+
async function createVariableReceipt(deps, repo, name, value) {
|
|
44776
|
+
const ctx = beginOp();
|
|
44777
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
44778
|
+
let en;
|
|
44779
|
+
try {
|
|
44780
|
+
en = await enumerateRepositoryVariables(deps, repo, ctx.log);
|
|
44781
|
+
} catch (e) {
|
|
44782
|
+
const classified = classifyUpstreamError(e);
|
|
44783
|
+
const { status, reason } = classified;
|
|
44784
|
+
return {
|
|
44785
|
+
...receiptBase(deps, "create", repo, ctx, now),
|
|
44786
|
+
operation: "create",
|
|
44787
|
+
name,
|
|
44788
|
+
outcome: "not-dispatched",
|
|
44789
|
+
casLimitation: "no-atomic-compare-and-swap",
|
|
44790
|
+
baseline: { presence: "unknown", source: "complete-enumeration" },
|
|
44791
|
+
reason: `no observed absent baseline: ${reason === "denied" ? `HTTP ${status}: the App installation cannot list repository variables` : reason === "mint-refused" ? mintDetail(classified) : String(e.message).slice(0, 160)}`,
|
|
44792
|
+
...reason === "denied" ? { capability: { permission: "variables:read", outcome: "denied" } } : {},
|
|
44793
|
+
...reason === "mint-refused" ? { capability: mintCapability(classified, deps.mintPermission ?? "variables:write") } : {}
|
|
44794
|
+
};
|
|
44795
|
+
}
|
|
44796
|
+
const base = { ...receiptBase(deps, "create", repo, ctx, now), operation: "create", name, casLimitation: "no-atomic-compare-and-swap" };
|
|
44797
|
+
if (!en.complete) {
|
|
44798
|
+
return { ...base, outcome: "not-dispatched", baseline: { presence: "unknown", source: "complete-enumeration" }, reason: `no observed absent baseline: ${en.reason} (${en.detail})` };
|
|
44799
|
+
}
|
|
44800
|
+
const existing = findEntry(en.entries, name);
|
|
44801
|
+
if (existing) {
|
|
44802
|
+
return { ...base, outcome: "not-dispatched", baseline: { presence: "present", ...existing.value !== void 0 ? { value: existing.value } : {}, source: "complete-enumeration" }, reason: "variable already present \u2014 create only dispatches against an observed absent baseline (use update)" };
|
|
44803
|
+
}
|
|
44804
|
+
try {
|
|
44805
|
+
await call(deps, ctx.log, "POST", `repos/${repo}/actions/variables`, { name, value });
|
|
44806
|
+
} catch (e) {
|
|
44807
|
+
const classified = classifyUpstreamError(e);
|
|
44808
|
+
const { status, reason } = classified;
|
|
44809
|
+
const absentBaseline = { presence: "absent", source: "complete-enumeration" };
|
|
44810
|
+
if (reason === "mint-refused") return { ...base, outcome: "not-dispatched", baseline: absentBaseline, reason: mintDetail(classified), capability: mintCapability(classified, deps.mintPermission ?? "variables:write") };
|
|
44811
|
+
if (reason === "denied") return { ...base, outcome: "rejected", baseline: absentBaseline, reason: `HTTP ${status}: the App installation lacks repository Variables write`, capability: { permission: "variables:write", outcome: "denied" } };
|
|
44812
|
+
if (status === 404) return { ...base, outcome: "rejected", baseline: absentBaseline, reason: "HTTP 404: repository missing or not visible to the installation" };
|
|
44813
|
+
if (status === 409 || status === 422) return { ...base, outcome: "rejected", baseline: absentBaseline, reason: `HTTP ${status}: ${String(e.message).slice(0, 160)}` };
|
|
44814
|
+
return unknownWrite({ ...base, schema: VARS_RECEIPT_SCHEMA, operation: "create", repo }, { presence: "absent", source: "complete-enumeration" }, `${reason === "timeout" ? "dispatch timed out" : `HTTP ${status || "no response"}`}; the create MAY have applied \u2014 reconcile read-only, never replay`);
|
|
44815
|
+
}
|
|
44816
|
+
const rb = await readVariable(deps, repo, name, ctx.log);
|
|
44817
|
+
if (rb.presence === "present") {
|
|
44818
|
+
if (rb.value === value) {
|
|
44819
|
+
return { ...base, outcome: "verified", baseline: { presence: "absent", source: "complete-enumeration" }, readback: { presence: "present", value: rb.value } };
|
|
44820
|
+
}
|
|
44821
|
+
return unknownWrite(base, { presence: "absent", source: "complete-enumeration" }, "read-back shows a different value than dispatched \u2014 a concurrent outside write is possible; reconcile read-only", { presence: "present", value: rb.value });
|
|
44822
|
+
}
|
|
44823
|
+
if (rb.presence === "absent") {
|
|
44824
|
+
return unknownWrite(base, { presence: "absent", source: "complete-enumeration" }, "accepted but the read-back proves absence \u2014 reconcile read-only before any retry", { presence: "absent" });
|
|
44825
|
+
}
|
|
44826
|
+
return {
|
|
44827
|
+
...base,
|
|
44828
|
+
outcome: "accepted-but-unverified",
|
|
44829
|
+
baseline: { presence: "absent", source: "complete-enumeration" },
|
|
44830
|
+
readback: { presence: "unknown" },
|
|
44831
|
+
reason: `create accepted but the read-back could not verify: ${rb.unknownReason ?? "unknown"}`
|
|
44832
|
+
};
|
|
44833
|
+
}
|
|
44834
|
+
async function updateVariableReceipt(deps, repo, name, value) {
|
|
44835
|
+
const ctx = beginOp();
|
|
44836
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
44837
|
+
const base = { ...receiptBase(deps, "update", repo, ctx, now), operation: "update", name, casLimitation: "no-atomic-compare-and-swap" };
|
|
44838
|
+
const baseline = await readBaseline(deps, repo, name, ctx.log);
|
|
44839
|
+
if (baseline.presence !== "present") {
|
|
44840
|
+
return {
|
|
44841
|
+
...base,
|
|
44842
|
+
outcome: "not-dispatched",
|
|
44843
|
+
baseline: { presence: baseline.presence, ...baseline.value !== void 0 ? { value: baseline.value } : {}, source: "get" },
|
|
44844
|
+
reason: baseline.presence === "absent" ? "no present baseline \u2014 the enumeration proved absence (use create)" : `no retained present baseline (${baseline.unknownReason ?? "unknown"}); a bare 404 is never a present baseline`,
|
|
44845
|
+
...baseline.capability ? { capability: baseline.capability } : {}
|
|
44846
|
+
};
|
|
44847
|
+
}
|
|
44848
|
+
const live = await readVariable(deps, repo, name, ctx.log);
|
|
44849
|
+
if (live.presence !== "present" || live.value !== baseline.value) {
|
|
44850
|
+
return {
|
|
44851
|
+
...base,
|
|
44852
|
+
outcome: "not-dispatched",
|
|
44853
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44854
|
+
...live.presence === "present" ? { readback: { presence: "present", value: live.value } } : {},
|
|
44855
|
+
reason: live.presence === "present" ? "concurrent change: the live value no longer matches the retained baseline \u2014 stopped for reconciliation" : `baseline unconfirmed before dispatch (${live.unknownReason ?? "unknown"}) \u2014 stopped`
|
|
44856
|
+
};
|
|
44857
|
+
}
|
|
44858
|
+
try {
|
|
44859
|
+
await call(deps, ctx.log, "PATCH", `repos/${repo}/actions/variables/${name}`, { value });
|
|
44860
|
+
} catch (e) {
|
|
44861
|
+
const classified = classifyUpstreamError(e);
|
|
44862
|
+
const { status, reason } = classified;
|
|
44863
|
+
if (reason === "mint-refused") return { ...base, outcome: "not-dispatched", baseline: { presence: "present", value: baseline.value, source: "get" }, reason: mintDetail(classified), capability: mintCapability(classified, deps.mintPermission ?? "variables:write") };
|
|
44864
|
+
if (reason === "denied") return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: the App installation lacks repository Variables write`, { permission: "variables:write", outcome: "denied" });
|
|
44865
|
+
if (status === 404) return rejectedWrite(base, { presence: "present", value: baseline.value }, "HTTP 404 at write: the variable vanished after the baseline check (concurrent deletion suspected) \u2014 reconcile read-only, never create");
|
|
44866
|
+
if (status === 409 || status === 422) return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: ${String(e.message).slice(0, 160)}`);
|
|
44867
|
+
return unknownWrite(base, { presence: "present", value: baseline.value }, `${reason === "timeout" ? "dispatch timed out" : `HTTP ${status || "no response"}`}; the update MAY have applied \u2014 reconcile read-only, never replay`);
|
|
44868
|
+
}
|
|
44869
|
+
const rb = await readVariable(deps, repo, name, ctx.log);
|
|
44870
|
+
if (rb.presence === "present") {
|
|
44871
|
+
if (rb.value === value) return { ...base, outcome: "verified", baseline: { presence: "present", value: baseline.value, source: "get" }, readback: { presence: "present", value: rb.value } };
|
|
44872
|
+
return unknownWrite(base, { presence: "present", value: baseline.value }, "read-back shows a different value than dispatched \u2014 a concurrent outside write is possible; reconcile read-only", { presence: "present", value: rb.value });
|
|
44873
|
+
}
|
|
44874
|
+
if (rb.presence === "absent") {
|
|
44875
|
+
return unknownWrite(base, { presence: "present", value: baseline.value }, "accepted but the read-back proves absence \u2014 reconcile read-only before any retry", { presence: "absent" });
|
|
44876
|
+
}
|
|
44877
|
+
return {
|
|
44878
|
+
...base,
|
|
44879
|
+
outcome: "accepted-but-unverified",
|
|
44880
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44881
|
+
readback: { presence: "unknown" },
|
|
44882
|
+
reason: `update accepted but the read-back could not verify: ${rb.unknownReason ?? "unknown"}`
|
|
44883
|
+
};
|
|
44884
|
+
}
|
|
44885
|
+
async function deleteVariableReceipt(deps, repo, name) {
|
|
44886
|
+
const ctx = beginOp();
|
|
44887
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
44888
|
+
const base = { ...receiptBase(deps, "delete", repo, ctx, now), operation: "delete", name, casLimitation: "no-atomic-compare-and-swap" };
|
|
44889
|
+
const baseline = await readBaseline(deps, repo, name, ctx.log);
|
|
44890
|
+
if (baseline.presence !== "present") {
|
|
44891
|
+
return {
|
|
44892
|
+
...base,
|
|
44893
|
+
outcome: "not-dispatched",
|
|
44894
|
+
baseline: { presence: baseline.presence, ...baseline.value !== void 0 ? { value: baseline.value } : {}, source: "get" },
|
|
44895
|
+
reason: baseline.presence === "absent" ? "no present baseline \u2014 the enumeration already proves absence; nothing to delete" : `no retained present baseline (${baseline.unknownReason ?? "unknown"}); a bare 404 never authorizes a delete`,
|
|
44896
|
+
...baseline.capability ? { capability: baseline.capability } : {}
|
|
44897
|
+
};
|
|
44898
|
+
}
|
|
44899
|
+
const live = await readVariable(deps, repo, name, ctx.log);
|
|
44900
|
+
if (live.presence !== "present" || live.value !== baseline.value) {
|
|
44901
|
+
return {
|
|
44902
|
+
...base,
|
|
44903
|
+
outcome: "not-dispatched",
|
|
44904
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44905
|
+
...live.presence === "present" ? { readback: { presence: "present", value: live.value } } : {},
|
|
44906
|
+
reason: live.presence === "present" ? "concurrent change: the live value no longer matches the retained baseline \u2014 stopped for reconciliation" : `baseline unconfirmed before dispatch (${live.unknownReason ?? "unknown"}) \u2014 stopped`
|
|
44907
|
+
};
|
|
44908
|
+
}
|
|
44909
|
+
try {
|
|
44910
|
+
await call(deps, ctx.log, "DELETE", `repos/${repo}/actions/variables/${name}`);
|
|
44911
|
+
} catch (e) {
|
|
44912
|
+
const classified = classifyUpstreamError(e);
|
|
44913
|
+
const { status, reason } = classified;
|
|
44914
|
+
if (reason === "mint-refused") return { ...base, outcome: "not-dispatched", baseline: { presence: "present", value: baseline.value, source: "get" }, reason: mintDetail(classified), capability: mintCapability(classified, deps.mintPermission ?? "variables:write") };
|
|
44915
|
+
if (reason === "denied") return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: the App installation lacks repository Variables write`, { permission: "variables:write", outcome: "denied" });
|
|
44916
|
+
if (status === 404) return rejectedWrite(base, { presence: "present", value: baseline.value }, "HTTP 404 at write: the variable vanished after the baseline check (concurrent deletion suspected) \u2014 reconcile read-only");
|
|
44917
|
+
if (status === 409 || status === 422) return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: ${String(e.message).slice(0, 160)}`);
|
|
44918
|
+
return unknownWrite(base, { presence: "present", value: baseline.value }, `${reason === "timeout" ? "dispatch timed out" : `HTTP ${status || "no response"}`}; the delete MAY have applied \u2014 reconcile read-only, never replay`);
|
|
44919
|
+
}
|
|
44920
|
+
try {
|
|
44921
|
+
const en = await enumerateRepositoryVariables(deps, repo);
|
|
44922
|
+
if (en.complete && !findEntry(en.entries, name)) {
|
|
44923
|
+
return { ...base, outcome: "verified", baseline: { presence: "present", value: baseline.value, source: "get" }, readback: { presence: "absent" }, reason: `absence proven by complete enumeration (${en.pages} page(s), ${en.totalVariables} variable(s), name excluded)` };
|
|
44924
|
+
}
|
|
44925
|
+
if (en.complete) {
|
|
44926
|
+
const still = findEntry(en.entries, name);
|
|
44927
|
+
return unknownWrite(base, { presence: "present", value: baseline.value }, "delete accepted but the variable is still enumerated \u2014 reconcile read-only, never replay", { presence: "present", ...still.value !== void 0 ? { value: still.value } : {} });
|
|
44928
|
+
}
|
|
44929
|
+
return {
|
|
44930
|
+
...base,
|
|
44931
|
+
outcome: "accepted-but-unverified",
|
|
44932
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44933
|
+
reason: `delete accepted but absence is unproven: ${en.reason} (${en.detail})`
|
|
44934
|
+
};
|
|
44935
|
+
} catch (e) {
|
|
44936
|
+
return {
|
|
44937
|
+
...base,
|
|
44938
|
+
outcome: "accepted-but-unverified",
|
|
44939
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44940
|
+
reason: `delete accepted but absence is unproven (${classifyUpstreamError(e).reason}): ${String(e.message).slice(0, 160)}`
|
|
44941
|
+
};
|
|
44942
|
+
}
|
|
44943
|
+
}
|
|
44944
|
+
var VARS_HELP_AFTER = [
|
|
44945
|
+
"",
|
|
44946
|
+
"Presence semantics (#9780): present | absent | unknown. A bare 404 is UNKNOWN, never absence \u2014",
|
|
44947
|
+
"absence is claimed only from a complete bounded enumeration excluding the exact name, with",
|
|
44948
|
+
"pagination evidence. Truncated pagination, malformed pages, denial, parser refusal or timeout is",
|
|
44949
|
+
"unknown. Values are preserved exactly (including `off`) and never coerced.",
|
|
44950
|
+
"Mutations verify by read-back (delete: complete enumeration) \u2014 an HTTP success alone is never",
|
|
44951
|
+
"verified; an ambiguous result is reported unknown and reconciled READ-ONLY, never replayed.",
|
|
44952
|
+
"The pre-write check is NOT atomic compare-and-swap: a concurrent outside write between the check",
|
|
44953
|
+
"and the dispatch is not prevented. Repository scope only \u2014 no organization/environment scope, no",
|
|
44954
|
+
"secret store, no rename, no workflow dispatch, no credential fallback."
|
|
44955
|
+
].join("\n");
|
|
44956
|
+
function resolveRepoOrFail(raw, projects, verb) {
|
|
44957
|
+
const resolution = resolveCanonicalRepo(raw, projects);
|
|
44958
|
+
if (resolution.kind === "resolved") return resolution.repo;
|
|
44959
|
+
if (resolution.kind === "conflict") return fail(`vars ${verb}: '${raw}' resolves to MULTIPLE registered repositories (${resolution.candidates.join(", ")}) \u2014 pass the exact owner/repo; ambiguous aliases are refused (#9780)`);
|
|
44960
|
+
if (resolution.kind === "unavailable") return fail(`vars ${verb}: the Hub project registry is unreadable; refusing to address a repo this command cannot prove registered`);
|
|
44961
|
+
return fail(`vars ${verb}: '${raw}' is not a registered repository in the Hub registry; App variables access is limited to registered repos \u2014 pass the exact owner/repo`);
|
|
44962
|
+
}
|
|
44963
|
+
function registerDevopsVarsCommands(program3, deps) {
|
|
44964
|
+
const vars = program3.command("vars").description("repository Actions variables \u2014 typed get/list/create/update/delete on the org App (Variables read/write), Hub-registered repos only (#9780)");
|
|
44965
|
+
const runRead = async (commandPath4, verb, repoArg, name, o) => {
|
|
44966
|
+
const projects = await deps.projects();
|
|
44967
|
+
const repo = resolveRepoOrFail(repoArg, projects, verb);
|
|
44968
|
+
const ops = { transport: await deps.appClient(commandPath4), ...deps.actor ? { actor: deps.actor() } : {} };
|
|
44969
|
+
const receipt = verb === "get" && name !== void 0 ? await getVariableReceipt(ops, repo, name) : await listVariablesReceipt(ops, repo);
|
|
44970
|
+
if (o.json) console.log(JSON.stringify(receipt, null, 2));
|
|
44971
|
+
else printReadText(receipt);
|
|
44972
|
+
const failed2 = receipt.operation === "get" ? receipt.presence === "unknown" : !receipt.pagination.complete;
|
|
44973
|
+
if (failed2) process.exitCode = 1;
|
|
44974
|
+
};
|
|
44975
|
+
const runMutation = async (commandPath4, verb, nameArg, value, repoArg, o) => {
|
|
44976
|
+
let name;
|
|
44977
|
+
let exactValue;
|
|
44978
|
+
try {
|
|
44979
|
+
name = parseVarsName(nameArg);
|
|
44980
|
+
if (verb !== "delete") exactValue = parseVarsValue(value);
|
|
44981
|
+
} catch (e) {
|
|
44982
|
+
return fail(`vars ${verb}: ${e.message}`);
|
|
44983
|
+
}
|
|
44984
|
+
const projects = await deps.projects();
|
|
44985
|
+
const repo = resolveRepoOrFail(repoArg, projects, verb);
|
|
44986
|
+
const ops = { transport: await deps.appClient(commandPath4), mintPermission: "variables:write", ...deps.actor ? { actor: deps.actor() } : {} };
|
|
44987
|
+
const receipt = verb === "create" ? await createVariableReceipt(ops, repo, name, exactValue) : verb === "update" ? await updateVariableReceipt(ops, repo, name, exactValue) : await deleteVariableReceipt(ops, repo, name);
|
|
44988
|
+
if (o.json) console.log(JSON.stringify(receipt, null, 2));
|
|
44989
|
+
else printMutationText(receipt);
|
|
44990
|
+
if (receipt.outcome !== "verified") process.exitCode = 1;
|
|
44991
|
+
};
|
|
44992
|
+
vars.command("get <name>").description("exact variable baseline or typed observed absence/unknown \u2014 a bare 404 is unknown; absence needs a complete enumeration (#9780)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runRead("vars get", "get", o.repo ?? "", name, o));
|
|
44993
|
+
vars.command("list").description("exhaustive bounded enumeration: names + provider timestamps only (never values), with pagination/completeness evidence (#9780)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (o) => runRead("vars list", "list", o.repo ?? "", void 0, o));
|
|
44994
|
+
vars.command("create <name>").description("create ONLY against an observed absent baseline (complete enumeration); value preserved exactly; verified by read-back (#9780)").requiredOption("--value <value>", "exact string value (never coerced; e.g. the literal `off`)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runMutation("vars create", "create", name, o.value, o.repo ?? "", o));
|
|
44995
|
+
vars.command("update <name>").description("replace ONLY against a retained present baseline, re-checked just before dispatch; concurrent changes stop the write (#9780)").requiredOption("--value <value>", "exact string value (never coerced)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runMutation("vars update", "update", name, o.value, o.repo ?? "", o));
|
|
44996
|
+
vars.command("delete <name>").description("delete ONLY against a retained present baseline; absence proven afterwards by a complete enumeration (#9780)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runMutation("vars delete", "delete", name, void 0, o.repo ?? "", o));
|
|
44997
|
+
}
|
|
44998
|
+
function printReadText(receipt) {
|
|
44999
|
+
if (receipt.operation === "get") {
|
|
45000
|
+
if (receipt.presence === "present") {
|
|
45001
|
+
console.log(`vars get: ${receipt.repo} ${receipt.name} = ${JSON.stringify(receipt.value ?? "")} (presence: present, source: ${receipt.observed?.source ?? "get"})`);
|
|
45002
|
+
} else if (receipt.presence === "absent") {
|
|
45003
|
+
const p = receipt.absenceProof;
|
|
45004
|
+
console.log(`vars get: ${receipt.repo} ${receipt.name} absent \u2014 proven by complete enumeration (${p.pages} page(s), ${p.totalVariables} variable(s), name excluded)`);
|
|
45005
|
+
} else {
|
|
45006
|
+
console.log(`vars get: ${receipt.repo} ${receipt.name} presence UNKNOWN (${receipt.unknownReason}${receipt.capability ? `, capability ${receipt.capability.outcome}` : ""}) \u2014 absence is never claimed from this read`);
|
|
45007
|
+
}
|
|
45008
|
+
return;
|
|
45009
|
+
}
|
|
45010
|
+
if (receipt.pagination.complete) {
|
|
45011
|
+
console.log(`vars list: ${receipt.repo} \u2014 ${receipt.pagination.totalVariables} variable(s), complete (${receipt.pagination.pages} page(s)): ${receipt.variables.map((v) => v.name).join(", ") || "(none)"}`);
|
|
45012
|
+
} else {
|
|
45013
|
+
console.log(`vars list: ${receipt.repo} \u2014 INCOMPLETE enumeration (${receipt.pagination.reason ?? "unknown"}${receipt.pagination.detail ? `: ${receipt.pagination.detail}` : ""}); the names below are NOT the full set: ${receipt.variables.map((v) => v.name).join(", ") || "(none)"}`);
|
|
45014
|
+
}
|
|
45015
|
+
}
|
|
45016
|
+
function printMutationText(receipt) {
|
|
45017
|
+
const baseline = receipt.baseline.presence === "present" ? `baseline ${JSON.stringify(receipt.baseline.value ?? "")}` : `baseline ${receipt.baseline.presence}`;
|
|
45018
|
+
const readback = receipt.readback ? `, read-back ${receipt.readback.presence}${receipt.readback.value !== void 0 ? ` ${JSON.stringify(receipt.readback.value)}` : ""}` : "";
|
|
45019
|
+
console.log(`vars ${receipt.operation}: ${receipt.repo} ${receipt.name} \u2014 ${receipt.outcome} (${baseline}${readback})${receipt.reason ? ` \u2014 ${receipt.reason}` : ""} [no atomic compare-and-swap; writes are never replayed]`);
|
|
45020
|
+
}
|
|
45021
|
+
|
|
45022
|
+
// src/repo-archive-status.ts
|
|
45023
|
+
var import_node_crypto14 = require("node:crypto");
|
|
45024
|
+
init_github_client();
|
|
45025
|
+
init_cli_shared();
|
|
45026
|
+
var ARCHIVE_STATUS_RECEIPT_SCHEMA = "mmi-cli.repo-archive-status/1";
|
|
45027
|
+
var ARCHIVE_SOURCE_TIME_NOTE = "GitHub exposes no archived_at; observedAt is this read's window end and the provider timestamps are the repository's own updated_at/pushed_at from the same response that carried the archived flag";
|
|
45028
|
+
async function call2(deps, log, method, path2) {
|
|
45029
|
+
try {
|
|
45030
|
+
const res = await deps.transport.restWithStatus(method, path2);
|
|
45031
|
+
log.push({ method, path: path2, status: res.status });
|
|
45032
|
+
return res;
|
|
45033
|
+
} catch (e) {
|
|
45034
|
+
if (e instanceof AppActorError) throw e;
|
|
45035
|
+
const status = e instanceof GitHubApiError ? e.status : 0;
|
|
45036
|
+
log.push({ method, path: path2, status });
|
|
45037
|
+
throw e;
|
|
45038
|
+
}
|
|
45039
|
+
}
|
|
45040
|
+
function classify2(e) {
|
|
45041
|
+
if (e instanceof AppActorError) {
|
|
45042
|
+
const status = e.mintRefusal?.status ?? 0;
|
|
45043
|
+
return { status, reason: "mint-refused", mint: { status, reason: e.mintRefusal?.reason ?? e.message } };
|
|
45044
|
+
}
|
|
45045
|
+
if (e instanceof GitHubApiError) {
|
|
45046
|
+
if (e.status === 404) return { status: 404, reason: "missing-or-invisible" };
|
|
45047
|
+
if (e.status === 403 || e.status === 401) return { status: e.status, reason: "denied" };
|
|
45048
|
+
return { status: e.status, reason: "transport-error" };
|
|
45049
|
+
}
|
|
45050
|
+
const name = e?.name ?? "";
|
|
45051
|
+
if (name === "TimeoutError" || name === "AbortError" || /timeout|abort/i.test(String(e?.message ?? ""))) return { status: 0, reason: "timeout" };
|
|
45052
|
+
return { status: 0, reason: "transport-error" };
|
|
45053
|
+
}
|
|
45054
|
+
async function readArchiveStatusReceipt(deps, repo) {
|
|
45055
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
45056
|
+
const startedAt = now();
|
|
45057
|
+
const log = [];
|
|
45058
|
+
const base = {
|
|
45059
|
+
schema: ARCHIVE_STATUS_RECEIPT_SCHEMA,
|
|
45060
|
+
repo,
|
|
45061
|
+
sourceTime: { observedAt: "", note: ARCHIVE_SOURCE_TIME_NOTE },
|
|
45062
|
+
window: { startedAt: startedAt.toISOString(), endedAt: "", atomicSnapshot: false },
|
|
45063
|
+
actor: deps.actor ?? { credential: "app_installation" },
|
|
45064
|
+
correlationId: (deps.correlationId ?? import_node_crypto14.randomUUID)(),
|
|
45065
|
+
upstream: log
|
|
45066
|
+
};
|
|
45067
|
+
try {
|
|
45068
|
+
const { body } = await call2(deps, log, "GET", `repos/${repo}`);
|
|
45069
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
45070
|
+
return { ...base, archived: "unknown", unknownReason: "parser-refused", sourceTime: { ...base.sourceTime, observedAt: now().toISOString() }, window: { ...base.window, endedAt: now().toISOString() } };
|
|
45071
|
+
}
|
|
45072
|
+
const payload = body;
|
|
45073
|
+
if (typeof payload.archived !== "boolean") {
|
|
45074
|
+
return { ...base, archived: "unknown", unknownReason: "parser-refused", sourceTime: { ...base.sourceTime, observedAt: now().toISOString() }, window: { ...base.window, endedAt: now().toISOString() } };
|
|
45075
|
+
}
|
|
45076
|
+
if (payload.updated_at !== void 0 && typeof payload.updated_at !== "string") payload.updated_at = void 0;
|
|
45077
|
+
if (payload.pushed_at !== void 0 && typeof payload.pushed_at !== "string") payload.pushed_at = void 0;
|
|
45078
|
+
return {
|
|
45079
|
+
...base,
|
|
45080
|
+
archived: payload.archived,
|
|
45081
|
+
sourceTime: {
|
|
45082
|
+
observedAt: now().toISOString(),
|
|
45083
|
+
...typeof payload.updated_at === "string" ? { providerUpdatedAt: payload.updated_at } : {},
|
|
45084
|
+
...typeof payload.pushed_at === "string" ? { providerPushedAt: payload.pushed_at } : {},
|
|
45085
|
+
note: ARCHIVE_SOURCE_TIME_NOTE
|
|
45086
|
+
},
|
|
45087
|
+
window: { ...base.window, endedAt: now().toISOString() }
|
|
45088
|
+
};
|
|
45089
|
+
} catch (e) {
|
|
45090
|
+
const { reason, mint } = classify2(e);
|
|
45091
|
+
return {
|
|
45092
|
+
...base,
|
|
45093
|
+
archived: "unknown",
|
|
45094
|
+
unknownReason: reason,
|
|
45095
|
+
...reason === "denied" ? { capability: { permission: "metadata:read", outcome: "denied" } } : {},
|
|
45096
|
+
...mint ? { capability: { permission: "metadata:read", outcome: "mint-refused", status: mint.status, reason: mint.reason } } : {},
|
|
45097
|
+
...reason === "missing-or-invisible" ? { sourceTime: { ...base.sourceTime, observedAt: now().toISOString(), note: `${ARCHIVE_SOURCE_TIME_NOTE} \u2014 HTTP 404: the repository is missing or not visible to the installation, which is NOT archived:false` } } : { sourceTime: { ...base.sourceTime, observedAt: now().toISOString() } },
|
|
45098
|
+
window: { ...base.window, endedAt: now().toISOString() }
|
|
45099
|
+
};
|
|
45100
|
+
}
|
|
45101
|
+
}
|
|
45102
|
+
function registerRepoArchiveStatusCommand(program3, deps) {
|
|
45103
|
+
const repoRoot2 = program3.command("repo").description("canonical repository typed reads \u2014 archive status (#9779)");
|
|
45104
|
+
repoRoot2.command("archive-status").description("typed archived-state read for one canonical repository: archived + source time, App metadata:read; a 404 is missing-or-invisible, never archived:false (#9779)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").action(async (o) => {
|
|
45105
|
+
const projects = await deps.projects();
|
|
45106
|
+
const resolution = resolveCanonicalRepo(o.repo ?? "", projects);
|
|
45107
|
+
const repo = resolution.kind === "resolved" ? resolution.repo : resolution.kind === "conflict" ? fail(`repo archive-status: '${o.repo ?? ""}' resolves to MULTIPLE registered repositories (${resolution.candidates.join(", ")}) \u2014 pass the exact owner/repo (#9779)`) : resolution.kind === "unavailable" ? fail("repo archive-status: the Hub project registry is unreadable; refusing to address a repo this command cannot prove registered") : fail(`repo archive-status: '${o.repo ?? ""}' is not a registered repository in the Hub registry \u2014 pass the exact owner/repo`);
|
|
45108
|
+
const receipt = await readArchiveStatusReceipt({ transport: await deps.appClient("repo archive-status"), ...deps.actor ? { actor: deps.actor() } : {} }, repo);
|
|
45109
|
+
if (o.json) console.log(JSON.stringify(receipt, null, 2));
|
|
45110
|
+
else if (receipt.archived === "unknown") console.log(`repo archive-status: ${receipt.repo} \u2014 archived state UNKNOWN (${receipt.unknownReason}${receipt.capability?.outcome === "mint-refused" ? `: App token mint refused${receipt.capability.status ? ` (HTTP ${receipt.capability.status})` : ""}: ${receipt.capability.reason}` : ""}) \u2014 never reads as not-archived`);
|
|
45111
|
+
else console.log(`repo archive-status: ${receipt.repo} \u2014 archived: ${receipt.archived} (observed ${receipt.sourceTime.observedAt}${receipt.sourceTime.providerUpdatedAt ? `, provider updated_at ${receipt.sourceTime.providerUpdatedAt}` : ""})`);
|
|
45112
|
+
if (receipt.archived === "unknown") process.exitCode = 1;
|
|
45113
|
+
});
|
|
45114
|
+
}
|
|
45115
|
+
|
|
45116
|
+
// src/merge-queue-lifecycle.ts
|
|
45117
|
+
var import_node_crypto15 = require("node:crypto");
|
|
45118
|
+
var import_node_fs35 = require("node:fs");
|
|
45119
|
+
var import_node_path32 = require("node:path");
|
|
45120
|
+
init_github_client();
|
|
45121
|
+
init_cli_shared();
|
|
45122
|
+
var LIFECYCLE_SCHEMA = "mmi-cli.merge-queue-lifecycle/1";
|
|
45123
|
+
var COHORT_SCHEMA = "mmi.merge-queue-cohort/1";
|
|
45124
|
+
var LIFECYCLE_SOURCE = {
|
|
45125
|
+
provider: "github-graphql",
|
|
45126
|
+
operation: "PullRequest.timelineItems",
|
|
45127
|
+
itemTypes: ["ADDED_TO_MERGE_QUEUE_EVENT", "REMOVED_FROM_MERGE_QUEUE_EVENT", "MERGED_EVENT", "HEAD_REF_FORCE_PUSHED_EVENT", "BASE_REF_CHANGED_EVENT"],
|
|
45128
|
+
schemaRef: "https://docs.github.com/public/fpt/schema.docs.graphql",
|
|
45129
|
+
version: "merge-queue-lifecycle-source/1",
|
|
45130
|
+
permission: "pull_requests:read"
|
|
45131
|
+
};
|
|
45132
|
+
var LIFECYCLE_QUERY = "query($owner:String!,$name:String!,$number:Int!,$after:String){repository(owner:$owner,name:$name){pullRequest(number:$number){number baseRefName mergedAt mergeCommit{oid} timelineItems(first:100,after:$after,itemTypes:[ADDED_TO_MERGE_QUEUE_EVENT,REMOVED_FROM_MERGE_QUEUE_EVENT,MERGED_EVENT,HEAD_REF_FORCE_PUSHED_EVENT,BASE_REF_CHANGED_EVENT]){pageInfo{hasNextPage endCursor} nodes{__typename ... on AddedToMergeQueueEvent{id createdAt} ... on RemovedFromMergeQueueEvent{id createdAt reason beforeCommit{oid}} ... on MergedEvent{id createdAt commit{oid}} ... on HeadRefForcePushedEvent{id createdAt beforeCommit{oid} afterCommit{oid}} ... on BaseRefChangedEvent{id createdAt previousRefName currentRefName}}}}}}";
|
|
45133
|
+
var UTC_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/;
|
|
45134
|
+
var SHA_RE2 = /^[0-9a-f]{40}$/;
|
|
45135
|
+
function parseUtc(raw, flag) {
|
|
45136
|
+
if (!UTC_RE.test(raw) || Number.isNaN(Date.parse(raw))) throw new Error(`${flag} must be a UTC timestamp like 2026-11-02T00:00:00Z \u2014 received '${raw.slice(0, 40)}'`);
|
|
45137
|
+
return raw;
|
|
45138
|
+
}
|
|
45139
|
+
function parseBound(raw, flag, max) {
|
|
45140
|
+
if (!/^\d+$/.test(raw) || Number(raw) < 1 || Number(raw) > max) throw new Error(`${flag} must be an integer 1-${max} \u2014 received '${raw.slice(0, 20)}'`);
|
|
45141
|
+
return Number(raw);
|
|
45142
|
+
}
|
|
45143
|
+
function optionalUtc(v, where) {
|
|
45144
|
+
if (v === void 0) return void 0;
|
|
45145
|
+
if (typeof v !== "string") throw new Error(`${where} must be a UTC timestamp string`);
|
|
45146
|
+
return parseUtc(v, where);
|
|
45147
|
+
}
|
|
45148
|
+
function parseCohortManifest(text) {
|
|
45149
|
+
let raw;
|
|
45150
|
+
try {
|
|
45151
|
+
raw = JSON.parse(text);
|
|
45152
|
+
} catch {
|
|
45153
|
+
throw new Error("cohort manifest is not valid JSON");
|
|
45154
|
+
}
|
|
45155
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("cohort manifest must be a JSON object");
|
|
45156
|
+
const m = raw;
|
|
45157
|
+
if (m.schema !== COHORT_SCHEMA) throw new Error(`cohort manifest schema must be '${COHORT_SCHEMA}'`);
|
|
45158
|
+
if (typeof m.repo !== "string" || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(m.repo)) throw new Error("cohort manifest repo must be owner/repo");
|
|
45159
|
+
if (typeof m.base_branch !== "string" || m.base_branch.trim() === "") throw new Error("cohort manifest base_branch is required");
|
|
45160
|
+
const receipt = m.measurement_receipt;
|
|
45161
|
+
if (!receipt || typeof receipt.id !== "string" || receipt.id.trim() === "" || typeof receipt.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(receipt.sha256)) {
|
|
45162
|
+
throw new Error("cohort manifest measurement_receipt needs an id and a 64-hex sha256 binding the existing measurement");
|
|
45163
|
+
}
|
|
45164
|
+
const since = parseUtc(String(m.since ?? ""), "manifest since");
|
|
45165
|
+
const until = parseUtc(String(m.until ?? ""), "manifest until");
|
|
45166
|
+
if (!Array.isArray(m.prs) || m.prs.length === 0) throw new Error("cohort manifest prs must list explicit PR identities");
|
|
45167
|
+
const seen = /* @__PURE__ */ new Set();
|
|
45168
|
+
const prs = m.prs.map((p, i) => {
|
|
45169
|
+
const row = p;
|
|
45170
|
+
if (!row || !Number.isInteger(row.number) || row.number < 1) throw new Error(`cohort manifest prs[${i}].number must be a positive integer`);
|
|
45171
|
+
const number = row.number;
|
|
45172
|
+
if (seen.has(number)) throw new Error(`cohort manifest lists PR #${number} twice`);
|
|
45173
|
+
seen.add(number);
|
|
45174
|
+
if (row.excluded !== void 0 && (typeof row.excluded !== "string" || row.excluded.trim() === "")) throw new Error(`cohort manifest prs[${i}].excluded must be a reason string`);
|
|
45175
|
+
return { number, ...typeof row.excluded === "string" ? { excluded: row.excluded } : {} };
|
|
45176
|
+
});
|
|
45177
|
+
const runs2 = m.runs === void 0 ? [] : m.runs;
|
|
45178
|
+
if (!Array.isArray(runs2)) throw new Error("cohort manifest runs must be an array");
|
|
45179
|
+
const parsedRuns = runs2.map((r, i) => {
|
|
45180
|
+
const row = r;
|
|
45181
|
+
const where = `cohort manifest runs[${i}]`;
|
|
45182
|
+
if (!row || !Number.isInteger(row.run_id) || !Number.isInteger(row.run_attempt) || row.run_attempt < 1) throw new Error(`${where} needs integer run_id and run_attempt >= 1`);
|
|
45183
|
+
if (typeof row.head_sha !== "string" || !SHA_RE2.test(row.head_sha)) throw new Error(`${where}.head_sha must be a full 40-hex SHA`);
|
|
45184
|
+
if (!Array.isArray(row.members)) throw new Error(`${where}.members must list explicit PR memberships`);
|
|
45185
|
+
const members = row.members.map((mem, j) => {
|
|
45186
|
+
const mm = mem;
|
|
45187
|
+
if (!mm || !Number.isInteger(mm.pr)) throw new Error(`${where}.members[${j}].pr must be an integer`);
|
|
45188
|
+
if (mm.admission_event_id !== void 0 && (typeof mm.admission_event_id !== "string" || mm.admission_event_id === "")) throw new Error(`${where}.members[${j}].admission_event_id must be a provider event id`);
|
|
45189
|
+
return { pr: mm.pr, ...typeof mm.admission_event_id === "string" ? { admission_event_id: mm.admission_event_id } : {} };
|
|
45190
|
+
});
|
|
45191
|
+
const started = optionalUtc(row.started_at, `${where}.started_at`);
|
|
45192
|
+
const failureResult = optionalUtc(row.failure_result_at, `${where}.failure_result_at`);
|
|
45193
|
+
const failureObserved = optionalUtc(row.failure_observed_at, `${where}.failure_observed_at`);
|
|
45194
|
+
return {
|
|
45195
|
+
run_id: row.run_id,
|
|
45196
|
+
run_attempt: row.run_attempt,
|
|
45197
|
+
head_sha: row.head_sha,
|
|
45198
|
+
members,
|
|
45199
|
+
...started ? { started_at: started } : {},
|
|
45200
|
+
...failureResult ? { failure_result_at: failureResult } : {},
|
|
45201
|
+
...failureObserved ? { failure_observed_at: failureObserved } : {}
|
|
45202
|
+
};
|
|
45203
|
+
});
|
|
45204
|
+
return { schema: COHORT_SCHEMA, repo: m.repo, base_branch: m.base_branch, measurement_receipt: { id: receipt.id, sha256: receipt.sha256 }, since, until, prs, runs: parsedRuns };
|
|
45205
|
+
}
|
|
45206
|
+
var oidOf = (v) => v && typeof v === "object" && typeof v.oid === "string" ? v.oid : null;
|
|
45207
|
+
function normalizeNodes(nodes, malformed) {
|
|
45208
|
+
const out = [];
|
|
45209
|
+
for (const node of nodes) {
|
|
45210
|
+
const n = node;
|
|
45211
|
+
if (!n || typeof n.id !== "string" || typeof n.createdAt !== "string") {
|
|
45212
|
+
malformed.push("event-missing-id-or-createdAt");
|
|
45213
|
+
continue;
|
|
45214
|
+
}
|
|
45215
|
+
const base = { id: n.id, at: n.createdAt };
|
|
45216
|
+
switch (n.__typename) {
|
|
45217
|
+
case "AddedToMergeQueueEvent":
|
|
45218
|
+
out.push({ kind: "enqueued", ...base });
|
|
45219
|
+
break;
|
|
45220
|
+
case "RemovedFromMergeQueueEvent":
|
|
45221
|
+
out.push({ kind: "removed", ...base, reason: typeof n.reason === "string" && n.reason !== "" ? n.reason : null, before_head: oidOf(n.beforeCommit) });
|
|
45222
|
+
break;
|
|
45223
|
+
case "MergedEvent":
|
|
45224
|
+
out.push({ kind: "merged", ...base, merge_sha: oidOf(n.commit) });
|
|
45225
|
+
break;
|
|
45226
|
+
case "HeadRefForcePushedEvent":
|
|
45227
|
+
out.push({ kind: "head_changed", ...base, before: oidOf(n.beforeCommit), after: oidOf(n.afterCommit) });
|
|
45228
|
+
break;
|
|
45229
|
+
case "BaseRefChangedEvent":
|
|
45230
|
+
if (typeof n.previousRefName !== "string" || typeof n.currentRefName !== "string") {
|
|
45231
|
+
malformed.push("base-change-missing-refs");
|
|
45232
|
+
break;
|
|
45233
|
+
}
|
|
45234
|
+
out.push({ kind: "base_changed", ...base, from: n.previousRefName, to: n.currentRefName });
|
|
45235
|
+
break;
|
|
45236
|
+
default:
|
|
45237
|
+
malformed.push(`unexpected-typename:${String(n.__typename).slice(0, 40)}`);
|
|
45238
|
+
}
|
|
45239
|
+
}
|
|
45240
|
+
return out;
|
|
45241
|
+
}
|
|
45242
|
+
function classifyError(e) {
|
|
45243
|
+
if (e instanceof GitHubApiError) {
|
|
45244
|
+
if (e.rateLimited) return { code: "rate-limited", status: e.status };
|
|
45245
|
+
const types = (e.graphqlErrors ?? []).map((g) => g.type ?? "");
|
|
45246
|
+
if (e.status === 401 || e.status === 403 || types.includes("FORBIDDEN")) return { code: "permission-denied", status: e.status };
|
|
45247
|
+
if (e.status === 404 || types.includes("NOT_FOUND")) return { code: "not-found-or-masked", status: e.status };
|
|
45248
|
+
if (types.some((t) => t !== "")) return { code: "unsupported-source", status: e.status };
|
|
45249
|
+
if (/did not include data/i.test(e.message)) return { code: "malformed-response", status: e.status };
|
|
45250
|
+
return { code: "transport-error", status: e.status };
|
|
45251
|
+
}
|
|
45252
|
+
const name = e?.name ?? "";
|
|
45253
|
+
if (name === "TimeoutError" || name === "AbortError" || /timeout|timed out|abort/i.test(String(e?.message ?? ""))) return { code: "timeout", status: 0 };
|
|
45254
|
+
return { code: "transport-error", status: 0 };
|
|
45255
|
+
}
|
|
45256
|
+
function classifyRemovalReason(reason, headChangedBeforeReentry) {
|
|
45257
|
+
if (headChangedBeforeReentry) return "head-change";
|
|
45258
|
+
if (reason === null) return "unknown";
|
|
45259
|
+
if (/manual|dequeued by|removed by|user/i.test(reason)) return "manual";
|
|
45260
|
+
if (/fail|timed? ?out|timeout|check|status/i.test(reason)) return "failure-linked";
|
|
45261
|
+
if (/conflict|protection|invalid|base|push|commit|head/i.test(reason)) return "invalidation";
|
|
45262
|
+
return "unknown";
|
|
45263
|
+
}
|
|
45264
|
+
var secondsBetween = (from, to) => {
|
|
45265
|
+
const d = (Date.parse(to) - Date.parse(from)) / 1e3;
|
|
45266
|
+
if (!Number.isFinite(d)) return { value: null, reason: "unparseable-timestamp" };
|
|
45267
|
+
if (d < 0) return { value: null, reason: "negative-duration" };
|
|
45268
|
+
return { value: d };
|
|
45269
|
+
};
|
|
45270
|
+
var ZERO_BOUNCES = () => ({ manual: 0, "failure-linked": 0, "head-change": 0, invalidation: 0, unknown: 0 });
|
|
45271
|
+
function buildPrLifecycle(state, manifest, joins) {
|
|
45272
|
+
const entry = manifest.prs.find((p) => p.number === state.number);
|
|
45273
|
+
const anomalies = [...state.malformed ?? []];
|
|
45274
|
+
const events = state.events.map((e, i) => ({ e, i })).filter(({ e }) => {
|
|
45275
|
+
if (UTC_RE.test(e.at) && !Number.isNaN(Date.parse(e.at))) return true;
|
|
45276
|
+
anomalies.push(`malformed-timestamp:${e.id}`);
|
|
45277
|
+
return false;
|
|
45278
|
+
});
|
|
45279
|
+
for (let k = 1; k < events.length; k++) if (Date.parse(events[k].e.at) < Date.parse(events[k - 1].e.at)) anomalies.push(`out-of-order:${events[k].e.id}`);
|
|
45280
|
+
const ordered = events.sort((a, b) => Date.parse(a.e.at) - Date.parse(b.e.at) || a.i - b.i).map(({ e }) => e);
|
|
45281
|
+
const prBase = state.pr?.base_ref ?? null;
|
|
45282
|
+
const baseChanges = ordered.filter((e) => e.kind === "base_changed");
|
|
45283
|
+
const baseAt = (at) => {
|
|
45284
|
+
const next = baseChanges.find((c) => Date.parse(c.at) > Date.parse(at));
|
|
45285
|
+
if (next) return { value: next.from, reason: "derived-from-base-ref-changed-event" };
|
|
45286
|
+
return prBase === null ? { value: null, reason: "base-unavailable" } : { value: prBase, reason: baseChanges.length ? "derived-from-base-ref-changed-event" : "current-base-no-recorded-change" };
|
|
45287
|
+
};
|
|
45288
|
+
const episodes = [];
|
|
45289
|
+
let open2 = null;
|
|
45290
|
+
let leftCensored = false;
|
|
45291
|
+
let sawQueueEvent = false;
|
|
45292
|
+
let mergedEvent = null;
|
|
45293
|
+
for (const e of ordered) {
|
|
45294
|
+
if (e.kind === "enqueued") {
|
|
45295
|
+
if (open2) {
|
|
45296
|
+
anomalies.push(`admission-without-removal:${e.id}`);
|
|
45297
|
+
episodes.push(open2);
|
|
45298
|
+
}
|
|
45299
|
+
const prev = open2 ?? episodes[episodes.length - 1] ?? null;
|
|
45300
|
+
const headChanged = prev ? ordered.some((h) => h.kind === "head_changed" && Date.parse(h.at) >= Date.parse(prev.terminal.at ?? prev.enqueued_at) && Date.parse(h.at) <= Date.parse(e.at)) : null;
|
|
45301
|
+
open2 = {
|
|
45302
|
+
episode_id: e.id,
|
|
45303
|
+
enqueued_at: e.at,
|
|
45304
|
+
head_sha: { value: null, reason: "source-omits-admission-head (AddedToMergeQueueEvent has no commit field)" },
|
|
45305
|
+
base: baseAt(e.at),
|
|
45306
|
+
terminal: { type: null, at: null, event_id: null, reason: null, reason_class: null, head_at_removal: null },
|
|
45307
|
+
reentry_of: prev && prev.terminal.type === "removed" ? prev.episode_id : null,
|
|
45308
|
+
head_changed_since_previous_episode: headChanged,
|
|
45309
|
+
runs: [],
|
|
45310
|
+
queue_wait_to_execution_seconds: { value: null, reason: "no-explicit-run-join" }
|
|
45311
|
+
};
|
|
45312
|
+
sawQueueEvent = true;
|
|
45313
|
+
} else if (e.kind === "removed") {
|
|
45314
|
+
if (!open2) {
|
|
45315
|
+
if (!sawQueueEvent) leftCensored = true;
|
|
45316
|
+
anomalies.push(`removal-without-admission:${e.id}`);
|
|
45317
|
+
sawQueueEvent = true;
|
|
45318
|
+
continue;
|
|
45319
|
+
}
|
|
45320
|
+
open2.terminal = { type: "removed", at: e.at, event_id: e.id, reason: e.reason, reason_class: null, head_at_removal: e.before_head };
|
|
45321
|
+
episodes.push(open2);
|
|
45322
|
+
open2 = null;
|
|
45323
|
+
} else if (e.kind === "merged") {
|
|
45324
|
+
mergedEvent = e;
|
|
45325
|
+
if (open2) {
|
|
45326
|
+
open2.terminal = { type: "merged", at: e.at, event_id: e.id, reason: null, reason_class: null, head_at_removal: null };
|
|
45327
|
+
episodes.push(open2);
|
|
45328
|
+
open2 = null;
|
|
45329
|
+
}
|
|
45330
|
+
}
|
|
45331
|
+
}
|
|
45332
|
+
if (open2) episodes.push(open2);
|
|
45333
|
+
let bounceCount = 0;
|
|
45334
|
+
const byReason = ZERO_BOUNCES();
|
|
45335
|
+
episodes.forEach((ep, idx) => {
|
|
45336
|
+
if (ep.terminal.type !== "removed") return;
|
|
45337
|
+
const next = episodes[idx + 1];
|
|
45338
|
+
const headChange = next ? next.head_changed_since_previous_episode === true : false;
|
|
45339
|
+
ep.terminal.reason_class = classifyRemovalReason(ep.terminal.reason, headChange);
|
|
45340
|
+
if (next && next.reentry_of === ep.episode_id) {
|
|
45341
|
+
bounceCount++;
|
|
45342
|
+
byReason[ep.terminal.reason_class]++;
|
|
45343
|
+
}
|
|
45344
|
+
});
|
|
45345
|
+
for (const run of manifest.runs) {
|
|
45346
|
+
for (const member of run.members) {
|
|
45347
|
+
if (member.pr !== state.number) continue;
|
|
45348
|
+
if (!member.admission_event_id) continue;
|
|
45349
|
+
const ep = episodes.find((x) => x.episode_id === member.admission_event_id);
|
|
45350
|
+
const join54 = joins.find((j) => j.run_id === run.run_id && j.run_attempt === run.run_attempt && j.pr === member.pr && j.admission_event_id === member.admission_event_id);
|
|
45351
|
+
if (!join54) continue;
|
|
45352
|
+
if (!ep) {
|
|
45353
|
+
if (state.status === "complete") {
|
|
45354
|
+
join54.status = "unresolved";
|
|
45355
|
+
join54.reason = "admission-event-not-in-history";
|
|
45356
|
+
} else {
|
|
45357
|
+
join54.status = "unresolved";
|
|
45358
|
+
join54.reason = "history-incomplete";
|
|
45359
|
+
}
|
|
45360
|
+
continue;
|
|
45361
|
+
}
|
|
45362
|
+
join54.status = "joined";
|
|
45363
|
+
delete join54.reason;
|
|
45364
|
+
ep.runs.push({ run_id: run.run_id, run_attempt: run.run_attempt, head_sha: run.head_sha });
|
|
45365
|
+
}
|
|
45366
|
+
}
|
|
45367
|
+
const runOf = (ep) => manifest.runs.filter((r) => ep.runs.some((x) => x.run_id === r.run_id && x.run_attempt === r.run_attempt));
|
|
45368
|
+
const earliest = (ep, field) => {
|
|
45369
|
+
const vals = runOf(ep).map((r) => r[field]).filter((v) => typeof v === "string").sort((a, b) => Date.parse(a) - Date.parse(b));
|
|
45370
|
+
return vals[0] ?? null;
|
|
45371
|
+
};
|
|
45372
|
+
for (const ep of episodes) {
|
|
45373
|
+
if (!ep.runs.length) continue;
|
|
45374
|
+
const started = earliest(ep, "started_at");
|
|
45375
|
+
ep.queue_wait_to_execution_seconds = started ? secondsBetween(ep.enqueued_at, started) : { value: null, reason: "joined-run-has-no-execution-start" };
|
|
45376
|
+
}
|
|
45377
|
+
const mergedAt = state.pr?.merged_at ?? null;
|
|
45378
|
+
let cohort;
|
|
45379
|
+
let cohortReason;
|
|
45380
|
+
if (entry.excluded || state.status === "excluded") {
|
|
45381
|
+
cohort = "excluded";
|
|
45382
|
+
cohortReason = `manifest-exclusion: ${entry.excluded ?? "excluded"}`;
|
|
45383
|
+
} else if (state.status !== "complete") {
|
|
45384
|
+
cohort = "unknown";
|
|
45385
|
+
cohortReason = state.status === "failed" || state.status === "unsupported" ? `lookup-${state.status}:${state.error?.code ?? "unknown"}` : "history-incomplete";
|
|
45386
|
+
} else if (mergedAt === null) {
|
|
45387
|
+
cohort = "right-censored";
|
|
45388
|
+
cohortReason = "not-merged-pending-or-closed";
|
|
45389
|
+
} else if (Date.parse(mergedAt) >= Date.parse(manifest.until)) {
|
|
45390
|
+
cohort = "right-censored";
|
|
45391
|
+
cohortReason = "merged-at-or-after-until";
|
|
45392
|
+
} else if (Date.parse(mergedAt) < Date.parse(manifest.since)) {
|
|
45393
|
+
cohort = "outside-window";
|
|
45394
|
+
cohortReason = "merged-before-since";
|
|
45395
|
+
} else cohort = "eligible";
|
|
45396
|
+
const blocked = () => {
|
|
45397
|
+
if (state.status === "excluded") return "excluded-by-manifest";
|
|
45398
|
+
if (state.status !== "complete") return state.status === "incomplete" || state.status === "pending" ? "history-incomplete" : `lookup-${state.status}`;
|
|
45399
|
+
if (mergedAt === null) return "not-merged";
|
|
45400
|
+
if (anomalies.some((a) => a.startsWith("malformed") || a.startsWith("event-missing") || a.startsWith("unexpected-typename") || a.startsWith("base-change-missing"))) return "malformed-history";
|
|
45401
|
+
if (anomalies.some((a) => a.startsWith("out-of-order") || a.startsWith("admission-without-removal"))) return "contradictory-ordering";
|
|
45402
|
+
if (mergedEvent && mergedEvent.at !== mergedAt && Date.parse(mergedEvent.at) !== Date.parse(mergedAt)) return "conflicting-merge-time";
|
|
45403
|
+
if (mergedEvent && state.pr?.merge_sha && mergedEvent.merge_sha && mergedEvent.merge_sha !== state.pr.merge_sha) return "conflicting-merge-sha";
|
|
45404
|
+
return null;
|
|
45405
|
+
};
|
|
45406
|
+
const block = blocked();
|
|
45407
|
+
const nul = (reason) => ({ value: null, reason });
|
|
45408
|
+
let first;
|
|
45409
|
+
if (block) first = nul(block);
|
|
45410
|
+
else if (leftCensored) first = nul("left-censored: removal precedes any recorded admission");
|
|
45411
|
+
else if (!episodes.length) first = nul("no-verified-admission-in-history (not proof of queue bypass)");
|
|
45412
|
+
else first = secondsBetween(episodes[0].enqueued_at, mergedAt);
|
|
45413
|
+
const successful = episodes.filter((ep) => ep.terminal.type === "merged");
|
|
45414
|
+
let success;
|
|
45415
|
+
let successEp = null;
|
|
45416
|
+
if (block) success = nul(block);
|
|
45417
|
+
else if (successful.length !== 1) success = nul(successful.length ? "ambiguous-successful-episode" : "no-episode-terminated-by-merge");
|
|
45418
|
+
else {
|
|
45419
|
+
successEp = successful[0];
|
|
45420
|
+
const headMoved = ordered.some((h) => h.kind === "head_changed" && Date.parse(h.at) > Date.parse(successEp.enqueued_at) && Date.parse(h.at) <= Date.parse(mergedAt));
|
|
45421
|
+
success = headMoved ? nul("head-changed-within-successful-episode") : secondsBetween(successEp.enqueued_at, mergedAt);
|
|
45422
|
+
}
|
|
45423
|
+
const wait = block ? nul(block) : successEp ? successEp.queue_wait_to_execution_seconds : nul("no-successful-episode");
|
|
45424
|
+
const failureEp = episodes.find((ep) => earliest(ep, "failure_result_at") !== null);
|
|
45425
|
+
const observedEp = episodes.find((ep) => earliest(ep, "failure_observed_at") !== null);
|
|
45426
|
+
const failureResult = failureEp ? secondsBetween(failureEp.enqueued_at, earliest(failureEp, "failure_result_at")) : nul("no-joined-failure-result-evidence");
|
|
45427
|
+
const failureObserved = observedEp ? secondsBetween(observedEp.enqueued_at, earliest(observedEp, "failure_observed_at")) : nul("no-joined-failure-observation-evidence");
|
|
45428
|
+
const historyUsable = state.status === "complete" && !leftCensored && !(block && block !== "not-merged");
|
|
45429
|
+
const failureBlock = block === "not-merged" ? null : block;
|
|
45430
|
+
return {
|
|
45431
|
+
number: state.number,
|
|
45432
|
+
lookup: state.status,
|
|
45433
|
+
...state.error ? { error: state.error } : {},
|
|
45434
|
+
cohort,
|
|
45435
|
+
...cohortReason ? { cohort_reason: cohortReason } : {},
|
|
45436
|
+
merged_at: mergedAt,
|
|
45437
|
+
merge_sha: state.pr?.merge_sha ?? null,
|
|
45438
|
+
base_ref: prBase,
|
|
45439
|
+
head_transitions: ordered.filter((e) => e.kind === "head_changed").map((e) => ({ event_id: e.id, at: e.at, before: e.before, after: e.after })),
|
|
45440
|
+
base_transitions: baseChanges.map((e) => ({ event_id: e.id, at: e.at, from: e.from, to: e.to })),
|
|
45441
|
+
episodes,
|
|
45442
|
+
events: ordered,
|
|
45443
|
+
anomalies,
|
|
45444
|
+
metrics: {
|
|
45445
|
+
first_enqueue_to_merge_seconds: first,
|
|
45446
|
+
successful_episode_enqueue_to_merge_seconds: success,
|
|
45447
|
+
queue_wait_to_execution_seconds: wait,
|
|
45448
|
+
enqueue_to_failure_result_seconds: { ...failureBlock ? nul(failureBlock) : failureResult, semantics: "provider-completion-time" },
|
|
45449
|
+
enqueue_to_first_observed_failure_seconds: { ...failureBlock ? nul(failureBlock) : failureObserved, semantics: "measurement-poll-observation" },
|
|
45450
|
+
bounce_count: historyUsable ? bounceCount : null,
|
|
45451
|
+
bounce_by_reason: historyUsable ? byReason : null
|
|
45452
|
+
}
|
|
45453
|
+
};
|
|
45454
|
+
}
|
|
45455
|
+
function median(values) {
|
|
45456
|
+
if (!values.length) return null;
|
|
45457
|
+
const s = [...values].sort((a, b) => a - b);
|
|
45458
|
+
const mid = Math.floor(s.length / 2);
|
|
45459
|
+
return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
|
|
45460
|
+
}
|
|
45461
|
+
function summarizeMetric(prs, key, definition, complete) {
|
|
45462
|
+
const values = [];
|
|
45463
|
+
const missing = {};
|
|
45464
|
+
for (const pr of prs) {
|
|
45465
|
+
if (pr.cohort !== "eligible") continue;
|
|
45466
|
+
const m = pr.metrics[key];
|
|
45467
|
+
if (m.value === null) missing[m.reason ?? "unknown"] = (missing[m.reason ?? "unknown"] ?? 0) + 1;
|
|
45468
|
+
else values.push(m.value);
|
|
45469
|
+
}
|
|
45470
|
+
const count = (c) => prs.filter((p) => p.cohort === c).length;
|
|
45471
|
+
const missingTotal = Object.values(missing).reduce((a, b) => a + b, 0);
|
|
45472
|
+
return {
|
|
45473
|
+
definition,
|
|
45474
|
+
label: complete && missingTotal === 0 && count("unknown") === 0 ? "completed-cohort-median" : "available-case-median",
|
|
45475
|
+
median_seconds: median(values),
|
|
45476
|
+
cohort_denominator: prs.length,
|
|
45477
|
+
eligible: values.length,
|
|
45478
|
+
missing_by_reason: missing,
|
|
45479
|
+
right_censored: count("right-censored"),
|
|
45480
|
+
excluded: count("excluded"),
|
|
45481
|
+
outside_window: count("outside-window"),
|
|
45482
|
+
unknown: count("unknown")
|
|
45483
|
+
};
|
|
45484
|
+
}
|
|
45485
|
+
async function readMergeQueueLifecycle(deps, req) {
|
|
45486
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
45487
|
+
const started = now();
|
|
45488
|
+
const deadlineMs = started.getTime() + req.deadlineSeconds * 1e3;
|
|
45489
|
+
const [owner, name] = req.repo.split("/");
|
|
45490
|
+
const prior = new Map((req.resume?.prs ?? []).map((p) => [p.number, p]));
|
|
45491
|
+
const states = req.manifest.prs.map((p) => {
|
|
45492
|
+
const was = prior.get(p.number);
|
|
45493
|
+
if (was && was.status !== "incomplete" && was.status !== "pending") return was;
|
|
45494
|
+
if (was) return { ...was, status: "pending" };
|
|
45495
|
+
return { number: p.number, status: "pending", pages: 0, cursors: [], next_cursor: null, events: [], duplicates_dropped: 0 };
|
|
45496
|
+
});
|
|
45497
|
+
let pages = 0;
|
|
45498
|
+
let eventCount = 0;
|
|
45499
|
+
const exhausted = /* @__PURE__ */ new Set();
|
|
45500
|
+
for (const st of states) {
|
|
45501
|
+
if (st.status !== "pending") continue;
|
|
45502
|
+
if (req.manifest.prs.find((p) => p.number === st.number)?.excluded) {
|
|
45503
|
+
st.status = "excluded";
|
|
45504
|
+
continue;
|
|
45505
|
+
}
|
|
45506
|
+
const ids = new Set(st.events.map((e) => e.id));
|
|
45507
|
+
for (; ; ) {
|
|
45508
|
+
if (exhausted.size) break;
|
|
45509
|
+
if (pages >= req.maxPages) {
|
|
45510
|
+
exhausted.add("max-pages");
|
|
45511
|
+
break;
|
|
45512
|
+
}
|
|
45513
|
+
if (eventCount >= req.maxEvents) {
|
|
45514
|
+
exhausted.add("max-events");
|
|
45515
|
+
break;
|
|
45516
|
+
}
|
|
45517
|
+
const remainingMs = deadlineMs - now().getTime();
|
|
45518
|
+
if (remainingMs <= 0) {
|
|
45519
|
+
exhausted.add("deadline");
|
|
45520
|
+
break;
|
|
45521
|
+
}
|
|
45522
|
+
const after = st.next_cursor;
|
|
45523
|
+
let page;
|
|
45524
|
+
try {
|
|
45525
|
+
page = await deps.transport.graphql(LIFECYCLE_QUERY, { owner, name, number: st.number, after }, { timeoutMs: Math.min(6e4, remainingMs) });
|
|
45526
|
+
} catch (e) {
|
|
45527
|
+
const err = classifyError(e);
|
|
45528
|
+
if (err.code === "rate-limited") {
|
|
45529
|
+
exhausted.add("rate-limited");
|
|
45530
|
+
st.error = err;
|
|
45531
|
+
break;
|
|
45532
|
+
}
|
|
45533
|
+
st.status = err.code === "unsupported-source" ? "unsupported" : "failed";
|
|
45534
|
+
st.error = err;
|
|
45535
|
+
break;
|
|
45536
|
+
}
|
|
45537
|
+
pages++;
|
|
45538
|
+
st.pages++;
|
|
45539
|
+
st.cursors.push(after);
|
|
45540
|
+
const pr = page?.repository?.pullRequest;
|
|
45541
|
+
const items = pr?.timelineItems;
|
|
45542
|
+
const hasNext = items?.pageInfo?.hasNextPage;
|
|
45543
|
+
const endCursor = items?.pageInfo?.endCursor;
|
|
45544
|
+
if (!pr || typeof pr.baseRefName !== "string" || !items || !Array.isArray(items.nodes) || typeof hasNext !== "boolean" || hasNext && typeof endCursor !== "string") {
|
|
45545
|
+
st.status = "failed";
|
|
45546
|
+
st.error = { code: pr === null ? "not-found-or-masked" : "malformed-response", status: 200 };
|
|
45547
|
+
break;
|
|
45548
|
+
}
|
|
45549
|
+
st.pr = { base_ref: pr.baseRefName, merged_at: typeof pr.mergedAt === "string" ? pr.mergedAt : null, merge_sha: oidOf(pr.mergeCommit) };
|
|
45550
|
+
const malformed = st.malformed ?? [];
|
|
45551
|
+
for (const ev of normalizeNodes(items.nodes, malformed)) {
|
|
45552
|
+
if (ids.has(ev.id)) {
|
|
45553
|
+
st.duplicates_dropped++;
|
|
45554
|
+
continue;
|
|
45555
|
+
}
|
|
45556
|
+
ids.add(ev.id);
|
|
45557
|
+
st.events.push(ev);
|
|
45558
|
+
eventCount++;
|
|
45559
|
+
}
|
|
45560
|
+
if (malformed.length) st.malformed = malformed;
|
|
45561
|
+
if (!hasNext) {
|
|
45562
|
+
st.status = "complete";
|
|
45563
|
+
st.next_cursor = null;
|
|
45564
|
+
break;
|
|
45565
|
+
}
|
|
45566
|
+
if (endCursor === after) {
|
|
45567
|
+
st.status = "failed";
|
|
45568
|
+
st.error = { code: "malformed-response", status: 200 };
|
|
45569
|
+
break;
|
|
45570
|
+
}
|
|
45571
|
+
st.next_cursor = endCursor;
|
|
45572
|
+
}
|
|
45573
|
+
if (st.status === "pending") st.status = "incomplete";
|
|
45574
|
+
}
|
|
45575
|
+
const joins = [];
|
|
45576
|
+
const cohortNumbers = new Set(req.manifest.prs.map((p) => p.number));
|
|
45577
|
+
for (const run of req.manifest.runs) {
|
|
45578
|
+
if (!run.members.length) {
|
|
45579
|
+
joins.push({ run_id: run.run_id, run_attempt: run.run_attempt, pr: null, admission_event_id: null, status: "unresolved", reason: "no-explicit-pr-membership" });
|
|
45580
|
+
continue;
|
|
45581
|
+
}
|
|
45582
|
+
const prsInRun = run.members.map((m) => m.pr);
|
|
45583
|
+
for (const member of run.members) {
|
|
45584
|
+
const base = { run_id: run.run_id, run_attempt: run.run_attempt, pr: member.pr, admission_event_id: member.admission_event_id ?? null };
|
|
45585
|
+
if (!cohortNumbers.has(member.pr)) joins.push({ ...base, status: "unresolved", reason: "pr-not-in-cohort" });
|
|
45586
|
+
else if (prsInRun.filter((n) => n === member.pr).length > 1) joins.push({ ...base, status: "unresolved", reason: "conflicting-membership" });
|
|
45587
|
+
else if (!member.admission_event_id) joins.push({ ...base, status: "unresolved", reason: "no-explicit-episode-identity" });
|
|
45588
|
+
else joins.push({ ...base, status: "unresolved", reason: "pending-history" });
|
|
45589
|
+
}
|
|
45590
|
+
}
|
|
45591
|
+
const joinable = {
|
|
45592
|
+
...req.manifest,
|
|
45593
|
+
runs: req.manifest.runs.map((r) => ({ ...r, members: r.members.filter((m) => joins.some((j) => j.run_id === r.run_id && j.run_attempt === r.run_attempt && j.pr === m.pr && j.reason === "pending-history")) }))
|
|
45594
|
+
};
|
|
45595
|
+
const prs = states.map((st) => buildPrLifecycle(st, joinable, joins));
|
|
45596
|
+
for (const j of joins) if (j.reason === "pending-history") j.reason = "history-incomplete";
|
|
45597
|
+
const complete = !exhausted.size && states.every((s) => s.status === "complete" || s.status === "excluded");
|
|
45598
|
+
const bounceTotals = ZERO_BOUNCES();
|
|
45599
|
+
let bounceTotal = 0;
|
|
45600
|
+
let bouncePrs = 0;
|
|
45601
|
+
for (const pr of prs) {
|
|
45602
|
+
if (pr.metrics.bounce_count === null || pr.cohort === "excluded") continue;
|
|
45603
|
+
bouncePrs++;
|
|
45604
|
+
bounceTotal += pr.metrics.bounce_count;
|
|
45605
|
+
for (const [k, v] of Object.entries(pr.metrics.bounce_by_reason)) bounceTotals[k] += v;
|
|
45606
|
+
}
|
|
45607
|
+
return {
|
|
45608
|
+
schema: LIFECYCLE_SCHEMA,
|
|
45609
|
+
repo: req.repo,
|
|
45610
|
+
provider: "github.com",
|
|
45611
|
+
base_branch: req.manifest.base_branch,
|
|
45612
|
+
cohort: { manifest_sha256: req.manifestSha256, measurement_receipt: req.manifest.measurement_receipt, pr_count: req.manifest.prs.length, run_count: req.manifest.runs.length },
|
|
45613
|
+
window: { since: req.since, until: req.until, semantics: "half-open [since, until) over merged_at; queue history is read from each PR timeline start, not truncated at since" },
|
|
45614
|
+
observation: { started_at: started.toISOString(), ended_at: now().toISOString() },
|
|
45615
|
+
source: LIFECYCLE_SOURCE,
|
|
45616
|
+
actor: deps.actor ?? { credential: "app_installation" },
|
|
45617
|
+
correlation_id: (deps.correlationId ?? import_node_crypto15.randomUUID)(),
|
|
45618
|
+
bounds: { max_pages: req.maxPages, max_events: req.maxEvents, deadline_seconds: req.deadlineSeconds },
|
|
45619
|
+
counts: { pages, events: eventCount, duplicates_dropped: states.reduce((a, s) => a + s.duplicates_dropped, 0) },
|
|
45620
|
+
complete,
|
|
45621
|
+
exhausted: [...exhausted],
|
|
45622
|
+
continuation: complete ? null : {
|
|
45623
|
+
repo: req.repo,
|
|
45624
|
+
manifest_sha256: req.manifestSha256,
|
|
45625
|
+
since: req.since,
|
|
45626
|
+
until: req.until,
|
|
45627
|
+
source_version: LIFECYCLE_SOURCE.version,
|
|
45628
|
+
remaining: states.filter((s) => s.status === "incomplete" || s.status === "pending").map((s) => ({ pr: s.number, after: s.next_cursor }))
|
|
45629
|
+
},
|
|
45630
|
+
resumed_from: req.resume?.receipt_sha256 ?? null,
|
|
45631
|
+
prs,
|
|
45632
|
+
unresolved_joins: joins.filter((j) => j.status === "unresolved"),
|
|
45633
|
+
joins,
|
|
45634
|
+
medians: {
|
|
45635
|
+
first_enqueue_to_merge_seconds: summarizeMetric(prs, "first_enqueue_to_merge_seconds", "merged_at minus the first verified admission of the complete PR queue journey (re-entries included)", complete),
|
|
45636
|
+
successful_episode_enqueue_to_merge_seconds: summarizeMetric(prs, "successful_episode_enqueue_to_merge_seconds", "merged_at minus the admission of the unique episode terminated by the merge", complete),
|
|
45637
|
+
queue_wait_to_execution_seconds: summarizeMetric(prs, "queue_wait_to_execution_seconds", "successful-episode admission to the earliest execution start of an explicitly joined run", complete)
|
|
45638
|
+
},
|
|
45639
|
+
bounces: { total: bounceTotal, by_reason: bounceTotals, prs_counted: bouncePrs },
|
|
45640
|
+
checkpoint: { prs: states }
|
|
45641
|
+
};
|
|
45642
|
+
}
|
|
45643
|
+
var sha256 = (text) => (0, import_node_crypto15.createHash)("sha256").update(text).digest("hex");
|
|
45644
|
+
function writeLifecycleReceipt(path2, receipt) {
|
|
45645
|
+
const text = `${JSON.stringify(receipt, null, 2)}
|
|
45646
|
+
`;
|
|
45647
|
+
(0, import_node_fs35.mkdirSync)((0, import_node_path32.dirname)(path2), { recursive: true });
|
|
45648
|
+
(0, import_node_fs35.writeFileSync)(path2, text, { encoding: "utf8", flag: "wx" });
|
|
45649
|
+
const back = (0, import_node_fs35.readFileSync)(path2);
|
|
45650
|
+
if (back.toString("utf8") !== text) throw new Error(`receipt read-back mismatch at ${path2}`);
|
|
45651
|
+
return { path: path2, sha256: sha256(back), bytes: back.length };
|
|
45652
|
+
}
|
|
45653
|
+
function loadCheckpoint(text, scope) {
|
|
45654
|
+
let parsed;
|
|
45655
|
+
try {
|
|
45656
|
+
parsed = JSON.parse(text);
|
|
45657
|
+
} catch {
|
|
45658
|
+
throw new Error("checkpoint receipt is not valid JSON");
|
|
45659
|
+
}
|
|
45660
|
+
const r = parsed.result;
|
|
45661
|
+
if (!r || r.schema !== LIFECYCLE_SCHEMA || !r.checkpoint || !Array.isArray(r.checkpoint.prs)) throw new Error("checkpoint is not a merge-queue lifecycle receipt");
|
|
45662
|
+
const mismatch = [];
|
|
45663
|
+
if (r.repo.toLowerCase() !== scope.repo.toLowerCase()) mismatch.push("repo");
|
|
45664
|
+
if (r.cohort?.manifest_sha256 !== scope.manifestSha256) mismatch.push("cohort manifest");
|
|
45665
|
+
if (r.window?.since !== scope.since || r.window?.until !== scope.until) mismatch.push("window");
|
|
45666
|
+
if (r.source?.version !== LIFECYCLE_SOURCE.version) mismatch.push("source version");
|
|
45667
|
+
if (mismatch.length) throw new Error(`checkpoint scope mismatch (${mismatch.join(", ")}) \u2014 a continuation is bound to the same repository, cohort, window and source; it never widens scope`);
|
|
45668
|
+
return { receipt_sha256: sha256(text), prs: r.checkpoint.prs };
|
|
45669
|
+
}
|
|
45670
|
+
var LIFECYCLE_COMMAND_PATH = "read merge-queue lifecycle";
|
|
45671
|
+
function registerMergeQueueLifecycleCommand(readRoot, deps) {
|
|
45672
|
+
readRoot.command("merge-queue").description("historical merge-queue reads (#9788)").command("lifecycle").description("typed per-PR merge-queue history for a retained cohort: one fixed GraphQL timeline query on the App pull_requests:read profile, bounded, receipt-bound; unknown values are null with a reason, never zero (#9788)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").requiredOption("--input <manifest>", `retained cohort manifest JSON (${COHORT_SCHEMA})`).requiredOption("--since <utc>", "window start, inclusive (UTC, e.g. 2026-11-02T00:00:00Z)").requiredOption("--until <utc>", "window end, exclusive (UTC)").requiredOption("--max-pages <n>", "page bound across the cohort (1-1000)").requiredOption("--max-events <n>", "event bound across the cohort (1-100000)").requiredOption("--deadline-seconds <n>", "wall-clock bound (1-3600)").requiredOption("--receipt <new-path>", "new append-only receipt file (refused if it exists)").option("--resume <prior-receipt>", "continue an incomplete read from its receipt (same repo, cohort, window and source only)").option("--json", "machine-readable result").action(async (o) => {
|
|
45673
|
+
const label = "read merge-queue lifecycle";
|
|
45674
|
+
let since, until, maxPages, maxEvents, deadlineSeconds, manifest, manifestText;
|
|
45675
|
+
try {
|
|
45676
|
+
since = parseUtc(o.since, "--since");
|
|
45677
|
+
until = parseUtc(o.until, "--until");
|
|
45678
|
+
if (Date.parse(since) >= Date.parse(until)) throw new Error("--since must be earlier than --until (half-open window)");
|
|
45679
|
+
maxPages = parseBound(o.maxPages, "--max-pages", 1e3);
|
|
45680
|
+
maxEvents = parseBound(o.maxEvents, "--max-events", 1e5);
|
|
45681
|
+
deadlineSeconds = parseBound(o.deadlineSeconds, "--deadline-seconds", 3600);
|
|
45682
|
+
manifestText = (0, import_node_fs35.readFileSync)(o.input, "utf8");
|
|
45683
|
+
manifest = parseCohortManifest(manifestText);
|
|
45684
|
+
if (manifest.since !== since || manifest.until !== until) throw new Error("--since/--until must equal the manifest window exactly; the authorized window is never widened or narrowed silently");
|
|
45685
|
+
} catch (e) {
|
|
45686
|
+
return fail(`${label}: ${e.message}`);
|
|
45687
|
+
}
|
|
45688
|
+
const resolution = resolveCanonicalRepo(o.repo, await deps.projects());
|
|
45689
|
+
const repo = resolution.kind === "resolved" ? resolution.repo : resolution.kind === "conflict" ? fail(`${label}: '${o.repo}' resolves to MULTIPLE registered repositories (${resolution.candidates.join(", ")}) \u2014 pass the exact owner/repo`) : resolution.kind === "unavailable" ? fail(`${label}: the Hub project registry is unreadable; refusing to address a repo this command cannot prove registered`) : fail(`${label}: '${o.repo}' is not a registered repository in the Hub registry \u2014 pass the exact owner/repo`);
|
|
45690
|
+
if (manifest.repo.toLowerCase() !== repo.toLowerCase()) return fail(`${label}: manifest repo ${manifest.repo} does not match --repo ${repo}; refusing before dispatch`);
|
|
45691
|
+
const manifestSha256 = sha256(manifestText);
|
|
45692
|
+
let resume;
|
|
45693
|
+
try {
|
|
45694
|
+
if (o.resume) resume = loadCheckpoint((0, import_node_fs35.readFileSync)(o.resume, "utf8"), { repo, manifestSha256, since, until });
|
|
45695
|
+
} catch (e) {
|
|
45696
|
+
return fail(`${label}: ${e.message}`);
|
|
45697
|
+
}
|
|
45698
|
+
const request = { repo, input: o.input, since, until, max_pages: maxPages, max_events: maxEvents, deadline_seconds: deadlineSeconds, resume: o.resume ?? null };
|
|
45699
|
+
const result = await readMergeQueueLifecycle(
|
|
45700
|
+
{ transport: await deps.appClient(LIFECYCLE_COMMAND_PATH), ...deps.actor ? { actor: deps.actor() } : {} },
|
|
45701
|
+
{ repo, since, until, maxPages, maxEvents, deadlineSeconds, manifestSha256, manifest, ...resume ? { resume } : {} }
|
|
45702
|
+
);
|
|
45703
|
+
let receipt;
|
|
45704
|
+
try {
|
|
45705
|
+
receipt = writeLifecycleReceipt(o.receipt, { request, result });
|
|
45706
|
+
} catch (e) {
|
|
45707
|
+
const code = e.code;
|
|
45708
|
+
return fail(`${label}: receipt not written (${code === "EEXIST" ? "path exists \u2014 receipts are append-only, pass a new path" : code ?? "write failed"})`);
|
|
45709
|
+
}
|
|
45710
|
+
const output = { ...result, receipt };
|
|
45711
|
+
if (o.json) console.log(JSON.stringify(output, null, 2));
|
|
45712
|
+
else {
|
|
45713
|
+
const m = result.medians;
|
|
45714
|
+
console.log(`${label}: ${repo} ${since}..${until} \u2014 ${result.complete ? "complete" : `INCOMPLETE (${result.exhausted.join(", ") || "lookup failures"})`}; pages ${result.counts.pages}, events ${result.counts.events}`);
|
|
45715
|
+
for (const [k, s] of Object.entries(m)) console.log(` ${k}: ${s.median_seconds ?? "UNKNOWN"} (${s.label}; eligible ${s.eligible}/${s.cohort_denominator}, right-censored ${s.right_censored}, unknown ${s.unknown})`);
|
|
45716
|
+
console.log(` bounces: ${result.bounces.total}; unresolved joins: ${result.unresolved_joins.length}; receipt ${receipt.path} sha256 ${receipt.sha256}`);
|
|
45717
|
+
}
|
|
45718
|
+
if (!result.complete) process.exitCode = 1;
|
|
45719
|
+
});
|
|
45720
|
+
}
|
|
45721
|
+
|
|
44439
45722
|
// src/board-commands.ts
|
|
44440
45723
|
init_cli_shared();
|
|
44441
45724
|
|
|
@@ -45063,9 +46346,9 @@ function registerBoardCommands(program3) {
|
|
|
45063
46346
|
}
|
|
45064
46347
|
|
|
45065
46348
|
// src/bootstrap-commands.ts
|
|
45066
|
-
var
|
|
46349
|
+
var import_node_fs39 = require("node:fs");
|
|
45067
46350
|
var import_node_os21 = require("node:os");
|
|
45068
|
-
var
|
|
46351
|
+
var import_node_path36 = require("node:path");
|
|
45069
46352
|
init_cli_shared();
|
|
45070
46353
|
init_clean_exit();
|
|
45071
46354
|
|
|
@@ -45250,8 +46533,8 @@ init_github_client();
|
|
|
45250
46533
|
init_cli_shared();
|
|
45251
46534
|
|
|
45252
46535
|
// src/port-registry.ts
|
|
45253
|
-
var
|
|
45254
|
-
var
|
|
46536
|
+
var import_node_fs36 = require("node:fs");
|
|
46537
|
+
var import_node_path33 = require("node:path");
|
|
45255
46538
|
|
|
45256
46539
|
// ../infra/port-geometry.mjs
|
|
45257
46540
|
var PORT_BLOCK = 100;
|
|
@@ -45265,8 +46548,8 @@ function nextPortBlock(registry2) {
|
|
|
45265
46548
|
return [base, base + PORT_SPAN];
|
|
45266
46549
|
}
|
|
45267
46550
|
function loadPortRegistry(path2) {
|
|
45268
|
-
if (!(0,
|
|
45269
|
-
const raw = JSON.parse((0,
|
|
46551
|
+
if (!(0, import_node_fs36.existsSync)(path2)) return {};
|
|
46552
|
+
const raw = JSON.parse((0, import_node_fs36.readFileSync)(path2, "utf8"));
|
|
45270
46553
|
const out = {};
|
|
45271
46554
|
for (const [key, value] of Object.entries(raw)) {
|
|
45272
46555
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -45294,22 +46577,22 @@ function existingPortRange(repo, registry2) {
|
|
|
45294
46577
|
return registry2[repo] ?? null;
|
|
45295
46578
|
}
|
|
45296
46579
|
function portRangeInfraAt(root, source) {
|
|
45297
|
-
const registryPath = (0,
|
|
45298
|
-
const ddbScriptPath = (0,
|
|
45299
|
-
if (!(0,
|
|
46580
|
+
const registryPath = (0, import_node_path33.join)(root, "infra", "port-ranges.json");
|
|
46581
|
+
const ddbScriptPath = (0, import_node_path33.join)(root, "infra", "port-ddb.mjs");
|
|
46582
|
+
if (!(0, import_node_fs36.existsSync)(registryPath) || !(0, import_node_fs36.existsSync)(ddbScriptPath)) return null;
|
|
45300
46583
|
return { root, source, registryPath, ddbScriptPath };
|
|
45301
46584
|
}
|
|
45302
46585
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
45303
46586
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
45304
46587
|
if (direct) return direct;
|
|
45305
|
-
for (let dir = cwd; ; dir = (0,
|
|
45306
|
-
const sibling = portRangeInfraAt((0,
|
|
46588
|
+
for (let dir = cwd; ; dir = (0, import_node_path33.dirname)(dir)) {
|
|
46589
|
+
const sibling = portRangeInfraAt((0, import_node_path33.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
45307
46590
|
if (sibling) return sibling;
|
|
45308
|
-
const parent = (0,
|
|
46591
|
+
const parent = (0, import_node_path33.dirname)(dir);
|
|
45309
46592
|
if (parent === dir) break;
|
|
45310
46593
|
}
|
|
45311
46594
|
if (packageDir) {
|
|
45312
|
-
const pkgRoot = (0,
|
|
46595
|
+
const pkgRoot = (0, import_node_path33.join)(packageDir, "..", "..");
|
|
45313
46596
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
45314
46597
|
if (pkgFrom) return pkgFrom;
|
|
45315
46598
|
}
|
|
@@ -45326,7 +46609,7 @@ async function ensurePortRangeAtomic(repo, path2, allocate) {
|
|
|
45326
46609
|
} catch (e) {
|
|
45327
46610
|
const why = e.message;
|
|
45328
46611
|
const missing = /@aws-sdk\/[\w-]+/.exec(why)?.[0];
|
|
45329
|
-
throw new Error(`DDB port allocator failed for ${repo} \u2014 refusing to fall back to the frozen ${path2} (it would hand out a block the cursor already gave away). ` + (missing && /cannot find|not found|ERR_MODULE_NOT_FOUND/i.test(why) ? `Missing dependency ${missing}: run \`npm ci\` in ${(0,
|
|
46612
|
+
throw new Error(`DDB port allocator failed for ${repo} \u2014 refusing to fall back to the frozen ${path2} (it would hand out a block the cursor already gave away). ` + (missing && /cannot find|not found|ERR_MODULE_NOT_FOUND/i.test(why) ? `Missing dependency ${missing}: run \`npm ci\` in ${(0, import_node_path33.dirname)(path2)}, then retry.` : "Check AWS credentials and that the registry table is reachable, then retry.") + ` Cause: ${why}`);
|
|
45330
46613
|
}
|
|
45331
46614
|
}
|
|
45332
46615
|
|
|
@@ -45378,13 +46661,13 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
|
|
|
45378
46661
|
}
|
|
45379
46662
|
|
|
45380
46663
|
// src/box-commands.ts
|
|
45381
|
-
var
|
|
46664
|
+
var import_node_fs38 = require("node:fs");
|
|
45382
46665
|
var import_node_child_process12 = require("node:child_process");
|
|
45383
46666
|
var import_node_os20 = require("node:os");
|
|
45384
|
-
var
|
|
46667
|
+
var import_node_path35 = require("node:path");
|
|
45385
46668
|
|
|
45386
46669
|
// src/box-control.ts
|
|
45387
|
-
var
|
|
46670
|
+
var import_node_crypto16 = require("node:crypto");
|
|
45388
46671
|
var MANIFEST_PATH = "infra/box-control/manifest.json";
|
|
45389
46672
|
var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
|
|
45390
46673
|
var sshdPost = (dest) => `if sshd -t; then systemctl reload ssh; else rm -f ${dest}; echo 'sshd -t rejected ${dest}: removed, not reloaded' >&2; exit 1; fi`;
|
|
@@ -45488,7 +46771,7 @@ function laneCoverageAction(files, box, op) {
|
|
|
45488
46771
|
return { notice: `LANE_COVERAGE: ${mismatch}` };
|
|
45489
46772
|
}
|
|
45490
46773
|
function md5(content) {
|
|
45491
|
-
return (0,
|
|
46774
|
+
return (0, import_node_crypto16.createHash)("md5").update(content).digest("hex");
|
|
45492
46775
|
}
|
|
45493
46776
|
function planRemoteScript(dests) {
|
|
45494
46777
|
const lines2 = ["set -u"];
|
|
@@ -45606,8 +46889,8 @@ init_clean_exit();
|
|
|
45606
46889
|
init_cli_shared();
|
|
45607
46890
|
|
|
45608
46891
|
// src/secrets-commands.ts
|
|
45609
|
-
var
|
|
45610
|
-
var
|
|
46892
|
+
var import_node_fs37 = require("node:fs");
|
|
46893
|
+
var import_node_path34 = require("node:path");
|
|
45611
46894
|
var import_node_os19 = require("node:os");
|
|
45612
46895
|
init_cli_shared();
|
|
45613
46896
|
init_hub_auth();
|
|
@@ -46804,18 +48087,18 @@ function collectMap(value, previous = []) {
|
|
|
46804
48087
|
return [...previous, value];
|
|
46805
48088
|
}
|
|
46806
48089
|
async function decryptRailsCredentials(input) {
|
|
46807
|
-
const appDir = (0,
|
|
48090
|
+
const appDir = (0, import_node_path34.resolve)(input.appDir ?? process.cwd());
|
|
46808
48091
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
46809
48092
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
46810
|
-
const credentialsPath = (0,
|
|
46811
|
-
const masterKeyPath = (0,
|
|
48093
|
+
const credentialsPath = (0, import_node_path34.resolve)(appDir, credentialsFile);
|
|
48094
|
+
const masterKeyPath = (0, import_node_path34.resolve)(appDir, masterKeyFile);
|
|
46812
48095
|
const env = {
|
|
46813
48096
|
...process.env,
|
|
46814
48097
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
46815
48098
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
46816
48099
|
};
|
|
46817
|
-
if ((0,
|
|
46818
|
-
env.RAILS_MASTER_KEY = (0,
|
|
48100
|
+
if ((0, import_node_fs37.existsSync)(masterKeyPath)) {
|
|
48101
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs37.readFileSync)(masterKeyPath, "utf8").trim();
|
|
46819
48102
|
}
|
|
46820
48103
|
const script = [
|
|
46821
48104
|
'require "json"',
|
|
@@ -46825,9 +48108,9 @@ async function decryptRailsCredentials(input) {
|
|
|
46825
48108
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
46826
48109
|
"puts JSON.generate(config.config)"
|
|
46827
48110
|
].join("\n");
|
|
46828
|
-
const scriptDir = (0,
|
|
46829
|
-
const scriptPath = (0,
|
|
46830
|
-
(0,
|
|
48111
|
+
const scriptDir = (0, import_node_fs37.mkdtempSync)((0, import_node_path34.join)((0, import_node_os19.tmpdir)(), "mmi-rails-decrypt-"));
|
|
48112
|
+
const scriptPath = (0, import_node_path34.join)(scriptDir, "decrypt.rb");
|
|
48113
|
+
(0, import_node_fs37.writeFileSync)(scriptPath, script, "utf8");
|
|
46831
48114
|
try {
|
|
46832
48115
|
const args = ["exec", "ruby", scriptPath];
|
|
46833
48116
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -46839,7 +48122,7 @@ async function decryptRailsCredentials(input) {
|
|
|
46839
48122
|
});
|
|
46840
48123
|
return JSON.parse(stdout);
|
|
46841
48124
|
} finally {
|
|
46842
|
-
(0,
|
|
48125
|
+
(0, import_node_fs37.rmSync)(scriptDir, { recursive: true, force: true });
|
|
46843
48126
|
}
|
|
46844
48127
|
}
|
|
46845
48128
|
async function readSecretStdin() {
|
|
@@ -46929,7 +48212,7 @@ function registerSecretsCommands(program3) {
|
|
|
46929
48212
|
let body;
|
|
46930
48213
|
if (o.file) {
|
|
46931
48214
|
try {
|
|
46932
|
-
body = (0,
|
|
48215
|
+
body = (0, import_node_fs37.readFileSync)((0, import_node_path34.resolve)(o.file), "utf8");
|
|
46933
48216
|
} catch (e) {
|
|
46934
48217
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
46935
48218
|
}
|
|
@@ -47065,7 +48348,7 @@ function registerSecretsCommands(program3) {
|
|
|
47065
48348
|
{
|
|
47066
48349
|
...d,
|
|
47067
48350
|
decryptRailsCredentials,
|
|
47068
|
-
removeFile: (path2) => (0,
|
|
48351
|
+
removeFile: (path2) => (0, import_node_fs37.unlinkSync)((0, import_node_path34.resolve)(o.appDir ?? process.cwd(), path2))
|
|
47069
48352
|
},
|
|
47070
48353
|
{
|
|
47071
48354
|
repo: o.repo,
|
|
@@ -47381,7 +48664,7 @@ function registerBoxCommands(program3) {
|
|
|
47381
48664
|
const stdinSource = o.body ? `"${o.body}"` : "<body.sh>";
|
|
47382
48665
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
47383
48666
|
const runLine = wroteScript ? useStdin ? `bash "${wroteScript}" < ${stdinSource}` : `bash "${wroteScript}"` : void 0;
|
|
47384
|
-
if (wroteScript) (0,
|
|
48667
|
+
if (wroteScript) (0, import_node_fs38.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
|
|
47385
48668
|
if (o.json) {
|
|
47386
48669
|
console.log(JSON.stringify({
|
|
47387
48670
|
box: found,
|
|
@@ -47508,17 +48791,17 @@ function hubGitShow(ref) {
|
|
|
47508
48791
|
return (path2) => git3(["show", `${ref}:${path2}`]);
|
|
47509
48792
|
}
|
|
47510
48793
|
function runOnBox(found, script) {
|
|
47511
|
-
const dir = (0,
|
|
48794
|
+
const dir = (0, import_node_fs38.mkdtempSync)((0, import_node_path35.join)((0, import_node_os20.tmpdir)(), "mmi-box-control-"));
|
|
47512
48795
|
try {
|
|
47513
|
-
const recipe = (0,
|
|
47514
|
-
(0,
|
|
48796
|
+
const recipe = (0, import_node_path35.join)(dir, "recipe.sh");
|
|
48797
|
+
(0, import_node_fs38.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
|
|
47515
48798
|
const r = (0, import_node_child_process12.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
|
|
47516
48799
|
if (r.status !== 0) {
|
|
47517
48800
|
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(" | ")}`);
|
|
47518
48801
|
}
|
|
47519
48802
|
return r.stdout;
|
|
47520
48803
|
} finally {
|
|
47521
|
-
(0,
|
|
48804
|
+
(0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
|
|
47522
48805
|
}
|
|
47523
48806
|
}
|
|
47524
48807
|
function formatControl(mode, box, rows) {
|
|
@@ -47646,7 +48929,7 @@ function renderDeployPortDoctor(report) {
|
|
|
47646
48929
|
}
|
|
47647
48930
|
|
|
47648
48931
|
// src/bootstrap-drift.ts
|
|
47649
|
-
var
|
|
48932
|
+
var import_node_crypto17 = require("node:crypto");
|
|
47650
48933
|
function byteComparableSeeds(manifest, cls) {
|
|
47651
48934
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
47652
48935
|
}
|
|
@@ -47666,7 +48949,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
47666
48949
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
47667
48950
|
}
|
|
47668
48951
|
function seedContentHash(content) {
|
|
47669
|
-
return (0,
|
|
48952
|
+
return (0, import_node_crypto17.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
47670
48953
|
}
|
|
47671
48954
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
47672
48955
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -48916,7 +50199,7 @@ function renderBootstrapVerifyReport(report) {
|
|
|
48916
50199
|
}
|
|
48917
50200
|
|
|
48918
50201
|
// src/bootstrap-seed-delivery.ts
|
|
48919
|
-
var
|
|
50202
|
+
var import_node_crypto18 = require("node:crypto");
|
|
48920
50203
|
init_github_client();
|
|
48921
50204
|
var SHA = /^[a-f0-9]{40}$/;
|
|
48922
50205
|
async function readSeedFile(repo, target, ref, client) {
|
|
@@ -48949,7 +50232,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
|
|
|
48949
50232
|
}
|
|
48950
50233
|
const baseSha = base?.object?.sha;
|
|
48951
50234
|
if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
|
|
48952
|
-
const identity = (0,
|
|
50235
|
+
const identity = (0, import_node_crypto18.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
|
|
48953
50236
|
const delivery = planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`);
|
|
48954
50237
|
const scoped = forcePr && delivery.mode === "direct" ? { mode: "pr", ref: `bootstrap-seed-${identity}-${slug}`, branch: `bootstrap-seed-${identity}-${slug}`, reason: "registry gate render requires review" } : delivery;
|
|
48955
50238
|
return { ...scoped, baseSha, baseBranch };
|
|
@@ -49019,12 +50302,12 @@ var declaredProjectAdmins = (meta) => {
|
|
|
49019
50302
|
};
|
|
49020
50303
|
var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
49021
50304
|
async function gateRenderDiff(current, rendered) {
|
|
49022
|
-
const dir = (0,
|
|
49023
|
-
const before = (0,
|
|
49024
|
-
const after = (0,
|
|
50305
|
+
const dir = (0, import_node_fs39.mkdtempSync)((0, import_node_path36.join)((0, import_node_os21.tmpdir)(), "mmi-gate-diff-"));
|
|
50306
|
+
const before = (0, import_node_path36.join)(dir, "committed-gate.yml");
|
|
50307
|
+
const after = (0, import_node_path36.join)(dir, "registry-render.yml");
|
|
49025
50308
|
try {
|
|
49026
|
-
(0,
|
|
49027
|
-
(0,
|
|
50309
|
+
(0, import_node_fs39.writeFileSync)(before, current);
|
|
50310
|
+
(0, import_node_fs39.writeFileSync)(after, rendered);
|
|
49028
50311
|
try {
|
|
49029
50312
|
return (await execFileP("git", ["diff", "--no-index", "--", before, after], { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
49030
50313
|
} catch (e) {
|
|
@@ -49033,7 +50316,7 @@ async function gateRenderDiff(current, rendered) {
|
|
|
49033
50316
|
throw e;
|
|
49034
50317
|
}
|
|
49035
50318
|
} finally {
|
|
49036
|
-
(0,
|
|
50319
|
+
(0, import_node_fs39.rmSync)(dir, { recursive: true, force: true });
|
|
49037
50320
|
}
|
|
49038
50321
|
}
|
|
49039
50322
|
function bootstrapTrainDoctorDeps() {
|
|
@@ -49054,9 +50337,9 @@ async function propagationScope(classes, what) {
|
|
|
49054
50337
|
}
|
|
49055
50338
|
const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
|
|
49056
50339
|
let independentCount = rosterRepos.length;
|
|
49057
|
-
if ((0,
|
|
50340
|
+
if ((0, import_node_fs39.existsSync)("projects.json")) {
|
|
49058
50341
|
try {
|
|
49059
|
-
const local = JSON.parse((0,
|
|
50342
|
+
const local = JSON.parse((0, import_node_fs39.readFileSync)("projects.json", "utf8"));
|
|
49060
50343
|
const localRepos = /* @__PURE__ */ new Set();
|
|
49061
50344
|
for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
|
|
49062
50345
|
const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
@@ -49181,13 +50464,13 @@ function registerBootstrapCommands(program3) {
|
|
|
49181
50464
|
})),
|
|
49182
50465
|
pushAllowlistOwners: pushAllowlistAuthority(verifyOwners, declaredProjectAdmins(meta)),
|
|
49183
50466
|
pushAllowlistOwnersRead: verifyOwnersRead,
|
|
49184
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
50467
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs39.existsSync)(path2) ? (0, import_node_fs39.readFileSync)(path2, "utf8") : null,
|
|
49185
50468
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
49186
50469
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
49187
50470
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
49188
50471
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
49189
50472
|
// sanction, which is the pre-#3664 behaviour.
|
|
49190
|
-
sanctionedAdmins: (0,
|
|
50473
|
+
sanctionedAdmins: (0, import_node_fs39.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs39.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
49191
50474
|
requiredGcpApis: (() => {
|
|
49192
50475
|
const v = meta?.requiredGcpApis;
|
|
49193
50476
|
if (Array.isArray(v)) return v;
|
|
@@ -49265,14 +50548,14 @@ function registerBootstrapCommands(program3) {
|
|
|
49265
50548
|
bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
|
|
49266
50549
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
49267
50550
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
49268
|
-
if (!(0,
|
|
50551
|
+
if (!(0, import_node_fs39.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
|
|
49269
50552
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
49270
50553
|
if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
|
|
49271
|
-
const manifest = loadBootstrapSeeds((0,
|
|
50554
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs39.readFileSync)(manifestPath, "utf8"));
|
|
49272
50555
|
const hubContents = /* @__PURE__ */ new Map();
|
|
49273
50556
|
for (const s of manifest.seeds) {
|
|
49274
50557
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
49275
|
-
hubContents.set(s.target, (0,
|
|
50558
|
+
hubContents.set(s.target, (0, import_node_fs39.existsSync)(s.target) ? (0, import_node_fs39.readFileSync)(s.target, "utf8") : null);
|
|
49276
50559
|
}
|
|
49277
50560
|
let targets;
|
|
49278
50561
|
let classOf = (_repo) => "deployable";
|
|
@@ -49528,24 +50811,24 @@ function registerBootstrapCommands(program3) {
|
|
|
49528
50811
|
return;
|
|
49529
50812
|
}
|
|
49530
50813
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
49531
|
-
if (!(0,
|
|
50814
|
+
if (!(0, import_node_fs39.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
|
|
49532
50815
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
49533
50816
|
if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
|
|
49534
|
-
const manifest = loadBootstrapSeeds((0,
|
|
50817
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs39.readFileSync)(manifestPath, "utf8"));
|
|
49535
50818
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
49536
50819
|
const slug = parsedRepo.slug;
|
|
49537
50820
|
const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
|
|
49538
50821
|
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
49539
|
-
const readFile10 = (p) => (0,
|
|
50822
|
+
const readFile10 = (p) => (0, import_node_fs39.existsSync)(p) ? (0, import_node_fs39.readFileSync)(p, "utf8") : null;
|
|
49540
50823
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
49541
50824
|
const putSeed = async (target, content, ref, sha) => {
|
|
49542
|
-
const tmp = (0,
|
|
49543
|
-
(0,
|
|
50825
|
+
const tmp = (0, import_node_path36.join)((0, import_node_os21.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
50826
|
+
(0, import_node_fs39.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
49544
50827
|
try {
|
|
49545
50828
|
await gh2(contentPutInputArgs(repo, target, tmp));
|
|
49546
50829
|
} finally {
|
|
49547
50830
|
try {
|
|
49548
|
-
(0,
|
|
50831
|
+
(0, import_node_fs39.unlinkSync)(tmp);
|
|
49549
50832
|
} catch {
|
|
49550
50833
|
}
|
|
49551
50834
|
}
|
|
@@ -50183,10 +51466,10 @@ LIVE apply to ${repo}:
|
|
|
50183
51466
|
if (rawFlag("--pins")) return propagatePins(rawFlag("--apply"), rawFlag("--json"));
|
|
50184
51467
|
const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
|
|
50185
51468
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
50186
|
-
if (!(0,
|
|
51469
|
+
if (!(0, import_node_fs39.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
|
|
50187
51470
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
50188
51471
|
if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
50189
|
-
const manifest = loadBootstrapSeeds((0,
|
|
51472
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs39.readFileSync)(manifestPath, "utf8"));
|
|
50190
51473
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
50191
51474
|
if (!o.target) {
|
|
50192
51475
|
return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
|
|
@@ -50195,9 +51478,9 @@ LIVE apply to ${repo}:
|
|
|
50195
51478
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
50196
51479
|
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
|
|
50197
51480
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
50198
|
-
if (!seed.managedBlock && !(0,
|
|
50199
|
-
const hubContent = seed.managedBlock ? null : (0,
|
|
50200
|
-
const readSeedFile2 = (path2) => (0,
|
|
51481
|
+
if (!seed.managedBlock && !(0, import_node_fs39.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
|
|
51482
|
+
const hubContent = seed.managedBlock ? null : (0, import_node_fs39.readFileSync)(seed.target, "utf8");
|
|
51483
|
+
const readSeedFile2 = (path2) => (0, import_node_fs39.existsSync)(path2) ? (0, import_node_fs39.readFileSync)(path2, "utf8") : null;
|
|
50201
51484
|
const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
|
|
50202
51485
|
const { projects, classOf, inClassRepos, canarySlug, sourcePilot } = await propagationScope(seed.classes, `target '${seed.target}'`);
|
|
50203
51486
|
const repos = inClassRepos.map((repo) => {
|
|
@@ -50316,7 +51599,7 @@ LIVE apply to ${repo}:
|
|
|
50316
51599
|
if (plan.refusedNoCanary) return fail("bootstrap propagate --execute: no canary declared for this target \u2014 refusing to write (set seedCanary:true on exactly one registry repo)");
|
|
50317
51600
|
const headSha = seedSource.sha;
|
|
50318
51601
|
const sourceBlob = (await execGitForSeedSource(["rev-parse", `${headSha}:${seedSourcePath(seed)}`])).trim();
|
|
50319
|
-
const runbookUnaffected = (0,
|
|
51602
|
+
const runbookUnaffected = (0, import_node_fs39.existsSync)(RUNBOOK_UNAFFECTED_PATH) ? runbookUnaffectedReason(JSON.parse((0, import_node_fs39.readFileSync)(RUNBOOK_UNAFFECTED_PATH, "utf8")), seed.target, sourceBlob) : void 0;
|
|
50320
51603
|
for (const rec of plan.records) {
|
|
50321
51604
|
if (rec.action !== "open-pr") continue;
|
|
50322
51605
|
const baseBranch = classOf(rec.repo) === "content" ? "main" : "development";
|
|
@@ -50338,15 +51621,15 @@ LIVE apply to ${repo}:
|
|
|
50338
51621
|
} catch {
|
|
50339
51622
|
existingSha = void 0;
|
|
50340
51623
|
}
|
|
50341
|
-
const tmp = (0,
|
|
51624
|
+
const tmp = (0, import_node_path36.join)((0, import_node_os21.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
50342
51625
|
const desiredContent = desiredByRepo.get(rec.repo);
|
|
50343
51626
|
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
50344
|
-
(0,
|
|
51627
|
+
(0, import_node_fs39.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha, runbookUnaffected)), "utf8");
|
|
50345
51628
|
try {
|
|
50346
51629
|
await gh2(contentPutInputArgs(rec.repo, seed.target, tmp));
|
|
50347
51630
|
} finally {
|
|
50348
51631
|
try {
|
|
50349
|
-
(0,
|
|
51632
|
+
(0, import_node_fs39.unlinkSync)(tmp);
|
|
50350
51633
|
} catch {
|
|
50351
51634
|
}
|
|
50352
51635
|
}
|
|
@@ -50415,10 +51698,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
50415
51698
|
return fail(`bootstrap rollback: ${e.message}`);
|
|
50416
51699
|
}
|
|
50417
51700
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
50418
|
-
if (!(0,
|
|
51701
|
+
if (!(0, import_node_fs39.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
|
|
50419
51702
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
50420
51703
|
if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
|
|
50421
|
-
const manifest = loadBootstrapSeeds((0,
|
|
51704
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs39.readFileSync)(manifestPath, "utf8"));
|
|
50422
51705
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
50423
51706
|
if (!o.target) {
|
|
50424
51707
|
return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
|
|
@@ -50435,10 +51718,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
50435
51718
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
50436
51719
|
let candidates;
|
|
50437
51720
|
if (o.record) {
|
|
50438
|
-
if (!(0,
|
|
51721
|
+
if (!(0, import_node_fs39.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
|
|
50439
51722
|
let parsed;
|
|
50440
51723
|
try {
|
|
50441
|
-
parsed = JSON.parse((0,
|
|
51724
|
+
parsed = JSON.parse((0, import_node_fs39.readFileSync)(o.record, "utf8"));
|
|
50442
51725
|
} catch (e) {
|
|
50443
51726
|
return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
|
|
50444
51727
|
}
|
|
@@ -50515,13 +51798,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
50515
51798
|
} catch {
|
|
50516
51799
|
existingSha = void 0;
|
|
50517
51800
|
}
|
|
50518
|
-
const tmp = (0,
|
|
50519
|
-
(0,
|
|
51801
|
+
const tmp = (0, import_node_path36.join)((0, import_node_os21.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
51802
|
+
(0, import_node_fs39.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
|
|
50520
51803
|
try {
|
|
50521
51804
|
await gh2(contentPutInputArgs(repo, seed.target, tmp));
|
|
50522
51805
|
} finally {
|
|
50523
51806
|
try {
|
|
50524
|
-
(0,
|
|
51807
|
+
(0, import_node_fs39.unlinkSync)(tmp);
|
|
50525
51808
|
} catch {
|
|
50526
51809
|
}
|
|
50527
51810
|
}
|
|
@@ -50834,7 +52117,7 @@ init_github_client();
|
|
|
50834
52117
|
init_cli_shared();
|
|
50835
52118
|
|
|
50836
52119
|
// src/suite-status.ts
|
|
50837
|
-
var
|
|
52120
|
+
var import_node_crypto19 = require("node:crypto");
|
|
50838
52121
|
var SUITE_REPO = "mutmutco/MM-Work";
|
|
50839
52122
|
var REPOS = {
|
|
50840
52123
|
"mmi-scout": "mutmutco/MMI-Scout",
|
|
@@ -50862,7 +52145,7 @@ function parseSuiteMapping(body) {
|
|
|
50862
52145
|
return void 0;
|
|
50863
52146
|
}
|
|
50864
52147
|
}
|
|
50865
|
-
var
|
|
52148
|
+
var sha2562 = (b) => (0, import_node_crypto19.createHash)("sha256").update(b).digest("hex");
|
|
50866
52149
|
async function proveBundle(url, tag, fetchUrl) {
|
|
50867
52150
|
const get = async (u) => {
|
|
50868
52151
|
try {
|
|
@@ -50874,7 +52157,7 @@ async function proveBundle(url, tag, fetchUrl) {
|
|
|
50874
52157
|
const bundle = await get(url);
|
|
50875
52158
|
if (typeof bundle === "string") return { state: "unprovable", detail: `bundle ${bundle}` };
|
|
50876
52159
|
if (bundle.status !== 200) return { state: "unprovable", detail: `bundle HTTP ${bundle.status}` };
|
|
50877
|
-
const served =
|
|
52160
|
+
const served = sha2562(bundle.body);
|
|
50878
52161
|
const recordUrl = new URL(url);
|
|
50879
52162
|
recordUrl.search = "";
|
|
50880
52163
|
recordUrl.pathname = recordUrl.pathname.replace(/[^/]*$/, "module.json");
|
|
@@ -51117,9 +52400,9 @@ async function waitSuiteDev(deps, opts) {
|
|
|
51117
52400
|
}
|
|
51118
52401
|
|
|
51119
52402
|
// src/suite-train.ts
|
|
51120
|
-
var
|
|
51121
|
-
var
|
|
51122
|
-
var
|
|
52403
|
+
var import_node_fs40 = require("node:fs");
|
|
52404
|
+
var import_node_path37 = require("node:path");
|
|
52405
|
+
var import_node_crypto20 = require("node:crypto");
|
|
51123
52406
|
|
|
51124
52407
|
// src/kit-version.ts
|
|
51125
52408
|
var parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(typeof v === "string" ? v.trim() : "");
|
|
@@ -51204,7 +52487,7 @@ function suiteReleaseTag(rc) {
|
|
|
51204
52487
|
function readSuiteCut(path2) {
|
|
51205
52488
|
let raw;
|
|
51206
52489
|
try {
|
|
51207
|
-
raw = (0,
|
|
52490
|
+
raw = (0, import_node_fs40.readFileSync)(path2, "utf8");
|
|
51208
52491
|
} catch (e) {
|
|
51209
52492
|
if (e.code === "ENOENT") return void 0;
|
|
51210
52493
|
throw e;
|
|
@@ -51220,11 +52503,11 @@ function readSuiteCut(path2) {
|
|
|
51220
52503
|
return record2;
|
|
51221
52504
|
}
|
|
51222
52505
|
function writeSuiteCut(path2, record2) {
|
|
51223
|
-
(0,
|
|
51224
|
-
const temp = `${path2}.${(0,
|
|
52506
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path37.dirname)(path2), { recursive: true });
|
|
52507
|
+
const temp = `${path2}.${(0, import_node_crypto20.randomBytes)(8).toString("hex")}.tmp`;
|
|
51225
52508
|
try {
|
|
51226
|
-
(0,
|
|
51227
|
-
(0,
|
|
52509
|
+
(0, import_node_fs40.writeFileSync)(temp, JSON.stringify(record2, null, 2), { flag: "wx" });
|
|
52510
|
+
(0, import_node_fs40.renameSync)(temp, path2);
|
|
51228
52511
|
} catch (e) {
|
|
51229
52512
|
throw new Error(`suite cut record write failed at ${path2}: ${e.message}; stop, verify the file before retrying`);
|
|
51230
52513
|
}
|
|
@@ -51273,25 +52556,25 @@ async function suiteMarkAbandoned(deps, ownerGo = false) {
|
|
|
51273
52556
|
return tags.map((t) => `${t.repo}@${t.tag}`);
|
|
51274
52557
|
}
|
|
51275
52558
|
async function retireFailedLeadRcLedger(deps, ownerGo) {
|
|
51276
|
-
if (!(0,
|
|
52559
|
+
if (!(0, import_node_fs40.existsSync)(deps.ledgerPath)) return void 0;
|
|
51277
52560
|
return withLedgerLock(deps.ledgerPath, async (fence) => {
|
|
51278
|
-
const ledger = parseReleaseLedger((0,
|
|
52561
|
+
const ledger = parseReleaseLedger((0, import_node_fs40.readFileSync)(deps.ledgerPath, "utf8"));
|
|
51279
52562
|
const states = Object.values(ledger.phases).map((p) => p.state);
|
|
51280
52563
|
if (ledger.repo !== SUITE_REPO || ledger.lane !== "rcand" || ledger.phases.promotion.state === "complete" || !states.includes("failed")) return void 0;
|
|
51281
52564
|
if (!ownerGo) throw new Error(`the lead's failed rcand ledger for ${ledger.tag} blocks every train; retire it with suite abandon --owner-go`);
|
|
51282
52565
|
if ((await deps.rcHead())?.toLowerCase() === ledger.tagSha.toLowerCase()) throw new Error(`origin rc already points at ${ledger.tag}; it was promoted, so its ledger is not retired`);
|
|
51283
|
-
const archive = (0,
|
|
51284
|
-
if ((0,
|
|
52566
|
+
const archive = (0, import_node_path37.join)((0, import_node_path37.dirname)(deps.ledgerPath), `phases.archive-${ledger.tag}.json`);
|
|
52567
|
+
if ((0, import_node_fs40.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
|
|
51285
52568
|
await deps.markAbandoned(SUITE_REPO, ledger.tag, ledger.tagSha);
|
|
51286
|
-
await fence(() => (0,
|
|
52569
|
+
await fence(() => (0, import_node_fs40.renameSync)(deps.ledgerPath, archive));
|
|
51287
52570
|
return `${ledger.tag} (ledger archived to ${archive})`;
|
|
51288
52571
|
});
|
|
51289
52572
|
}
|
|
51290
52573
|
function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Date()) {
|
|
51291
52574
|
const record2 = abandonableCut(recordPath, ownerGo);
|
|
51292
52575
|
const archive = `${recordPath}.abandoned-${record2.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
|
|
51293
|
-
if ((0,
|
|
51294
|
-
(0,
|
|
52576
|
+
if ((0, import_node_fs40.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
|
|
52577
|
+
(0, import_node_fs40.renameSync)(recordPath, archive);
|
|
51295
52578
|
return { archive, orphanTags: record2.members.filter((m) => m.tag).map((m) => `${m.repo}@${m.tag}`) };
|
|
51296
52579
|
}
|
|
51297
52580
|
async function suiteRestoreMainPins(deps) {
|
|
@@ -51378,12 +52661,12 @@ async function suiteRcandRun(deps, opts) {
|
|
|
51378
52661
|
}
|
|
51379
52662
|
if ((record2?.announcement === "sent" || record2?.announcement === "skipped") && record2.leadReleased) {
|
|
51380
52663
|
const archive = `${deps.recordPath}.${record2.leadRcTag}.json`;
|
|
51381
|
-
if ((0,
|
|
51382
|
-
(0,
|
|
52664
|
+
if ((0, import_node_fs40.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
|
|
52665
|
+
(0, import_node_fs40.renameSync)(deps.recordPath, archive);
|
|
51383
52666
|
record2 = void 0;
|
|
51384
52667
|
}
|
|
51385
52668
|
if (record2 && !record2.pinPr && !record2.leadRcTag && record2.members.every((m) => !m.tag)) {
|
|
51386
|
-
(0,
|
|
52669
|
+
(0, import_node_fs40.rmSync)(deps.recordPath, { force: true });
|
|
51387
52670
|
record2 = void 0;
|
|
51388
52671
|
}
|
|
51389
52672
|
if (!record2) {
|
|
@@ -51437,7 +52720,7 @@ async function suiteRcandRun(deps, opts) {
|
|
|
51437
52720
|
quiet.push(member);
|
|
51438
52721
|
}
|
|
51439
52722
|
if (record2.members.every((m) => m.carried || quiet.includes(m)) && await deps.contains(SUITE_REPO, record2.leadDevelopment, record2.leadPreviousRc)) {
|
|
51440
|
-
(0,
|
|
52723
|
+
(0, import_node_fs40.rmSync)(deps.recordPath, { force: true });
|
|
51441
52724
|
throw new Error("nothing to promote: neither MM-Work nor any module has new development commits since its rc; no suite cut started");
|
|
51442
52725
|
}
|
|
51443
52726
|
for (const member of quiet) {
|
|
@@ -51455,7 +52738,7 @@ async function suiteRcandRun(deps, opts) {
|
|
|
51455
52738
|
}
|
|
51456
52739
|
if (refusals.length) {
|
|
51457
52740
|
const untouched = record2.members.every((m) => !m.tag) && !record2.pinPr;
|
|
51458
|
-
if (untouched) (0,
|
|
52741
|
+
if (untouched) (0, import_node_fs40.rmSync)(deps.recordPath, { force: true });
|
|
51459
52742
|
throw new Error(`module rc serving is not ready; ${untouched ? "no suite cut started" : "no new tag was cut"}:
|
|
51460
52743
|
${refusals.join("\n")}
|
|
51461
52744
|
Each module must ship a live release that bind-mounts <deployPath>/data read-only and serves <data>/rc/module.js at /rc/module.js (docs/Guides/suite-trains.md).`);
|
|
@@ -51647,11 +52930,11 @@ Each member's live release must serve <data>/main/module.js at its main pin path
|
|
|
51647
52930
|
}
|
|
51648
52931
|
return record2;
|
|
51649
52932
|
}
|
|
51650
|
-
var hotfixRecordPath = (deps) => (0,
|
|
52933
|
+
var hotfixRecordPath = (deps) => (0, import_node_path37.join)((0, import_node_path37.dirname)(deps.recordPath), "hotfix.json");
|
|
51651
52934
|
function readSuiteHotfix(path2) {
|
|
51652
52935
|
let raw;
|
|
51653
52936
|
try {
|
|
51654
|
-
raw = (0,
|
|
52937
|
+
raw = (0, import_node_fs40.readFileSync)(path2, "utf8");
|
|
51655
52938
|
} catch (e) {
|
|
51656
52939
|
if (e.code === "ENOENT") return void 0;
|
|
51657
52940
|
throw e;
|
|
@@ -51694,7 +52977,7 @@ async function suiteHotfix(deps, opts) {
|
|
|
51694
52977
|
if (completed) record2 = void 0;
|
|
51695
52978
|
if (record2 && record2.repo !== found.repo) throw new Error(`an unfinished suite hotfix for ${record2.repo} is recorded; finish it (rerun with that member) first`);
|
|
51696
52979
|
if (!record2?.tag) await deps.validateHotfixSummary();
|
|
51697
|
-
if (completed) (0,
|
|
52980
|
+
if (completed) (0, import_node_fs40.renameSync)(path2, `${path2}.${completed.leadTag}.json`);
|
|
51698
52981
|
const rec = record2 ?? { schema: "mmi-suite-hotfix/v1", member: found.id, repo: found.repo, from: opts.from, ...opts.ack?.length ? { ack: opts.ack } : {} };
|
|
51699
52982
|
const save = () => writeSuiteHotfix(path2, rec);
|
|
51700
52983
|
save();
|
|
@@ -51962,9 +53245,9 @@ async function runRcDataSwitch(deps, opts) {
|
|
|
51962
53245
|
|
|
51963
53246
|
// src/suite-train-io.ts
|
|
51964
53247
|
var import_node_child_process19 = require("node:child_process");
|
|
51965
|
-
var import_node_path49 = require("node:path");
|
|
51966
|
-
var import_node_fs53 = require("node:fs");
|
|
51967
53248
|
var import_node_path50 = require("node:path");
|
|
53249
|
+
var import_node_fs54 = require("node:fs");
|
|
53250
|
+
var import_node_path51 = require("node:path");
|
|
51968
53251
|
init_cli_shared();
|
|
51969
53252
|
|
|
51970
53253
|
// src/command-register-train-operations.ts
|
|
@@ -53656,8 +54939,8 @@ function checkHotfixCarries(options) {
|
|
|
53656
54939
|
}
|
|
53657
54940
|
|
|
53658
54941
|
// src/merge-cleanup.ts
|
|
53659
|
-
var
|
|
53660
|
-
var
|
|
54942
|
+
var import_node_fs41 = require("node:fs");
|
|
54943
|
+
var import_node_path38 = require("node:path");
|
|
53661
54944
|
var import_node_os22 = require("node:os");
|
|
53662
54945
|
init_cli_shared();
|
|
53663
54946
|
init_github_client();
|
|
@@ -53702,16 +54985,16 @@ async function ghWithAppOn401(label, args, options) {
|
|
|
53702
54985
|
}
|
|
53703
54986
|
function withAppOn401(client, label = "post-merge") {
|
|
53704
54987
|
let app;
|
|
53705
|
-
const run = async (
|
|
54988
|
+
const run = async (call3) => {
|
|
53706
54989
|
try {
|
|
53707
|
-
return await
|
|
54990
|
+
return await call3(app ?? client);
|
|
53708
54991
|
} catch (e) {
|
|
53709
54992
|
if (!isUnauthorized(e)) throw e;
|
|
53710
54993
|
if (app) throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
|
|
53711
54994
|
const token = await appTokenFor(label, e);
|
|
53712
54995
|
app = createGitHubClient({ token: async () => token });
|
|
53713
54996
|
try {
|
|
53714
|
-
return await
|
|
54997
|
+
return await call3(app);
|
|
53715
54998
|
} catch (e2) {
|
|
53716
54999
|
if (isUnauthorized(e2)) throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
|
|
53717
55000
|
throw e2;
|
|
@@ -54065,13 +55348,13 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
|
|
|
54065
55348
|
return rewritten === base ? null : rewritten;
|
|
54066
55349
|
}
|
|
54067
55350
|
function writeSquashBodyFile(body) {
|
|
54068
|
-
const dir = (0,
|
|
54069
|
-
const path2 = (0,
|
|
54070
|
-
(0,
|
|
55351
|
+
const dir = (0, import_node_fs41.mkdtempSync)((0, import_node_path38.join)((0, import_node_os22.tmpdir)(), "mmi-squash-body-"));
|
|
55352
|
+
const path2 = (0, import_node_path38.join)(dir, "body.txt");
|
|
55353
|
+
(0, import_node_fs41.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
|
|
54071
55354
|
`, "utf8");
|
|
54072
55355
|
return { path: path2, cleanup: () => {
|
|
54073
55356
|
try {
|
|
54074
|
-
(0,
|
|
55357
|
+
(0, import_node_fs41.rmSync)(dir, { recursive: true, force: true });
|
|
54075
55358
|
} catch {
|
|
54076
55359
|
}
|
|
54077
55360
|
} };
|
|
@@ -54322,9 +55605,9 @@ async function deleteMergedRemoteBranch(options) {
|
|
|
54322
55605
|
init_github_client();
|
|
54323
55606
|
|
|
54324
55607
|
// src/release-custody.ts
|
|
54325
|
-
var
|
|
54326
|
-
var
|
|
54327
|
-
var
|
|
55608
|
+
var import_node_crypto21 = require("node:crypto");
|
|
55609
|
+
var import_node_fs42 = require("node:fs");
|
|
55610
|
+
var import_node_path39 = require("node:path");
|
|
54328
55611
|
init_github_client();
|
|
54329
55612
|
|
|
54330
55613
|
// src/release-custody-bootstrap.ts
|
|
@@ -54545,7 +55828,7 @@ var ReleaseCustodyError = class extends Error {
|
|
|
54545
55828
|
this.name = "ReleaseCustodyError";
|
|
54546
55829
|
}
|
|
54547
55830
|
};
|
|
54548
|
-
var custodyDigest = (value) => (0,
|
|
55831
|
+
var custodyDigest = (value) => (0, import_node_crypto21.createHash)("sha256").update(value).digest("hex");
|
|
54549
55832
|
function parseCustody(raw, repo) {
|
|
54550
55833
|
const s = JSON.parse(raw);
|
|
54551
55834
|
if (s.schema !== "mmi-release-custody/v1" || s.repo !== repo || !s.precutId || !Number.isSafeInteger(s.revision) || s.revision < 1 || !s.controller || !Number.isSafeInteger(s.controller.runId) || s.controller.runId <= 0 || !Number.isSafeInteger(s.controller.runAttempt) || s.controller.runAttempt <= 0 || !s.controller.token || !(s.ledger === null || typeof s.ledger === "string") || !s.archives || !s.effects) {
|
|
@@ -54633,7 +55916,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
|
|
|
54633
55916
|
throw new ReleaseCustodyError(`previous controller run ${found.state.controller.runId} attempt ${found.state.controller.runAttempt} is not proven terminal`);
|
|
54634
55917
|
}
|
|
54635
55918
|
let state = {
|
|
54636
|
-
...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0,
|
|
55919
|
+
...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0, import_node_crypto21.randomUUID)(), effects: {}, ...await initialize() },
|
|
54637
55920
|
revision: (found.state?.revision ?? 0) + 1,
|
|
54638
55921
|
controller
|
|
54639
55922
|
};
|
|
@@ -54694,7 +55977,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
|
|
|
54694
55977
|
proof: { kind: "ledger-lifecycle", controller: state.controller }
|
|
54695
55978
|
};
|
|
54696
55979
|
}
|
|
54697
|
-
return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0,
|
|
55980
|
+
return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0, import_node_crypto21.randomUUID)() : state.precutId };
|
|
54698
55981
|
});
|
|
54699
55982
|
}
|
|
54700
55983
|
async preserveLocal(raw) {
|
|
@@ -54794,7 +56077,7 @@ function reclassifySupersededArchives(state) {
|
|
|
54794
56077
|
}
|
|
54795
56078
|
function localRaw(path2) {
|
|
54796
56079
|
try {
|
|
54797
|
-
return (0,
|
|
56080
|
+
return (0, import_node_fs42.readFileSync)(path2, "utf8");
|
|
54798
56081
|
} catch (e) {
|
|
54799
56082
|
if (e.code === "ENOENT") return null;
|
|
54800
56083
|
throw e;
|
|
@@ -54803,14 +56086,14 @@ function localRaw(path2) {
|
|
|
54803
56086
|
function hydrateLocal(path2, raw) {
|
|
54804
56087
|
const previous = localRaw(path2);
|
|
54805
56088
|
if (previous === raw) return;
|
|
54806
|
-
(0,
|
|
56089
|
+
(0, import_node_fs42.mkdirSync)((0, import_node_path39.dirname)(path2), { recursive: true });
|
|
54807
56090
|
if (previous !== null) {
|
|
54808
56091
|
const ledger = parseReleaseLedger(previous);
|
|
54809
|
-
const archive = (0,
|
|
54810
|
-
if (!(0,
|
|
54811
|
-
if (raw === null) (0,
|
|
56092
|
+
const archive = (0, import_node_path39.join)((0, import_node_path39.dirname)(path2), `phases.local-${ledger.tagSha}-${custodyDigest(previous)}.json`);
|
|
56093
|
+
if (!(0, import_node_fs42.existsSync)(archive)) (0, import_node_fs42.writeFileSync)(archive, previous, { flag: "wx" });
|
|
56094
|
+
if (raw === null) (0, import_node_fs42.renameSync)(path2, `${path2}.retired-${custodyDigest(previous)}`);
|
|
54812
56095
|
}
|
|
54813
|
-
if (raw !== null) (0,
|
|
56096
|
+
if (raw !== null) (0, import_node_fs42.writeFileSync)(path2, raw, "utf8");
|
|
54814
56097
|
}
|
|
54815
56098
|
async function commandIdentity(file, args, run, repo) {
|
|
54816
56099
|
if (file === "git" && args[0] === "push") {
|
|
@@ -54920,7 +56203,7 @@ function withHubReleaseCustody(deps) {
|
|
|
54920
56203
|
if (!authority.ok || !authority.train || authority.authenticatedDelegation !== true) {
|
|
54921
56204
|
throw new ReleaseCustodyError("repository custody requires the server-verified owner-triggered train session");
|
|
54922
56205
|
}
|
|
54923
|
-
const controller = { ...runIdentity, token: (0,
|
|
56206
|
+
const controller = { ...runIdentity, token: (0, import_node_crypto21.randomUUID)() };
|
|
54924
56207
|
const raw = localRaw(path2);
|
|
54925
56208
|
const requiredContexts = () => discoverRequiredCheckContexts(
|
|
54926
56209
|
deps,
|
|
@@ -55000,8 +56283,8 @@ function withHubReleaseCustody(deps) {
|
|
|
55000
56283
|
}
|
|
55001
56284
|
|
|
55002
56285
|
// src/project-info-sync.ts
|
|
55003
|
-
var
|
|
55004
|
-
var
|
|
56286
|
+
var import_node_fs43 = require("node:fs");
|
|
56287
|
+
var import_node_path40 = require("node:path");
|
|
55005
56288
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
55006
56289
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
55007
56290
|
projectV2 { id }
|
|
@@ -55048,14 +56331,14 @@ function sharedName(entries, fallback) {
|
|
|
55048
56331
|
}
|
|
55049
56332
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
55050
56333
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
55051
|
-
const readmePath = (0,
|
|
55052
|
-
if (!(0,
|
|
56334
|
+
const readmePath = (0, import_node_path40.join)(repoRoot2, "README.md");
|
|
56335
|
+
if (!(0, import_node_fs43.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
55053
56336
|
const entries = entriesFor(project2, projects);
|
|
55054
56337
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
55055
56338
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
55056
56339
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
55057
56340
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
55058
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
56341
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs43.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
55059
56342
|
const lines2 = [
|
|
55060
56343
|
`# ${projectName}`,
|
|
55061
56344
|
"",
|
|
@@ -55074,8 +56357,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
55074
56357
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
55075
56358
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
55076
56359
|
const orgDocs = [
|
|
55077
|
-
(0,
|
|
55078
|
-
(0,
|
|
56360
|
+
(0, import_node_fs43.existsSync)((0, import_node_path40.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
|
|
56361
|
+
(0, import_node_fs43.existsSync)((0, import_node_path40.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
|
|
55079
56362
|
].filter(Boolean);
|
|
55080
56363
|
if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
|
|
55081
56364
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
|
|
@@ -55097,8 +56380,8 @@ async function syncProjectInfo(plan, client, apply) {
|
|
|
55097
56380
|
}
|
|
55098
56381
|
|
|
55099
56382
|
// src/train-commands.ts
|
|
55100
|
-
var
|
|
55101
|
-
var
|
|
56383
|
+
var import_node_fs44 = require("node:fs");
|
|
56384
|
+
var import_node_path41 = require("node:path");
|
|
55102
56385
|
init_cli_shared();
|
|
55103
56386
|
init_clean_exit();
|
|
55104
56387
|
init_client_version();
|
|
@@ -55113,7 +56396,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
55113
56396
|
}
|
|
55114
56397
|
function readRepoVersion() {
|
|
55115
56398
|
try {
|
|
55116
|
-
return JSON.parse((0,
|
|
56399
|
+
return JSON.parse((0, import_node_fs44.readFileSync)((0, import_node_path41.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
55117
56400
|
} catch {
|
|
55118
56401
|
return void 0;
|
|
55119
56402
|
}
|
|
@@ -55224,7 +56507,7 @@ function releaseProgressVerdict(status, subject = "Release") {
|
|
|
55224
56507
|
|
|
55225
56508
|
// src/suite-pin.ts
|
|
55226
56509
|
var import_promises6 = require("node:fs/promises");
|
|
55227
|
-
var
|
|
56510
|
+
var import_node_path42 = require("node:path");
|
|
55228
56511
|
var SuitePinError = class _SuitePinError extends Error {
|
|
55229
56512
|
diagnostic;
|
|
55230
56513
|
constructor(repo, tag, error) {
|
|
@@ -55292,8 +56575,8 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
55292
56575
|
if (input.carriedOnly) return { status: "unchanged" };
|
|
55293
56576
|
throw new Error("pins already match without a merged cut PR \u2014 provenance unverified");
|
|
55294
56577
|
}
|
|
55295
|
-
await (0, import_promises6.mkdir)((0,
|
|
55296
|
-
scratch = await (0, import_promises6.mkdtemp)((0,
|
|
56578
|
+
await (0, import_promises6.mkdir)((0, import_node_path42.join)(deps.root, "tmp"), { recursive: true });
|
|
56579
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path42.join)(deps.root, "tmp", "suite-pin-"));
|
|
55297
56580
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
55298
56581
|
await run("git", ["clone", "--quiet", "--branch", prs.length ? branch : "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
55299
56582
|
if (prs.length) {
|
|
@@ -55305,7 +56588,7 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
55305
56588
|
await run("git", ["switch", "-c", branch]);
|
|
55306
56589
|
}
|
|
55307
56590
|
for (const stage of ["rc", "main"]) {
|
|
55308
|
-
const path2 = (0,
|
|
56591
|
+
const path2 = (0, import_node_path42.join)(scratch, `${stage}-pins.json`);
|
|
55309
56592
|
await (0, import_promises6.writeFile)(path2, JSON.stringify(input.pins.map((p) => ({ module: p.module, url: p[stage] }))));
|
|
55310
56593
|
await run("node", ["scripts/pin-modules.mjs", "--stage", stage, "--pins", path2]);
|
|
55311
56594
|
}
|
|
@@ -55358,12 +56641,12 @@ async function openSuiteRestorePinPr(deps, input) {
|
|
|
55358
56641
|
}
|
|
55359
56642
|
});
|
|
55360
56643
|
if (!todo.length) return void 0;
|
|
55361
|
-
await (0, import_promises6.mkdir)((0,
|
|
55362
|
-
scratch = await (0, import_promises6.mkdtemp)((0,
|
|
56644
|
+
await (0, import_promises6.mkdir)((0, import_node_path42.join)(deps.root, "tmp"), { recursive: true });
|
|
56645
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path42.join)(deps.root, "tmp", "suite-pin-"));
|
|
55363
56646
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
55364
56647
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
55365
56648
|
await run("git", ["switch", "-c", branch]);
|
|
55366
|
-
const path2 = (0,
|
|
56649
|
+
const path2 = (0, import_node_path42.join)(scratch, "main-pins.json");
|
|
55367
56650
|
await (0, import_promises6.writeFile)(path2, JSON.stringify(todo.map((p) => ({ module: p.module, url: p.prior }))));
|
|
55368
56651
|
await run("node", ["scripts/pin-modules.mjs", "--stage", "main", "--pins", path2]);
|
|
55369
56652
|
await run("git", ["add", "seed/modules.json"]);
|
|
@@ -55437,8 +56720,8 @@ async function openSuitePinPr(deps, input) {
|
|
|
55437
56720
|
if (newer.length) return { status: "superseded", pr: newer[0].url, url: pin };
|
|
55438
56721
|
const open2 = onBranch.find((p) => p.state === "OPEN");
|
|
55439
56722
|
if (open2 && tagOf(open2.title ?? "") === tag) return { status: "pending", pr: open2.url, url: pin };
|
|
55440
|
-
await (0, import_promises6.mkdir)((0,
|
|
55441
|
-
scratch = await (0, import_promises6.mkdtemp)((0,
|
|
56723
|
+
await (0, import_promises6.mkdir)((0, import_node_path42.join)(deps.root, "tmp"), { recursive: true });
|
|
56724
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path42.join)(deps.root, "tmp", "suite-pin-"));
|
|
55442
56725
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
55443
56726
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
55444
56727
|
await run("git", ["switch", "-c", branch]);
|
|
@@ -55490,7 +56773,7 @@ function prTitleRefusal(title) {
|
|
|
55490
56773
|
|
|
55491
56774
|
// src/command-register-collaboration.ts
|
|
55492
56775
|
var import_node_child_process18 = require("node:child_process");
|
|
55493
|
-
var
|
|
56776
|
+
var import_node_fs53 = require("node:fs");
|
|
55494
56777
|
var import_promises8 = require("node:fs/promises");
|
|
55495
56778
|
init_clean_exit();
|
|
55496
56779
|
init_cli_shared();
|
|
@@ -55514,7 +56797,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
55514
56797
|
}
|
|
55515
56798
|
|
|
55516
56799
|
// src/command-register-collaboration.ts
|
|
55517
|
-
var
|
|
56800
|
+
var import_node_path49 = require("node:path");
|
|
55518
56801
|
init_house_map();
|
|
55519
56802
|
|
|
55520
56803
|
// src/issue-body.ts
|
|
@@ -55631,8 +56914,8 @@ init_github_client();
|
|
|
55631
56914
|
init_github_quota();
|
|
55632
56915
|
|
|
55633
56916
|
// src/issue-commands.ts
|
|
55634
|
-
var
|
|
55635
|
-
var
|
|
56917
|
+
var import_node_fs45 = require("node:fs");
|
|
56918
|
+
var import_node_crypto22 = require("node:crypto");
|
|
55636
56919
|
init_cli_shared();
|
|
55637
56920
|
init_clean_exit();
|
|
55638
56921
|
init_error_codes();
|
|
@@ -55824,7 +57107,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
55824
57107
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
55825
57108
|
const patch = {};
|
|
55826
57109
|
let bodyChanged = false;
|
|
55827
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
57110
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs45.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
55828
57111
|
if (options.titleFile !== void 0) {
|
|
55829
57112
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
55830
57113
|
} else if (options.title !== void 0) {
|
|
@@ -56109,7 +57392,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
56109
57392
|
const identity = `${spec.type}
|
|
56110
57393
|
${spec.title.trim()}
|
|
56111
57394
|
${spec.body ?? ""}`;
|
|
56112
|
-
const hash = (0,
|
|
57395
|
+
const hash = (0, import_node_crypto22.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
56113
57396
|
return `${batchKey}:${hash}`;
|
|
56114
57397
|
}
|
|
56115
57398
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -56491,7 +57774,7 @@ function extendCreateCommand(issue, batchAttach) {
|
|
|
56491
57774
|
if (opts.batch) {
|
|
56492
57775
|
let specs;
|
|
56493
57776
|
try {
|
|
56494
|
-
const raw = (0,
|
|
57777
|
+
const raw = (0, import_node_fs45.readFileSync)(opts.batch, "utf8");
|
|
56495
57778
|
specs = JSON.parse(raw);
|
|
56496
57779
|
if (!Array.isArray(specs)) {
|
|
56497
57780
|
const top = specs === null ? "null" : typeof specs === "object" ? `an object with keys: ${Object.keys(specs).join(", ") || "(none)"}` : `a ${typeof specs}`;
|
|
@@ -56568,8 +57851,8 @@ ${lines2}`, {
|
|
|
56568
57851
|
}
|
|
56569
57852
|
|
|
56570
57853
|
// src/post-merge-recon.ts
|
|
56571
|
-
var
|
|
56572
|
-
var
|
|
57854
|
+
var import_node_fs46 = require("node:fs");
|
|
57855
|
+
var import_node_path43 = require("node:path");
|
|
56573
57856
|
|
|
56574
57857
|
// src/cross-repo-filing-issue.ts
|
|
56575
57858
|
init_github_client();
|
|
@@ -56739,16 +58022,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
56739
58022
|
}
|
|
56740
58023
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
56741
58024
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
56742
|
-
(0,
|
|
56743
|
-
(0,
|
|
58025
|
+
(0, import_node_fs46.mkdirSync)((0, import_node_path43.dirname)(path2), { recursive: true });
|
|
58026
|
+
(0, import_node_fs46.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
56744
58027
|
`, "utf8");
|
|
56745
58028
|
return path2;
|
|
56746
58029
|
}
|
|
56747
58030
|
function clearPostMergeReconRecovery(cwd, repo, pr) {
|
|
56748
58031
|
const path2 = postMergeReconStatePath(cwd, repo, pr);
|
|
56749
|
-
if (!(0,
|
|
58032
|
+
if (!(0, import_node_fs46.existsSync)(path2)) return;
|
|
56750
58033
|
try {
|
|
56751
|
-
(0,
|
|
58034
|
+
(0, import_node_fs46.unlinkSync)(path2);
|
|
56752
58035
|
} catch {
|
|
56753
58036
|
}
|
|
56754
58037
|
}
|
|
@@ -56783,7 +58066,7 @@ var import_promises7 = require("node:fs/promises");
|
|
|
56783
58066
|
init_cli_shared();
|
|
56784
58067
|
|
|
56785
58068
|
// src/api-read.ts
|
|
56786
|
-
var
|
|
58069
|
+
var import_node_fs47 = require("node:fs");
|
|
56787
58070
|
init_cli_shared();
|
|
56788
58071
|
var API_ACCEPT_HEADERS = {
|
|
56789
58072
|
json: "application/vnd.github+json",
|
|
@@ -57048,7 +58331,7 @@ function registerApiReadCommands(program3, overrides) {
|
|
|
57048
58331
|
if (req.workflowRuns) body = sliceWorkflowRunsPage(body, req.workflowRuns);
|
|
57049
58332
|
if (o.jq !== void 0) body = await jqRun(o.jq, body, localJqEnv(), ...bindings.length ? [bindings] : []);
|
|
57050
58333
|
if (zip) {
|
|
57051
|
-
(0,
|
|
58334
|
+
(0, import_node_fs47.writeFileSync)(o.out, body, "latin1");
|
|
57052
58335
|
return;
|
|
57053
58336
|
}
|
|
57054
58337
|
} catch (e) {
|
|
@@ -57147,6 +58430,8 @@ function registerApiReadCommands(program3, overrides) {
|
|
|
57147
58430
|
// src/run-log-read.ts
|
|
57148
58431
|
init_cli_shared();
|
|
57149
58432
|
var FAILED2 = /* @__PURE__ */ new Set(["failure", "cancelled", "timed_out"]);
|
|
58433
|
+
var RUN_LOG_RANGE_LINE_CAP = 2e3;
|
|
58434
|
+
var RUN_LOG_RANGE_CHAR_CAP = 32 * 1024;
|
|
57150
58435
|
function logRepo(raw) {
|
|
57151
58436
|
const repo = parseBrokerRepo(raw, "--repo");
|
|
57152
58437
|
if (repo.split("/").some((s) => s === "." || s === "..")) throw new Error("read run log: --repo refuses dot segments");
|
|
@@ -57165,6 +58450,78 @@ function sliceRunLog(text, tail) {
|
|
|
57165
58450
|
});
|
|
57166
58451
|
return { errorLines: lines2.filter((_, i) => context.has(i)), tail: lines2.slice(-tail) };
|
|
57167
58452
|
}
|
|
58453
|
+
function parseLineRange(raw) {
|
|
58454
|
+
const match = /^(\d+):(\d+)$/.exec(raw.trim());
|
|
58455
|
+
if (!match) throw new Error(`read run log: --lines must be <start:end> line numbers, e.g. --lines 1:2000 (received '${raw}')`);
|
|
58456
|
+
const start = Number(match[1]);
|
|
58457
|
+
const end = Number(match[2]);
|
|
58458
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 1 || end < start) {
|
|
58459
|
+
throw new Error(`read run log: --lines must be a 1-based start:end range with start <= end and safe-integer bounds (received '${raw}')`);
|
|
58460
|
+
}
|
|
58461
|
+
return { start, end };
|
|
58462
|
+
}
|
|
58463
|
+
async function readRunLogRange(id, o, runner, env = brokerChildEnv()) {
|
|
58464
|
+
const { start, end: requestedEnd } = parseLineRange(o.lines);
|
|
58465
|
+
if (o.tail !== void 0) throw new Error("read run log: --lines and --tail are mutually exclusive (the range already bounds the returned lines)");
|
|
58466
|
+
if (o.failed) throw new Error("read run log: --lines and --failed are mutually exclusive (--lines reads exactly one named job)");
|
|
58467
|
+
if (o.attempt === void 0) throw new Error("read run log: --lines requires an explicit --attempt <n> (1-1000) \u2014 Actions job ids are minted per attempt, so the id must name one attempt");
|
|
58468
|
+
if (o.job === void 0 || !/^\d+$/.test(o.job.trim())) throw new Error("read run log: --lines requires a numeric --job <id> \u2014 job names are ambiguous across attempts; take the id from the attempt-pinned jobs read");
|
|
58469
|
+
const runId = parseBrokerId(id, "run id");
|
|
58470
|
+
const repo = logRepo(o.repo);
|
|
58471
|
+
const attempt = parseBrokerInt(o.attempt, "--attempt", 1, 1e3);
|
|
58472
|
+
const jobId = Number(parseBrokerId(o.job, "job id"));
|
|
58473
|
+
if (!Number.isSafeInteger(jobId) || jobId < 1) throw new Error(`read run log: --lines requires a numeric --job id (received '${o.job}')`);
|
|
58474
|
+
const pages = JSON.parse(await readAppApi(`repos/${repo}/actions/runs/${runId}/attempts/${attempt}/jobs`, runner));
|
|
58475
|
+
const listed = pages.flatMap((page) => page.jobs);
|
|
58476
|
+
const job = listed.find((candidate) => candidate.id === jobId);
|
|
58477
|
+
if (!job) {
|
|
58478
|
+
const shown = listed.slice(0, 30).map((j) => `${j.name} (${j.id})`).join(", ");
|
|
58479
|
+
return {
|
|
58480
|
+
result: { runId, repo, jobs: [] },
|
|
58481
|
+
exitCode: 1,
|
|
58482
|
+
message: `read run log: job ${jobId} is not in run ${runId} attempt ${attempt} \u2014 Actions job ids are minted per workflow run and per re-run attempt, so only an id from this attempt's listing matches; jobs in run ${runId} attempt ${attempt}: ${shown || "none"}${listed.length > 30 ? `, \u2026+${listed.length - 30} more` : ""}`
|
|
58483
|
+
};
|
|
58484
|
+
}
|
|
58485
|
+
const text = await readAppApi(`repos/${repo}/actions/jobs/${job.id}/logs`, runner);
|
|
58486
|
+
const redacted = redactPrivateCredential(text, env);
|
|
58487
|
+
const all = redacted.replace(/\r\n/g, "\n").split("\n");
|
|
58488
|
+
if (all.at(-1) === "") all.pop();
|
|
58489
|
+
const total = all.length;
|
|
58490
|
+
if (start > total) {
|
|
58491
|
+
return {
|
|
58492
|
+
result: { runId, repo, jobs: [] },
|
|
58493
|
+
exitCode: 1,
|
|
58494
|
+
message: `read run log: --lines ${start}:${requestedEnd} starts beyond the end of the log \u2014 job ${job.name} (${job.id}) attempt ${attempt} retains ${total} lines; nothing to return`
|
|
58495
|
+
};
|
|
58496
|
+
}
|
|
58497
|
+
const requestedCount = requestedEnd - start + 1;
|
|
58498
|
+
const selected = all.slice(start - 1, Math.min(requestedEnd, start - 1 + RUN_LOG_RANGE_LINE_CAP));
|
|
58499
|
+
let truncated = requestedCount > selected.length;
|
|
58500
|
+
let section = selected.join("\n");
|
|
58501
|
+
let charCut = false;
|
|
58502
|
+
if (section.length > RUN_LOG_RANGE_CHAR_CAP) {
|
|
58503
|
+
section = section.slice(0, RUN_LOG_RANGE_CHAR_CAP);
|
|
58504
|
+
charCut = true;
|
|
58505
|
+
truncated = true;
|
|
58506
|
+
}
|
|
58507
|
+
const lines2 = section.split("\n");
|
|
58508
|
+
if (charCut && lines2.at(-1) === "") lines2.pop();
|
|
58509
|
+
return {
|
|
58510
|
+
result: {
|
|
58511
|
+
runId,
|
|
58512
|
+
repo,
|
|
58513
|
+
attempt,
|
|
58514
|
+
job: { id: job.id, name: job.name, conclusion: job.conclusion },
|
|
58515
|
+
lines: lines2,
|
|
58516
|
+
start,
|
|
58517
|
+
end: start + lines2.length - 1,
|
|
58518
|
+
requestedEnd,
|
|
58519
|
+
total,
|
|
58520
|
+
truncated
|
|
58521
|
+
},
|
|
58522
|
+
exitCode: 0
|
|
58523
|
+
};
|
|
58524
|
+
}
|
|
57168
58525
|
async function readRunLog(id, o, runner) {
|
|
57169
58526
|
const runId = parseBrokerId(id, "run id");
|
|
57170
58527
|
const repo = logRepo(o.repo);
|
|
@@ -57190,14 +58547,17 @@ async function readRunLog(id, o, runner) {
|
|
|
57190
58547
|
return { result, exitCode: 0 };
|
|
57191
58548
|
}
|
|
57192
58549
|
function registerRunLogRead(run, runner) {
|
|
57193
|
-
run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name or numeric job id").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3))
|
|
58550
|
+
run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136), or a bounded --lines range from one attempt-pinned job (#9773); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name or numeric job id").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3))).option("--attempt <n>", "run attempt (1-1000; default latest)", (v) => String(parseBrokerInt(v, "--attempt", 1, 1e3))).addOption(new Option("--lines <start:end>", "read lines start:end of one job (needs --attempt and a numeric --job id; capped at 2000 lines and 32KiB characters; conflicts with --tail/--failed)").conflicts("failed")).option("--json", "emit the read as JSON (jobs with failedStep/errorLines/tail, or the --lines selection)").argument("<runId>", "workflow run number", (v) => parseBrokerId(v, "run id")).action(async (id, o) => {
|
|
57194
58551
|
requireBrokerApp("read run log");
|
|
57195
58552
|
const env = brokerChildEnv();
|
|
57196
58553
|
try {
|
|
57197
|
-
const { result, message: message2, exitCode } = await readRunLog(id, o, runner);
|
|
57198
|
-
if (message2 && (!o.json || exitCode !== 0)) printLine(message2);
|
|
58554
|
+
const { result, message: message2, exitCode } = o.lines === void 0 ? await readRunLog(id, o, runner) : await readRunLogRange(id, o, runner, env);
|
|
58555
|
+
if (message2 && (!o.json || exitCode !== 0)) printLine(redactPrivateCredential(message2, env));
|
|
57199
58556
|
else if (o.json) printLine(redactPrivateCredential(JSON.stringify(result), env));
|
|
57200
|
-
else
|
|
58557
|
+
else if ("lines" in result) {
|
|
58558
|
+
printLine(redactPrivateCredential(`== ${result.job.name} (${result.job.id}) ${result.job.conclusion}: lines ${result.start}-${result.end} of ${result.total} (attempt ${result.attempt}${result.truncated ? ", truncated" : ""})`, env));
|
|
58559
|
+
for (const line of result.lines) printLine(redactPrivateCredential(line, env));
|
|
58560
|
+
} else for (const job of result.jobs) {
|
|
57201
58561
|
printLine(redactPrivateCredential(`== ${job.name} (${job.id}) ${job.conclusion}: failing step "${job.failedStep ?? "none"}"`, env));
|
|
57202
58562
|
for (const line of [...job.errorLines, ...job.tail]) printLine(redactPrivateCredential(line, env));
|
|
57203
58563
|
}
|
|
@@ -57383,7 +58743,7 @@ function parseBrokerPrState(raw) {
|
|
|
57383
58743
|
function buildPrListSpec(o) {
|
|
57384
58744
|
const repo = parseBrokerRepo(o.repo, "--repo");
|
|
57385
58745
|
const fields = rejectPrViewQueueFields(parseBrokerJsonFields(o.json), "<number>", repo, "list");
|
|
57386
|
-
const args = withJson(withRepo(["pr", "list"], repo), fields, o.json);
|
|
58746
|
+
const args = withJson(withRepo(["pr", "list"], repo), fields, o.json, PR_LIST_JSON_FIELDS);
|
|
57387
58747
|
const state = parseBrokerPrState(o.state);
|
|
57388
58748
|
if (state) args.push("--state", state);
|
|
57389
58749
|
for (const flag of ["base", "head"]) {
|
|
@@ -57402,9 +58762,11 @@ function buildPrListSpec(o) {
|
|
|
57402
58762
|
function withRepo(args, repo) {
|
|
57403
58763
|
return repo ? [...args, "--repo", repo] : args;
|
|
57404
58764
|
}
|
|
57405
|
-
function withJson(args, jsonFields, jsonFlag) {
|
|
58765
|
+
function withJson(args, jsonFields, jsonFlag, bareDefault) {
|
|
57406
58766
|
if (jsonFlag === void 0 || jsonFlag === false) return args;
|
|
57407
|
-
|
|
58767
|
+
const fields = typeof jsonFields === "string" ? jsonFields : bareDefault;
|
|
58768
|
+
if (fields === void 0) throw new Error(`read: --json needs a comma-separated field list for gh ${args.slice(0, 2).join(" ")} (e.g. --json number,title,state)`);
|
|
58769
|
+
return [...args, "--json", fields];
|
|
57408
58770
|
}
|
|
57409
58771
|
function buildRunViewSpec(id, o) {
|
|
57410
58772
|
const args = withJson(withRepo(["run", "view", parseBrokerId(id, "run id"), ...o.attempt !== void 0 ? ["--attempt", String(parseBrokerInt(o.attempt, "--attempt", 1, 1e3))] : []], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
|
|
@@ -57422,8 +58784,11 @@ function parseBrokerCommit(v) {
|
|
|
57422
58784
|
if (!/^[0-9a-f]{40}$/i.test(v)) throw new Error(`read: --commit must be a full 40-character commit SHA (received '${v}')`);
|
|
57423
58785
|
return v;
|
|
57424
58786
|
}
|
|
58787
|
+
var PR_LIST_JSON_FIELDS = "number,title,state,isDraft,headRefName,baseRefName,author,createdAt,updatedAt,url";
|
|
58788
|
+
var ISSUE_LIST_JSON_FIELDS = "number,title,state,labels,assignees,createdAt,updatedAt,url";
|
|
58789
|
+
var RUN_LIST_JSON_FIELDS = "attempt,conclusion,createdAt,databaseId,displayTitle,event,headBranch,headSha,name,number,startedAt,status,updatedAt,url,workflowDatabaseId,workflowName";
|
|
57425
58790
|
function buildRunListSpec(o) {
|
|
57426
|
-
const args = withJson(withRepo(["run", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
|
|
58791
|
+
const args = withJson(withRepo(["run", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json, RUN_LIST_JSON_FIELDS);
|
|
57427
58792
|
const workflow = parseBrokerWorkflow(o.workflow);
|
|
57428
58793
|
if (workflow) args.push("--workflow", workflow);
|
|
57429
58794
|
const branch = parseBrokerBranch(o.branch);
|
|
@@ -57506,7 +58871,7 @@ function parseBrokerLabel(raw) {
|
|
|
57506
58871
|
return raw;
|
|
57507
58872
|
}
|
|
57508
58873
|
function buildIssueListSpec(o) {
|
|
57509
|
-
const args = withJson(withRepo(["issue", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
|
|
58874
|
+
const args = withJson(withRepo(["issue", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json, ISSUE_LIST_JSON_FIELDS);
|
|
57510
58875
|
const search = parseBrokerSearch(o.search);
|
|
57511
58876
|
if (search !== void 0) args.push("--search", search);
|
|
57512
58877
|
const assignee = parseBrokerAssignee(o.assignee);
|
|
@@ -58507,9 +59872,9 @@ function registerPrLifecycleCommands(program3) {
|
|
|
58507
59872
|
|
|
58508
59873
|
// src/review-verdict.ts
|
|
58509
59874
|
var import_node_child_process15 = require("node:child_process");
|
|
58510
|
-
var
|
|
59875
|
+
var import_node_fs48 = require("node:fs");
|
|
58511
59876
|
var import_node_os24 = require("node:os");
|
|
58512
|
-
var
|
|
59877
|
+
var import_node_path44 = require("node:path");
|
|
58513
59878
|
init_cli_shared();
|
|
58514
59879
|
var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
|
|
58515
59880
|
var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
|
|
@@ -58674,15 +60039,15 @@ async function readPrIssueComments(number, repo) {
|
|
|
58674
60039
|
return comments;
|
|
58675
60040
|
}
|
|
58676
60041
|
async function postPrCommentFromFile(number, repo, body) {
|
|
58677
|
-
const dir = (0,
|
|
58678
|
-
const path2 = (0,
|
|
60042
|
+
const dir = (0, import_node_fs48.mkdtempSync)((0, import_node_path44.join)((0, import_node_os24.tmpdir)(), "mmi-review-verdict-"));
|
|
60043
|
+
const path2 = (0, import_node_path44.join)(dir, "body.md");
|
|
58679
60044
|
try {
|
|
58680
|
-
(0,
|
|
60045
|
+
(0, import_node_fs48.writeFileSync)(path2, body, "utf8");
|
|
58681
60046
|
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
58682
60047
|
return stdout.trim();
|
|
58683
60048
|
} finally {
|
|
58684
60049
|
try {
|
|
58685
|
-
(0,
|
|
60050
|
+
(0, import_node_fs48.rmSync)(dir, { recursive: true, force: true });
|
|
58686
60051
|
} catch {
|
|
58687
60052
|
}
|
|
58688
60053
|
}
|
|
@@ -59095,18 +60460,18 @@ function createPreparationDeps(input, io = {}) {
|
|
|
59095
60460
|
}
|
|
59096
60461
|
|
|
59097
60462
|
// src/worktree-residue-sweep.ts
|
|
59098
|
-
var
|
|
59099
|
-
var
|
|
60463
|
+
var import_node_fs52 = require("node:fs");
|
|
60464
|
+
var import_node_path48 = require("node:path");
|
|
59100
60465
|
|
|
59101
60466
|
// src/worktree-merge-cleanup.ts
|
|
59102
|
-
var
|
|
59103
|
-
var
|
|
60467
|
+
var import_node_fs51 = require("node:fs");
|
|
60468
|
+
var import_node_path47 = require("node:path");
|
|
59104
60469
|
init_cli_shared();
|
|
59105
60470
|
|
|
59106
60471
|
// src/stage-runner.ts
|
|
59107
60472
|
var import_node_child_process17 = require("node:child_process");
|
|
59108
|
-
var
|
|
59109
|
-
var
|
|
60473
|
+
var import_node_fs50 = require("node:fs");
|
|
60474
|
+
var import_node_path46 = require("node:path");
|
|
59110
60475
|
var import_node_net = require("node:net");
|
|
59111
60476
|
var import_node_util5 = require("node:util");
|
|
59112
60477
|
|
|
@@ -59116,8 +60481,8 @@ function normalizeEol2(s) {
|
|
|
59116
60481
|
}
|
|
59117
60482
|
|
|
59118
60483
|
// src/env-detection.ts
|
|
59119
|
-
var
|
|
59120
|
-
var
|
|
60484
|
+
var import_node_fs49 = require("node:fs");
|
|
60485
|
+
var import_node_path45 = require("node:path");
|
|
59121
60486
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
|
|
59122
60487
|
function isEnvBaseName(name) {
|
|
59123
60488
|
return name.toLowerCase() === ".env";
|
|
@@ -59127,7 +60492,7 @@ function findEnvFiles(root) {
|
|
|
59127
60492
|
const walk2 = (dir, rel) => {
|
|
59128
60493
|
let entries;
|
|
59129
60494
|
try {
|
|
59130
|
-
entries = (0,
|
|
60495
|
+
entries = (0, import_node_fs49.readdirSync)(dir, { withFileTypes: true });
|
|
59131
60496
|
} catch {
|
|
59132
60497
|
return;
|
|
59133
60498
|
}
|
|
@@ -59135,13 +60500,13 @@ function findEnvFiles(root) {
|
|
|
59135
60500
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
59136
60501
|
if (entry.isDirectory()) {
|
|
59137
60502
|
if (SKIPPED_DIRS.has(entry.name)) continue;
|
|
59138
|
-
walk2((0,
|
|
60503
|
+
walk2((0, import_node_path45.join)(dir, entry.name), relPath);
|
|
59139
60504
|
continue;
|
|
59140
60505
|
}
|
|
59141
60506
|
if (isEnvBaseName(entry.name)) {
|
|
59142
60507
|
let isDir = false;
|
|
59143
60508
|
try {
|
|
59144
|
-
isDir = (0,
|
|
60509
|
+
isDir = (0, import_node_fs49.lstatSync)((0, import_node_path45.join)(dir, entry.name)).isDirectory();
|
|
59145
60510
|
} catch {
|
|
59146
60511
|
}
|
|
59147
60512
|
if (!isDir) found.push(relPath);
|
|
@@ -59280,12 +60645,12 @@ var DOCKER_TIMEOUT_MS = 15e3;
|
|
|
59280
60645
|
function stageBash() {
|
|
59281
60646
|
if (process.platform !== "win32") return "bash";
|
|
59282
60647
|
const configured = process.env.SHELL;
|
|
59283
|
-
if (configured && (0,
|
|
60648
|
+
if (configured && (0, import_node_path46.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs50.existsSync)(configured)) return configured;
|
|
59284
60649
|
const candidates = [
|
|
59285
|
-
(0,
|
|
59286
|
-
...process.env.LOCALAPPDATA ? [(0,
|
|
60650
|
+
(0, import_node_path46.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
|
|
60651
|
+
...process.env.LOCALAPPDATA ? [(0, import_node_path46.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
|
|
59287
60652
|
];
|
|
59288
|
-
const bash = candidates.find((path2) => (0,
|
|
60653
|
+
const bash = candidates.find((path2) => (0, import_node_fs50.existsSync)(path2));
|
|
59289
60654
|
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.");
|
|
59290
60655
|
return bash;
|
|
59291
60656
|
}
|
|
@@ -59423,7 +60788,7 @@ function appendForceRecreate(up) {
|
|
|
59423
60788
|
return `${up.trimEnd()} --force-recreate`;
|
|
59424
60789
|
}
|
|
59425
60790
|
function stageStatePath(cwd = process.cwd()) {
|
|
59426
|
-
return (0,
|
|
60791
|
+
return (0, import_node_path46.join)(cwd, "tmp", "stage", "state.json");
|
|
59427
60792
|
}
|
|
59428
60793
|
function normPath(path2) {
|
|
59429
60794
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -59640,8 +61005,8 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
59640
61005
|
}
|
|
59641
61006
|
function composeResolvesPort(cwd) {
|
|
59642
61007
|
if (process.env.PORT) return true;
|
|
59643
|
-
const envFile = (0,
|
|
59644
|
-
return (0,
|
|
61008
|
+
const envFile = (0, import_node_path46.join)(cwd, ".env");
|
|
61009
|
+
return (0, import_node_fs50.existsSync)(envFile) && envFileKeys((0, import_node_fs50.readFileSync)(envFile, "utf8")).has("PORT");
|
|
59645
61010
|
}
|
|
59646
61011
|
function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
59647
61012
|
const composeProject = config.env?.COMPOSE_PROJECT_NAME ?? vaultEnvMerge?.COMPOSE_PROJECT_NAME ?? stageComposeProjectName(cwd);
|
|
@@ -59656,13 +61021,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
|
59656
61021
|
function stageComposeFiles(cwd) {
|
|
59657
61022
|
const files = ["docker-compose.yml"];
|
|
59658
61023
|
for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
|
|
59659
|
-
if ((0,
|
|
61024
|
+
if ((0, import_node_fs50.existsSync)((0, import_node_path46.join)(cwd, conventional))) files.push(conventional);
|
|
59660
61025
|
}
|
|
59661
61026
|
return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
|
|
59662
61027
|
}
|
|
59663
61028
|
async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
59664
61029
|
const keys = Object.keys(vaultEnvMerge ?? {});
|
|
59665
|
-
if (!keys.length || !(0,
|
|
61030
|
+
if (!keys.length || !(0, import_node_fs50.existsSync)((0, import_node_path46.join)(cwd, "docker-compose.yml"))) return {};
|
|
59666
61031
|
let configJson;
|
|
59667
61032
|
try {
|
|
59668
61033
|
const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
|
|
@@ -59678,21 +61043,21 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
|
59678
61043
|
}
|
|
59679
61044
|
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson), runtimeEnvSelects(configJson));
|
|
59680
61045
|
if (!body) return {};
|
|
59681
|
-
const overridePath = (0,
|
|
59682
|
-
(0,
|
|
59683
|
-
(0,
|
|
61046
|
+
const overridePath = (0, import_node_path46.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
|
|
61047
|
+
(0, import_node_fs50.mkdirSync)((0, import_node_path46.join)(cwd, "tmp", "stage"), { recursive: true });
|
|
61048
|
+
(0, import_node_fs50.writeFileSync)(overridePath, body, "utf8");
|
|
59684
61049
|
return stageComposeFileEnv(stageComposeFiles(cwd));
|
|
59685
61050
|
}
|
|
59686
61051
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
59687
61052
|
if (!config.ensureEnv) return;
|
|
59688
|
-
const target = (0,
|
|
59689
|
-
const example = (0,
|
|
59690
|
-
if (!(0,
|
|
59691
|
-
(0,
|
|
59692
|
-
} else if ((0,
|
|
59693
|
-
const stale = detectStaleEnvFile((0,
|
|
59694
|
-
exampleMtimeMs: (0,
|
|
59695
|
-
targetMtimeMs: (0,
|
|
61053
|
+
const target = (0, import_node_path46.join)(cwd, config.ensureEnv.target);
|
|
61054
|
+
const example = (0, import_node_path46.join)(cwd, config.ensureEnv.example);
|
|
61055
|
+
if (!(0, import_node_fs50.existsSync)(target) && (0, import_node_fs50.existsSync)(example)) {
|
|
61056
|
+
(0, import_node_fs50.copyFileSync)(example, target);
|
|
61057
|
+
} else if ((0, import_node_fs50.existsSync)(target) && (0, import_node_fs50.existsSync)(example)) {
|
|
61058
|
+
const stale = detectStaleEnvFile((0, import_node_fs50.readFileSync)(example, "utf8"), (0, import_node_fs50.readFileSync)(target, "utf8"), {
|
|
61059
|
+
exampleMtimeMs: (0, import_node_fs50.statSync)(example).mtimeMs,
|
|
61060
|
+
targetMtimeMs: (0, import_node_fs50.statSync)(target).mtimeMs
|
|
59696
61061
|
});
|
|
59697
61062
|
if (stale) {
|
|
59698
61063
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running mmi-cli stage`;
|
|
@@ -59700,8 +61065,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
59700
61065
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
59701
61066
|
}
|
|
59702
61067
|
}
|
|
59703
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
59704
|
-
(0,
|
|
61068
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs50.existsSync)(target)) {
|
|
61069
|
+
(0, import_node_fs50.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs50.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
59705
61070
|
}
|
|
59706
61071
|
}
|
|
59707
61072
|
async function gitText(cwd, args) {
|
|
@@ -59731,20 +61096,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
59731
61096
|
return void 0;
|
|
59732
61097
|
}
|
|
59733
61098
|
function readState(path2) {
|
|
59734
|
-
if (!(0,
|
|
61099
|
+
if (!(0, import_node_fs50.existsSync)(path2)) return null;
|
|
59735
61100
|
try {
|
|
59736
|
-
return JSON.parse((0,
|
|
61101
|
+
return JSON.parse((0, import_node_fs50.readFileSync)(path2, "utf8"));
|
|
59737
61102
|
} catch {
|
|
59738
61103
|
return null;
|
|
59739
61104
|
}
|
|
59740
61105
|
}
|
|
59741
61106
|
function mkdirFor(path2) {
|
|
59742
61107
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
59743
|
-
(0,
|
|
61108
|
+
(0, import_node_fs50.mkdirSync)(dir, { recursive: true });
|
|
59744
61109
|
}
|
|
59745
61110
|
function writeState(path2, state) {
|
|
59746
61111
|
mkdirFor(path2);
|
|
59747
|
-
(0,
|
|
61112
|
+
(0, import_node_fs50.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
59748
61113
|
}
|
|
59749
61114
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
59750
61115
|
const reservation = {
|
|
@@ -59773,7 +61138,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd, currentEn
|
|
|
59773
61138
|
);
|
|
59774
61139
|
}
|
|
59775
61140
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
59776
|
-
(0,
|
|
61141
|
+
(0, import_node_fs50.rmSync)(path2, { force: true });
|
|
59777
61142
|
}
|
|
59778
61143
|
}
|
|
59779
61144
|
async function killTree(pid) {
|
|
@@ -60020,8 +61385,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
60020
61385
|
});
|
|
60021
61386
|
}
|
|
60022
61387
|
} catch (e) {
|
|
60023
|
-
(0,
|
|
60024
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
61388
|
+
(0, import_node_fs50.rmSync)(statePath, { force: true });
|
|
61389
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs50.rmSync)(globalStatePath, { force: true });
|
|
60025
61390
|
throw e;
|
|
60026
61391
|
}
|
|
60027
61392
|
const started = await startStage(config, {
|
|
@@ -60040,9 +61405,9 @@ function normPath2(p) {
|
|
|
60040
61405
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
60041
61406
|
}
|
|
60042
61407
|
function unlinkNodeModulesJunction(wtPath) {
|
|
60043
|
-
const nm = (0,
|
|
61408
|
+
const nm = (0, import_node_path47.join)(wtPath, "node_modules");
|
|
60044
61409
|
try {
|
|
60045
|
-
if ((0,
|
|
61410
|
+
if ((0, import_node_fs51.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs51.rmdirSync)(nm);
|
|
60046
61411
|
return { ok: true };
|
|
60047
61412
|
} catch (e) {
|
|
60048
61413
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -60053,8 +61418,8 @@ function defaultSleep2(ms) {
|
|
|
60053
61418
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
60054
61419
|
}
|
|
60055
61420
|
async function removeResidueDirectory(wtPath, options = {}) {
|
|
60056
|
-
const { sleep: sleep2 = defaultSleep2, removeEmptyDir =
|
|
60057
|
-
const removeRecursive = options.removeRecursive ?? ((path2) => (0,
|
|
61421
|
+
const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs51.rmdirSync, exists = import_node_fs51.existsSync } = options;
|
|
61422
|
+
const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs51.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
|
|
60058
61423
|
const junction = unlinkNodeModulesJunction(wtPath);
|
|
60059
61424
|
if (!junction.ok) return junction;
|
|
60060
61425
|
try {
|
|
@@ -60177,28 +61542,28 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
|
|
|
60177
61542
|
}
|
|
60178
61543
|
async function listNestedIgnoredNodeModules(wtPath, execGit) {
|
|
60179
61544
|
const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
|
|
60180
|
-
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0,
|
|
61545
|
+
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path47.join)(wtPath, line.slice(0, -1)));
|
|
60181
61546
|
}
|
|
60182
61547
|
function safeRemoveTree(path2) {
|
|
60183
|
-
const stat4 = (0,
|
|
61548
|
+
const stat4 = (0, import_node_fs51.lstatSync)(path2);
|
|
60184
61549
|
if (stat4.isSymbolicLink()) {
|
|
60185
61550
|
try {
|
|
60186
|
-
(0,
|
|
61551
|
+
(0, import_node_fs51.rmdirSync)(path2);
|
|
60187
61552
|
} catch {
|
|
60188
|
-
(0,
|
|
61553
|
+
(0, import_node_fs51.unlinkSync)(path2);
|
|
60189
61554
|
}
|
|
60190
61555
|
return;
|
|
60191
61556
|
}
|
|
60192
61557
|
if (stat4.isDirectory()) {
|
|
60193
|
-
for (const entry of (0,
|
|
60194
|
-
(0,
|
|
61558
|
+
for (const entry of (0, import_node_fs51.readdirSync)(path2)) safeRemoveTree((0, import_node_path47.join)(path2, entry));
|
|
61559
|
+
(0, import_node_fs51.rmdirSync)(path2);
|
|
60195
61560
|
return;
|
|
60196
61561
|
}
|
|
60197
|
-
(0,
|
|
61562
|
+
(0, import_node_fs51.unlinkSync)(path2);
|
|
60198
61563
|
}
|
|
60199
61564
|
async function removeWorktreeNodeModules(wtPath) {
|
|
60200
61565
|
try {
|
|
60201
|
-
safeRemoveTree((0,
|
|
61566
|
+
safeRemoveTree((0, import_node_path47.join)(wtPath, "node_modules"));
|
|
60202
61567
|
return { ok: true };
|
|
60203
61568
|
} catch (e) {
|
|
60204
61569
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -60210,7 +61575,7 @@ function errorMessage(e) {
|
|
|
60210
61575
|
}
|
|
60211
61576
|
function resolvedOrRaw(path2) {
|
|
60212
61577
|
try {
|
|
60213
|
-
return normPath2((0,
|
|
61578
|
+
return normPath2((0, import_node_fs51.realpathSync)(path2));
|
|
60214
61579
|
} catch {
|
|
60215
61580
|
return normPath2(path2);
|
|
60216
61581
|
}
|
|
@@ -60218,10 +61583,10 @@ function resolvedOrRaw(path2) {
|
|
|
60218
61583
|
function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
60219
61584
|
let realRoot;
|
|
60220
61585
|
try {
|
|
60221
|
-
if ((0,
|
|
61586
|
+
if ((0, import_node_fs51.lstatSync)(root).isSymbolicLink()) {
|
|
60222
61587
|
return { ok: false, error: `delete root ${normPath2(root)} is a reparse point resolving to ${resolvedOrRaw(root)}` };
|
|
60223
61588
|
}
|
|
60224
|
-
realRoot = normPath2((0,
|
|
61589
|
+
realRoot = normPath2((0, import_node_fs51.realpathSync)(root));
|
|
60225
61590
|
} catch (e) {
|
|
60226
61591
|
if (e.code === "ENOENT") return { ok: true, unlinked: [] };
|
|
60227
61592
|
return { ok: false, error: `cannot resolve delete root ${normPath2(root)}: ${errorMessage(e)}` };
|
|
@@ -60237,17 +61602,17 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
60237
61602
|
const dir = stack.pop();
|
|
60238
61603
|
let entries;
|
|
60239
61604
|
try {
|
|
60240
|
-
entries = (0,
|
|
61605
|
+
entries = (0, import_node_fs51.readdirSync)(dir, { withFileTypes: true });
|
|
60241
61606
|
} catch (e) {
|
|
60242
61607
|
if (e.code === "ENOENT") continue;
|
|
60243
61608
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
60244
61609
|
}
|
|
60245
61610
|
for (const entry of entries) {
|
|
60246
|
-
const child2 = (0,
|
|
61611
|
+
const child2 = (0, import_node_path47.join)(dir, entry.name);
|
|
60247
61612
|
if (entry.isSymbolicLink()) {
|
|
60248
61613
|
let target = "";
|
|
60249
61614
|
try {
|
|
60250
|
-
target = normPath2((0,
|
|
61615
|
+
target = normPath2((0, import_node_fs51.realpathSync)(child2));
|
|
60251
61616
|
} catch {
|
|
60252
61617
|
target = "";
|
|
60253
61618
|
}
|
|
@@ -60430,7 +61795,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
60430
61795
|
return report;
|
|
60431
61796
|
}
|
|
60432
61797
|
const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
60433
|
-
const pathExists = options.pathExists ??
|
|
61798
|
+
const pathExists = options.pathExists ?? import_node_fs51.existsSync;
|
|
60434
61799
|
let afterWorktrees = [];
|
|
60435
61800
|
try {
|
|
60436
61801
|
afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
|
|
@@ -60570,7 +61935,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
60570
61935
|
const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
|
|
60571
61936
|
if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
|
|
60572
61937
|
unlinkedReparsePoints.push(...preHelperGuard.unlinked);
|
|
60573
|
-
if (pathExists((0,
|
|
61938
|
+
if (pathExists((0, import_node_path47.join)(wtPath, "node_modules"))) {
|
|
60574
61939
|
const nmRemoved = await (options.removeRealNodeModules ?? removeWorktreeNodeModules)(wtPath);
|
|
60575
61940
|
if (!nmRemoved.ok) {
|
|
60576
61941
|
report.worktree = {
|
|
@@ -60706,18 +62071,18 @@ function renderPrMergeCleanupLines(cleanup) {
|
|
|
60706
62071
|
// src/worktree-residue-sweep.ts
|
|
60707
62072
|
var RESIDUE_LEDGER_FILE = "mmi-worktree-residue.json";
|
|
60708
62073
|
function canonicalWorktreeRoot(primary) {
|
|
60709
|
-
return (0,
|
|
62074
|
+
return (0, import_node_path48.join)((0, import_node_path48.dirname)(primary), "mmi-worktrees", (0, import_node_path48.basename)(primary));
|
|
60710
62075
|
}
|
|
60711
62076
|
function norm(p) {
|
|
60712
62077
|
const s = p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
60713
62078
|
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
60714
62079
|
}
|
|
60715
62080
|
function residueLedgerPath(gitCommonDir) {
|
|
60716
|
-
return (0,
|
|
62081
|
+
return (0, import_node_path48.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
|
|
60717
62082
|
}
|
|
60718
62083
|
function readPaths(file, deps) {
|
|
60719
62084
|
try {
|
|
60720
|
-
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0,
|
|
62085
|
+
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs52.readFileSync)(f, "utf8")))(file));
|
|
60721
62086
|
return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
|
|
60722
62087
|
} catch {
|
|
60723
62088
|
return [];
|
|
@@ -60726,8 +62091,8 @@ function readPaths(file, deps) {
|
|
|
60726
62091
|
function writePaths(file, paths, deps) {
|
|
60727
62092
|
try {
|
|
60728
62093
|
const write = deps.writeLedger ?? ((f, t) => {
|
|
60729
|
-
(0,
|
|
60730
|
-
(0,
|
|
62094
|
+
(0, import_node_fs52.mkdirSync)((0, import_node_path48.dirname)(f), { recursive: true });
|
|
62095
|
+
(0, import_node_fs52.writeFileSync)(f, t, "utf8");
|
|
60731
62096
|
});
|
|
60732
62097
|
write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
|
|
60733
62098
|
`);
|
|
@@ -60741,17 +62106,17 @@ function recordResidue(gitCommonDir, path2, deps = {}) {
|
|
|
60741
62106
|
writePaths(file, paths, deps);
|
|
60742
62107
|
}
|
|
60743
62108
|
async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
60744
|
-
const exists = deps.exists ??
|
|
60745
|
-
const readDir = deps.readDir ?? ((p) => (0,
|
|
62109
|
+
const exists = deps.exists ?? import_node_fs52.existsSync;
|
|
62110
|
+
const readDir = deps.readDir ?? ((p) => (0, import_node_fs52.readdirSync)(p));
|
|
60746
62111
|
const isDir = deps.isDir ?? ((p) => {
|
|
60747
62112
|
try {
|
|
60748
|
-
return (0,
|
|
62113
|
+
return (0, import_node_fs52.statSync)(p).isDirectory();
|
|
60749
62114
|
} catch {
|
|
60750
62115
|
return false;
|
|
60751
62116
|
}
|
|
60752
62117
|
});
|
|
60753
62118
|
const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
|
|
60754
|
-
const removeEmpty = deps.removeEmpty ?? ((p) => (0,
|
|
62119
|
+
const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs52.rmdirSync)(p));
|
|
60755
62120
|
const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
|
|
60756
62121
|
const live = new Set(registered.map(norm));
|
|
60757
62122
|
const primary = registered[0] ? norm(registered[0]) : void 0;
|
|
@@ -60786,7 +62151,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
|
60786
62151
|
} catch {
|
|
60787
62152
|
}
|
|
60788
62153
|
for (const name of children) {
|
|
60789
|
-
const child2 = (0,
|
|
62154
|
+
const child2 = (0, import_node_path48.join)(root, name);
|
|
60790
62155
|
const key = norm(child2);
|
|
60791
62156
|
if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
|
|
60792
62157
|
let entries;
|
|
@@ -60829,10 +62194,10 @@ function argvWantsJson2() {
|
|
|
60829
62194
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
60830
62195
|
}
|
|
60831
62196
|
function hubRoot() {
|
|
60832
|
-
const fromPkg = (0,
|
|
62197
|
+
const fromPkg = (0, import_node_path49.join)(__dirname, "..", "..");
|
|
60833
62198
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
60834
|
-
if ((0,
|
|
60835
|
-
if ((0,
|
|
62199
|
+
if ((0, import_node_fs53.existsSync)((0, import_node_path49.join)(fromPkg, marker))) return fromPkg;
|
|
62200
|
+
if ((0, import_node_fs53.existsSync)((0, import_node_path49.join)(process.cwd(), marker))) return process.cwd();
|
|
60836
62201
|
return null;
|
|
60837
62202
|
}
|
|
60838
62203
|
function ciAuditDeps() {
|
|
@@ -60844,8 +62209,8 @@ function ciAuditDeps() {
|
|
|
60844
62209
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
60845
62210
|
readSeedFile: (path2) => {
|
|
60846
62211
|
if (!root) return null;
|
|
60847
|
-
const fullPath = (0,
|
|
60848
|
-
return (0,
|
|
62212
|
+
const fullPath = (0, import_node_path49.join)(root, path2);
|
|
62213
|
+
return (0, import_node_fs53.existsSync)(fullPath) ? (0, import_node_fs53.readFileSync)(fullPath, "utf8") : null;
|
|
60849
62214
|
}
|
|
60850
62215
|
};
|
|
60851
62216
|
}
|
|
@@ -61712,11 +63077,11 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
61712
63077
|
}
|
|
61713
63078
|
});
|
|
61714
63079
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
61715
|
-
const wfDir = (0,
|
|
61716
|
-
if (!(0,
|
|
61717
|
-
return (0,
|
|
63080
|
+
const wfDir = (0, import_node_path49.join)(cwd, ".github", "workflows");
|
|
63081
|
+
if (!(0, import_node_fs53.existsSync)(wfDir)) return [];
|
|
63082
|
+
return (0, import_node_fs53.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
61718
63083
|
try {
|
|
61719
|
-
return workflowReportsPrChecks((0,
|
|
63084
|
+
return workflowReportsPrChecks((0, import_node_fs53.readFileSync)((0, import_node_path49.join)(wfDir, name), "utf8"));
|
|
61720
63085
|
} catch {
|
|
61721
63086
|
return true;
|
|
61722
63087
|
}
|
|
@@ -61921,7 +63286,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
61921
63286
|
jsonParity(pr.command("review-verdict <number>").description("post an explicit review verdict comment: computes the PR diff patch-id and head sha, renders the v1 contract body, and posts it as a PR comment").requiredOption("--repo <owner/repo>", "target repo").requiredOption("--verdict <verdict>", `one of ${REVIEW_VERDICTS.join("|")}`).requiredOption("--scope <text>", "one line: what was reviewed").requiredOption("--risk <text>", "one line: the residual risk").option("--unverified <text>", "a claim the reviewer could not verify (repeatable)", (v, acc) => [...acc, v], []).requiredOption("--reviewer <id>", "reviewer seat or model id").option("--findings-file <path>", "Markdown findings appended after the JSON block")).action(async (number, o) => {
|
|
61922
63287
|
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
61923
63288
|
const verdict = o.verdict;
|
|
61924
|
-
const findings = o.findingsFile ? (0,
|
|
63289
|
+
const findings = o.findingsFile ? (0, import_node_fs53.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
61925
63290
|
let patch;
|
|
61926
63291
|
let head;
|
|
61927
63292
|
try {
|
|
@@ -62199,7 +63564,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
62199
63564
|
const cleanupMerged = async (r) => {
|
|
62200
63565
|
if (opts.preserveWorktree) return "cleanup skipped (--preserve-worktree)";
|
|
62201
63566
|
const ran = await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(["pr", "merge", r.pr, "--repo", r.repo])], {
|
|
62202
|
-
cwd: (0,
|
|
63567
|
+
cwd: (0, import_node_fs53.existsSync)(cwd0) ? cwd0 : primaryRoot0,
|
|
62203
63568
|
timeout: GH_MUTATION_TIMEOUT_MS * 3
|
|
62204
63569
|
}).then((x) => ({ stdout: x.stdout, stderr: x.stderr }), (e) => ({ stdout: e.stdout ?? "", stderr: e.stderr ?? "" }));
|
|
62205
63570
|
const json = ran.stdout.split("\n").reverse().find((l) => l.startsWith("{"));
|
|
@@ -62270,7 +63635,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
62270
63635
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
62271
63636
|
closingGuardInput,
|
|
62272
63637
|
method === "--squash",
|
|
62273
|
-
o.squashBodyFile ? (0,
|
|
63638
|
+
o.squashBodyFile ? (0, import_node_fs53.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
62274
63639
|
);
|
|
62275
63640
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
62276
63641
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -62302,7 +63667,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
62302
63667
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
62303
63668
|
let foreignCheckout;
|
|
62304
63669
|
if (foreignCwd) {
|
|
62305
|
-
const sibling = (0,
|
|
63670
|
+
const sibling = (0, import_node_path49.join)((0, import_node_path49.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
|
|
62306
63671
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
62307
63672
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
62308
63673
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -62421,7 +63786,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
62421
63786
|
}
|
|
62422
63787
|
if (!repoForPostCleanup) throw e;
|
|
62423
63788
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
62424
|
-
const commitMessage = bodyFile ? (0,
|
|
63789
|
+
const commitMessage = bodyFile ? (0, import_node_fs53.readFileSync)(bodyFile, "utf8") : void 0;
|
|
62425
63790
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
62426
63791
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
62427
63792
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -62536,7 +63901,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
62536
63901
|
let residueSweep;
|
|
62537
63902
|
if (!(foreignCwd && !foreignCheckout)) {
|
|
62538
63903
|
try {
|
|
62539
|
-
gitCommonDir = (0,
|
|
63904
|
+
gitCommonDir = (0, import_node_path49.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
|
|
62540
63905
|
residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
|
|
62541
63906
|
} catch {
|
|
62542
63907
|
}
|
|
@@ -62554,7 +63919,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
62554
63919
|
preserveWorktree: o.preserveWorktree,
|
|
62555
63920
|
gcAcknowledged: o.gc,
|
|
62556
63921
|
expectedHeadOid: headRefOid,
|
|
62557
|
-
pathExists: (p) => (0,
|
|
63922
|
+
pathExists: (p) => (0, import_node_fs53.existsSync)(p),
|
|
62558
63923
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
62559
63924
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
62560
63925
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -63820,7 +65185,7 @@ function rcRunCauseFromLog(log) {
|
|
|
63820
65185
|
function memberAnnounceSummaryFile(repo, summaryFile, memberSummaryFiles) {
|
|
63821
65186
|
return repo === SUITE_REPO ? summaryFile : memberSummaryFiles[repo];
|
|
63822
65187
|
}
|
|
63823
|
-
var suiteStateDir = (root) => (0,
|
|
65188
|
+
var suiteStateDir = (root) => (0, import_node_path51.join)(resolvePrimaryGitDir(root), "mmi-runtime", "suite");
|
|
63824
65189
|
async function runSuiteMemberRcand(repo, previousRc, rcWasAbsent) {
|
|
63825
65190
|
const deps = trainApplyDeps();
|
|
63826
65191
|
deps.suiteCut = suiteCutContext(repo, SUITE_REPO);
|
|
@@ -63867,16 +65232,16 @@ async function rcandInCheckout(repo, deps, previousRc, rcWasAbsent) {
|
|
|
63867
65232
|
}
|
|
63868
65233
|
}
|
|
63869
65234
|
function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr, memberSummaryFilesTr = {}) {
|
|
63870
|
-
const recordPath = (0,
|
|
63871
|
-
const base = (0,
|
|
65235
|
+
const recordPath = (0, import_node_path51.join)(suiteStateDir(root), "cut.json");
|
|
65236
|
+
const base = (0, import_node_path51.join)(suiteStateDir(root), "checkouts");
|
|
63872
65237
|
const checkout = async (repo) => {
|
|
63873
65238
|
if (repo === SUITE_REPO) return root;
|
|
63874
|
-
const target = (0,
|
|
63875
|
-
if (!(0,
|
|
63876
|
-
(0,
|
|
65239
|
+
const target = (0, import_node_path51.join)(base, repo.split("/")[1]);
|
|
65240
|
+
if (!(0, import_node_fs54.existsSync)(target)) {
|
|
65241
|
+
(0, import_node_fs54.mkdirSync)(base, { recursive: true });
|
|
63877
65242
|
await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
|
|
63878
65243
|
}
|
|
63879
|
-
if (!(0,
|
|
65244
|
+
if (!(0, import_node_fs54.existsSync)((0, import_node_path51.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
|
|
63880
65245
|
const origin = await exec("git", ["remote", "get-url", "origin"], target);
|
|
63881
65246
|
if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
|
|
63882
65247
|
throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
|
|
@@ -63940,7 +65305,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
63940
65305
|
if (repo === SUITE_REPO) return inTrain(repo, (deps) => rcandInCheckout(repo, deps, previousRc, rcWasAbsent));
|
|
63941
65306
|
const target = await checkout(repo);
|
|
63942
65307
|
return new Promise((resolveResult, reject2) => {
|
|
63943
|
-
const child2 = (0, import_node_child_process19.fork)((0,
|
|
65308
|
+
const child2 = (0, import_node_child_process19.fork)((0, import_node_path50.resolve)(process.argv[1]), ["devops", "suite", "member-rcand-internal", repo, previousRc ?? "", ...rcWasAbsent ? ["--rc-was-absent"] : []], {
|
|
63944
65309
|
cwd: target,
|
|
63945
65310
|
stdio: ["ignore", "inherit", "inherit", "ipc"],
|
|
63946
65311
|
execArgv: []
|
|
@@ -64183,9 +65548,9 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
64183
65548
|
const body = await gh(["release", "view", leadTag, "--repo", SUITE_REPO, "--json", "body", "--jq", ".body"]);
|
|
64184
65549
|
if (parseSuiteMapping(body)) return;
|
|
64185
65550
|
const dir = suiteStateDir(root);
|
|
64186
|
-
(0,
|
|
64187
|
-
const file = (0,
|
|
64188
|
-
(0,
|
|
65551
|
+
(0, import_node_fs54.mkdirSync)(dir, { recursive: true });
|
|
65552
|
+
const file = (0, import_node_path51.join)(dir, `notes-${leadTag}.md`);
|
|
65553
|
+
(0, import_node_fs54.writeFileSync)(file, `${body}
|
|
64189
65554
|
|
|
64190
65555
|
${suiteMappingBlock(leadTag, mapping)}`);
|
|
64191
65556
|
await gh(["release", "edit", leadTag, "--repo", SUITE_REPO, "--notes-file", file]);
|
|
@@ -64226,7 +65591,7 @@ ${suiteMappingBlock(leadTag, mapping)}`);
|
|
|
64226
65591
|
|
|
64227
65592
|
// src/suite-commands.ts
|
|
64228
65593
|
init_cli_shared();
|
|
64229
|
-
var
|
|
65594
|
+
var import_node_fs55 = require("node:fs");
|
|
64230
65595
|
function parseMemberSummaryFiles(entries) {
|
|
64231
65596
|
const memberSummaryFiles = {};
|
|
64232
65597
|
for (const entry of entries) {
|
|
@@ -64320,7 +65685,7 @@ function registerSuiteCommands(program3) {
|
|
|
64320
65685
|
try {
|
|
64321
65686
|
const io = suiteTrainIo(root);
|
|
64322
65687
|
retired = await retireFailedLeadRcLedger({ ledgerPath: releaseLedgerPath(root), rcHead: () => io.head(SUITE_REPO, "rc").then((h) => h ?? void 0), markAbandoned: io.markAbandoned }, o.ownerGo === true);
|
|
64323
|
-
if (retired && !(0,
|
|
65688
|
+
if (retired && !(0, import_node_fs55.existsSync)(io.recordPath)) {
|
|
64324
65689
|
console.log(o.json ? JSON.stringify({ retiredLeadRc: retired }, null, 2) : `suite abandon: retired the lead's failed, never-promoted rc candidate ${retired}; marked abandoned on origin (#7854)`);
|
|
64325
65690
|
return;
|
|
64326
65691
|
}
|
|
@@ -64560,9 +65925,9 @@ function registerJevCommands(program3) {
|
|
|
64560
65925
|
|
|
64561
65926
|
// src/packages-adopt-export.ts
|
|
64562
65927
|
var import_node_child_process20 = require("node:child_process");
|
|
64563
|
-
var
|
|
65928
|
+
var import_node_fs56 = require("node:fs");
|
|
64564
65929
|
var import_node_os25 = require("node:os");
|
|
64565
|
-
var
|
|
65930
|
+
var import_node_path52 = require("node:path");
|
|
64566
65931
|
var import_node_util6 = require("node:util");
|
|
64567
65932
|
init_cli_shared();
|
|
64568
65933
|
|
|
@@ -65182,38 +66547,38 @@ function expectedFromRun(run, opts) {
|
|
|
65182
66547
|
}
|
|
65183
66548
|
async function adoptExport(opts, deps) {
|
|
65184
66549
|
if (!/^sha256:[0-9a-f]{64}$/.test(opts.digest)) throw new Error("--digest must be sha256:<64 hex>, from the export run summary");
|
|
65185
|
-
const packagePath = (0,
|
|
65186
|
-
const lockPath = (0,
|
|
65187
|
-
const packageText = (0,
|
|
65188
|
-
const lockText = (0,
|
|
65189
|
-
const work = (0,
|
|
66550
|
+
const packagePath = (0, import_node_path52.join)(opts.dir, "package.json");
|
|
66551
|
+
const lockPath = (0, import_node_path52.join)(opts.dir, "package-lock.json");
|
|
66552
|
+
const packageText = (0, import_node_fs56.readFileSync)(packagePath, "utf8");
|
|
66553
|
+
const lockText = (0, import_node_fs56.readFileSync)(lockPath, "utf8");
|
|
66554
|
+
const work = (0, import_node_fs56.mkdtempSync)((0, import_node_path52.join)((0, import_node_os25.tmpdir)(), "mmi-adopt-export-"));
|
|
65190
66555
|
try {
|
|
65191
66556
|
const run = JSON.parse((await deps.ghApi(`repos/${EXPORT_REPO}/actions/runs/${opts.run}`)).toString("utf8"));
|
|
65192
|
-
(0,
|
|
65193
|
-
(0,
|
|
65194
|
-
(0,
|
|
65195
|
-
const script = (0,
|
|
65196
|
-
(0,
|
|
66557
|
+
(0, import_node_fs56.writeFileSync)((0, import_node_path52.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
|
|
66558
|
+
(0, import_node_fs56.writeFileSync)((0, import_node_path52.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
|
|
66559
|
+
(0, import_node_fs56.writeFileSync)((0, import_node_path52.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
|
|
66560
|
+
const script = (0, import_node_path52.join)(work, "package_export.py");
|
|
66561
|
+
(0, import_node_fs56.writeFileSync)(script, package_export_default);
|
|
65197
66562
|
await deps.python(script, ["verify", work, opts.ds, opts.screens]);
|
|
65198
|
-
const verified = (0,
|
|
65199
|
-
const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0,
|
|
66563
|
+
const verified = (0, import_node_path52.join)(work, "verified");
|
|
66564
|
+
const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0, import_node_fs56.readFileSync)((0, import_node_path52.join)(verified, `${short3}.metadata.json`), "utf8"))]));
|
|
65200
66565
|
const plan = planAdoption(packageText, lockText, metadata);
|
|
65201
66566
|
for (const { name } of plan.result.adopted) {
|
|
65202
66567
|
const short3 = PACKAGES.find((p) => p.name === name).short;
|
|
65203
|
-
await deps.npm(["cache", "add", (0,
|
|
66568
|
+
await deps.npm(["cache", "add", (0, import_node_path52.join)(verified, `${short3}.tgz`)], opts.dir);
|
|
65204
66569
|
}
|
|
65205
|
-
(0,
|
|
65206
|
-
(0,
|
|
66570
|
+
(0, import_node_fs56.writeFileSync)(packagePath, plan.packageText);
|
|
66571
|
+
(0, import_node_fs56.writeFileSync)(lockPath, plan.lockText);
|
|
65207
66572
|
try {
|
|
65208
66573
|
await deps.npm(["ci", "--offline", "--ignore-scripts", "--no-audit", "--no-fund"], opts.dir);
|
|
65209
66574
|
} catch (error) {
|
|
65210
|
-
(0,
|
|
65211
|
-
(0,
|
|
66575
|
+
(0, import_node_fs56.writeFileSync)(packagePath, packageText);
|
|
66576
|
+
(0, import_node_fs56.writeFileSync)(lockPath, lockText);
|
|
65212
66577
|
throw new Error(`npm ci --offline failed, package.json and package-lock.json restored: ${error.message}`);
|
|
65213
66578
|
}
|
|
65214
66579
|
return plan.result;
|
|
65215
66580
|
} finally {
|
|
65216
|
-
(0,
|
|
66581
|
+
(0, import_node_fs56.rmSync)(work, { recursive: true, force: true });
|
|
65217
66582
|
}
|
|
65218
66583
|
}
|
|
65219
66584
|
var execFileBuffer = (0, import_node_util6.promisify)(import_node_child_process20.execFile);
|
|
@@ -65317,12 +66682,12 @@ function registerPackagesCommands(program3) {
|
|
|
65317
66682
|
init_cli_shared();
|
|
65318
66683
|
|
|
65319
66684
|
// src/wave-status.ts
|
|
65320
|
-
var
|
|
66685
|
+
var import_node_fs57 = require("node:fs");
|
|
65321
66686
|
function observeStage(worktreePath) {
|
|
65322
66687
|
const statePath = stageStatePath(worktreePath);
|
|
65323
66688
|
let raw;
|
|
65324
66689
|
try {
|
|
65325
|
-
raw = (0,
|
|
66690
|
+
raw = (0, import_node_fs57.readFileSync)(statePath, "utf8");
|
|
65326
66691
|
} catch (error) {
|
|
65327
66692
|
if (error.code === "ENOENT") return { state: "ok", value: { running: false } };
|
|
65328
66693
|
return { state: "unavailable", reason: "stage state could not be read" };
|
|
@@ -65389,9 +66754,9 @@ async function collectWaveStatus(deps) {
|
|
|
65389
66754
|
// src/discovery-commands.ts
|
|
65390
66755
|
init_github_client();
|
|
65391
66756
|
init_cli_shared();
|
|
65392
|
-
var
|
|
66757
|
+
var import_node_fs58 = require("node:fs");
|
|
65393
66758
|
var import_node_os26 = require("node:os");
|
|
65394
|
-
var
|
|
66759
|
+
var import_node_path53 = require("node:path");
|
|
65395
66760
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
65396
66761
|
var defaultStatusDeps = {
|
|
65397
66762
|
repo: resolveRepo,
|
|
@@ -65612,8 +66977,8 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
65612
66977
|
}
|
|
65613
66978
|
const home = (0, import_node_os26.homedir)();
|
|
65614
66979
|
const plugin = onboardPluginGate({
|
|
65615
|
-
readKnown: () => readFileSyncSafe((0,
|
|
65616
|
-
readSettings: () => readFileSyncSafe((0,
|
|
66980
|
+
readKnown: () => readFileSyncSafe((0, import_node_path53.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs58.readFileSync),
|
|
66981
|
+
readSettings: () => readFileSyncSafe((0, import_node_path53.join)(home, ".claude", "settings.json"), import_node_fs58.readFileSync)
|
|
65617
66982
|
});
|
|
65618
66983
|
return {
|
|
65619
66984
|
guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
|
|
@@ -68744,8 +70109,8 @@ function registerSessionReport(program3) {
|
|
|
68744
70109
|
}
|
|
68745
70110
|
|
|
68746
70111
|
// src/stage-commands.ts
|
|
68747
|
-
var
|
|
68748
|
-
var
|
|
70112
|
+
var import_node_fs59 = require("node:fs");
|
|
70113
|
+
var import_node_path54 = require("node:path");
|
|
68749
70114
|
init_cli_shared();
|
|
68750
70115
|
init_clean_exit();
|
|
68751
70116
|
|
|
@@ -68993,8 +70358,8 @@ function registerStageCommands(program3) {
|
|
|
68993
70358
|
const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
|
|
68994
70359
|
const resolution = decideStage({
|
|
68995
70360
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
68996
|
-
hasCompose: (0,
|
|
68997
|
-
hasEnvExample: (0,
|
|
70361
|
+
hasCompose: (0, import_node_fs59.existsSync)((0, import_node_path54.join)(process.cwd(), "docker-compose.yml")),
|
|
70362
|
+
hasEnvExample: (0, import_node_fs59.existsSync)((0, import_node_path54.join)(process.cwd(), ".env.example")),
|
|
68998
70363
|
repo
|
|
68999
70364
|
});
|
|
69000
70365
|
if (resolution.source === "derived" && resolution.derived) {
|
|
@@ -69286,25 +70651,25 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
69286
70651
|
}
|
|
69287
70652
|
|
|
69288
70653
|
// src/tenant-artifact.ts
|
|
69289
|
-
var
|
|
69290
|
-
var
|
|
70654
|
+
var import_node_crypto23 = require("node:crypto");
|
|
70655
|
+
var import_node_fs60 = require("node:fs");
|
|
69291
70656
|
var import_promises11 = require("node:fs/promises");
|
|
69292
|
-
var
|
|
70657
|
+
var import_node_path55 = require("node:path");
|
|
69293
70658
|
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}$/;
|
|
69294
70659
|
var MAX_BYTES2 = 5 * 1024 * 1024 * 1024;
|
|
69295
70660
|
async function sha256File(path2) {
|
|
69296
|
-
const hash = (0,
|
|
69297
|
-
for await (const chunk of (0,
|
|
70661
|
+
const hash = (0, import_node_crypto23.createHash)("sha256");
|
|
70662
|
+
for await (const chunk of (0, import_node_fs60.createReadStream)(path2)) hash.update(chunk);
|
|
69298
70663
|
return hash.digest("hex");
|
|
69299
70664
|
}
|
|
69300
70665
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
69301
70666
|
if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
|
|
69302
|
-
const path2 = (0,
|
|
70667
|
+
const path2 = (0, import_node_path55.resolve)(inputPath);
|
|
69303
70668
|
const info = await (0, import_promises11.stat)(path2);
|
|
69304
70669
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
69305
70670
|
if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES2) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES2} bytes`);
|
|
69306
|
-
const
|
|
69307
|
-
const prepared = await tenantArtifactUpload({ repo, stage, size: info.size, sha256:
|
|
70671
|
+
const sha2564 = await sha256File(path2);
|
|
70672
|
+
const prepared = await tenantArtifactUpload({ repo, stage, size: info.size, sha256: sha2564 }, deps);
|
|
69308
70673
|
if (!prepared.ok) {
|
|
69309
70674
|
const detail = prepared.body?.error ?? prepared.error ?? `HTTP ${prepared.status}`;
|
|
69310
70675
|
throw new Error(`tenant artifact put: ${detail}`);
|
|
@@ -69317,7 +70682,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
69317
70682
|
return [key, value];
|
|
69318
70683
|
}));
|
|
69319
70684
|
headers["content-length"] = String(info.size);
|
|
69320
|
-
const stream = (0,
|
|
70685
|
+
const stream = (0, import_node_fs60.createReadStream)(path2);
|
|
69321
70686
|
let uploaded;
|
|
69322
70687
|
try {
|
|
69323
70688
|
uploaded = await fetch(body.uploadUrl, {
|
|
@@ -69332,8 +70697,8 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
69332
70697
|
throw error;
|
|
69333
70698
|
}
|
|
69334
70699
|
if (!uploaded.ok) throw new Error(`tenant artifact put: object upload failed (HTTP ${uploaded.status})`);
|
|
69335
|
-
if (body.size !== info.size || body.sha256 !==
|
|
69336
|
-
return { artifactId: body.artifactId, repo, stage, size: info.size, sha256:
|
|
70700
|
+
if (body.size !== info.size || body.sha256 !== sha2564 || typeof body.expiresAt !== "string" || !Number.isFinite(Date.parse(body.expiresAt))) throw new Error("tenant artifact put: Hub returned inconsistent artifact metadata");
|
|
70701
|
+
return { artifactId: body.artifactId, repo, stage, size: info.size, sha256: sha2564, expiresAt: body.expiresAt };
|
|
69337
70702
|
}
|
|
69338
70703
|
|
|
69339
70704
|
// src/tenant-logs.ts
|
|
@@ -69671,7 +71036,7 @@ function renderVerifySecrets(body) {
|
|
|
69671
71036
|
}
|
|
69672
71037
|
|
|
69673
71038
|
// src/command-register-developer.ts
|
|
69674
|
-
var
|
|
71039
|
+
var import_node_fs65 = require("node:fs");
|
|
69675
71040
|
init_clean_exit();
|
|
69676
71041
|
init_cli_shared();
|
|
69677
71042
|
init_error_codes();
|
|
@@ -69704,12 +71069,12 @@ async function resolveWhoami(deps) {
|
|
|
69704
71069
|
}
|
|
69705
71070
|
|
|
69706
71071
|
// src/command-register-developer.ts
|
|
69707
|
-
var
|
|
71072
|
+
var import_node_path62 = require("node:path");
|
|
69708
71073
|
|
|
69709
71074
|
// src/doc-refs-command-source.ts
|
|
69710
71075
|
var import_node_child_process21 = require("node:child_process");
|
|
69711
|
-
var
|
|
69712
|
-
var
|
|
71076
|
+
var import_node_fs61 = require("node:fs");
|
|
71077
|
+
var import_node_path56 = require("node:path");
|
|
69713
71078
|
var BUNDLE_MANIFEST_TIMEOUT_MS = 12e4;
|
|
69714
71079
|
var BUNDLE_MANIFEST_MAX_BUFFER = 64 * 1024 * 1024;
|
|
69715
71080
|
var flagsOf = (options) => new Set((options ?? []).flatMap((option) => String(option?.flags ?? "").match(/--[\w-]+/g) ?? []));
|
|
@@ -69730,7 +71095,7 @@ function inventoryFromManifest(manifest, source) {
|
|
|
69730
71095
|
function readBundleManifest(bundle, exec3 = import_node_child_process21.execFileSync) {
|
|
69731
71096
|
try {
|
|
69732
71097
|
const text = exec3(process.execPath, [bundle, "commands", "--all", "--json"], {
|
|
69733
|
-
cwd: (0,
|
|
71098
|
+
cwd: (0, import_node_path56.dirname)(bundle),
|
|
69734
71099
|
encoding: "utf8",
|
|
69735
71100
|
windowsHide: true,
|
|
69736
71101
|
timeout: BUNDLE_MANIFEST_TIMEOUT_MS,
|
|
@@ -69746,19 +71111,19 @@ function readBundleManifest(bundle, exec3 = import_node_child_process21.execFile
|
|
|
69746
71111
|
function isRunningBundle(bundle, argv1 = process.argv[1], platform2 = process.platform) {
|
|
69747
71112
|
if (!argv1) return false;
|
|
69748
71113
|
const norm2 = (path2) => {
|
|
69749
|
-
const absolute = (0,
|
|
71114
|
+
const absolute = (0, import_node_path56.resolve)(path2);
|
|
69750
71115
|
return platform2 === "win32" ? absolute.toLowerCase() : absolute;
|
|
69751
71116
|
};
|
|
69752
71117
|
const running = norm2(argv1);
|
|
69753
|
-
if (!["main.cjs", "index.cjs"].includes((0,
|
|
69754
|
-
return (0,
|
|
71118
|
+
if (!["main.cjs", "index.cjs"].includes((0, import_node_path56.basename)(running))) return false;
|
|
71119
|
+
return (0, import_node_path56.dirname)(running) === (0, import_node_path56.dirname)(norm2(bundle));
|
|
69755
71120
|
}
|
|
69756
71121
|
function resolveCommandInventory(deps) {
|
|
69757
|
-
const exists = deps.exists ??
|
|
71122
|
+
const exists = deps.exists ?? import_node_fs61.existsSync;
|
|
69758
71123
|
const read = deps.read ?? ((bundle) => readBundleManifest(bundle));
|
|
69759
71124
|
const warnings = [];
|
|
69760
71125
|
if (deps.from) {
|
|
69761
|
-
const bundle = (0,
|
|
71126
|
+
const bundle = (0, import_node_path56.isAbsolute)(deps.from) ? deps.from : (0, import_node_path56.resolve)(deps.root, deps.from);
|
|
69762
71127
|
if (!exists(bundle)) {
|
|
69763
71128
|
throw new Error(`docs refs --commands-from: ${bundle} does not exist \u2014 name the CLI bundle to read commands from (usually cli/dist/main.cjs)`);
|
|
69764
71129
|
}
|
|
@@ -69768,7 +71133,7 @@ function resolveCommandInventory(deps) {
|
|
|
69768
71133
|
}
|
|
69769
71134
|
return { inventory: inventory2, warnings };
|
|
69770
71135
|
}
|
|
69771
|
-
const treeBundle = (0,
|
|
71136
|
+
const treeBundle = (0, import_node_path56.join)(deps.root, "cli", "dist", "main.cjs");
|
|
69772
71137
|
if (!exists(treeBundle) || isRunningBundle(treeBundle, deps.argv1, deps.platform)) {
|
|
69773
71138
|
return { inventory: deps.inProcess(), warnings };
|
|
69774
71139
|
}
|
|
@@ -69885,28 +71250,28 @@ async function executeWaveLand(plan, deps) {
|
|
|
69885
71250
|
|
|
69886
71251
|
// src/dist-drift.ts
|
|
69887
71252
|
var import_node_child_process22 = require("node:child_process");
|
|
69888
|
-
var
|
|
69889
|
-
var
|
|
71253
|
+
var import_node_crypto25 = require("node:crypto");
|
|
71254
|
+
var import_node_fs63 = require("node:fs");
|
|
69890
71255
|
var import_node_os27 = require("node:os");
|
|
69891
|
-
var
|
|
71256
|
+
var import_node_path58 = require("node:path");
|
|
69892
71257
|
|
|
69893
71258
|
// ../scripts/distribution-digest.mjs
|
|
69894
|
-
var
|
|
69895
|
-
var
|
|
69896
|
-
var
|
|
71259
|
+
var import_node_crypto24 = require("node:crypto");
|
|
71260
|
+
var import_node_fs62 = require("node:fs");
|
|
71261
|
+
var import_node_path57 = require("node:path");
|
|
69897
71262
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
69898
71263
|
function repoPath(root, declaredPath, label) {
|
|
69899
|
-
const absoluteRoot = (0,
|
|
69900
|
-
const target = (0,
|
|
69901
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
71264
|
+
const absoluteRoot = (0, import_node_path57.resolve)(root);
|
|
71265
|
+
const target = (0, import_node_path57.resolve)(root, declaredPath);
|
|
71266
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path57.sep}`)) {
|
|
69902
71267
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
69903
71268
|
}
|
|
69904
71269
|
return target;
|
|
69905
71270
|
}
|
|
69906
71271
|
function digestFiles(files) {
|
|
69907
|
-
const hash = (0,
|
|
71272
|
+
const hash = (0, import_node_crypto24.createHash)("sha256");
|
|
69908
71273
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
69909
|
-
const content = file.stat.isSymbolicLink() ? Buffer.from((0,
|
|
71274
|
+
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs62.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs62.readFileSync)(file.absolute);
|
|
69910
71275
|
hash.update(file.relative, "utf8");
|
|
69911
71276
|
hash.update("\0");
|
|
69912
71277
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -69921,8 +71286,8 @@ function digestFiles(files) {
|
|
|
69921
71286
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
69922
71287
|
return digestFiles(packedFiles.map((path2) => {
|
|
69923
71288
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
69924
|
-
if (!(0,
|
|
69925
|
-
return { absolute, relative: slash(path2), stat: (0,
|
|
71289
|
+
if (!(0, import_node_fs62.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
|
|
71290
|
+
return { absolute, relative: slash(path2), stat: (0, import_node_fs62.lstatSync)(absolute) };
|
|
69926
71291
|
}));
|
|
69927
71292
|
}
|
|
69928
71293
|
|
|
@@ -69935,10 +71300,10 @@ var DIST_ARTIFACTS = [
|
|
|
69935
71300
|
];
|
|
69936
71301
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
69937
71302
|
var ABSENT = "absent";
|
|
69938
|
-
var
|
|
71303
|
+
var sha2563 = (bytes) => `sha256:${(0, import_node_crypto25.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
69939
71304
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
69940
|
-
const committed = committedBytes ?
|
|
69941
|
-
const rebuiltExpected = rebuiltBytes ?
|
|
71305
|
+
const committed = committedBytes ? sha2563(committedBytes) : ABSENT;
|
|
71306
|
+
const rebuiltExpected = rebuiltBytes ? sha2563(rebuiltBytes) : ABSENT;
|
|
69942
71307
|
return { path: path2, status: committed === rebuiltExpected ? "current" : "stale", committed, rebuiltExpected };
|
|
69943
71308
|
}
|
|
69944
71309
|
function distTreeIdentity(labels, rebuilt, committed, digest) {
|
|
@@ -70007,13 +71372,13 @@ function renderDistDriftReceipt(receipt) {
|
|
|
70007
71372
|
return lines2;
|
|
70008
71373
|
}
|
|
70009
71374
|
function readOrNull(path2) {
|
|
70010
|
-
return (0,
|
|
71375
|
+
return (0, import_node_fs63.existsSync)(path2) ? (0, import_node_fs63.readFileSync)(path2) : null;
|
|
70011
71376
|
}
|
|
70012
71377
|
function walkFiles(root) {
|
|
70013
71378
|
const files = [];
|
|
70014
71379
|
const walk2 = (directory) => {
|
|
70015
|
-
for (const entry of (0,
|
|
70016
|
-
const child2 = (0,
|
|
71380
|
+
for (const entry of (0, import_node_fs63.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
71381
|
+
const child2 = (0, import_node_path58.join)(directory, entry.name);
|
|
70017
71382
|
if (entry.isDirectory()) walk2(child2);
|
|
70018
71383
|
else files.push(child2);
|
|
70019
71384
|
}
|
|
@@ -70023,10 +71388,10 @@ function walkFiles(root) {
|
|
|
70023
71388
|
}
|
|
70024
71389
|
function bomPathFor(root) {
|
|
70025
71390
|
try {
|
|
70026
|
-
const registry2 = JSON.parse((0,
|
|
70027
|
-
return (0,
|
|
71391
|
+
const registry2 = JSON.parse((0, import_node_fs63.readFileSync)((0, import_node_path58.join)(root, "surfaces.json"), "utf8"));
|
|
71392
|
+
return (0, import_node_path58.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
70028
71393
|
} catch {
|
|
70029
|
-
return (0,
|
|
71394
|
+
return (0, import_node_path58.join)(root, "distribution-bom.json");
|
|
70030
71395
|
}
|
|
70031
71396
|
}
|
|
70032
71397
|
function rebuildTo(packageRoot, outDir) {
|
|
@@ -70039,40 +71404,40 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
70039
71404
|
});
|
|
70040
71405
|
}
|
|
70041
71406
|
function runDistStatus(root) {
|
|
70042
|
-
const stage = (0,
|
|
71407
|
+
const stage = (0, import_node_fs63.mkdtempSync)((0, import_node_path58.join)((0, import_node_os27.tmpdir)(), "mmi-dist-drift-"));
|
|
70043
71408
|
let overlayCount = 0;
|
|
70044
71409
|
try {
|
|
70045
|
-
const cliOut = (0,
|
|
70046
|
-
const hubOut = (0,
|
|
70047
|
-
rebuildTo((0,
|
|
70048
|
-
rebuildTo((0,
|
|
71410
|
+
const cliOut = (0, import_node_path58.join)(stage, "cli-dist");
|
|
71411
|
+
const hubOut = (0, import_node_path58.join)(stage, "hub-dist");
|
|
71412
|
+
rebuildTo((0, import_node_path58.join)(root, "cli"), cliOut);
|
|
71413
|
+
rebuildTo((0, import_node_path58.join)(root, "updater"), hubOut);
|
|
70049
71414
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
70050
71415
|
const rebuilt = (path2) => {
|
|
70051
71416
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
70052
|
-
return spec ? readOrNull((0,
|
|
71417
|
+
return spec ? readOrNull((0, import_node_path58.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
70053
71418
|
};
|
|
70054
|
-
const committed = (path2) => readOrNull((0,
|
|
70055
|
-
const tree = (path2) => readOrNull((0,
|
|
70056
|
-
const distRoot = (0,
|
|
70057
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
70058
|
-
const bom = JSON.parse((0,
|
|
71419
|
+
const committed = (path2) => readOrNull((0, import_node_path58.join)(root, path2));
|
|
71420
|
+
const tree = (path2) => readOrNull((0, import_node_path58.join)(root, path2));
|
|
71421
|
+
const distRoot = (0, import_node_path58.join)(root, "cli", "dist");
|
|
71422
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path58.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
71423
|
+
const bom = JSON.parse((0, import_node_fs63.readFileSync)(bomPathFor(root), "utf8"));
|
|
70059
71424
|
const digest = (entries) => {
|
|
70060
|
-
const overlay = (0,
|
|
71425
|
+
const overlay = (0, import_node_path58.join)(stage, `overlay-${overlayCount++}`);
|
|
70061
71426
|
for (const entry of entries) {
|
|
70062
|
-
const target = (0,
|
|
70063
|
-
(0,
|
|
70064
|
-
(0,
|
|
71427
|
+
const target = (0, import_node_path58.join)(overlay, entry.path);
|
|
71428
|
+
(0, import_node_fs63.mkdirSync)((0, import_node_path58.dirname)(target), { recursive: true });
|
|
71429
|
+
(0, import_node_fs63.writeFileSync)(target, entry.bytes);
|
|
70065
71430
|
}
|
|
70066
71431
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
70067
71432
|
};
|
|
70068
71433
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
70069
71434
|
} finally {
|
|
70070
|
-
(0,
|
|
71435
|
+
(0, import_node_fs63.rmSync)(stage, { recursive: true, force: true });
|
|
70071
71436
|
}
|
|
70072
71437
|
}
|
|
70073
71438
|
|
|
70074
71439
|
// src/doc-refs-live.ts
|
|
70075
|
-
var
|
|
71440
|
+
var import_node_path59 = require("node:path");
|
|
70076
71441
|
var concrete = (value) => !!value && !/[<>{}$]/.test(value);
|
|
70077
71442
|
var liveOption = (tokens2, flag) => {
|
|
70078
71443
|
const index = tokens2.indexOf(flag);
|
|
@@ -70145,7 +71510,7 @@ async function runLiveDocRefs(root, deps, live) {
|
|
|
70145
71510
|
const offline = runDocRefs(root, { ...deps, runbooksOnly: true });
|
|
70146
71511
|
const read = deps.readFile ?? readFileOrNull;
|
|
70147
71512
|
const docs = Object.fromEntries((deps.listDocs ?? defaultListDocs)(root).flatMap((doc) => {
|
|
70148
|
-
const body = read((0,
|
|
71513
|
+
const body = read((0, import_node_path59.join)(root, doc));
|
|
70149
71514
|
return body !== null && isRunbook(doc, body) && !deps.isIgnored?.([doc]).has(doc) ? [[doc, body]] : [];
|
|
70150
71515
|
}));
|
|
70151
71516
|
const remote = await checkRunbookLive(docs, live);
|
|
@@ -70356,7 +71721,7 @@ async function diagnoseHub(deps) {
|
|
|
70356
71721
|
|
|
70357
71722
|
// src/schedules-lift-command.ts
|
|
70358
71723
|
var import_promises12 = require("node:fs/promises");
|
|
70359
|
-
var
|
|
71724
|
+
var import_node_path60 = require("node:path");
|
|
70360
71725
|
init_clean_exit();
|
|
70361
71726
|
init_cli_shared();
|
|
70362
71727
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -70385,7 +71750,7 @@ async function readWorkflowFiles(dir) {
|
|
|
70385
71750
|
const files = [];
|
|
70386
71751
|
for (const name of names.sort()) {
|
|
70387
71752
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
70388
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0,
|
|
71753
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0, import_node_path60.join)(dir, name), "utf8") });
|
|
70389
71754
|
}
|
|
70390
71755
|
return files;
|
|
70391
71756
|
}
|
|
@@ -70468,8 +71833,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
70468
71833
|
|
|
70469
71834
|
// src/spawn-policy-core.ts
|
|
70470
71835
|
var import_node_child_process24 = require("node:child_process");
|
|
70471
|
-
var
|
|
70472
|
-
var
|
|
71836
|
+
var import_node_fs64 = require("node:fs");
|
|
71837
|
+
var import_node_path61 = require("node:path");
|
|
70473
71838
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
70474
71839
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
70475
71840
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -70504,10 +71869,10 @@ function findViolationsInSource(raw) {
|
|
|
70504
71869
|
if (imported.size === 0) return [];
|
|
70505
71870
|
const rawLines = raw.split("\n");
|
|
70506
71871
|
const src = stripComments(raw);
|
|
70507
|
-
const
|
|
71872
|
+
const call3 = new RegExp(CALL_SOURCE, "g");
|
|
70508
71873
|
const found = [];
|
|
70509
71874
|
let m;
|
|
70510
|
-
while (m =
|
|
71875
|
+
while (m = call3.exec(src)) {
|
|
70511
71876
|
const callee = m[2];
|
|
70512
71877
|
if (!imported.has(callee)) continue;
|
|
70513
71878
|
const open2 = m.index + m[0].length - 1;
|
|
@@ -70555,7 +71920,7 @@ function runSpawnPolicy(root) {
|
|
|
70555
71920
|
for (const file of files) {
|
|
70556
71921
|
let raw;
|
|
70557
71922
|
try {
|
|
70558
|
-
raw = (0,
|
|
71923
|
+
raw = (0, import_node_fs64.readFileSync)((0, import_node_path61.join)(root, file), "utf8");
|
|
70559
71924
|
} catch {
|
|
70560
71925
|
continue;
|
|
70561
71926
|
}
|
|
@@ -70575,19 +71940,19 @@ function runSpawnPolicy(root) {
|
|
|
70575
71940
|
function registerDeveloperCommands(program3) {
|
|
70576
71941
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
70577
71942
|
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) => {
|
|
70578
|
-
const path2 = (0,
|
|
70579
|
-
const current = (0,
|
|
71943
|
+
const path2 = (0, import_node_path62.join)(process.cwd(), ".gitignore");
|
|
71944
|
+
const current = (0, import_node_fs65.existsSync)(path2) ? (0, import_node_fs65.readFileSync)(path2, "utf8") : null;
|
|
70580
71945
|
const plan = planManagedGitignore(current);
|
|
70581
71946
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
70582
71947
|
if (opts.json) {
|
|
70583
|
-
if (opts.write && plan.changed) (0,
|
|
71948
|
+
if (opts.write && plan.changed) (0, import_node_fs65.writeFileSync)(path2, plan.content, "utf8");
|
|
70584
71949
|
console.log(JSON.stringify(plan, null, 2));
|
|
70585
71950
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
70586
71951
|
return;
|
|
70587
71952
|
}
|
|
70588
71953
|
if (opts.write) {
|
|
70589
71954
|
if (plan.changed) {
|
|
70590
|
-
(0,
|
|
71955
|
+
(0, import_node_fs65.writeFileSync)(path2, plan.content, "utf8");
|
|
70591
71956
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
70592
71957
|
} else {
|
|
70593
71958
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -70779,8 +72144,8 @@ function registerDeveloperCommands(program3) {
|
|
|
70779
72144
|
return { commandPaths: new Set(manifest.index.map((entry) => entry.path)), commandOptions: commandOptions2, source: "in-process" };
|
|
70780
72145
|
};
|
|
70781
72146
|
const resolved = resolveCommandInventory({ root, from: o.commandsFrom, inProcess });
|
|
70782
|
-
const { commandPaths, commandOptions } = resolved.inventory;
|
|
70783
|
-
const deps = { commandPaths, commandOptions, base: o.base, runbooksOnly: o.runbooksOnly };
|
|
72147
|
+
const { commandPaths, commandOptions, source } = resolved.inventory;
|
|
72148
|
+
const deps = { commandPaths, commandOptions, commandSource: source, base: o.base, runbooksOnly: o.runbooksOnly };
|
|
70784
72149
|
const result = o.live ? await runLiveDocRefs(root, deps, createRunbookLiveDeps(process.argv[1])) : runDocRefs(root, deps);
|
|
70785
72150
|
result.warnings.push(...resolved.warnings);
|
|
70786
72151
|
if (o.json) {
|
|
@@ -70904,7 +72269,7 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
70904
72269
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
70905
72270
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
70906
72271
|
function envHealLockPath(home) {
|
|
70907
|
-
return (0,
|
|
72272
|
+
return (0, import_node_path63.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
70908
72273
|
}
|
|
70909
72274
|
async function withEnvHealLock(what, run) {
|
|
70910
72275
|
try {
|
|
@@ -71042,7 +72407,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
71042
72407
|
);
|
|
71043
72408
|
const result = applyPluginCachePlan(
|
|
71044
72409
|
plan,
|
|
71045
|
-
(p) => (0,
|
|
72410
|
+
(p) => (0, import_node_fs66.rmSync)(p, { recursive: true }),
|
|
71046
72411
|
stagingApplyFsGuard(configRoot)
|
|
71047
72412
|
);
|
|
71048
72413
|
return {
|
|
@@ -71077,7 +72442,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
71077
72442
|
// has no generated routing index would
|
|
71078
72443
|
// get a permanent — demanding an artifact it never asked for.
|
|
71079
72444
|
docsIndexState: (root) => {
|
|
71080
|
-
if (!(0,
|
|
72445
|
+
if (!(0, import_node_fs66.existsSync)((0, import_node_path63.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
71081
72446
|
const real = createDocsIndexDeps(root);
|
|
71082
72447
|
let docs;
|
|
71083
72448
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -71086,7 +72451,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
71086
72451
|
},
|
|
71087
72452
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
71088
72453
|
healDocsIndex: (root) => {
|
|
71089
|
-
if (!(0,
|
|
72454
|
+
if (!(0, import_node_fs66.existsSync)((0, import_node_path63.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
71090
72455
|
const real = createDocsIndexDeps(root);
|
|
71091
72456
|
let docs;
|
|
71092
72457
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -71477,7 +72842,22 @@ registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand
|
|
|
71477
72842
|
appActorCommand = commandPath4;
|
|
71478
72843
|
setAuditCommand(commandPath4);
|
|
71479
72844
|
if (commandPath4 !== "whoami diagnose-hub") emitCliCallTelemetry(commandPath4);
|
|
71480
|
-
|
|
72845
|
+
const LAZY_APP_MINT_COMMANDS = /* @__PURE__ */ new Set([
|
|
72846
|
+
"ci cancel-orphans",
|
|
72847
|
+
"run cancel",
|
|
72848
|
+
"run rerun",
|
|
72849
|
+
// #9779/#9780: the vars verbs and archive-status validate --repo/name/value before the first
|
|
72850
|
+
// dispatch, so their scoped App mint waits for the action handler like the run writes above.
|
|
72851
|
+
"vars get",
|
|
72852
|
+
"vars list",
|
|
72853
|
+
"vars create",
|
|
72854
|
+
"vars update",
|
|
72855
|
+
"vars delete",
|
|
72856
|
+
"repo archive-status",
|
|
72857
|
+
// #9788: the lifecycle read validates its manifest/window/bounds before its scoped mint.
|
|
72858
|
+
"read merge-queue lifecycle"
|
|
72859
|
+
]);
|
|
72860
|
+
if (LAZY_APP_MINT_COMMANDS.has(commandPath4)) return;
|
|
71481
72861
|
try {
|
|
71482
72862
|
await activateAppActor(commandPath4, process.env, () => APP_WRITE_COMMANDS.has(commandPath4) ? mintCommandWriteToken(appActorDeps(), commandPath4) : mintInstallationToken(appActorDeps()));
|
|
71483
72863
|
} catch (e) {
|
|
@@ -71874,7 +73254,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
71874
73254
|
if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
71875
73255
|
if (o.secretsFile) {
|
|
71876
73256
|
try {
|
|
71877
|
-
vars.push(`secrets=${(0,
|
|
73257
|
+
vars.push(`secrets=${(0, import_node_fs66.readFileSync)(o.secretsFile, "utf8")}`);
|
|
71878
73258
|
} catch (e) {
|
|
71879
73259
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
71880
73260
|
}
|
|
@@ -72266,16 +73646,16 @@ function ciAuditDeps2() {
|
|
|
72266
73646
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
72267
73647
|
readSeedFile: (path2) => {
|
|
72268
73648
|
if (!root) return null;
|
|
72269
|
-
const fullPath = (0,
|
|
72270
|
-
return (0,
|
|
73649
|
+
const fullPath = (0, import_node_path63.join)(root, path2);
|
|
73650
|
+
return (0, import_node_fs66.existsSync)(fullPath) ? (0, import_node_fs66.readFileSync)(fullPath, "utf8") : null;
|
|
72271
73651
|
}
|
|
72272
73652
|
};
|
|
72273
73653
|
}
|
|
72274
73654
|
function hubRoot2() {
|
|
72275
|
-
const fromPkg = (0,
|
|
73655
|
+
const fromPkg = (0, import_node_path63.join)(__dirname, "..", "..");
|
|
72276
73656
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
72277
|
-
if ((0,
|
|
72278
|
-
if ((0,
|
|
73657
|
+
if ((0, import_node_fs66.existsSync)((0, import_node_path63.join)(fromPkg, marker))) return fromPkg;
|
|
73658
|
+
if ((0, import_node_fs66.existsSync)((0, import_node_path63.join)(process.cwd(), marker))) return process.cwd();
|
|
72279
73659
|
return null;
|
|
72280
73660
|
}
|
|
72281
73661
|
registerQueryCommands(program2);
|
|
@@ -72319,6 +73699,20 @@ registerRunRerun(
|
|
|
72319
73699
|
async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run rerun")).token,
|
|
72320
73700
|
async () => fetchProjectsList(registryClientDeps(await loadConfig()))
|
|
72321
73701
|
);
|
|
73702
|
+
var varsAppClient = (commandPath4) => async () => (
|
|
73703
|
+
// The concrete client always provides restWithStatus (optional on the interface only so hand-built
|
|
73704
|
+
// test stubs of GitHubClient keep compiling).
|
|
73705
|
+
createGitHubClient({ token: async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), commandPath4)).token })
|
|
73706
|
+
);
|
|
73707
|
+
var varsProjects = async () => fetchProjectsList(registryClientDeps(await loadConfig()));
|
|
73708
|
+
var varsActor = () => ({ credential: "app_installation", ...activeAppQuotaScope() ? { quotaScope: activeAppQuotaScope() } : {} });
|
|
73709
|
+
registerDevopsVarsCommands(program2, { appClient: (commandPath4) => varsAppClient(commandPath4)(), projects: varsProjects, actor: varsActor });
|
|
73710
|
+
registerRepoArchiveStatusCommand(program2, { appClient: (commandPath4) => varsAppClient(commandPath4)(), projects: varsProjects, actor: varsActor });
|
|
73711
|
+
registerMergeQueueLifecycleCommand(program2.commands.find((c) => c.name() === "read"), {
|
|
73712
|
+
appClient: async (commandPath4) => createGitHubClient({ token: async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), commandPath4)).token }),
|
|
73713
|
+
projects: varsProjects,
|
|
73714
|
+
actor: varsActor
|
|
73715
|
+
});
|
|
72322
73716
|
ci.command("reconcile").description("audit + optionally apply merge settings and product ruleset activation (master-admin)").option("--json", "machine-readable output").option("--repo <owner/repo>", "reconcile one repo instead of the full registry").option("--apply", "PATCH merge settings + activate product ruleset when missing (master role required); activation is skipped while the repo's gate has never passed (#3694)").option("--park-ruleset", "stop the product ruleset enforcing without deleting it \u2014 keeps its required contexts (master role required)").action(async (o) => {
|
|
72323
73717
|
if (o.apply && o.parkRuleset) {
|
|
72324
73718
|
return fail("ci reconcile: --apply and --park-ruleset ask for opposite things; pass one");
|
|
@@ -72418,10 +73812,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
72418
73812
|
targets = resolution.targets;
|
|
72419
73813
|
}
|
|
72420
73814
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
72421
|
-
const fileMatrix = (0,
|
|
73815
|
+
const fileMatrix = (0, import_node_fs66.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs66.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
72422
73816
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
72423
73817
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
72424
|
-
const sanctioned = (0,
|
|
73818
|
+
const sanctioned = (0, import_node_fs66.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs66.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
72425
73819
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
72426
73820
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
72427
73821
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -72458,16 +73852,16 @@ function directoryBytes(path2) {
|
|
|
72458
73852
|
let total = 0;
|
|
72459
73853
|
let entries;
|
|
72460
73854
|
try {
|
|
72461
|
-
entries = (0,
|
|
73855
|
+
entries = (0, import_node_fs66.readdirSync)(path2, { withFileTypes: true });
|
|
72462
73856
|
} catch {
|
|
72463
73857
|
return 0;
|
|
72464
73858
|
}
|
|
72465
73859
|
for (const entry of entries) {
|
|
72466
|
-
const child2 = (0,
|
|
73860
|
+
const child2 = (0, import_node_path63.join)(path2, entry.name);
|
|
72467
73861
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
72468
73862
|
else {
|
|
72469
73863
|
try {
|
|
72470
|
-
total += (0,
|
|
73864
|
+
total += (0, import_node_fs66.statSync)(child2).size;
|
|
72471
73865
|
} catch {
|
|
72472
73866
|
}
|
|
72473
73867
|
}
|
|
@@ -72475,25 +73869,25 @@ function directoryBytes(path2) {
|
|
|
72475
73869
|
return total;
|
|
72476
73870
|
}
|
|
72477
73871
|
function listDirEntries(dir) {
|
|
72478
|
-
return (0,
|
|
73872
|
+
return (0, import_node_fs66.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
72479
73873
|
}
|
|
72480
73874
|
function readInstalledPluginRefs(configRoot) {
|
|
72481
73875
|
const p = installedPluginsPathForConfig(configRoot);
|
|
72482
|
-
if (!(0,
|
|
73876
|
+
if (!(0, import_node_fs66.existsSync)(p)) return [];
|
|
72483
73877
|
try {
|
|
72484
|
-
return installedPluginPaths((0,
|
|
73878
|
+
return installedPluginPaths((0, import_node_fs66.readFileSync)(p, "utf8"));
|
|
72485
73879
|
} catch {
|
|
72486
73880
|
return null;
|
|
72487
73881
|
}
|
|
72488
73882
|
}
|
|
72489
73883
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
72490
73884
|
return {
|
|
72491
|
-
exists: (p) => (0,
|
|
72492
|
-
listVersionDirs: (root) => (0,
|
|
73885
|
+
exists: (p) => (0, import_node_fs66.existsSync)(p),
|
|
73886
|
+
listVersionDirs: (root) => (0, import_node_fs66.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
72493
73887
|
dirBytes,
|
|
72494
|
-
listStagingDirs: (root) => (0,
|
|
73888
|
+
listStagingDirs: (root) => (0, import_node_fs66.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
72495
73889
|
try {
|
|
72496
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
73890
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path63.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs66.statSync)(p).mtimeMs) };
|
|
72497
73891
|
} catch {
|
|
72498
73892
|
return { name: d.name, mtimeMs: Date.now() };
|
|
72499
73893
|
}
|
|
@@ -72507,10 +73901,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
72507
73901
|
return {
|
|
72508
73902
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
72509
73903
|
mtimeMs: (name) => {
|
|
72510
|
-
const p = (0,
|
|
72511
|
-
if (!(0,
|
|
73904
|
+
const p = (0, import_node_path63.join)(stagingRoot, name);
|
|
73905
|
+
if (!(0, import_node_fs66.existsSync)(p)) return null;
|
|
72512
73906
|
try {
|
|
72513
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
73907
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs66.statSync)(q).mtimeMs);
|
|
72514
73908
|
} catch {
|
|
72515
73909
|
return null;
|
|
72516
73910
|
}
|
|
@@ -72536,7 +73930,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
72536
73930
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
72537
73931
|
);
|
|
72538
73932
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
72539
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
73933
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs66.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
72540
73934
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
72541
73935
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
72542
73936
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -72546,7 +73940,7 @@ program2.command("archive-prune").description("inventory worktree evidence archi
|
|
|
72546
73940
|
let primary = o.repoRoot;
|
|
72547
73941
|
if (!primary) {
|
|
72548
73942
|
const common = await gitOut(["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
72549
|
-
primary = common ? (0,
|
|
73943
|
+
primary = common ? (0, import_node_path63.dirname)(common) : "";
|
|
72550
73944
|
}
|
|
72551
73945
|
if (!primary) {
|
|
72552
73946
|
const detail = "archive-prune: not inside a git checkout (pass --repo-root <primary checkout>)";
|