@mutmutco/cli 4.6.11 → 4.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +1265 -750
- package/dist/runbook-sweep.cjs +42 -16
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -1409,13 +1409,7 @@ async function ghFilingAppEnv(file, args, requireApp = false) {
|
|
|
1409
1409
|
if (!ghWriteTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || !requireApp && process.env.MMI_ACTOR?.trim() === "personal") return void 0;
|
|
1410
1410
|
if (!isAppFilingWrite(args)) return void 0;
|
|
1411
1411
|
const explicitApp = requireApp || process.env.MMI_ACTOR?.trim() === "app";
|
|
1412
|
-
|
|
1413
|
-
try {
|
|
1414
|
-
token = await ghWriteTokenProvider();
|
|
1415
|
-
} catch (e) {
|
|
1416
|
-
if (explicitApp) throw e;
|
|
1417
|
-
return void 0;
|
|
1418
|
-
}
|
|
1412
|
+
const token = await ghWriteTokenProvider();
|
|
1419
1413
|
if (!token && explicitApp) {
|
|
1420
1414
|
throw new Error("app actor: filing write token unavailable \u2014 refusing personal-token fallback under MMI_ACTOR=app");
|
|
1421
1415
|
}
|
|
@@ -1642,7 +1636,7 @@ var init_cli_shared = __esm({
|
|
|
1642
1636
|
try {
|
|
1643
1637
|
return await tag(run(appEnv), "app_installation");
|
|
1644
1638
|
} catch (e) {
|
|
1645
|
-
if (shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(), ambient);
|
|
1639
|
+
if (!reportsCredential && shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(), ambient);
|
|
1646
1640
|
throw e;
|
|
1647
1641
|
}
|
|
1648
1642
|
};
|
|
@@ -1680,6 +1674,7 @@ __export(board_write_exports, {
|
|
|
1680
1674
|
BoardAttachError: () => BoardAttachError,
|
|
1681
1675
|
addIssueToProjectViaHub: () => addIssueToProjectViaHub,
|
|
1682
1676
|
boardAttachHttpError: () => boardAttachHttpError,
|
|
1677
|
+
createBoardIssueViaHub: () => createBoardIssueViaHub,
|
|
1683
1678
|
writeHubBoardField: () => writeHubBoardField
|
|
1684
1679
|
});
|
|
1685
1680
|
async function writeHubBoardField(request) {
|
|
@@ -1721,6 +1716,18 @@ function boardAttachHttpError(status, detail) {
|
|
|
1721
1716
|
const code = status === 401 ? "hub_session_missing" : status === 403 ? "forbidden" : status === 429 ? "rate_limited" : status >= 500 ? "hub_unavailable" : "refused";
|
|
1722
1717
|
return new BoardAttachError(code, status, detail);
|
|
1723
1718
|
}
|
|
1719
|
+
async function createBoardIssueViaHub(request) {
|
|
1720
|
+
const config = await loadConfig();
|
|
1721
|
+
const res = await fetch(`${config.sagaApiUrl?.replace(/\/$/, "")}/board/issues`, {
|
|
1722
|
+
method: "POST",
|
|
1723
|
+
headers: await hubHeaders({ "content-type": "application/json" }),
|
|
1724
|
+
body: JSON.stringify(request),
|
|
1725
|
+
signal: AbortSignal.timeout(25e3)
|
|
1726
|
+
});
|
|
1727
|
+
const body = await res.json().catch(() => null);
|
|
1728
|
+
if (!res.ok || !body?.number || !body.url) throw new Error(`board issue create HTTP ${res.status}: ${body?.error ?? "unconfirmed result"}`);
|
|
1729
|
+
return { number: body.number, url: body.url, projectItemId: body.projectItemId, onBoard: Boolean(body.onBoard) };
|
|
1730
|
+
}
|
|
1724
1731
|
var BOARD_ATTACH_HINTS, BoardAttachError;
|
|
1725
1732
|
var init_board_write = __esm({
|
|
1726
1733
|
"src/board-write.ts"() {
|
|
@@ -1729,7 +1736,7 @@ var init_board_write = __esm({
|
|
|
1729
1736
|
BOARD_ATTACH_HINTS = {
|
|
1730
1737
|
// #8965: honest, not a promise — attach did NOT run. App automation never has a Hub session by design.
|
|
1731
1738
|
hub_session_missing: "No Hub session (an App installation token cannot open one). Hub adds new issues in registered repos to their board automatically when they are opened; check membership with `mmi-cli oracle board show <n> --repo <owner/repo>`. A human login or MMI_HUB_TOKEN (Actions /auth/session-ci) is needed only to attach older or transferred issues.",
|
|
1732
|
-
forbidden: "the Hub denied this login: attach needs org-owner
|
|
1739
|
+
forbidden: "the Hub denied this login: attach needs org-owner, repo-admin or verified repository write authority on <owner/repo>",
|
|
1733
1740
|
hub_unavailable: "the Hub or GitHub failed transiently; the add is idempotent, so rerun `mmi-cli oracle board attach <issue>`",
|
|
1734
1741
|
rate_limited: "GitHub quota is exhausted; rerun `mmi-cli oracle board attach <issue>` after the reset",
|
|
1735
1742
|
refused: "the Hub refused this attach; check the board registration for <owner/repo>"
|
|
@@ -13512,7 +13519,7 @@ function useColor() {
|
|
|
13512
13519
|
var program = new Command();
|
|
13513
13520
|
|
|
13514
13521
|
// src/command-composition.ts
|
|
13515
|
-
var
|
|
13522
|
+
var import_node_fs65 = require("node:fs");
|
|
13516
13523
|
init_clean_exit();
|
|
13517
13524
|
init_cli_shared();
|
|
13518
13525
|
|
|
@@ -15129,8 +15136,8 @@ function resolveVerbAliasShim(argv) {
|
|
|
15129
15136
|
}
|
|
15130
15137
|
|
|
15131
15138
|
// src/command-composition.ts
|
|
15132
|
-
var
|
|
15133
|
-
var
|
|
15139
|
+
var import_node_os28 = require("node:os");
|
|
15140
|
+
var import_node_path62 = require("node:path");
|
|
15134
15141
|
|
|
15135
15142
|
// src/board-read.ts
|
|
15136
15143
|
var import_node_fs18 = require("node:fs");
|
|
@@ -16084,6 +16091,14 @@ async function pollToken() {
|
|
|
16084
16091
|
});
|
|
16085
16092
|
return pollTokenOnce;
|
|
16086
16093
|
}
|
|
16094
|
+
async function refreshPollToken() {
|
|
16095
|
+
if (process.env.MMI_ACTOR?.trim() === "personal" || !pollTokenProvider) return void 0;
|
|
16096
|
+
pollTokenOnce = pollTokenProvider({ fresh: true }).catch((e) => {
|
|
16097
|
+
if (e instanceof AppActorError && e.code === "mint-failed") throw e;
|
|
16098
|
+
return void 0;
|
|
16099
|
+
});
|
|
16100
|
+
return pollTokenOnce;
|
|
16101
|
+
}
|
|
16087
16102
|
async function ghReadEnv() {
|
|
16088
16103
|
const appToken = await pollToken();
|
|
16089
16104
|
return appToken ? { ...process.env, GH_TOKEN: appToken, MMI_GH_POOL: "app" } : void 0;
|
|
@@ -25439,11 +25454,25 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
25439
25454
|
}
|
|
25440
25455
|
|
|
25441
25456
|
// src/gate-budget.ts
|
|
25442
|
-
var
|
|
25457
|
+
var gate_budget_exports = {};
|
|
25458
|
+
__export(gate_budget_exports, {
|
|
25459
|
+
BLESSED_JEV_FAILURES_SHA: () => BLESSED_JEV_FAILURES_SHA,
|
|
25460
|
+
BLESSED_KIT_AGENT_FILES_SHA: () => BLESSED_KIT_AGENT_FILES_SHA,
|
|
25461
|
+
BLESSED_RUNBOOK_GUARD_SHA: () => BLESSED_RUNBOOK_GUARD_SHA,
|
|
25462
|
+
BLESSED_RUNNER_CHOICE_SHA: () => BLESSED_RUNNER_CHOICE_SHA,
|
|
25463
|
+
BLESSED_RUNNER_GATE_SHA: () => BLESSED_RUNNER_GATE_SHA,
|
|
25464
|
+
BLESSED_RUN_WITH_BUDGET_SHA: () => BLESSED_RUN_WITH_BUDGET_SHA,
|
|
25465
|
+
BLESSED_VERDICT_RECEIPT_SHA: () => BLESSED_VERDICT_RECEIPT_SHA,
|
|
25466
|
+
MAX_SECONDS_CEILING: () => MAX_SECONDS_CEILING,
|
|
25467
|
+
checkGateBudget: () => checkGateBudget,
|
|
25468
|
+
isGateWorkflowPath: () => isGateWorkflowPath
|
|
25469
|
+
});
|
|
25470
|
+
var BLESSED_RUN_WITH_BUDGET_SHA = "cb31a94c89729beac83500ad4d301132dcfe2e4c";
|
|
25443
25471
|
var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
25444
25472
|
var BLESSED_KIT_AGENT_FILES_SHA = "e976df208d891b215844386c1cac0f161946b6fa";
|
|
25445
25473
|
var BLESSED_JEV_FAILURES_SHA = "d5278c6133318b33727483ddfdfddc8f841e3ef6";
|
|
25446
|
-
var
|
|
25474
|
+
var BLESSED_VERDICT_RECEIPT_SHA = "";
|
|
25475
|
+
var BLESSED_RUNNER_CHOICE_SHA = "f6b691f966611ec2a3b688c74c92a92f23133f41";
|
|
25447
25476
|
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
25448
25477
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
25449
25478
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
@@ -25828,7 +25857,10 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
25828
25857
|
GATE_SERVICES_YAML: "",
|
|
25829
25858
|
GATE_EXTRA_STEPS_YAML: "",
|
|
25830
25859
|
// #9293: Jev failure-memory fetch/report steps from the blessed jev-failures pin (an empty pin renders none).
|
|
25831
|
-
...gateJevStepsYaml(BLESSED_JEV_FAILURES_SHA, runtime)
|
|
25860
|
+
...gateJevStepsYaml(BLESSED_JEV_FAILURES_SHA, runtime),
|
|
25861
|
+
// Org CI H1: exact-SHA verdict receipt from the blessed verdict-receipt pin (an empty pin renders the legacy #8206 rc-tag step).
|
|
25862
|
+
GATE_VERDICT_SHA: BLESSED_VERDICT_RECEIPT_SHA,
|
|
25863
|
+
GATE_VERDICT_STEP_YAML: gateVerdictStepYaml(BLESSED_VERDICT_RECEIPT_SHA)
|
|
25832
25864
|
};
|
|
25833
25865
|
const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
|
|
25834
25866
|
const trackBranches = track === "trunk" ? ["main"] : track === "single" ? ["development"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
|
|
@@ -25977,6 +26009,47 @@ function gateServicesYaml(services) {
|
|
|
25977
26009
|
}
|
|
25978
26010
|
return out;
|
|
25979
26011
|
}
|
|
26012
|
+
var LEGACY_RC_REUSE_STEP_YAML = String.raw`
|
|
26013
|
+
# #8206: keep a fresh successful tag gate; promote the exact development verdict, not a ref.
|
|
26014
|
+
- name: Reuse development gate for rc tag
|
|
26015
|
+
id: development-gate
|
|
26016
|
+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
|
26017
|
+
with:
|
|
26018
|
+
script: |
|
|
26019
|
+
core.setOutput('reuse', 'false');
|
|
26020
|
+
if (context.eventName !== 'push' || !/^refs\/tags\/v.+-rc\.\d+$/.test(context.ref) || !/^[0-9a-f]{40}$/.test(context.sha)) return;
|
|
26021
|
+
try {
|
|
26022
|
+
const { data: current } = await github.rest.actions.getWorkflowRun({ ...context.repo, run_id: context.runId });
|
|
26023
|
+
const { data } = await github.rest.actions.listWorkflowRuns({
|
|
26024
|
+
...context.repo, workflow_id: current.workflow_id, branch: 'development',
|
|
26025
|
+
event: 'push', head_sha: context.sha, per_page: 100
|
|
26026
|
+
});
|
|
26027
|
+
// API returns newest first: a newer red/pending rerun must not reuse an older green.
|
|
26028
|
+
const run = data.workflow_runs[0];
|
|
26029
|
+
if (!run || run.head_sha !== context.sha || run.head_branch !== 'development' ||
|
|
26030
|
+
run.event !== 'push' || run.status !== 'completed' || run.conclusion !== 'success') return;
|
|
26031
|
+
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
|
|
26032
|
+
...context.repo, run_id: run.id, filter: 'latest', per_page: 100
|
|
26033
|
+
});
|
|
26034
|
+
if (!jobs.some(job => job.name === 'gate' && job.status === 'completed' && job.conclusion === 'success')) return;
|
|
26035
|
+
core.setOutput('reuse', 'true');
|
|
26036
|
+
core.info('Reusing successful development gate run ' + run.id + ' for exact SHA ' + context.sha);
|
|
26037
|
+
} catch (error) {
|
|
26038
|
+
core.warning('Development gate proof unavailable; running normal gate: ' + error.message);
|
|
26039
|
+
}`;
|
|
26040
|
+
function gateVerdictStepYaml(sha) {
|
|
26041
|
+
if (!/^[0-9a-f]{40}$/.test(sha)) return LEGACY_RC_REUSE_STEP_YAML;
|
|
26042
|
+
return [
|
|
26043
|
+
"\n # Org CI H1: exact-SHA verdict receipt. Any missing or partial proof reports reuse=false and the gate runs.",
|
|
26044
|
+
"\n - name: Reuse the exact-SHA gate verdict",
|
|
26045
|
+
"\n id: development-gate",
|
|
26046
|
+
"\n if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/{{GATE_FULL_RUN_BRANCH}}' || (startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '-rc.'))) }}",
|
|
26047
|
+
"\n uses: mutmutco/MMI-Hub/.github/actions/verdict-receipt@{{GATE_VERDICT_SHA}}",
|
|
26048
|
+
"\n with:",
|
|
26049
|
+
"\n source: ${{ github.ref_type == 'tag' && 'push' || 'merge_group' }}",
|
|
26050
|
+
"\n queue-is-full-verdict: 'true'"
|
|
26051
|
+
].join("");
|
|
26052
|
+
}
|
|
25980
26053
|
function gateJevStepsYaml(sha, runtime = "node") {
|
|
25981
26054
|
if (!/^[0-9a-f]{40}$/.test(sha)) return { GATE_JEV_FETCH_YAML: "", GATE_JEV_REPORT_YAML: "" };
|
|
25982
26055
|
const listFiles = runtime === "python" ? `git ls-files -z -- 'test_*.py' '*_test.py' | python3 -c "import sys,json;print(json.dumps([p for p in sys.stdin.read().split(chr(0)) if p]))" > .jerv/jev-test-files.json` : `git ls-files -z -- '*.test.*' '*.spec.*' | node -e "process.stdout.write(JSON.stringify(require('fs').readFileSync(0,'utf8').split('\\0').filter(Boolean)))" > .jerv/jev-test-files.json`;
|
|
@@ -26950,6 +27023,24 @@ async function reconcileOrgNoAgentFilesRuleset(plan, client, org = ORG_LOGIN) {
|
|
|
26950
27023
|
};
|
|
26951
27024
|
}
|
|
26952
27025
|
|
|
27026
|
+
// src/registry-repositories.ts
|
|
27027
|
+
function qualifyRegistryRepo(raw) {
|
|
27028
|
+
return raw.includes("/") ? raw : `mutmutco/${raw}`;
|
|
27029
|
+
}
|
|
27030
|
+
function registryRepoKey(repo) {
|
|
27031
|
+
return repo.toLowerCase();
|
|
27032
|
+
}
|
|
27033
|
+
function repoProjectClaim(project2, repoKey) {
|
|
27034
|
+
if (!project2) return 0;
|
|
27035
|
+
const slug = typeof project2.slug === "string" ? project2.slug.toLowerCase() : void 0;
|
|
27036
|
+
if (slug !== void 0 && slug === repoKey.split("/")[1]) return 3;
|
|
27037
|
+
if (project2.projectType !== "hub-service") return 2;
|
|
27038
|
+
return 1;
|
|
27039
|
+
}
|
|
27040
|
+
function preferPrimaryRepoProject(current, candidate, repoKey) {
|
|
27041
|
+
return repoProjectClaim(candidate, repoKey) > repoProjectClaim(current, repoKey) ? candidate : current;
|
|
27042
|
+
}
|
|
27043
|
+
|
|
26953
27044
|
// src/safe-json.ts
|
|
26954
27045
|
function safeJson(text, fallback) {
|
|
26955
27046
|
try {
|
|
@@ -27231,17 +27322,23 @@ async function auditOrgAccess(targets, deps, matrix = {}, dataAccess, sanctioned
|
|
|
27231
27322
|
}
|
|
27232
27323
|
function loadAccessTargets(projectsJson) {
|
|
27233
27324
|
const projects = safeJson(projectsJson, {}).projects ?? [];
|
|
27234
|
-
const
|
|
27235
|
-
const targets = [];
|
|
27325
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
27236
27326
|
for (const project2 of projects) {
|
|
27237
27327
|
for (const repo of project2.repos ?? []) {
|
|
27238
|
-
|
|
27239
|
-
|
|
27240
|
-
|
|
27241
|
-
|
|
27242
|
-
|
|
27328
|
+
const key = repo.toLowerCase();
|
|
27329
|
+
const existing = byRepo.get(key);
|
|
27330
|
+
byRepo.set(key, {
|
|
27331
|
+
repo: existing?.repo ?? repo,
|
|
27332
|
+
project: preferPrimaryRepoProject(existing?.project, project2, key)
|
|
27333
|
+
});
|
|
27243
27334
|
}
|
|
27244
27335
|
}
|
|
27336
|
+
const targets = [];
|
|
27337
|
+
for (const { repo, project: project2 } of byRepo.values()) {
|
|
27338
|
+
const cls = project2.class === "content" || project2.deployModel === "content" ? "content" : "deployable";
|
|
27339
|
+
const releaseTrack = cls === "content" ? "trunk" : resolveReleaseTrack(project2, void 0, repo);
|
|
27340
|
+
targets.push({ repo, class: cls, releaseTrack });
|
|
27341
|
+
}
|
|
27245
27342
|
return targets;
|
|
27246
27343
|
}
|
|
27247
27344
|
function resolveOrgAuditTargets(registryProjects) {
|
|
@@ -27605,10 +27702,10 @@ var rollout_plan_default = {
|
|
|
27605
27702
|
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)."
|
|
27606
27703
|
},
|
|
27607
27704
|
baseline: {
|
|
27608
|
-
version: "4.6.
|
|
27609
|
-
tag: "v4.6.
|
|
27610
|
-
commit: "
|
|
27611
|
-
npm: "@mutmutco/cli@4.6.
|
|
27705
|
+
version: "4.6.13",
|
|
27706
|
+
tag: "v4.6.13",
|
|
27707
|
+
commit: "70ee5f41b61b",
|
|
27708
|
+
npm: "@mutmutco/cli@4.6.13"
|
|
27612
27709
|
},
|
|
27613
27710
|
exitCriterion: "fleet-n-of-n",
|
|
27614
27711
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27625,14 +27722,14 @@ var rollout_plan_default = {
|
|
|
27625
27722
|
repo: "mutmutco/mmi-hub",
|
|
27626
27723
|
role: "canary",
|
|
27627
27724
|
schedule: "train",
|
|
27628
|
-
v3Target: "v4.6.
|
|
27725
|
+
v3Target: "v4.6.13"
|
|
27629
27726
|
}
|
|
27630
27727
|
],
|
|
27631
27728
|
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.",
|
|
27632
27729
|
rollback: {
|
|
27633
27730
|
independent: true,
|
|
27634
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
27635
|
-
v3Target: "v4.6.
|
|
27731
|
+
mechanism: "npm dist-tag latest -> 4.6.13 and redeploy the Hub Lambda from tag v4.6.13 (70ee5f41b61b); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
27732
|
+
v3Target: "v4.6.13 (@mutmutco/cli@4.6.13, tag commit 70ee5f41b61b \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
27636
27733
|
}
|
|
27637
27734
|
},
|
|
27638
27735
|
{
|
|
@@ -27863,7 +27960,10 @@ function buildOrgVersionsReport(projects, probes, generatedAt) {
|
|
|
27863
27960
|
name: String(project2.name ?? project2.slug ?? "?")
|
|
27864
27961
|
});
|
|
27865
27962
|
}
|
|
27866
|
-
for (const repo of repos2)
|
|
27963
|
+
for (const repo of repos2) {
|
|
27964
|
+
const key = repo.trim().toLowerCase();
|
|
27965
|
+
projectByRepo.set(key, preferPrimaryRepoProject(projectByRepo.get(key), project2, key));
|
|
27966
|
+
}
|
|
27867
27967
|
}
|
|
27868
27968
|
const repos = orgVersionRepos(projects).map((repo) => {
|
|
27869
27969
|
const project2 = projectByRepo.get(repo.toLowerCase());
|
|
@@ -28882,7 +28982,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
28882
28982
|
}
|
|
28883
28983
|
for (const repo of repos2) {
|
|
28884
28984
|
const key = repo.trim().toLowerCase();
|
|
28885
|
-
projectByRepo.set(key, projectByRepo.get(key)
|
|
28985
|
+
projectByRepo.set(key, preferPrimaryRepoProject(projectByRepo.get(key), project2, key));
|
|
28886
28986
|
}
|
|
28887
28987
|
}
|
|
28888
28988
|
const repos = orgVersionRepos(projects).map((repo) => {
|
|
@@ -28970,15 +29070,16 @@ function defaultFleetTrackInventoryDeps(consistent = false) {
|
|
|
28970
29070
|
async function readFleetTrackInventory(deps = defaultFleetTrackInventoryDeps()) {
|
|
28971
29071
|
const projects = await deps.projects();
|
|
28972
29072
|
const repos = orgVersionRepos(projects);
|
|
28973
|
-
const
|
|
29073
|
+
const projectByRepo = /* @__PURE__ */ new Map();
|
|
28974
29074
|
for (const project2 of projects) {
|
|
28975
29075
|
for (const repo of (project2.repos ?? []).filter(validRepo2)) {
|
|
28976
29076
|
const key = repo.trim().toLowerCase();
|
|
28977
|
-
|
|
28978
|
-
branchByRepo.set(key, typeof project2.branch === "string" ? project2.branch : void 0);
|
|
28979
|
-
}
|
|
29077
|
+
projectByRepo.set(key, preferPrimaryRepoProject(projectByRepo.get(key), project2, key));
|
|
28980
29078
|
}
|
|
28981
29079
|
}
|
|
29080
|
+
const branchByRepo = new Map(
|
|
29081
|
+
[...projectByRepo].map(([key, project2]) => [key, typeof project2.branch === "string" ? project2.branch : void 0])
|
|
29082
|
+
);
|
|
28982
29083
|
const [releaseProbes, pluginProbes, onboardingProbes] = await Promise.all([
|
|
28983
29084
|
mapBounded2(repos, 6, (repo) => readOrgVersionProbe(deps.query, repo)),
|
|
28984
29085
|
mapBounded2(repos, 6, (repo) => readFleetPluginProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase()))),
|
|
@@ -38671,17 +38772,388 @@ async function runPipelineWithBump(bump, mode, input) {
|
|
|
38671
38772
|
return { ...await runTrainApplyPipeline(mode, input), bump };
|
|
38672
38773
|
}
|
|
38673
38774
|
|
|
38674
|
-
// src/
|
|
38675
|
-
|
|
38676
|
-
|
|
38775
|
+
// src/hub-action-pins.ts
|
|
38776
|
+
var import_node_crypto11 = require("node:crypto");
|
|
38777
|
+
var import_node_fs32 = require("node:fs");
|
|
38778
|
+
var import_node_os17 = require("node:os");
|
|
38779
|
+
var import_node_path29 = require("node:path");
|
|
38780
|
+
|
|
38781
|
+
// src/bootstrap-propagate.ts
|
|
38782
|
+
function archivedWaiver(repoMeta) {
|
|
38783
|
+
return repoMeta?.archived === true ? "archived repository (read-only)" : void 0;
|
|
38677
38784
|
}
|
|
38678
|
-
function
|
|
38679
|
-
|
|
38785
|
+
function assertPropagationCoverage(rosterCount, independentRegistryCount) {
|
|
38786
|
+
if (rosterCount === 0) {
|
|
38787
|
+
throw new Error("bootstrap propagate: roster read 0 repos \u2014 refusing to report a plan from a roster this command could not establish (#4232 coverage invariant)");
|
|
38788
|
+
}
|
|
38789
|
+
if (rosterCount < independentRegistryCount) {
|
|
38790
|
+
throw new Error(
|
|
38791
|
+
`bootstrap propagate: roster read ${rosterCount} repo(s) but the independent registry count is ${independentRegistryCount} \u2014 this read did not cover the fleet (#4232 coverage invariant)`
|
|
38792
|
+
);
|
|
38793
|
+
}
|
|
38794
|
+
}
|
|
38795
|
+
function assignWaves(repos, canarySlug) {
|
|
38796
|
+
const waves = /* @__PURE__ */ new Map();
|
|
38797
|
+
if (!canarySlug) return waves;
|
|
38798
|
+
const rest = repos.filter((r) => r.slug !== canarySlug).slice().sort((a, b) => a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
|
|
38799
|
+
const canary = repos.find((r) => r.slug === canarySlug);
|
|
38800
|
+
if (canary) waves.set(canary.repo, 0);
|
|
38801
|
+
const wave1Count = Math.ceil(rest.length * 0.25);
|
|
38802
|
+
rest.forEach((r, i) => waves.set(r.repo, i < wave1Count ? 1 : 2));
|
|
38803
|
+
return waves;
|
|
38804
|
+
}
|
|
38805
|
+
function statusFor(read, currentSha) {
|
|
38806
|
+
if (!read) return { status: "pending", record: {} };
|
|
38807
|
+
if (read.drift === "match") return { status: "match", record: { mergeSha: read.pr?.mergeSha } };
|
|
38808
|
+
if (read.prUnread) return { status: "red", record: {} };
|
|
38809
|
+
const pr = read.pr;
|
|
38810
|
+
if (!pr) return { status: "pending", record: {} };
|
|
38811
|
+
if (pr.sourceSha && pr.sourceSha !== currentSha) return { status: "pending", record: {} };
|
|
38812
|
+
if (pr.state === "closed") return { status: "closed-unmerged", record: { prNumber: pr.number, prUrl: pr.url } };
|
|
38813
|
+
if (pr.checks === "red") return { status: "red", record: { prNumber: pr.number, prUrl: pr.url } };
|
|
38814
|
+
return { status: "open-pending", record: { prNumber: pr.number, prUrl: pr.url, mergeSha: pr.mergeSha } };
|
|
38815
|
+
}
|
|
38816
|
+
var HALTING_STATUSES = /* @__PURE__ */ new Set(["red", "closed-unmerged"]);
|
|
38817
|
+
function planPropagationTick(input) {
|
|
38818
|
+
const { target, repos, canarySlug, reads, isWorkflowSeed, functionGateSatisfied, currentSha } = input;
|
|
38819
|
+
const sourcePilot = input.sourcePilot === true;
|
|
38820
|
+
const readByRepo = new Map(reads.map((r) => [r.repo, r]));
|
|
38821
|
+
const records = [];
|
|
38822
|
+
const opened = [];
|
|
38823
|
+
const waived = repos.filter((r) => r.waiver);
|
|
38824
|
+
for (const r of waived) {
|
|
38825
|
+
records.push({ repo: r.repo, target, wave: null, status: "skipped-waived", action: "none", detail: `waived: ${r.waiver}` });
|
|
38826
|
+
}
|
|
38827
|
+
const eligible = repos.filter((r) => !r.waiver);
|
|
38828
|
+
const refusedNoCanary = !canarySlug || !sourcePilot && !eligible.some((r) => r.slug === canarySlug);
|
|
38829
|
+
if (refusedNoCanary) {
|
|
38830
|
+
for (const r of eligible) {
|
|
38831
|
+
records.push({ repo: r.repo, target, wave: null, status: "not-yet-reached", action: "none", detail: "no canary declared for this target \u2014 refusing to plan any wave (#4238 canary requirement)" });
|
|
38832
|
+
}
|
|
38833
|
+
return { target, reposInScope: repos.length, canary: null, refusedNoCanary: true, halted: false, haltReason: null, opened, records };
|
|
38834
|
+
}
|
|
38835
|
+
const waveOf = assignWaves(eligible, canarySlug);
|
|
38836
|
+
const byWave = { 0: [], 1: [], 2: [] };
|
|
38837
|
+
for (const r of eligible) byWave[waveOf.get(r.repo) ?? 2].push(r);
|
|
38838
|
+
let halted = false;
|
|
38839
|
+
let haltReason = null;
|
|
38840
|
+
let waveGateOpen = !(sourcePilot && isWorkflowSeed && !functionGateSatisfied);
|
|
38841
|
+
for (const waveNum of [0, 1, 2]) {
|
|
38842
|
+
const waveRepos = byWave[waveNum];
|
|
38843
|
+
if (!waveRepos.length) continue;
|
|
38844
|
+
if (!waveGateOpen || halted) {
|
|
38845
|
+
for (const r of waveRepos) {
|
|
38846
|
+
records.push({ repo: r.repo, target, wave: waveNum, status: "not-yet-reached", action: "none", detail: "prior wave not yet converged" });
|
|
38847
|
+
}
|
|
38848
|
+
continue;
|
|
38849
|
+
}
|
|
38850
|
+
let waveAllMatch = true;
|
|
38851
|
+
let waveHasRed = false;
|
|
38852
|
+
let waveHasClosed = false;
|
|
38853
|
+
for (const r of waveRepos) {
|
|
38854
|
+
const { status, record: record2 } = statusFor(readByRepo.get(r.repo), currentSha);
|
|
38855
|
+
const shouldOpen = status === "pending" || status === "closed-unmerged";
|
|
38856
|
+
if (shouldOpen) opened.push(r.repo);
|
|
38857
|
+
records.push({
|
|
38858
|
+
repo: r.repo,
|
|
38859
|
+
target,
|
|
38860
|
+
wave: waveNum,
|
|
38861
|
+
status,
|
|
38862
|
+
action: shouldOpen ? "open-pr" : "none",
|
|
38863
|
+
detail: status === "closed-unmerged" ? `previous PR#${record2.prNumber} was closed unmerged and no open PR replaced it \u2014 opening a replacement against the live base this tick (#9232)` : status === "pending" ? "no open propagation PR and not yet matching \u2014 opening this tick" : status,
|
|
38864
|
+
...record2
|
|
38865
|
+
});
|
|
38866
|
+
if (status !== "match") waveAllMatch = false;
|
|
38867
|
+
if (HALTING_STATUSES.has(status)) waveHasRed = true;
|
|
38868
|
+
if (status === "closed-unmerged") waveHasClosed = true;
|
|
38869
|
+
}
|
|
38870
|
+
if (waveHasRed) {
|
|
38871
|
+
halted = true;
|
|
38872
|
+
haltReason = `wave ${waveNum} has a red or closed-unmerged PR \u2014 halting; no further wave opens (#4238 halt-and-alarm)` + (waveHasClosed ? "; the closed PR(s) were re-opened against the live base this tick (#9232) \u2014 land or close the replacement to advance" : "");
|
|
38873
|
+
waveGateOpen = false;
|
|
38874
|
+
continue;
|
|
38875
|
+
}
|
|
38876
|
+
if (waveNum === 0 && isWorkflowSeed && !functionGateSatisfied) {
|
|
38877
|
+
waveGateOpen = false;
|
|
38878
|
+
continue;
|
|
38879
|
+
}
|
|
38880
|
+
waveGateOpen = waveAllMatch;
|
|
38881
|
+
}
|
|
38882
|
+
return { target, reposInScope: repos.length, canary: canarySlug, refusedNoCanary: false, halted, haltReason, opened, records };
|
|
38883
|
+
}
|
|
38884
|
+
function summarizePropagationTick(plan) {
|
|
38885
|
+
const parts = [];
|
|
38886
|
+
parts.push(plan.opened.length ? `opened ${plan.opened.length} PR(s) this tick: ${plan.opened.join(", ")}` : "opened no PR this tick");
|
|
38887
|
+
const stillNotCurrent = plan.records.filter((r) => r.status !== "match" && r.status !== "skipped-waived" && r.action !== "open-pr");
|
|
38888
|
+
parts.push(stillNotCurrent.length ? `not current and not opened this tick: ${stillNotCurrent.map((r) => `${r.repo} (${r.status})`).join(", ")}` : "every in-scope repo either matches the Hub seed or had a PR opened this tick");
|
|
38889
|
+
if (plan.halted) parts.push(`HALTED \u2014 ${plan.haltReason}`);
|
|
38890
|
+
return parts.join("; ");
|
|
38891
|
+
}
|
|
38892
|
+
function renderPropagationReport(plan) {
|
|
38893
|
+
const lines2 = [`bootstrap propagate \u2014 target ${plan.target}: ${plan.reposInScope} repo(s) in scope, canary=${plan.canary ?? "NONE"}`];
|
|
38894
|
+
if (plan.refusedNoCanary) {
|
|
38895
|
+
lines2.push(" REFUSED \u2014 no canary declared for this target; plan every repo not-yet-reached");
|
|
38896
|
+
lines2.push(` ${summarizePropagationTick(plan)}`);
|
|
38897
|
+
return lines2.join("\n");
|
|
38898
|
+
}
|
|
38899
|
+
for (const r of plan.records) {
|
|
38900
|
+
const wave = r.wave == null ? "-" : String(r.wave);
|
|
38901
|
+
lines2.push(` wave${wave.padEnd(2)} ${r.status.padEnd(16)} ${r.repo}${r.prNumber ? ` PR#${r.prNumber}` : ""} \u2014 ${r.detail}`);
|
|
38902
|
+
}
|
|
38903
|
+
lines2.push(` ${summarizePropagationTick(plan)}`);
|
|
38904
|
+
return lines2.join("\n");
|
|
38905
|
+
}
|
|
38906
|
+
|
|
38907
|
+
// src/bootstrap-propagation-identity.ts
|
|
38908
|
+
var import_node_crypto10 = require("node:crypto");
|
|
38909
|
+
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
38910
|
+
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
38911
|
+
function safeBranchPart(value, maxLength, fallback) {
|
|
38912
|
+
const safe = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength).replace(/-+$/g, "");
|
|
38913
|
+
return safe || fallback;
|
|
38914
|
+
}
|
|
38915
|
+
function repoSlug2(repo) {
|
|
38916
|
+
return repo.trim().split("/").pop()?.toLowerCase() || "repo";
|
|
38917
|
+
}
|
|
38918
|
+
function propagationBranch(repo, target) {
|
|
38919
|
+
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
38920
|
+
const targetPart = safeBranchPart(target, 48, "target");
|
|
38921
|
+
const hash = (0, import_node_crypto10.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
38922
|
+
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
38923
|
+
}
|
|
38924
|
+
function legacyPropagationBranch(repo) {
|
|
38925
|
+
return `${PROPAGATION_BRANCH_PREFIX}${repoSlug2(repo)}`;
|
|
38926
|
+
}
|
|
38927
|
+
function renderPropagationTargetMarker(target) {
|
|
38928
|
+
return `<!-- ${TARGET_MARKER_NAME}: ${JSON.stringify(target)} -->`;
|
|
38929
|
+
}
|
|
38930
|
+
function targetMarkers(body) {
|
|
38931
|
+
const present = body.includes(`${TARGET_MARKER_NAME}:`);
|
|
38932
|
+
const targets = [];
|
|
38933
|
+
const marker = new RegExp(`<!--\\s*${TARGET_MARKER_NAME}:\\s*(.*?)\\s*-->`, "g");
|
|
38934
|
+
for (const match of body.matchAll(marker)) {
|
|
38935
|
+
try {
|
|
38936
|
+
const parsed = JSON.parse(match[1]);
|
|
38937
|
+
if (typeof parsed === "string") targets.push(parsed);
|
|
38938
|
+
} catch {
|
|
38939
|
+
}
|
|
38940
|
+
}
|
|
38941
|
+
return { present, targets };
|
|
38942
|
+
}
|
|
38943
|
+
function canonicalProseTargets(body) {
|
|
38944
|
+
const targets = [];
|
|
38945
|
+
const command = /bootstrap propagate --target (.+?) --execute`/g;
|
|
38946
|
+
const prose = /(?:org-owned copy of|declared marker-bounded block inside repo-owned) `([^`]+)` — the file this PR carries and nothing else/g;
|
|
38947
|
+
for (const match of body.matchAll(command)) targets.push(match[1]);
|
|
38948
|
+
for (const match of body.matchAll(prose)) targets.push(match[1]);
|
|
38949
|
+
return targets;
|
|
38950
|
+
}
|
|
38951
|
+
function propagationPrMatchesTarget(pr, target, identity) {
|
|
38952
|
+
const body = pr.body ?? "";
|
|
38953
|
+
const markers = targetMarkers(body);
|
|
38954
|
+
if (markers.present) {
|
|
38955
|
+
if (markers.targets.length !== 1 || markers.targets[0] !== target) return false;
|
|
38956
|
+
return !pr.files?.length || pr.files.length === 1 && pr.files[0] === target;
|
|
38957
|
+
}
|
|
38958
|
+
if (identity === "target-specific") return false;
|
|
38959
|
+
const proseTargets = canonicalProseTargets(body);
|
|
38960
|
+
if (proseTargets.some((candidate) => candidate !== target)) return false;
|
|
38961
|
+
if (pr.files?.length) {
|
|
38962
|
+
return pr.files.length === 1 && pr.files[0] === target;
|
|
38963
|
+
}
|
|
38964
|
+
return proseTargets.length > 0;
|
|
38965
|
+
}
|
|
38966
|
+
function propagationPrCandidatesForTarget(target, targetSpecificPrs, legacyPrs) {
|
|
38967
|
+
const targetSpecific = targetSpecificPrs.filter((pr) => propagationPrMatchesTarget(pr, target, "target-specific"));
|
|
38968
|
+
if (targetSpecific.length) return targetSpecific;
|
|
38969
|
+
return legacyPrs.filter((pr) => propagationPrMatchesTarget(pr, target, "legacy"));
|
|
38970
|
+
}
|
|
38971
|
+
|
|
38972
|
+
// src/hub-action-pins.ts
|
|
38973
|
+
var PIN_TARGET = "hub-action-pins";
|
|
38974
|
+
var HUB_REPO4 = "mutmutco/MMI-Hub";
|
|
38975
|
+
var FULL_SHA2 = /^[0-9a-f]{40}$/;
|
|
38976
|
+
var PIN_ACTION_ALIASES = {
|
|
38977
|
+
BLESSED_RUNNER_GATE_SHA: ["runner-checkout", "runner-node-toolchain"],
|
|
38978
|
+
BLESSED_KIT_AGENT_FILES_SHA: ["check-kit-agent-files"]
|
|
38979
|
+
};
|
|
38980
|
+
function blessedHubActionPins(constants = gate_budget_exports) {
|
|
38981
|
+
const pins = /* @__PURE__ */ new Map();
|
|
38982
|
+
for (const [name, sha] of Object.entries(constants)) {
|
|
38983
|
+
const m = /^BLESSED_([A-Z0-9_]+)_SHA$/.exec(name);
|
|
38984
|
+
if (!m || typeof sha !== "string" || !FULL_SHA2.test(sha)) continue;
|
|
38985
|
+
for (const action of PIN_ACTION_ALIASES[name] ?? [m[1].toLowerCase().replace(/_/g, "-")]) pins.set(action, sha);
|
|
38986
|
+
}
|
|
38987
|
+
return pins;
|
|
38988
|
+
}
|
|
38989
|
+
function pinTableDigest(pins) {
|
|
38990
|
+
return (0, import_node_crypto11.createHash)("sha1").update([...pins].map(([a, s]) => `${a}@${s}`).sort().join("\n")).digest("hex");
|
|
38991
|
+
}
|
|
38992
|
+
var HUB_ACTION_USE = /^(\s*-?\s*uses:\s*['"]?mutmutco\/mmi-hub\/\.github\/actions\/)([\w.-]+)@([^\s'"#]+)([^\n]*)$/i;
|
|
38993
|
+
function staleHubActionPins(body, pins = blessedHubActionPins()) {
|
|
38994
|
+
const stale = [];
|
|
38995
|
+
body.split("\n").forEach((text, i) => {
|
|
38996
|
+
const m = HUB_ACTION_USE.exec(text);
|
|
38997
|
+
const to = m ? pins.get(m[2]) : void 0;
|
|
38998
|
+
if (!m || !to || m[3] === to || !(m[3] === "development" || FULL_SHA2.test(m[3]))) return;
|
|
38999
|
+
stale.push({ line: i + 1, action: m[2], from: m[3], to });
|
|
39000
|
+
});
|
|
39001
|
+
return stale;
|
|
39002
|
+
}
|
|
39003
|
+
function rewriteHubActionPins(body, pins = blessedHubActionPins(), keep = () => false) {
|
|
39004
|
+
const changes = staleHubActionPins(body, pins).filter((use) => !keep(use));
|
|
39005
|
+
const lines2 = body.split("\n");
|
|
39006
|
+
for (const c of changes) {
|
|
39007
|
+
lines2[c.line - 1] = lines2[c.line - 1].replace(HUB_ACTION_USE, (_all, pre, action, _ref, rest) => `${pre}${action}@${c.to}${rest}`);
|
|
39008
|
+
}
|
|
39009
|
+
return { body: lines2.join("\n"), changes };
|
|
39010
|
+
}
|
|
39011
|
+
var ghJson = async (gh2, args) => JSON.parse((await gh2(args)).stdout || "null");
|
|
39012
|
+
var pinPathEnc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
39013
|
+
async function readPinFile(gh2, repo, path2, ref) {
|
|
39014
|
+
const f = await ghJson(gh2, ["api", `repos/${repo}/contents/${pinPathEnc(path2)}?ref=${encodeURIComponent(ref)}`]);
|
|
39015
|
+
return f?.encoding === "base64" && typeof f.content === "string" ? { body: decodeGitHubContents(f.content), sha: f.sha } : null;
|
|
39016
|
+
}
|
|
39017
|
+
async function runPinTick(input) {
|
|
39018
|
+
const { gh: gh2, repos } = input;
|
|
39019
|
+
const pins = input.pins ?? blessedHubActionPins();
|
|
39020
|
+
const digest = pinTableDigest(pins);
|
|
39021
|
+
const ahead = /* @__PURE__ */ new Map();
|
|
39022
|
+
const isAhead = (use) => {
|
|
39023
|
+
if (!FULL_SHA2.test(use.from)) return Promise.resolve(false);
|
|
39024
|
+
const key = `${use.to}...${use.from}`;
|
|
39025
|
+
if (!ahead.has(key)) ahead.set(key, gh2(["api", `repos/${HUB_REPO4}/compare/${key}`, "--jq", ".status"]).then((o) => o.stdout.trim() === "ahead", () => false));
|
|
39026
|
+
return ahead.get(key);
|
|
39027
|
+
};
|
|
39028
|
+
const keepSet = /* @__PURE__ */ new Set();
|
|
39029
|
+
const keep = (use) => keepSet.has(`${use.to}...${use.from}`);
|
|
39030
|
+
const reads = [];
|
|
39031
|
+
const diffs = [];
|
|
39032
|
+
for (const r of repos) {
|
|
39033
|
+
if (r.waiver) continue;
|
|
39034
|
+
const base = input.baseBranchOf(r.repo);
|
|
39035
|
+
let listing = [];
|
|
39036
|
+
try {
|
|
39037
|
+
listing = await ghJson(gh2, ["api", `repos/${r.repo}/contents/.github/workflows?ref=${encodeURIComponent(base)}`]) ?? [];
|
|
39038
|
+
} catch (e) {
|
|
39039
|
+
if (!/HTTP 404|Not Found/i.test(e.message)) {
|
|
39040
|
+
reads.push({ repo: r.repo, drift: "drift", prUnread: true });
|
|
39041
|
+
continue;
|
|
39042
|
+
}
|
|
39043
|
+
}
|
|
39044
|
+
const files = [];
|
|
39045
|
+
for (const entry of listing) {
|
|
39046
|
+
if (entry.type !== "file" || !entry.path || !/\.ya?ml$/.test(entry.path)) continue;
|
|
39047
|
+
const file = await readPinFile(gh2, r.repo, entry.path, base);
|
|
39048
|
+
if (!file) continue;
|
|
39049
|
+
for (const use of staleHubActionPins(file.body, pins)) if (await isAhead(use)) keepSet.add(`${use.to}...${use.from}`);
|
|
39050
|
+
const { changes } = rewriteHubActionPins(file.body, pins, keep);
|
|
39051
|
+
if (changes.length) files.push({ path: entry.path, changes });
|
|
39052
|
+
}
|
|
39053
|
+
if (files.length) diffs.push({ repo: r.repo, files });
|
|
39054
|
+
let pr;
|
|
39055
|
+
let prUnread = false;
|
|
39056
|
+
try {
|
|
39057
|
+
const listed = await ghJson(gh2, ["pr", "list", "--repo", r.repo, "--head", propagationBranch(r.repo, PIN_TARGET), "--base", base, "--state", "all", "--json", "number,url,state,statusCheckRollup,mergeCommit,body", "--limit", "100"]) ?? [];
|
|
39058
|
+
const p = propagationPrCandidatesForTarget(PIN_TARGET, listed, [])[0];
|
|
39059
|
+
if (p) {
|
|
39060
|
+
const rollup = p.statusCheckRollup ?? [];
|
|
39061
|
+
const checks = rollup.length === 0 ? "none" : rollup.some((c) => c.conclusion === "FAILURE" || c.state === "FAILURE") ? "red" : rollup.every((c) => c.conclusion === "SUCCESS" || c.state === "SUCCESS") ? "success" : "pending";
|
|
39062
|
+
pr = {
|
|
39063
|
+
number: p.number,
|
|
39064
|
+
url: p.url,
|
|
39065
|
+
checks,
|
|
39066
|
+
mergeSha: p.mergeCommit?.oid,
|
|
39067
|
+
state: p.state === "MERGED" ? "merged" : p.state === "CLOSED" ? "closed" : "open",
|
|
39068
|
+
sourceSha: p.body?.match(/Hub action pins ([0-9a-f]{40})/)?.[1]
|
|
39069
|
+
};
|
|
39070
|
+
}
|
|
39071
|
+
} catch {
|
|
39072
|
+
prUnread = true;
|
|
39073
|
+
}
|
|
39074
|
+
reads.push({ repo: r.repo, drift: files.length ? "drift" : "match", pr, prUnread });
|
|
39075
|
+
}
|
|
39076
|
+
const plan = planPropagationTick({
|
|
39077
|
+
target: PIN_TARGET,
|
|
39078
|
+
repos,
|
|
39079
|
+
canarySlug: input.canarySlug,
|
|
39080
|
+
reads,
|
|
39081
|
+
isWorkflowSeed: false,
|
|
39082
|
+
functionGateSatisfied: true,
|
|
39083
|
+
currentSha: digest,
|
|
39084
|
+
sourcePilot: input.sourcePilot
|
|
39085
|
+
});
|
|
39086
|
+
if (!input.apply || plan.refusedNoCanary) return { plan, digest, diffs };
|
|
39087
|
+
for (const rec of plan.records) {
|
|
39088
|
+
if (rec.action !== "open-pr") continue;
|
|
39089
|
+
const diff = diffs.find((d) => d.repo === rec.repo);
|
|
39090
|
+
if (!diff) continue;
|
|
39091
|
+
const base = input.baseBranchOf(rec.repo);
|
|
39092
|
+
const branch = propagationBranch(rec.repo, PIN_TARGET);
|
|
39093
|
+
const baseSha = (await gh2(["api", `repos/${rec.repo}/git/ref/heads/${base}`, "--jq", ".object.sha"])).stdout.trim();
|
|
39094
|
+
let branchExists = true;
|
|
39095
|
+
try {
|
|
39096
|
+
await gh2(["api", `repos/${rec.repo}/git/ref/heads/${branch}`]);
|
|
39097
|
+
} catch {
|
|
39098
|
+
branchExists = false;
|
|
39099
|
+
}
|
|
39100
|
+
if (!branchExists) await gh2(["api", `repos/${rec.repo}/git/refs`, "-f", `ref=refs/heads/${branch}`, "-f", `sha=${baseSha}`]);
|
|
39101
|
+
else await gh2(["api", "-X", "PATCH", `repos/${rec.repo}/git/refs/heads/${branch}`, "-f", `sha=${baseSha}`, "-F", "force=true"]);
|
|
39102
|
+
const lines2 = [];
|
|
39103
|
+
for (const { path: path2 } of diff.files) {
|
|
39104
|
+
const file = await readPinFile(gh2, rec.repo, path2, branch);
|
|
39105
|
+
if (!file) continue;
|
|
39106
|
+
const next = rewriteHubActionPins(file.body, pins, keep);
|
|
39107
|
+
if (!next.changes.length) continue;
|
|
39108
|
+
lines2.push(...next.changes.map((c) => `- \`${path2}:${c.line}\` ${c.action}: \`${c.from.slice(0, 12)}\` \u2192 \`${c.to.slice(0, 12)}\``));
|
|
39109
|
+
const tmp = (0, import_node_path29.join)((0, import_node_os17.tmpdir)(), `mmi-pin-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
39110
|
+
const put = { message: `ci: pin MMI-Hub actions in ${path2} to the blessed SHAs`, content: encodeGitHubContents(next.body), branch };
|
|
39111
|
+
if (file.sha) put.sha = file.sha;
|
|
39112
|
+
(0, import_node_fs32.writeFileSync)(tmp, JSON.stringify(put), "utf8");
|
|
39113
|
+
try {
|
|
39114
|
+
await gh2(["api", "-X", "PUT", `repos/${rec.repo}/contents/${pinPathEnc(path2)}`, "--input", tmp]);
|
|
39115
|
+
} finally {
|
|
39116
|
+
try {
|
|
39117
|
+
(0, import_node_fs32.unlinkSync)(tmp);
|
|
39118
|
+
} catch {
|
|
39119
|
+
}
|
|
39120
|
+
}
|
|
39121
|
+
}
|
|
39122
|
+
if (!lines2.length) continue;
|
|
39123
|
+
const body = `Auto-opened by \`mmi-cli devops bootstrap propagate --pins --apply\` (org CI plan H4), wave ${rec.wave}.
|
|
39124
|
+
|
|
39125
|
+
Moves each \`mutmutco/MMI-Hub/.github/actions/<x>\` pin in \`.github/workflows\` to the blessed SHA from MMI-Hub \`cli/src/gate-budget.ts\`. No other line changes.
|
|
39126
|
+
|
|
39127
|
+
${lines2.join("\n")}
|
|
39128
|
+
|
|
39129
|
+
Hub action pins ${digest}
|
|
39130
|
+
${renderPropagationTargetMarker(PIN_TARGET)}`;
|
|
39131
|
+
const open2 = await ghJson(gh2, ["pr", "list", "--repo", rec.repo, "--head", branch, "--base", base, "--state", "open", "--json", "number,url,body"]) ?? [];
|
|
39132
|
+
const decision = decideSeedPrAction(propagationPrCandidatesForTarget(PIN_TARGET, open2, []));
|
|
39133
|
+
let prUrl;
|
|
39134
|
+
if (decision.action === "reuse") {
|
|
39135
|
+
prUrl = decision.url;
|
|
39136
|
+
await gh2(["pr", "edit", prUrl, "--repo", rec.repo, "--body", body]);
|
|
39137
|
+
} else {
|
|
39138
|
+
if (!input.createPr) throw new Error("runPinTick: apply needs createPr");
|
|
39139
|
+
prUrl = await input.createPr(["pr", "create", "--repo", rec.repo, "--base", base, "--head", branch, "--title", `ci: pin MMI-Hub actions to the blessed SHAs (wave ${rec.wave})`, "--body", body]);
|
|
39140
|
+
}
|
|
39141
|
+
if (rec.wave !== 0 && input.mayAutoMerge && await input.mayAutoMerge(rec.repo)) {
|
|
39142
|
+
await gh2(["pr", "merge", prUrl, "--repo", rec.repo, "--auto", "--squash"]).catch(() => {
|
|
39143
|
+
});
|
|
39144
|
+
}
|
|
39145
|
+
rec.prUrl = prUrl;
|
|
39146
|
+
}
|
|
39147
|
+
return { plan, digest, diffs };
|
|
39148
|
+
}
|
|
39149
|
+
function renderPinDiffs(tick) {
|
|
39150
|
+
if (!tick.diffs.length) return "hub action pins: every in-scope repo already pins the blessed SHAs";
|
|
39151
|
+
return tick.diffs.map((d) => [`${d.repo}:`, ...d.files.flatMap((f) => f.changes.map((c) => ` ${f.path}:${c.line} ${c.action} ${c.from === "development" ? "@development" : c.from.slice(0, 12)} -> ${c.to.slice(0, 12)}`))].join("\n")).join("\n");
|
|
38680
39152
|
}
|
|
38681
39153
|
|
|
38682
39154
|
// src/ci-audit.ts
|
|
38683
39155
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
38684
|
-
var
|
|
39156
|
+
var HUB_REPO5 = "mutmutco/MMI-Hub";
|
|
38685
39157
|
var HUB_GATE_CONTEXTS = ["cli", "infra", "docs"];
|
|
38686
39158
|
var PRODUCT_GATE_PATH = ".github/workflows/gate.yml";
|
|
38687
39159
|
var PRODUCT_RULESET_REF = ".github/rulesets/mmi-product-required-checks.json";
|
|
@@ -38868,6 +39340,46 @@ function auditJevOutcomes(files) {
|
|
|
38868
39340
|
}
|
|
38869
39341
|
return checks;
|
|
38870
39342
|
}
|
|
39343
|
+
var VERDICT_RECEIPT = /(?:^|\/)\.github\/actions\/verdict-receipt(?:@|$)/;
|
|
39344
|
+
function auditVerdictReceipt(files) {
|
|
39345
|
+
const checks = [];
|
|
39346
|
+
for (const { path: path2, body } of files) {
|
|
39347
|
+
if (!isGateWorkflowPath(path2)) continue;
|
|
39348
|
+
let doc;
|
|
39349
|
+
try {
|
|
39350
|
+
doc = (0, import_yaml2.parse)(body);
|
|
39351
|
+
} catch {
|
|
39352
|
+
continue;
|
|
39353
|
+
}
|
|
39354
|
+
if (!plainRecord(doc)) continue;
|
|
39355
|
+
const on = doc.on;
|
|
39356
|
+
const events = typeof on === "string" ? [on] : Array.isArray(on) ? on : plainRecord(on) ? Object.keys(on) : [];
|
|
39357
|
+
if (!events.includes("merge_group") || !events.includes("push")) continue;
|
|
39358
|
+
const push = plainRecord(on) ? on.push : void 0;
|
|
39359
|
+
const branches = plainRecord(push) ? push.branches : void 0;
|
|
39360
|
+
if (branches !== void 0 && !(Array.isArray(branches) ? branches : [branches]).includes("development")) continue;
|
|
39361
|
+
const steps = Object.values(plainRecord(doc.jobs) ? doc.jobs : {}).flatMap((job) => plainRecord(job) && Array.isArray(job.steps) ? job.steps.filter(plainRecord) : []);
|
|
39362
|
+
if (steps.some((s) => typeof s.uses === "string" && VERDICT_RECEIPT.test(s.uses))) continue;
|
|
39363
|
+
if (!steps.some((s) => typeof s.uses === "string" && s.uses.includes("run-with-budget") || typeof s.run === "string" && RUNS_TESTS.test(s.run))) continue;
|
|
39364
|
+
checks.push({
|
|
39365
|
+
ok: true,
|
|
39366
|
+
severity: "info",
|
|
39367
|
+
label: `development push reuses the merge-queue verdict (org CI H1): ${path2}`,
|
|
39368
|
+
detail: "this gate runs heavy jobs on merge_group and again on the development push of the same SHA, with no verdict-receipt step to reuse the queue verdict",
|
|
39369
|
+
remediation: "Add mutmutco/MMI-Hub/.github/actions/verdict-receipt (source: merge_group, queue-is-full-verdict: true once merge_group is the full set) in the first job and skip heavy jobs when reuse == 'true' - see docs/Guides/operator.md#verdict-receipt"
|
|
39370
|
+
});
|
|
39371
|
+
}
|
|
39372
|
+
return checks;
|
|
39373
|
+
}
|
|
39374
|
+
function auditHubActionPins(files) {
|
|
39375
|
+
return files.flatMap(({ path: path2, body }) => staleHubActionPins(body).map((use) => ({
|
|
39376
|
+
ok: true,
|
|
39377
|
+
severity: "info",
|
|
39378
|
+
label: `Hub action pin is the blessed SHA (H4): ${path2}:${use.line}`,
|
|
39379
|
+
detail: `${use.action} pins ${use.from === "development" ? "@development (floating)" : use.from.slice(0, 12)}, blessed is ${use.to.slice(0, 12)}`,
|
|
39380
|
+
remediation: "mmi-cli devops bootstrap propagate --pins --apply (from MMI-Hub), or re-pin by hand to the blessed SHA"
|
|
39381
|
+
})));
|
|
39382
|
+
}
|
|
38871
39383
|
var PENDING_LANDING_LABEL = "pending PRs landing (#8783)";
|
|
38872
39384
|
var PENDING_LANDING_LIST_CAP = 50;
|
|
38873
39385
|
var PENDING_LANDING_STALL_MS = 15 * 60 * 1e3;
|
|
@@ -38935,7 +39447,7 @@ function slugFromRepo(repo) {
|
|
|
38935
39447
|
return (repo.includes("/") ? repo.split("/")[1] : repo).toLowerCase();
|
|
38936
39448
|
}
|
|
38937
39449
|
function classifyRepo(repo, meta) {
|
|
38938
|
-
if (repo.toLowerCase() ===
|
|
39450
|
+
if (repo.toLowerCase() === HUB_REPO5.toLowerCase()) return "hub";
|
|
38939
39451
|
if (meta?.class === "content") return "content";
|
|
38940
39452
|
if (meta?.class === "deployable" || meta?.deployModel && meta.deployModel !== "content" && meta.deployModel !== "none") return "deployable";
|
|
38941
39453
|
if (meta) return "deployable";
|
|
@@ -38947,6 +39459,11 @@ function rulesetStatusChecks(rulesets) {
|
|
|
38947
39459
|
function sortedUnique(values) {
|
|
38948
39460
|
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
38949
39461
|
}
|
|
39462
|
+
function isGitHub404(e) {
|
|
39463
|
+
const err = e;
|
|
39464
|
+
if (err?.status === 404) return true;
|
|
39465
|
+
return /\b404\b|\bnot found\b/i.test(String(err?.message ?? ""));
|
|
39466
|
+
}
|
|
38950
39467
|
async function restJson2(deps, path2, fallback) {
|
|
38951
39468
|
try {
|
|
38952
39469
|
return await deps.client.rest("GET", path2) ?? fallback;
|
|
@@ -38964,7 +39481,7 @@ function pinContainsFloor(deps, floor, pin) {
|
|
|
38964
39481
|
const key = `${floor}...${pin}`;
|
|
38965
39482
|
let comparison = comparisons.get(key);
|
|
38966
39483
|
if (!comparison) {
|
|
38967
|
-
comparison = deps.client.rest("GET", `repos/${
|
|
39484
|
+
comparison = deps.client.rest("GET", `repos/${HUB_REPO5}/compare/${key}`).then((result) => result?.status === "ahead" || result?.status === "identical" ? true : result?.status === "behind" || result?.status === "diverged" ? false : void 0).catch(() => void 0);
|
|
38968
39485
|
comparisons.set(key, comparison);
|
|
38969
39486
|
}
|
|
38970
39487
|
return comparison;
|
|
@@ -39237,7 +39754,7 @@ function collectRegistryRepos(projects) {
|
|
|
39237
39754
|
if (!seen.has(key)) seen.set(key, repo);
|
|
39238
39755
|
}
|
|
39239
39756
|
}
|
|
39240
|
-
if (!seen.has(
|
|
39757
|
+
if (!seen.has(HUB_REPO5.toLowerCase())) seen.set(HUB_REPO5.toLowerCase(), HUB_REPO5);
|
|
39241
39758
|
return [...seen.values()].sort((a, b) => a.localeCompare(b));
|
|
39242
39759
|
}
|
|
39243
39760
|
async function prTriggeredWorkflowsOnRef(deps, repo, branch, repoClass) {
|
|
@@ -39316,7 +39833,23 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
39316
39833
|
const registryRequiredChecks = meta?.requiredChecks;
|
|
39317
39834
|
const explicitNoCi = registryCi === "none" || registryRequiredChecks === null || Array.isArray(registryRequiredChecks) && registryRequiredChecks.length === 0;
|
|
39318
39835
|
const deployableGated = repoClass === "deployable" && !explicitNoCi;
|
|
39319
|
-
|
|
39836
|
+
let info;
|
|
39837
|
+
try {
|
|
39838
|
+
info = await deps.client.rest("GET", `repos/${repo}`) ?? {};
|
|
39839
|
+
} catch (e) {
|
|
39840
|
+
if (isGitHub404(e)) {
|
|
39841
|
+
return {
|
|
39842
|
+
repo,
|
|
39843
|
+
class: repoClass,
|
|
39844
|
+
mergePolicy: "wait-for-checks",
|
|
39845
|
+
ok: false,
|
|
39846
|
+
checks: [],
|
|
39847
|
+
explicitNoCi,
|
|
39848
|
+
excludedReason: "GitHub repository returned 404 (not found)"
|
|
39849
|
+
};
|
|
39850
|
+
}
|
|
39851
|
+
info = {};
|
|
39852
|
+
}
|
|
39320
39853
|
if (info.archived === true) {
|
|
39321
39854
|
return {
|
|
39322
39855
|
repo,
|
|
@@ -39663,6 +40196,8 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
39663
40196
|
}
|
|
39664
40197
|
checks.push(...auditSelfHostedRunnerHygiene(workflows));
|
|
39665
40198
|
checks.push(...auditJevOutcomes(workflows));
|
|
40199
|
+
checks.push(...auditVerdictReceipt(workflows));
|
|
40200
|
+
if (repoClass !== "hub") checks.push(...auditHubActionPins(workflows));
|
|
39666
40201
|
if (repoClass !== "hub") checks.push(...await auditSharedActionPins(workflows, deps));
|
|
39667
40202
|
} else {
|
|
39668
40203
|
checks.push({ ok: false, label: "runner workflow source is readable (#8958)", detail: "workflow source inventory unreadable" });
|
|
@@ -41226,10 +41761,10 @@ init_github_client();
|
|
|
41226
41761
|
init_cli_shared();
|
|
41227
41762
|
|
|
41228
41763
|
// src/pr-merge-queue.ts
|
|
41229
|
-
var
|
|
41230
|
-
var
|
|
41231
|
-
var
|
|
41232
|
-
var
|
|
41764
|
+
var import_node_crypto12 = require("node:crypto");
|
|
41765
|
+
var import_node_fs34 = require("node:fs");
|
|
41766
|
+
var import_node_os18 = require("node:os");
|
|
41767
|
+
var import_node_path31 = require("node:path");
|
|
41233
41768
|
init_cli_shared();
|
|
41234
41769
|
|
|
41235
41770
|
// src/pr-land.ts
|
|
@@ -41395,8 +41930,9 @@ async function runPrLandOnce(prNumber, options, deps) {
|
|
|
41395
41930
|
if (!verdict.ok) {
|
|
41396
41931
|
return { ...base, error: `train authority unavailable: ${verdict.error}` };
|
|
41397
41932
|
}
|
|
41398
|
-
base.train = { role: verdict.authority.role, train: verdict.authority.train };
|
|
41399
|
-
|
|
41933
|
+
base.train = { role: verdict.authority.role, train: verdict.authority.train, ...verdict.authority.developmentMerge !== void 0 ? { developmentMerge: verdict.authority.developmentMerge } : {} };
|
|
41934
|
+
const canMerge = verdict.authority.train || resolved.base === "development" && verdict.authority.developmentMerge === true;
|
|
41935
|
+
if (!canMerge) {
|
|
41400
41936
|
return {
|
|
41401
41937
|
...base,
|
|
41402
41938
|
error: `@${verdict.authority.login ?? "caller"} is ${verdict.authority.role} \u2014 train not authorized on ${repo}; cannot land PR`
|
|
@@ -41668,8 +42204,8 @@ var import_yaml3 = __toESM(require_dist(), 1);
|
|
|
41668
42204
|
|
|
41669
42205
|
// src/test-policy-core.ts
|
|
41670
42206
|
var import_node_child_process11 = require("node:child_process");
|
|
41671
|
-
var
|
|
41672
|
-
var
|
|
42207
|
+
var import_node_fs33 = require("node:fs");
|
|
42208
|
+
var import_node_path30 = require("node:path");
|
|
41673
42209
|
|
|
41674
42210
|
// src/test-command-policy-shared.mjs
|
|
41675
42211
|
var import_node_child_process10 = require("node:child_process");
|
|
@@ -42225,7 +42761,7 @@ function isMeaningfulRow(file) {
|
|
|
42225
42761
|
return file.meaningful !== false;
|
|
42226
42762
|
}
|
|
42227
42763
|
function loadPolicy(root, readFile10 = readFileOrNull2) {
|
|
42228
|
-
const raw = readFile10((0,
|
|
42764
|
+
const raw = readFile10((0, import_node_path30.join)(root, POLICY_FILE));
|
|
42229
42765
|
if (raw == null) return { mandatory: [], declared: false };
|
|
42230
42766
|
return parsePolicy(raw, POLICY_FILE);
|
|
42231
42767
|
}
|
|
@@ -42309,7 +42845,7 @@ function loadPolicySource(root, explicitRef, diffBase) {
|
|
|
42309
42845
|
}
|
|
42310
42846
|
function readFileOrNull2(path2) {
|
|
42311
42847
|
try {
|
|
42312
|
-
return (0,
|
|
42848
|
+
return (0, import_node_fs33.readFileSync)(path2, "utf8");
|
|
42313
42849
|
} catch {
|
|
42314
42850
|
return null;
|
|
42315
42851
|
}
|
|
@@ -42337,12 +42873,12 @@ function classify(changed, policy, present = () => false) {
|
|
|
42337
42873
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
42338
42874
|
return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
|
|
42339
42875
|
}
|
|
42340
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
42341
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
42876
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs33.existsSync)(path2)) {
|
|
42877
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
42342
42878
|
}
|
|
42343
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
42879
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs33.existsSync)(path2)) {
|
|
42344
42880
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
42345
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
42881
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
42346
42882
|
}
|
|
42347
42883
|
function evaluate(changed, policy, present = () => false) {
|
|
42348
42884
|
const { mandatoryHits, untestedHits, meaningfulTestChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -42516,20 +43052,20 @@ function runTestPolicy(root, deps = {}) {
|
|
|
42516
43052
|
if (deps.policyRef && (deps.policy !== void 0 || deps.changed !== void 0 || deps.override !== void 0 || deps.exists !== void 0)) {
|
|
42517
43053
|
throw new Error("--policy-ref cannot be combined with injected policy, changed rows, override, or filesystem evidence");
|
|
42518
43054
|
}
|
|
42519
|
-
const candidateTree = candidate ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", candidate.head], root).split("\0").filter(Boolean).map((p) => (0,
|
|
43055
|
+
const candidateTree = candidate ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", candidate.head], root).split("\0").filter(Boolean).map((p) => (0, import_node_path30.join)(root, p))) : null;
|
|
42520
43056
|
const loaded = candidate ? {
|
|
42521
|
-
policy: candidateTree.has((0,
|
|
43057
|
+
policy: candidateTree.has((0, import_node_path30.join)(root, POLICY_FILE)) ? parsePolicy(git2(["show", `${candidate.head}:${POLICY_FILE}`], root), `${candidate.head}:${POLICY_FILE}`) : { mandatory: [], declared: false },
|
|
42522
43058
|
source: { ref: candidate.head, sha: candidate.head, path: POLICY_FILE }
|
|
42523
43059
|
} : deps.policy ? { policy: deps.policy, source: { ref: null, sha: null, path: POLICY_FILE } } : loadPolicySource(root, deps.policyRef, deps.base);
|
|
42524
43060
|
const { policy, source: policySource } = loaded;
|
|
42525
|
-
const exists = candidateTree ? (path2) => candidateTree.has(path2) : deps.exists ?? ((path2) => (0,
|
|
43061
|
+
const exists = candidateTree ? (path2) => candidateTree.has(path2) : deps.exists ?? ((path2) => (0, import_node_fs33.existsSync)(path2));
|
|
42526
43062
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
42527
43063
|
const base = candidate ? git2(["merge-base", candidate.head, candidate.base], root).trim() : deps.changed ? "(injected)" : deps.policyRef ? resolveHotfixBase(root) : resolveBase(root, deps.base);
|
|
42528
43064
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
42529
43065
|
const raw = deps.changed ?? (refusal ? [] : changedFilesSince(base, root, candidate?.head));
|
|
42530
43066
|
const changed = deps.changed ? raw : annotateChangeMeaning(raw, policy, (path2) => ({
|
|
42531
43067
|
before: blobAt(base, path2, root),
|
|
42532
|
-
after: candidate ? blobAt(candidate.head, path2, root) : readFileOrNull2((0,
|
|
43068
|
+
after: candidate ? blobAt(candidate.head, path2, root) : readFileOrNull2((0, import_node_path30.join)(root, path2))
|
|
42533
43069
|
}));
|
|
42534
43070
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root, candidate?.head) : { override: null, refusals: [] };
|
|
42535
43071
|
const policyRefusals = policy.declared === false ? [{
|
|
@@ -42539,8 +43075,8 @@ function runTestPolicy(root, deps = {}) {
|
|
|
42539
43075
|
A gate with no policy cannot decide which test commands are required or permitted.
|
|
42540
43076
|
` + (deps.base === HOTFIX_DIFF_BASE ? ` Hotfix lane (--base ${HOTFIX_DIFF_BASE}): pass --policy-ref <exact-${HOTFIX_POLICY_REF}-commit> so the current policy is read from ${HOTFIX_POLICY_REF}.` : ` Restore ${POLICY_FILE} on this branch; the policy file belongs on every non-hotfix lane.`)
|
|
42541
43077
|
}] : [];
|
|
42542
|
-
const present = (path2) => exists((0,
|
|
42543
|
-
const policyTree = candidateTree ?? (policySource.sha ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", policySource.sha], root).split("\0").filter(Boolean).map((p) => (0,
|
|
43078
|
+
const present = (path2) => exists((0, import_node_path30.join)(root, path2));
|
|
43079
|
+
const policyTree = candidateTree ?? (policySource.sha ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", policySource.sha], root).split("\0").filter(Boolean).map((p) => (0, import_node_path30.join)(root, p))) : null);
|
|
42544
43080
|
const policyTreeExists = policyTree ? (abs) => policyTree.has(abs) : exists;
|
|
42545
43081
|
const where = policySource.sha ? `${policySource.ref}@${policySource.sha}` : "this worktree";
|
|
42546
43082
|
const removedByThisDiff = removedPaths(changed);
|
|
@@ -42660,16 +43196,16 @@ var autoMergeApi = async (pr, repo, head) => {
|
|
|
42660
43196
|
var ASYNC_HEADERS = ["-H", "Accept: application/vnd.github+json", "-H", "X-GitHub-Api-Version: 2026-03-10"];
|
|
42661
43197
|
var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
|
42662
43198
|
function admissionJournal(repo, pr, root) {
|
|
42663
|
-
const stateRoot = root ?? (process.platform === "win32" ? (0,
|
|
42664
|
-
const directory = (0,
|
|
42665
|
-
(0,
|
|
42666
|
-
const attempts = (0,
|
|
43199
|
+
const stateRoot = root ?? (process.platform === "win32" ? (0, import_node_path31.join)(process.env.LOCALAPPDATA || (0, import_node_path31.join)((0, import_node_os18.homedir)(), "AppData", "Local"), "MMI Future", "mmi-cli", "queue-admissions") : (0, import_node_path31.join)(process.env.XDG_STATE_HOME || (0, import_node_path31.join)((0, import_node_os18.homedir)(), ".local", "state"), "mmi-cli", "queue-admissions"));
|
|
43200
|
+
const directory = (0, import_node_path31.join)(stateRoot, (0, import_node_crypto12.createHash)("sha256").update(`${repo.toLowerCase()}#${pr}`).digest("hex"));
|
|
43201
|
+
(0, import_node_fs34.mkdirSync)(directory, { recursive: true, mode: 448 });
|
|
43202
|
+
const attempts = (0, import_node_fs34.readdirSync)(directory).map((name) => {
|
|
42667
43203
|
if (!/^[1-9][0-9]*\.jsonl$/.test(name)) throw new Error("queue admission receipt directory contains an unrecognized record");
|
|
42668
43204
|
return Number(name.slice(0, -6));
|
|
42669
43205
|
}).sort((a, b) => a - b);
|
|
42670
43206
|
if (attempts.some((number, index) => number !== index + 1)) throw new Error("queue admission receipt sequence is incomplete");
|
|
42671
43207
|
const attempt = attempts.length;
|
|
42672
|
-
const path2 = (number) => (0,
|
|
43208
|
+
const path2 = (number) => (0, import_node_path31.join)(directory, `${number}.jsonl`);
|
|
42673
43209
|
const validate = (value, previous) => {
|
|
42674
43210
|
const row = value;
|
|
42675
43211
|
if (!row || row.version !== 1 || row.repo !== repo.toLowerCase() || row.pr !== pr || !/^[a-f0-9]{40}$/i.test(row.head) || !UUID.test(row.intentId) || !Number.isSafeInteger(row.createdAt) || row.createdAt <= 0 || !Number.isSafeInteger(row.observedAt) || row.observedAt < row.createdAt || !["dispatching", "pending", "enqueued", "merged", "failed", "ejected"].includes(row.state) || row.credential !== void 0 && !["app_installation", "user"].includes(row.credential) || row.queueWitnessed !== void 0 && typeof row.queueWitnessed !== "boolean" || row.uuid !== void 0 && !UUID.test(row.uuid) || row.state === "pending" && !row.uuid || row.queueEntry !== void 0 && (!row.queueEntry || typeof row.queueEntry.headCommitOid !== "string" || !/^[a-f0-9]{40}$/i.test(row.queueEntry.headCommitOid) || typeof row.queueEntry.enqueuedAt !== "string" || !Number.isFinite(Date.parse(row.queueEntry.enqueuedAt)))) {
|
|
@@ -42684,7 +43220,7 @@ function admissionJournal(repo, pr, root) {
|
|
|
42684
43220
|
};
|
|
42685
43221
|
const load = (number = attempt) => {
|
|
42686
43222
|
let previous;
|
|
42687
|
-
for (const line of (0,
|
|
43223
|
+
for (const line of (0, import_node_fs34.readFileSync)(path2(number), "utf8").trimEnd().split("\n")) previous = validate(JSON.parse(line), previous);
|
|
42688
43224
|
return previous;
|
|
42689
43225
|
};
|
|
42690
43226
|
return {
|
|
@@ -42699,9 +43235,9 @@ function admissionJournal(repo, pr, root) {
|
|
|
42699
43235
|
},
|
|
42700
43236
|
create(head) {
|
|
42701
43237
|
const createdAt = Date.now();
|
|
42702
|
-
const initial = { version: 1, repo: repo.toLowerCase(), pr, head, intentId: (0,
|
|
43238
|
+
const initial = { version: 1, repo: repo.toLowerCase(), pr, head, intentId: (0, import_node_crypto12.randomUUID)(), createdAt, observedAt: createdAt, state: "dispatching" };
|
|
42703
43239
|
try {
|
|
42704
|
-
(0,
|
|
43240
|
+
(0, import_node_fs34.writeFileSync)(path2(attempt + 1), JSON.stringify(initial) + "\n", { flag: "wx", mode: 384, flush: true });
|
|
42705
43241
|
} catch (error) {
|
|
42706
43242
|
if (error.code === "EEXIST") return false;
|
|
42707
43243
|
throw error;
|
|
@@ -42721,7 +43257,7 @@ function admissionJournal(repo, pr, root) {
|
|
|
42721
43257
|
...queueWitnessed !== void 0 ? { queueWitnessed } : {},
|
|
42722
43258
|
...credential && previous.credential === void 0 ? { credential } : {}
|
|
42723
43259
|
}, previous);
|
|
42724
|
-
(0,
|
|
43260
|
+
(0, import_node_fs34.appendFileSync)(path2(attempt), JSON.stringify(receipt) + "\n", { flush: true });
|
|
42725
43261
|
load();
|
|
42726
43262
|
}
|
|
42727
43263
|
};
|
|
@@ -43614,6 +44150,15 @@ async function attachViaHubApp(cfg, contentNodeId) {
|
|
|
43614
44150
|
const { addIssueToProjectViaHub: addIssueToProjectViaHub2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
|
|
43615
44151
|
return addIssueToProjectViaHub2(cfg, contentNodeId);
|
|
43616
44152
|
}
|
|
44153
|
+
var PROJECTS_PERMISSION_HINT = "the GitHub App needs the organization permission 'Projects: Read and write' (REST name organization_projects: write), set by an org owner under Organization settings > GitHub Apps > the App > Permissions & events, then accepted on the installation";
|
|
44154
|
+
async function attachOnIntegrationRefusal(detail, cfg, contentNodeId, attach = attachViaHubApp) {
|
|
44155
|
+
if (!/Resource not accessible by integration/i.test(detail)) return void 0;
|
|
44156
|
+
try {
|
|
44157
|
+
return { projectItemId: await attach(cfg, contentNodeId) };
|
|
44158
|
+
} catch (e) {
|
|
44159
|
+
return { warning: `board attach refused: the gh login is an App installation token without Projects access (${detail}) and the Hub App leg failed (${e.message}). If the Hub reports the same refusal, ${PROJECTS_PERMISSION_HINT}` };
|
|
44160
|
+
}
|
|
44161
|
+
}
|
|
43617
44162
|
async function issueNodeIdForAttach(repo, number, ghView, client) {
|
|
43618
44163
|
try {
|
|
43619
44164
|
return (await ghView()).trim();
|
|
@@ -44223,9 +44768,9 @@ function registerBoardCommands(program3) {
|
|
|
44223
44768
|
}
|
|
44224
44769
|
|
|
44225
44770
|
// src/bootstrap-commands.ts
|
|
44226
|
-
var
|
|
44227
|
-
var
|
|
44228
|
-
var
|
|
44771
|
+
var import_node_fs38 = require("node:fs");
|
|
44772
|
+
var import_node_os21 = require("node:os");
|
|
44773
|
+
var import_node_path35 = require("node:path");
|
|
44229
44774
|
init_cli_shared();
|
|
44230
44775
|
init_clean_exit();
|
|
44231
44776
|
|
|
@@ -44410,8 +44955,8 @@ init_github_client();
|
|
|
44410
44955
|
init_cli_shared();
|
|
44411
44956
|
|
|
44412
44957
|
// src/port-registry.ts
|
|
44413
|
-
var
|
|
44414
|
-
var
|
|
44958
|
+
var import_node_fs35 = require("node:fs");
|
|
44959
|
+
var import_node_path32 = require("node:path");
|
|
44415
44960
|
|
|
44416
44961
|
// ../infra/port-geometry.mjs
|
|
44417
44962
|
var PORT_BLOCK = 100;
|
|
@@ -44425,8 +44970,8 @@ function nextPortBlock(registry2) {
|
|
|
44425
44970
|
return [base, base + PORT_SPAN];
|
|
44426
44971
|
}
|
|
44427
44972
|
function loadPortRegistry(path2) {
|
|
44428
|
-
if (!(0,
|
|
44429
|
-
const raw = JSON.parse((0,
|
|
44973
|
+
if (!(0, import_node_fs35.existsSync)(path2)) return {};
|
|
44974
|
+
const raw = JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8"));
|
|
44430
44975
|
const out = {};
|
|
44431
44976
|
for (const [key, value] of Object.entries(raw)) {
|
|
44432
44977
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -44454,22 +44999,22 @@ function existingPortRange(repo, registry2) {
|
|
|
44454
44999
|
return registry2[repo] ?? null;
|
|
44455
45000
|
}
|
|
44456
45001
|
function portRangeInfraAt(root, source) {
|
|
44457
|
-
const registryPath = (0,
|
|
44458
|
-
const ddbScriptPath = (0,
|
|
44459
|
-
if (!(0,
|
|
45002
|
+
const registryPath = (0, import_node_path32.join)(root, "infra", "port-ranges.json");
|
|
45003
|
+
const ddbScriptPath = (0, import_node_path32.join)(root, "infra", "port-ddb.mjs");
|
|
45004
|
+
if (!(0, import_node_fs35.existsSync)(registryPath) || !(0, import_node_fs35.existsSync)(ddbScriptPath)) return null;
|
|
44460
45005
|
return { root, source, registryPath, ddbScriptPath };
|
|
44461
45006
|
}
|
|
44462
45007
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
44463
45008
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
44464
45009
|
if (direct) return direct;
|
|
44465
|
-
for (let dir = cwd; ; dir = (0,
|
|
44466
|
-
const sibling = portRangeInfraAt((0,
|
|
45010
|
+
for (let dir = cwd; ; dir = (0, import_node_path32.dirname)(dir)) {
|
|
45011
|
+
const sibling = portRangeInfraAt((0, import_node_path32.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
44467
45012
|
if (sibling) return sibling;
|
|
44468
|
-
const parent = (0,
|
|
45013
|
+
const parent = (0, import_node_path32.dirname)(dir);
|
|
44469
45014
|
if (parent === dir) break;
|
|
44470
45015
|
}
|
|
44471
45016
|
if (packageDir) {
|
|
44472
|
-
const pkgRoot = (0,
|
|
45017
|
+
const pkgRoot = (0, import_node_path32.join)(packageDir, "..", "..");
|
|
44473
45018
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
44474
45019
|
if (pkgFrom) return pkgFrom;
|
|
44475
45020
|
}
|
|
@@ -44486,7 +45031,7 @@ async function ensurePortRangeAtomic(repo, path2, allocate) {
|
|
|
44486
45031
|
} catch (e) {
|
|
44487
45032
|
const why = e.message;
|
|
44488
45033
|
const missing = /@aws-sdk\/[\w-]+/.exec(why)?.[0];
|
|
44489
|
-
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,
|
|
45034
|
+
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_path32.dirname)(path2)}, then retry.` : "Check AWS credentials and that the registry table is reachable, then retry.") + ` Cause: ${why}`);
|
|
44490
45035
|
}
|
|
44491
45036
|
}
|
|
44492
45037
|
|
|
@@ -44538,13 +45083,13 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
|
|
|
44538
45083
|
}
|
|
44539
45084
|
|
|
44540
45085
|
// src/box-commands.ts
|
|
44541
|
-
var
|
|
45086
|
+
var import_node_fs37 = require("node:fs");
|
|
44542
45087
|
var import_node_child_process12 = require("node:child_process");
|
|
44543
|
-
var
|
|
44544
|
-
var
|
|
45088
|
+
var import_node_os20 = require("node:os");
|
|
45089
|
+
var import_node_path34 = require("node:path");
|
|
44545
45090
|
|
|
44546
45091
|
// src/box-control.ts
|
|
44547
|
-
var
|
|
45092
|
+
var import_node_crypto13 = require("node:crypto");
|
|
44548
45093
|
var MANIFEST_PATH = "infra/box-control/manifest.json";
|
|
44549
45094
|
var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
|
|
44550
45095
|
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`;
|
|
@@ -44648,7 +45193,7 @@ function laneCoverageAction(files, box, op) {
|
|
|
44648
45193
|
return { notice: `LANE_COVERAGE: ${mismatch}` };
|
|
44649
45194
|
}
|
|
44650
45195
|
function md5(content) {
|
|
44651
|
-
return (0,
|
|
45196
|
+
return (0, import_node_crypto13.createHash)("md5").update(content).digest("hex");
|
|
44652
45197
|
}
|
|
44653
45198
|
function planRemoteScript(dests) {
|
|
44654
45199
|
const lines2 = ["set -u"];
|
|
@@ -44766,9 +45311,9 @@ init_clean_exit();
|
|
|
44766
45311
|
init_cli_shared();
|
|
44767
45312
|
|
|
44768
45313
|
// src/secrets-commands.ts
|
|
44769
|
-
var
|
|
44770
|
-
var
|
|
44771
|
-
var
|
|
45314
|
+
var import_node_fs36 = require("node:fs");
|
|
45315
|
+
var import_node_path33 = require("node:path");
|
|
45316
|
+
var import_node_os19 = require("node:os");
|
|
44772
45317
|
init_cli_shared();
|
|
44773
45318
|
init_hub_auth();
|
|
44774
45319
|
init_github_client();
|
|
@@ -45964,18 +46509,18 @@ function collectMap(value, previous = []) {
|
|
|
45964
46509
|
return [...previous, value];
|
|
45965
46510
|
}
|
|
45966
46511
|
async function decryptRailsCredentials(input) {
|
|
45967
|
-
const appDir = (0,
|
|
46512
|
+
const appDir = (0, import_node_path33.resolve)(input.appDir ?? process.cwd());
|
|
45968
46513
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
45969
46514
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
45970
|
-
const credentialsPath = (0,
|
|
45971
|
-
const masterKeyPath = (0,
|
|
46515
|
+
const credentialsPath = (0, import_node_path33.resolve)(appDir, credentialsFile);
|
|
46516
|
+
const masterKeyPath = (0, import_node_path33.resolve)(appDir, masterKeyFile);
|
|
45972
46517
|
const env = {
|
|
45973
46518
|
...process.env,
|
|
45974
46519
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
45975
46520
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
45976
46521
|
};
|
|
45977
|
-
if ((0,
|
|
45978
|
-
env.RAILS_MASTER_KEY = (0,
|
|
46522
|
+
if ((0, import_node_fs36.existsSync)(masterKeyPath)) {
|
|
46523
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs36.readFileSync)(masterKeyPath, "utf8").trim();
|
|
45979
46524
|
}
|
|
45980
46525
|
const script = [
|
|
45981
46526
|
'require "json"',
|
|
@@ -45985,9 +46530,9 @@ async function decryptRailsCredentials(input) {
|
|
|
45985
46530
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
45986
46531
|
"puts JSON.generate(config.config)"
|
|
45987
46532
|
].join("\n");
|
|
45988
|
-
const scriptDir = (0,
|
|
45989
|
-
const scriptPath = (0,
|
|
45990
|
-
(0,
|
|
46533
|
+
const scriptDir = (0, import_node_fs36.mkdtempSync)((0, import_node_path33.join)((0, import_node_os19.tmpdir)(), "mmi-rails-decrypt-"));
|
|
46534
|
+
const scriptPath = (0, import_node_path33.join)(scriptDir, "decrypt.rb");
|
|
46535
|
+
(0, import_node_fs36.writeFileSync)(scriptPath, script, "utf8");
|
|
45991
46536
|
try {
|
|
45992
46537
|
const args = ["exec", "ruby", scriptPath];
|
|
45993
46538
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -45999,7 +46544,7 @@ async function decryptRailsCredentials(input) {
|
|
|
45999
46544
|
});
|
|
46000
46545
|
return JSON.parse(stdout);
|
|
46001
46546
|
} finally {
|
|
46002
|
-
(0,
|
|
46547
|
+
(0, import_node_fs36.rmSync)(scriptDir, { recursive: true, force: true });
|
|
46003
46548
|
}
|
|
46004
46549
|
}
|
|
46005
46550
|
async function readSecretStdin() {
|
|
@@ -46089,7 +46634,7 @@ function registerSecretsCommands(program3) {
|
|
|
46089
46634
|
let body;
|
|
46090
46635
|
if (o.file) {
|
|
46091
46636
|
try {
|
|
46092
|
-
body = (0,
|
|
46637
|
+
body = (0, import_node_fs36.readFileSync)((0, import_node_path33.resolve)(o.file), "utf8");
|
|
46093
46638
|
} catch (e) {
|
|
46094
46639
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
46095
46640
|
}
|
|
@@ -46225,7 +46770,7 @@ function registerSecretsCommands(program3) {
|
|
|
46225
46770
|
{
|
|
46226
46771
|
...d,
|
|
46227
46772
|
decryptRailsCredentials,
|
|
46228
|
-
removeFile: (path2) => (0,
|
|
46773
|
+
removeFile: (path2) => (0, import_node_fs36.unlinkSync)((0, import_node_path33.resolve)(o.appDir ?? process.cwd(), path2))
|
|
46229
46774
|
},
|
|
46230
46775
|
{
|
|
46231
46776
|
repo: o.repo,
|
|
@@ -46496,7 +47041,7 @@ function registerBoxCommands(program3) {
|
|
|
46496
47041
|
}
|
|
46497
47042
|
const remote = command && command.length ? command.join(" ") : void 0;
|
|
46498
47043
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
46499
|
-
if (wroteScript) (0,
|
|
47044
|
+
if (wroteScript) (0, import_node_fs37.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
|
|
46500
47045
|
if (o.json) {
|
|
46501
47046
|
console.log(JSON.stringify({
|
|
46502
47047
|
box: found,
|
|
@@ -46623,17 +47168,17 @@ function hubGitShow(ref) {
|
|
|
46623
47168
|
return (path2) => git3(["show", `${ref}:${path2}`]);
|
|
46624
47169
|
}
|
|
46625
47170
|
function runOnBox(found, script) {
|
|
46626
|
-
const dir = (0,
|
|
47171
|
+
const dir = (0, import_node_fs37.mkdtempSync)((0, import_node_path34.join)((0, import_node_os20.tmpdir)(), "mmi-box-control-"));
|
|
46627
47172
|
try {
|
|
46628
|
-
const recipe = (0,
|
|
46629
|
-
(0,
|
|
47173
|
+
const recipe = (0, import_node_path34.join)(dir, "recipe.sh");
|
|
47174
|
+
(0, import_node_fs37.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
|
|
46630
47175
|
const r = (0, import_node_child_process12.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
|
|
46631
47176
|
if (r.status !== 0) {
|
|
46632
47177
|
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(" | ")}`);
|
|
46633
47178
|
}
|
|
46634
47179
|
return r.stdout;
|
|
46635
47180
|
} finally {
|
|
46636
|
-
(0,
|
|
47181
|
+
(0, import_node_fs37.rmSync)(dir, { recursive: true, force: true });
|
|
46637
47182
|
}
|
|
46638
47183
|
}
|
|
46639
47184
|
function formatControl(mode, box, rows) {
|
|
@@ -46761,7 +47306,7 @@ function renderDeployPortDoctor(report) {
|
|
|
46761
47306
|
}
|
|
46762
47307
|
|
|
46763
47308
|
// src/bootstrap-drift.ts
|
|
46764
|
-
var
|
|
47309
|
+
var import_node_crypto14 = require("node:crypto");
|
|
46765
47310
|
function byteComparableSeeds(manifest, cls) {
|
|
46766
47311
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
46767
47312
|
}
|
|
@@ -46781,7 +47326,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
46781
47326
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
46782
47327
|
}
|
|
46783
47328
|
function seedContentHash(content) {
|
|
46784
|
-
return (0,
|
|
47329
|
+
return (0, import_node_crypto14.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
46785
47330
|
}
|
|
46786
47331
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
46787
47332
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -46831,197 +47376,6 @@ function renderSeedDriftReport(findings, reposAudited, seedsPerRepo) {
|
|
|
46831
47376
|
return lines2.join("\n");
|
|
46832
47377
|
}
|
|
46833
47378
|
|
|
46834
|
-
// src/bootstrap-propagate.ts
|
|
46835
|
-
function archivedWaiver(repoMeta) {
|
|
46836
|
-
return repoMeta?.archived === true ? "archived repository (read-only)" : void 0;
|
|
46837
|
-
}
|
|
46838
|
-
function assertPropagationCoverage(rosterCount, independentRegistryCount) {
|
|
46839
|
-
if (rosterCount === 0) {
|
|
46840
|
-
throw new Error("bootstrap propagate: roster read 0 repos \u2014 refusing to report a plan from a roster this command could not establish (#4232 coverage invariant)");
|
|
46841
|
-
}
|
|
46842
|
-
if (rosterCount < independentRegistryCount) {
|
|
46843
|
-
throw new Error(
|
|
46844
|
-
`bootstrap propagate: roster read ${rosterCount} repo(s) but the independent registry count is ${independentRegistryCount} \u2014 this read did not cover the fleet (#4232 coverage invariant)`
|
|
46845
|
-
);
|
|
46846
|
-
}
|
|
46847
|
-
}
|
|
46848
|
-
function assignWaves(repos, canarySlug) {
|
|
46849
|
-
const waves = /* @__PURE__ */ new Map();
|
|
46850
|
-
if (!canarySlug) return waves;
|
|
46851
|
-
const rest = repos.filter((r) => r.slug !== canarySlug).slice().sort((a, b) => a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
|
|
46852
|
-
const canary = repos.find((r) => r.slug === canarySlug);
|
|
46853
|
-
if (canary) waves.set(canary.repo, 0);
|
|
46854
|
-
const wave1Count = Math.ceil(rest.length * 0.25);
|
|
46855
|
-
rest.forEach((r, i) => waves.set(r.repo, i < wave1Count ? 1 : 2));
|
|
46856
|
-
return waves;
|
|
46857
|
-
}
|
|
46858
|
-
function statusFor(read, currentSha) {
|
|
46859
|
-
if (!read) return { status: "pending", record: {} };
|
|
46860
|
-
if (read.drift === "match") return { status: "match", record: { mergeSha: read.pr?.mergeSha } };
|
|
46861
|
-
if (read.prUnread) return { status: "red", record: {} };
|
|
46862
|
-
const pr = read.pr;
|
|
46863
|
-
if (!pr) return { status: "pending", record: {} };
|
|
46864
|
-
if (pr.sourceSha && pr.sourceSha !== currentSha) return { status: "pending", record: {} };
|
|
46865
|
-
if (pr.state === "closed") return { status: "closed-unmerged", record: { prNumber: pr.number, prUrl: pr.url } };
|
|
46866
|
-
if (pr.checks === "red") return { status: "red", record: { prNumber: pr.number, prUrl: pr.url } };
|
|
46867
|
-
return { status: "open-pending", record: { prNumber: pr.number, prUrl: pr.url, mergeSha: pr.mergeSha } };
|
|
46868
|
-
}
|
|
46869
|
-
var HALTING_STATUSES = /* @__PURE__ */ new Set(["red", "closed-unmerged"]);
|
|
46870
|
-
function planPropagationTick(input) {
|
|
46871
|
-
const { target, repos, canarySlug, reads, isWorkflowSeed, functionGateSatisfied, currentSha } = input;
|
|
46872
|
-
const sourcePilot = input.sourcePilot === true;
|
|
46873
|
-
const readByRepo = new Map(reads.map((r) => [r.repo, r]));
|
|
46874
|
-
const records = [];
|
|
46875
|
-
const opened = [];
|
|
46876
|
-
const waived = repos.filter((r) => r.waiver);
|
|
46877
|
-
for (const r of waived) {
|
|
46878
|
-
records.push({ repo: r.repo, target, wave: null, status: "skipped-waived", action: "none", detail: `waived: ${r.waiver}` });
|
|
46879
|
-
}
|
|
46880
|
-
const eligible = repos.filter((r) => !r.waiver);
|
|
46881
|
-
const refusedNoCanary = !canarySlug || !sourcePilot && !eligible.some((r) => r.slug === canarySlug);
|
|
46882
|
-
if (refusedNoCanary) {
|
|
46883
|
-
for (const r of eligible) {
|
|
46884
|
-
records.push({ repo: r.repo, target, wave: null, status: "not-yet-reached", action: "none", detail: "no canary declared for this target \u2014 refusing to plan any wave (#4238 canary requirement)" });
|
|
46885
|
-
}
|
|
46886
|
-
return { target, reposInScope: repos.length, canary: null, refusedNoCanary: true, halted: false, haltReason: null, opened, records };
|
|
46887
|
-
}
|
|
46888
|
-
const waveOf = assignWaves(eligible, canarySlug);
|
|
46889
|
-
const byWave = { 0: [], 1: [], 2: [] };
|
|
46890
|
-
for (const r of eligible) byWave[waveOf.get(r.repo) ?? 2].push(r);
|
|
46891
|
-
let halted = false;
|
|
46892
|
-
let haltReason = null;
|
|
46893
|
-
let waveGateOpen = !(sourcePilot && isWorkflowSeed && !functionGateSatisfied);
|
|
46894
|
-
for (const waveNum of [0, 1, 2]) {
|
|
46895
|
-
const waveRepos = byWave[waveNum];
|
|
46896
|
-
if (!waveRepos.length) continue;
|
|
46897
|
-
if (!waveGateOpen || halted) {
|
|
46898
|
-
for (const r of waveRepos) {
|
|
46899
|
-
records.push({ repo: r.repo, target, wave: waveNum, status: "not-yet-reached", action: "none", detail: "prior wave not yet converged" });
|
|
46900
|
-
}
|
|
46901
|
-
continue;
|
|
46902
|
-
}
|
|
46903
|
-
let waveAllMatch = true;
|
|
46904
|
-
let waveHasRed = false;
|
|
46905
|
-
let waveHasClosed = false;
|
|
46906
|
-
for (const r of waveRepos) {
|
|
46907
|
-
const { status, record: record2 } = statusFor(readByRepo.get(r.repo), currentSha);
|
|
46908
|
-
const shouldOpen = status === "pending" || status === "closed-unmerged";
|
|
46909
|
-
if (shouldOpen) opened.push(r.repo);
|
|
46910
|
-
records.push({
|
|
46911
|
-
repo: r.repo,
|
|
46912
|
-
target,
|
|
46913
|
-
wave: waveNum,
|
|
46914
|
-
status,
|
|
46915
|
-
action: shouldOpen ? "open-pr" : "none",
|
|
46916
|
-
detail: status === "closed-unmerged" ? `previous PR#${record2.prNumber} was closed unmerged and no open PR replaced it \u2014 opening a replacement against the live base this tick (#9232)` : status === "pending" ? "no open propagation PR and not yet matching \u2014 opening this tick" : status,
|
|
46917
|
-
...record2
|
|
46918
|
-
});
|
|
46919
|
-
if (status !== "match") waveAllMatch = false;
|
|
46920
|
-
if (HALTING_STATUSES.has(status)) waveHasRed = true;
|
|
46921
|
-
if (status === "closed-unmerged") waveHasClosed = true;
|
|
46922
|
-
}
|
|
46923
|
-
if (waveHasRed) {
|
|
46924
|
-
halted = true;
|
|
46925
|
-
haltReason = `wave ${waveNum} has a red or closed-unmerged PR \u2014 halting; no further wave opens (#4238 halt-and-alarm)` + (waveHasClosed ? "; the closed PR(s) were re-opened against the live base this tick (#9232) \u2014 land or close the replacement to advance" : "");
|
|
46926
|
-
waveGateOpen = false;
|
|
46927
|
-
continue;
|
|
46928
|
-
}
|
|
46929
|
-
if (waveNum === 0 && isWorkflowSeed && !functionGateSatisfied) {
|
|
46930
|
-
waveGateOpen = false;
|
|
46931
|
-
continue;
|
|
46932
|
-
}
|
|
46933
|
-
waveGateOpen = waveAllMatch;
|
|
46934
|
-
}
|
|
46935
|
-
return { target, reposInScope: repos.length, canary: canarySlug, refusedNoCanary: false, halted, haltReason, opened, records };
|
|
46936
|
-
}
|
|
46937
|
-
function summarizePropagationTick(plan) {
|
|
46938
|
-
const parts = [];
|
|
46939
|
-
parts.push(plan.opened.length ? `opened ${plan.opened.length} PR(s) this tick: ${plan.opened.join(", ")}` : "opened no PR this tick");
|
|
46940
|
-
const stillNotCurrent = plan.records.filter((r) => r.status !== "match" && r.status !== "skipped-waived" && r.action !== "open-pr");
|
|
46941
|
-
parts.push(stillNotCurrent.length ? `not current and not opened this tick: ${stillNotCurrent.map((r) => `${r.repo} (${r.status})`).join(", ")}` : "every in-scope repo either matches the Hub seed or had a PR opened this tick");
|
|
46942
|
-
if (plan.halted) parts.push(`HALTED \u2014 ${plan.haltReason}`);
|
|
46943
|
-
return parts.join("; ");
|
|
46944
|
-
}
|
|
46945
|
-
function renderPropagationReport(plan) {
|
|
46946
|
-
const lines2 = [`bootstrap propagate \u2014 target ${plan.target}: ${plan.reposInScope} repo(s) in scope, canary=${plan.canary ?? "NONE"}`];
|
|
46947
|
-
if (plan.refusedNoCanary) {
|
|
46948
|
-
lines2.push(" REFUSED \u2014 no canary declared for this target; plan every repo not-yet-reached");
|
|
46949
|
-
lines2.push(` ${summarizePropagationTick(plan)}`);
|
|
46950
|
-
return lines2.join("\n");
|
|
46951
|
-
}
|
|
46952
|
-
for (const r of plan.records) {
|
|
46953
|
-
const wave = r.wave == null ? "-" : String(r.wave);
|
|
46954
|
-
lines2.push(` wave${wave.padEnd(2)} ${r.status.padEnd(16)} ${r.repo}${r.prNumber ? ` PR#${r.prNumber}` : ""} \u2014 ${r.detail}`);
|
|
46955
|
-
}
|
|
46956
|
-
lines2.push(` ${summarizePropagationTick(plan)}`);
|
|
46957
|
-
return lines2.join("\n");
|
|
46958
|
-
}
|
|
46959
|
-
|
|
46960
|
-
// src/bootstrap-propagation-identity.ts
|
|
46961
|
-
var import_node_crypto13 = require("node:crypto");
|
|
46962
|
-
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
46963
|
-
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
46964
|
-
function safeBranchPart(value, maxLength, fallback) {
|
|
46965
|
-
const safe = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength).replace(/-+$/g, "");
|
|
46966
|
-
return safe || fallback;
|
|
46967
|
-
}
|
|
46968
|
-
function repoSlug2(repo) {
|
|
46969
|
-
return repo.trim().split("/").pop()?.toLowerCase() || "repo";
|
|
46970
|
-
}
|
|
46971
|
-
function propagationBranch(repo, target) {
|
|
46972
|
-
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
46973
|
-
const targetPart = safeBranchPart(target, 48, "target");
|
|
46974
|
-
const hash = (0, import_node_crypto13.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
46975
|
-
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
46976
|
-
}
|
|
46977
|
-
function legacyPropagationBranch(repo) {
|
|
46978
|
-
return `${PROPAGATION_BRANCH_PREFIX}${repoSlug2(repo)}`;
|
|
46979
|
-
}
|
|
46980
|
-
function renderPropagationTargetMarker(target) {
|
|
46981
|
-
return `<!-- ${TARGET_MARKER_NAME}: ${JSON.stringify(target)} -->`;
|
|
46982
|
-
}
|
|
46983
|
-
function targetMarkers(body) {
|
|
46984
|
-
const present = body.includes(`${TARGET_MARKER_NAME}:`);
|
|
46985
|
-
const targets = [];
|
|
46986
|
-
const marker = new RegExp(`<!--\\s*${TARGET_MARKER_NAME}:\\s*(.*?)\\s*-->`, "g");
|
|
46987
|
-
for (const match of body.matchAll(marker)) {
|
|
46988
|
-
try {
|
|
46989
|
-
const parsed = JSON.parse(match[1]);
|
|
46990
|
-
if (typeof parsed === "string") targets.push(parsed);
|
|
46991
|
-
} catch {
|
|
46992
|
-
}
|
|
46993
|
-
}
|
|
46994
|
-
return { present, targets };
|
|
46995
|
-
}
|
|
46996
|
-
function canonicalProseTargets(body) {
|
|
46997
|
-
const targets = [];
|
|
46998
|
-
const command = /bootstrap propagate --target (.+?) --execute`/g;
|
|
46999
|
-
const prose = /(?:org-owned copy of|declared marker-bounded block inside repo-owned) `([^`]+)` — the file this PR carries and nothing else/g;
|
|
47000
|
-
for (const match of body.matchAll(command)) targets.push(match[1]);
|
|
47001
|
-
for (const match of body.matchAll(prose)) targets.push(match[1]);
|
|
47002
|
-
return targets;
|
|
47003
|
-
}
|
|
47004
|
-
function propagationPrMatchesTarget(pr, target, identity) {
|
|
47005
|
-
const body = pr.body ?? "";
|
|
47006
|
-
const markers = targetMarkers(body);
|
|
47007
|
-
if (markers.present) {
|
|
47008
|
-
if (markers.targets.length !== 1 || markers.targets[0] !== target) return false;
|
|
47009
|
-
return !pr.files?.length || pr.files.length === 1 && pr.files[0] === target;
|
|
47010
|
-
}
|
|
47011
|
-
if (identity === "target-specific") return false;
|
|
47012
|
-
const proseTargets = canonicalProseTargets(body);
|
|
47013
|
-
if (proseTargets.some((candidate) => candidate !== target)) return false;
|
|
47014
|
-
if (pr.files?.length) {
|
|
47015
|
-
return pr.files.length === 1 && pr.files[0] === target;
|
|
47016
|
-
}
|
|
47017
|
-
return proseTargets.length > 0;
|
|
47018
|
-
}
|
|
47019
|
-
function propagationPrCandidatesForTarget(target, targetSpecificPrs, legacyPrs) {
|
|
47020
|
-
const targetSpecific = targetSpecificPrs.filter((pr) => propagationPrMatchesTarget(pr, target, "target-specific"));
|
|
47021
|
-
if (targetSpecific.length) return targetSpecific;
|
|
47022
|
-
return legacyPrs.filter((pr) => propagationPrMatchesTarget(pr, target, "legacy"));
|
|
47023
|
-
}
|
|
47024
|
-
|
|
47025
47379
|
// src/bootstrap-rollback.ts
|
|
47026
47380
|
function resolveRollbackRecord(records, repo, target) {
|
|
47027
47381
|
const candidates = records.filter((r) => r.repo === repo && r.target === target && r.mergeSha);
|
|
@@ -47466,7 +47820,7 @@ var requiredIssueTemplates = [
|
|
|
47466
47820
|
];
|
|
47467
47821
|
var requiredProductWorkflows = [".github/workflows/gate.yml"];
|
|
47468
47822
|
var requiredProductRulesetRef = ".github/rulesets/mmi-product-required-checks.json";
|
|
47469
|
-
var
|
|
47823
|
+
var HUB_REPO6 = "mutmutco/MMI-Hub";
|
|
47470
47824
|
var requiredLabels = ["bug", "feature", "task"];
|
|
47471
47825
|
var requiredPriorityOptions = ["Urgent", "High", "Medium", "Low"];
|
|
47472
47826
|
var strayDefaultLabels = ["documentation", "duplicate", "enhancement", "good first issue", "help wanted", "invalid", "question", "wontfix"];
|
|
@@ -47880,7 +48234,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
47880
48234
|
checks.push({ ok: await contentExists2(deps, repo, baseBranch, path2), label: `issue template exists: ${path2}` });
|
|
47881
48235
|
}
|
|
47882
48236
|
let productContexts = [PRODUCT_GATE_CONTEXT];
|
|
47883
|
-
if (repo !==
|
|
48237
|
+
if (repo !== HUB_REPO6 && repoClass === "deployable") {
|
|
47884
48238
|
const rulesetRaw = await contentText(deps, repo, baseBranch, requiredProductRulesetRef);
|
|
47885
48239
|
productContexts = committedRequiredContexts(rulesetRaw);
|
|
47886
48240
|
const workflowCandidates = [.../* @__PURE__ */ new Set([
|
|
@@ -48124,7 +48478,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
48124
48478
|
detail: coverage.ok ? void 0 : `${coverage.reason} (branches: ${coverage.branches.join(", ")})`
|
|
48125
48479
|
});
|
|
48126
48480
|
}
|
|
48127
|
-
if (repo ===
|
|
48481
|
+
if (repo === HUB_REPO6) {
|
|
48128
48482
|
const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
|
|
48129
48483
|
const missing = requiredHubStatusChecks.filter((check) => !statusChecks.has(check));
|
|
48130
48484
|
checks.push({
|
|
@@ -48222,7 +48576,7 @@ function renderBootstrapVerifyReport(report) {
|
|
|
48222
48576
|
}
|
|
48223
48577
|
|
|
48224
48578
|
// src/bootstrap-seed-delivery.ts
|
|
48225
|
-
var
|
|
48579
|
+
var import_node_crypto15 = require("node:crypto");
|
|
48226
48580
|
init_github_client();
|
|
48227
48581
|
var SHA = /^[a-f0-9]{40}$/;
|
|
48228
48582
|
async function readSeedFile(repo, target, ref, client) {
|
|
@@ -48255,7 +48609,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
|
|
|
48255
48609
|
}
|
|
48256
48610
|
const baseSha = base?.object?.sha;
|
|
48257
48611
|
if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
|
|
48258
|
-
const identity = (0,
|
|
48612
|
+
const identity = (0, import_node_crypto15.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
|
|
48259
48613
|
const delivery = planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`);
|
|
48260
48614
|
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;
|
|
48261
48615
|
return { ...scoped, baseSha, baseBranch };
|
|
@@ -48325,12 +48679,12 @@ var declaredProjectAdmins = (meta) => {
|
|
|
48325
48679
|
};
|
|
48326
48680
|
var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
48327
48681
|
async function gateRenderDiff(current, rendered) {
|
|
48328
|
-
const dir = (0,
|
|
48329
|
-
const before = (0,
|
|
48330
|
-
const after = (0,
|
|
48682
|
+
const dir = (0, import_node_fs38.mkdtempSync)((0, import_node_path35.join)((0, import_node_os21.tmpdir)(), "mmi-gate-diff-"));
|
|
48683
|
+
const before = (0, import_node_path35.join)(dir, "committed-gate.yml");
|
|
48684
|
+
const after = (0, import_node_path35.join)(dir, "registry-render.yml");
|
|
48331
48685
|
try {
|
|
48332
|
-
(0,
|
|
48333
|
-
(0,
|
|
48686
|
+
(0, import_node_fs38.writeFileSync)(before, current);
|
|
48687
|
+
(0, import_node_fs38.writeFileSync)(after, rendered);
|
|
48334
48688
|
try {
|
|
48335
48689
|
return (await execFileP("git", ["diff", "--no-index", "--", before, after], { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
48336
48690
|
} catch (e) {
|
|
@@ -48339,7 +48693,7 @@ async function gateRenderDiff(current, rendered) {
|
|
|
48339
48693
|
throw e;
|
|
48340
48694
|
}
|
|
48341
48695
|
} finally {
|
|
48342
|
-
(0,
|
|
48696
|
+
(0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
|
|
48343
48697
|
}
|
|
48344
48698
|
}
|
|
48345
48699
|
function bootstrapTrainDoctorDeps() {
|
|
@@ -48352,6 +48706,94 @@ function bootstrapTrainDoctorDeps() {
|
|
|
48352
48706
|
}
|
|
48353
48707
|
};
|
|
48354
48708
|
}
|
|
48709
|
+
async function propagationScope(classes, what) {
|
|
48710
|
+
const cfg = await loadConfig();
|
|
48711
|
+
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
48712
|
+
if (!projects || projects.length === 0) {
|
|
48713
|
+
return await failGraceful("bootstrap propagate: the registry roster is unreadable or empty \u2014 refusing to plan a tick from a scope this command could not establish (#4232 coverage invariant)");
|
|
48714
|
+
}
|
|
48715
|
+
const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
|
|
48716
|
+
let independentCount = rosterRepos.length;
|
|
48717
|
+
if ((0, import_node_fs38.existsSync)("projects.json")) {
|
|
48718
|
+
try {
|
|
48719
|
+
const local = JSON.parse((0, import_node_fs38.readFileSync)("projects.json", "utf8"));
|
|
48720
|
+
const localRepos = /* @__PURE__ */ new Set();
|
|
48721
|
+
for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
|
|
48722
|
+
const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
48723
|
+
if (full !== "mutmutco/mmi-hub") localRepos.add(full);
|
|
48724
|
+
}
|
|
48725
|
+
if (localRepos.size > 0) independentCount = localRepos.size;
|
|
48726
|
+
} catch {
|
|
48727
|
+
}
|
|
48728
|
+
}
|
|
48729
|
+
try {
|
|
48730
|
+
assertPropagationCoverage(rosterRepos.length, independentCount);
|
|
48731
|
+
} catch (e) {
|
|
48732
|
+
return fail(e.message);
|
|
48733
|
+
}
|
|
48734
|
+
const bySlugMeta = /* @__PURE__ */ new Map();
|
|
48735
|
+
for (const p of projects) for (const r of p.repos ?? []) {
|
|
48736
|
+
const key = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
48737
|
+
bySlugMeta.set(key, preferPrimaryRepoProject(bySlugMeta.get(key), p, key));
|
|
48738
|
+
}
|
|
48739
|
+
const classOf = (repo) => bySlugMeta.get(repo.toLowerCase())?.class ?? "deployable";
|
|
48740
|
+
const seedClasses = classes ?? [...new Set(rosterRepos.map(classOf))];
|
|
48741
|
+
const inClassRepos = rosterRepos.filter((repo) => seedClasses.includes(classOf(repo)));
|
|
48742
|
+
const classPriority = [.../* @__PURE__ */ new Set(["deployable", ...seedClasses])].filter((c) => seedClasses.includes(c));
|
|
48743
|
+
let canaryProject = null;
|
|
48744
|
+
for (const cls of classPriority) {
|
|
48745
|
+
const inClass = projects.filter((p) => p.seedCanary === true && (p.class ?? "deployable") === cls);
|
|
48746
|
+
if (inClass.length === 0) continue;
|
|
48747
|
+
if (inClass.length > 1) {
|
|
48748
|
+
const slugs = inClass.map((p) => (p.repos ?? [])[0]?.split("/").pop() ?? "?").join(", ");
|
|
48749
|
+
return fail(
|
|
48750
|
+
`bootstrap propagate: ${what} (classes [${seedClasses.join(", ")}]) matches ${inClass.length} seedCanary:true repos in class '${cls}' (${slugs}) \u2014 exactly one seedCanary:true repo per class is required`
|
|
48751
|
+
);
|
|
48752
|
+
}
|
|
48753
|
+
canaryProject = inClass[0];
|
|
48754
|
+
break;
|
|
48755
|
+
}
|
|
48756
|
+
if (!canaryProject) {
|
|
48757
|
+
return fail(
|
|
48758
|
+
`bootstrap propagate: ${what} needs classes [${seedClasses.join(", ")}] but no registry repo with seedCanary:true carries a matching class \u2014 set seedCanary:true on an in-class repo (mmi-cli oracle org project set <slug> --var seedCanary=true)`
|
|
48759
|
+
);
|
|
48760
|
+
}
|
|
48761
|
+
const canarySlug = (canaryProject.repos ?? [])[0]?.split("/").pop()?.toLowerCase() ?? null;
|
|
48762
|
+
const canaryFullRepo = (canaryProject.repos ?? [])[0];
|
|
48763
|
+
const sourcePilot = !!canaryFullRepo && (canaryFullRepo.includes("/") ? canaryFullRepo : `mutmutco/${canaryFullRepo}`).toLowerCase() === "mutmutco/mmi-hub";
|
|
48764
|
+
return { projects, classOf, inClassRepos, canarySlug, sourcePilot };
|
|
48765
|
+
}
|
|
48766
|
+
async function propagatePins(apply, json) {
|
|
48767
|
+
const source = await resolveHubSeedSource(execGitForSeedSource);
|
|
48768
|
+
if (apply && !source.ok) return fail(`bootstrap propagate --pins --apply: ${source.reason}`);
|
|
48769
|
+
const { classOf, inClassRepos, canarySlug, sourcePilot } = await propagationScope(null, "hub action pins");
|
|
48770
|
+
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
48771
|
+
const repos = [];
|
|
48772
|
+
for (const repo of inClassRepos) {
|
|
48773
|
+
let waiver;
|
|
48774
|
+
try {
|
|
48775
|
+
waiver = archivedWaiver(JSON.parse((await gh2(["api", `repos/${repo}`])).stdout));
|
|
48776
|
+
} catch {
|
|
48777
|
+
}
|
|
48778
|
+
repos.push({ repo, slug: repo.split("/").pop().toLowerCase(), waiver });
|
|
48779
|
+
}
|
|
48780
|
+
const tick = await runPinTick({
|
|
48781
|
+
repos,
|
|
48782
|
+
canarySlug,
|
|
48783
|
+
sourcePilot,
|
|
48784
|
+
gh: gh2,
|
|
48785
|
+
apply,
|
|
48786
|
+
baseBranchOf: (repo) => classOf(repo) === "content" ? "main" : "development",
|
|
48787
|
+
createPr: async (args) => requireGhCreateOk(await ghCreate(args), "bootstrap propagate --pins pr create").url,
|
|
48788
|
+
mayAutoMerge: (repo) => propagateMayAutoMerge(repo, classOf(repo), async (path2) => JSON.parse((await gh2(["api", path2])).stdout || "[]"))
|
|
48789
|
+
});
|
|
48790
|
+
if (json) console.log(JSON.stringify({ ...tick.plan, digest: tick.digest, diffs: tick.diffs, summary: summarizePropagationTick(tick.plan) }, null, 2));
|
|
48791
|
+
else console.log(`${renderPinDiffs(tick)}
|
|
48792
|
+
|
|
48793
|
+
${renderPropagationReport(tick.plan)}
|
|
48794
|
+
pins: ${tick.digest}${source.ok ? ` from MMI-Hub@${source.sha}` : ""}`);
|
|
48795
|
+
if (tick.plan.halted) process.exitCode = 1;
|
|
48796
|
+
}
|
|
48355
48797
|
function registerBootstrapCommands(program3) {
|
|
48356
48798
|
const bootstrap = program3.command("bootstrap").description("plan repo bootstrap operations; mutations require master-admin approval").option("--repo <owner/repo>", "target repo").addOption(new Option("--class <class>", "deployable | content").default("deployable").choices(["deployable", "content"])).option("--json", "machine-readable output").action((o) => {
|
|
48357
48799
|
if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
|
|
@@ -48399,13 +48841,13 @@ function registerBootstrapCommands(program3) {
|
|
|
48399
48841
|
})),
|
|
48400
48842
|
pushAllowlistOwners: pushAllowlistAuthority(verifyOwners, declaredProjectAdmins(meta)),
|
|
48401
48843
|
pushAllowlistOwnersRead: verifyOwnersRead,
|
|
48402
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
48844
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null,
|
|
48403
48845
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
48404
48846
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
48405
48847
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
48406
48848
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
48407
48849
|
// sanction, which is the pre-#3664 behaviour.
|
|
48408
|
-
sanctionedAdmins: (0,
|
|
48850
|
+
sanctionedAdmins: (0, import_node_fs38.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs38.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
48409
48851
|
requiredGcpApis: (() => {
|
|
48410
48852
|
const v = meta?.requiredGcpApis;
|
|
48411
48853
|
if (Array.isArray(v)) return v;
|
|
@@ -48483,14 +48925,14 @@ function registerBootstrapCommands(program3) {
|
|
|
48483
48925
|
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 () => {
|
|
48484
48926
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
48485
48927
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
48486
|
-
if (!(0,
|
|
48928
|
+
if (!(0, import_node_fs38.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`);
|
|
48487
48929
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
48488
48930
|
if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
|
|
48489
|
-
const manifest = loadBootstrapSeeds((0,
|
|
48931
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
48490
48932
|
const hubContents = /* @__PURE__ */ new Map();
|
|
48491
48933
|
for (const s of manifest.seeds) {
|
|
48492
48934
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
48493
|
-
hubContents.set(s.target, (0,
|
|
48935
|
+
hubContents.set(s.target, (0, import_node_fs38.existsSync)(s.target) ? (0, import_node_fs38.readFileSync)(s.target, "utf8") : null);
|
|
48494
48936
|
}
|
|
48495
48937
|
let targets;
|
|
48496
48938
|
let classOf = (_repo) => "deployable";
|
|
@@ -48506,13 +48948,12 @@ function registerBootstrapCommands(program3) {
|
|
|
48506
48948
|
return failGraceful("bootstrap drift: the registry roster is unreadable or empty \u2014 refusing to report a fleet verdict from a scope this command could not establish (#3808)");
|
|
48507
48949
|
}
|
|
48508
48950
|
targets = collectRegistryRepos(projects);
|
|
48509
|
-
const byRepo = /* @__PURE__ */ new Map();
|
|
48510
48951
|
const metaByRepo = /* @__PURE__ */ new Map();
|
|
48511
48952
|
for (const p of projects) for (const r of p.repos ?? []) {
|
|
48512
48953
|
const key = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
48513
|
-
|
|
48514
|
-
metaByRepo.set(key, p);
|
|
48954
|
+
metaByRepo.set(key, preferPrimaryRepoProject(metaByRepo.get(key), p, key));
|
|
48515
48955
|
}
|
|
48956
|
+
const byRepo = new Map([...metaByRepo].map(([key, p]) => [key, p.class ?? "deployable"]));
|
|
48516
48957
|
classOf = (repo) => byRepo.get(repo.toLowerCase()) ?? "deployable";
|
|
48517
48958
|
metaOf = (repo) => metaByRepo.get(repo.toLowerCase()) ?? null;
|
|
48518
48959
|
}
|
|
@@ -48747,24 +49188,24 @@ function registerBootstrapCommands(program3) {
|
|
|
48747
49188
|
return;
|
|
48748
49189
|
}
|
|
48749
49190
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
48750
|
-
if (!(0,
|
|
49191
|
+
if (!(0, import_node_fs38.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`);
|
|
48751
49192
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
48752
49193
|
if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
|
|
48753
|
-
const manifest = loadBootstrapSeeds((0,
|
|
49194
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
48754
49195
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
48755
49196
|
const slug = parsedRepo.slug;
|
|
48756
49197
|
const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
|
|
48757
49198
|
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
48758
|
-
const readFile10 = (p) => (0,
|
|
49199
|
+
const readFile10 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
|
|
48759
49200
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
48760
49201
|
const putSeed = async (target, content, ref, sha) => {
|
|
48761
|
-
const tmp = (0,
|
|
48762
|
-
(0,
|
|
49202
|
+
const tmp = (0, import_node_path35.join)((0, import_node_os21.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
49203
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
48763
49204
|
try {
|
|
48764
49205
|
await gh2(contentPutInputArgs(repo, target, tmp));
|
|
48765
49206
|
} finally {
|
|
48766
49207
|
try {
|
|
48767
|
-
(0,
|
|
49208
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
48768
49209
|
} catch {
|
|
48769
49210
|
}
|
|
48770
49211
|
}
|
|
@@ -49398,13 +49839,14 @@ LIVE apply to ${repo}:
|
|
|
49398
49839
|
if (deferredFailures.length) return failGraceful(`bootstrap apply: incomplete \u2014 every other step ran:
|
|
49399
49840
|
${deferredFailures.join("\n ")}`);
|
|
49400
49841
|
});
|
|
49401
|
-
bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").action(async () => {
|
|
49842
|
+
bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").option("--pins", "H4: move every registered repo's mutmutco/MMI-Hub action pins to the blessed SHAs (cli/src/gate-budget.ts) instead of a seed target").option("--plan", "with --pins: print the per-repo pin diff only (the default)").option("--apply", "with --pins: LIVE \u2014 open/refresh one pin PR per repo in canary\u2192wave order").action(async () => {
|
|
49843
|
+
if (rawFlag("--pins")) return propagatePins(rawFlag("--apply"), rawFlag("--json"));
|
|
49402
49844
|
const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
|
|
49403
49845
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
49404
|
-
if (!(0,
|
|
49846
|
+
if (!(0, import_node_fs38.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`);
|
|
49405
49847
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
49406
49848
|
if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
49407
|
-
const manifest = loadBootstrapSeeds((0,
|
|
49849
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
49408
49850
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
49409
49851
|
if (!o.target) {
|
|
49410
49852
|
return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
|
|
@@ -49413,59 +49855,11 @@ LIVE apply to ${repo}:
|
|
|
49413
49855
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
49414
49856
|
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
|
|
49415
49857
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
49416
|
-
if (!seed.managedBlock && !(0,
|
|
49417
|
-
const hubContent = seed.managedBlock ? null : (0,
|
|
49418
|
-
const readSeedFile2 = (path2) => (0,
|
|
49858
|
+
if (!seed.managedBlock && !(0, import_node_fs38.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
|
|
49859
|
+
const hubContent = seed.managedBlock ? null : (0, import_node_fs38.readFileSync)(seed.target, "utf8");
|
|
49860
|
+
const readSeedFile2 = (path2) => (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null;
|
|
49419
49861
|
const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
|
|
49420
|
-
const
|
|
49421
|
-
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
49422
|
-
if (!projects || projects.length === 0) {
|
|
49423
|
-
return failGraceful("bootstrap propagate: the registry roster is unreadable or empty \u2014 refusing to plan a tick from a scope this command could not establish (#4232 coverage invariant)");
|
|
49424
|
-
}
|
|
49425
|
-
const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
|
|
49426
|
-
let independentCount = rosterRepos.length;
|
|
49427
|
-
if ((0, import_node_fs37.existsSync)("projects.json")) {
|
|
49428
|
-
try {
|
|
49429
|
-
const local = JSON.parse((0, import_node_fs37.readFileSync)("projects.json", "utf8"));
|
|
49430
|
-
const localRepos = /* @__PURE__ */ new Set();
|
|
49431
|
-
for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
|
|
49432
|
-
const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
49433
|
-
if (full !== "mutmutco/mmi-hub") localRepos.add(full);
|
|
49434
|
-
}
|
|
49435
|
-
if (localRepos.size > 0) independentCount = localRepos.size;
|
|
49436
|
-
} catch {
|
|
49437
|
-
}
|
|
49438
|
-
}
|
|
49439
|
-
try {
|
|
49440
|
-
assertPropagationCoverage(rosterRepos.length, independentCount);
|
|
49441
|
-
} catch (e) {
|
|
49442
|
-
return fail(e.message);
|
|
49443
|
-
}
|
|
49444
|
-
const bySlugMeta = new Map(projects.flatMap((p) => (p.repos ?? []).map((r) => [(r.includes("/") ? r : `mutmutco/${r}`).toLowerCase(), p])));
|
|
49445
|
-
const classOf = (repo) => bySlugMeta.get(repo.toLowerCase())?.class ?? "deployable";
|
|
49446
|
-
const inClassRepos = rosterRepos.filter((repo) => seed.classes.includes(classOf(repo)));
|
|
49447
|
-
const classPriority = [.../* @__PURE__ */ new Set(["deployable", ...seed.classes])].filter((c) => seed.classes.includes(c));
|
|
49448
|
-
let canaryProject = null;
|
|
49449
|
-
for (const cls of classPriority) {
|
|
49450
|
-
const inClass = projects.filter((p) => p.seedCanary === true && (p.class ?? "deployable") === cls);
|
|
49451
|
-
if (inClass.length === 0) continue;
|
|
49452
|
-
if (inClass.length > 1) {
|
|
49453
|
-
const slugs = inClass.map((p) => (p.repos ?? [])[0]?.split("/").pop() ?? "?").join(", ");
|
|
49454
|
-
return fail(
|
|
49455
|
-
`bootstrap propagate: target '${seed.target}' (classes [${seed.classes.join(", ")}]) matches ${inClass.length} seedCanary:true repos in class '${cls}' (${slugs}) \u2014 exactly one seedCanary:true repo per class is required`
|
|
49456
|
-
);
|
|
49457
|
-
}
|
|
49458
|
-
canaryProject = inClass[0];
|
|
49459
|
-
break;
|
|
49460
|
-
}
|
|
49461
|
-
if (!canaryProject) {
|
|
49462
|
-
return fail(
|
|
49463
|
-
`bootstrap propagate: target '${seed.target}' needs classes [${seed.classes.join(", ")}] but no registry repo with seedCanary:true carries a matching class \u2014 set seedCanary:true on an in-class repo (mmi-cli oracle org project set <slug> --var seedCanary=true)`
|
|
49464
|
-
);
|
|
49465
|
-
}
|
|
49466
|
-
const canarySlug = (canaryProject.repos ?? [])[0]?.split("/").pop()?.toLowerCase() ?? null;
|
|
49467
|
-
const canaryFullRepo = (canaryProject.repos ?? [])[0];
|
|
49468
|
-
const sourcePilot = !!canaryFullRepo && (canaryFullRepo.includes("/") ? canaryFullRepo : `mutmutco/${canaryFullRepo}`).toLowerCase() === "mutmutco/mmi-hub";
|
|
49862
|
+
const { projects, classOf, inClassRepos, canarySlug, sourcePilot } = await propagationScope(seed.classes, `target '${seed.target}'`);
|
|
49469
49863
|
const repos = inClassRepos.map((repo) => {
|
|
49470
49864
|
const slug = repo.split("/").pop().toLowerCase();
|
|
49471
49865
|
const waiver = seed.waivers?.[slug];
|
|
@@ -49582,7 +49976,7 @@ LIVE apply to ${repo}:
|
|
|
49582
49976
|
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)");
|
|
49583
49977
|
const headSha = seedSource.sha;
|
|
49584
49978
|
const sourceBlob = (await execGitForSeedSource(["rev-parse", `${headSha}:${seedSourcePath(seed)}`])).trim();
|
|
49585
|
-
const runbookUnaffected = (0,
|
|
49979
|
+
const runbookUnaffected = (0, import_node_fs38.existsSync)(RUNBOOK_UNAFFECTED_PATH) ? runbookUnaffectedReason(JSON.parse((0, import_node_fs38.readFileSync)(RUNBOOK_UNAFFECTED_PATH, "utf8")), seed.target, sourceBlob) : void 0;
|
|
49586
49980
|
for (const rec of plan.records) {
|
|
49587
49981
|
if (rec.action !== "open-pr") continue;
|
|
49588
49982
|
const baseBranch = classOf(rec.repo) === "content" ? "main" : "development";
|
|
@@ -49604,15 +49998,15 @@ LIVE apply to ${repo}:
|
|
|
49604
49998
|
} catch {
|
|
49605
49999
|
existingSha = void 0;
|
|
49606
50000
|
}
|
|
49607
|
-
const tmp = (0,
|
|
50001
|
+
const tmp = (0, import_node_path35.join)((0, import_node_os21.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
49608
50002
|
const desiredContent = desiredByRepo.get(rec.repo);
|
|
49609
50003
|
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
49610
|
-
(0,
|
|
50004
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha, runbookUnaffected)), "utf8");
|
|
49611
50005
|
try {
|
|
49612
50006
|
await gh2(contentPutInputArgs(rec.repo, seed.target, tmp));
|
|
49613
50007
|
} finally {
|
|
49614
50008
|
try {
|
|
49615
|
-
(0,
|
|
50009
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
49616
50010
|
} catch {
|
|
49617
50011
|
}
|
|
49618
50012
|
}
|
|
@@ -49681,10 +50075,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
49681
50075
|
return fail(`bootstrap rollback: ${e.message}`);
|
|
49682
50076
|
}
|
|
49683
50077
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
49684
|
-
if (!(0,
|
|
50078
|
+
if (!(0, import_node_fs38.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)`);
|
|
49685
50079
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
49686
50080
|
if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
|
|
49687
|
-
const manifest = loadBootstrapSeeds((0,
|
|
50081
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
49688
50082
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
49689
50083
|
if (!o.target) {
|
|
49690
50084
|
return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
|
|
@@ -49701,10 +50095,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
49701
50095
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
49702
50096
|
let candidates;
|
|
49703
50097
|
if (o.record) {
|
|
49704
|
-
if (!(0,
|
|
50098
|
+
if (!(0, import_node_fs38.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
|
|
49705
50099
|
let parsed;
|
|
49706
50100
|
try {
|
|
49707
|
-
parsed = JSON.parse((0,
|
|
50101
|
+
parsed = JSON.parse((0, import_node_fs38.readFileSync)(o.record, "utf8"));
|
|
49708
50102
|
} catch (e) {
|
|
49709
50103
|
return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
|
|
49710
50104
|
}
|
|
@@ -49781,13 +50175,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
49781
50175
|
} catch {
|
|
49782
50176
|
existingSha = void 0;
|
|
49783
50177
|
}
|
|
49784
|
-
const tmp = (0,
|
|
49785
|
-
(0,
|
|
50178
|
+
const tmp = (0, import_node_path35.join)((0, import_node_os21.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
50179
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
|
|
49786
50180
|
try {
|
|
49787
50181
|
await gh2(contentPutInputArgs(repo, seed.target, tmp));
|
|
49788
50182
|
} finally {
|
|
49789
50183
|
try {
|
|
49790
|
-
(0,
|
|
50184
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
49791
50185
|
} catch {
|
|
49792
50186
|
}
|
|
49793
50187
|
}
|
|
@@ -50100,7 +50494,7 @@ init_github_client();
|
|
|
50100
50494
|
init_cli_shared();
|
|
50101
50495
|
|
|
50102
50496
|
// src/suite-status.ts
|
|
50103
|
-
var
|
|
50497
|
+
var import_node_crypto16 = require("node:crypto");
|
|
50104
50498
|
var SUITE_REPO = "mutmutco/MM-Work";
|
|
50105
50499
|
var REPOS = {
|
|
50106
50500
|
"mmi-scout": "mutmutco/MMI-Scout",
|
|
@@ -50128,7 +50522,7 @@ function parseSuiteMapping(body) {
|
|
|
50128
50522
|
return void 0;
|
|
50129
50523
|
}
|
|
50130
50524
|
}
|
|
50131
|
-
var sha256 = (b) => (0,
|
|
50525
|
+
var sha256 = (b) => (0, import_node_crypto16.createHash)("sha256").update(b).digest("hex");
|
|
50132
50526
|
async function proveBundle(url, tag, fetchUrl) {
|
|
50133
50527
|
const get = async (u) => {
|
|
50134
50528
|
try {
|
|
@@ -50383,9 +50777,9 @@ async function waitSuiteDev(deps, opts) {
|
|
|
50383
50777
|
}
|
|
50384
50778
|
|
|
50385
50779
|
// src/suite-train.ts
|
|
50386
|
-
var
|
|
50387
|
-
var
|
|
50388
|
-
var
|
|
50780
|
+
var import_node_fs39 = require("node:fs");
|
|
50781
|
+
var import_node_path36 = require("node:path");
|
|
50782
|
+
var import_node_crypto17 = require("node:crypto");
|
|
50389
50783
|
|
|
50390
50784
|
// src/kit-version.ts
|
|
50391
50785
|
var parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(typeof v === "string" ? v.trim() : "");
|
|
@@ -50470,7 +50864,7 @@ function suiteReleaseTag(rc) {
|
|
|
50470
50864
|
function readSuiteCut(path2) {
|
|
50471
50865
|
let raw;
|
|
50472
50866
|
try {
|
|
50473
|
-
raw = (0,
|
|
50867
|
+
raw = (0, import_node_fs39.readFileSync)(path2, "utf8");
|
|
50474
50868
|
} catch (e) {
|
|
50475
50869
|
if (e.code === "ENOENT") return void 0;
|
|
50476
50870
|
throw e;
|
|
@@ -50486,11 +50880,11 @@ function readSuiteCut(path2) {
|
|
|
50486
50880
|
return record2;
|
|
50487
50881
|
}
|
|
50488
50882
|
function writeSuiteCut(path2, record2) {
|
|
50489
|
-
(0,
|
|
50490
|
-
const temp = `${path2}.${(0,
|
|
50883
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path36.dirname)(path2), { recursive: true });
|
|
50884
|
+
const temp = `${path2}.${(0, import_node_crypto17.randomBytes)(8).toString("hex")}.tmp`;
|
|
50491
50885
|
try {
|
|
50492
|
-
(0,
|
|
50493
|
-
(0,
|
|
50886
|
+
(0, import_node_fs39.writeFileSync)(temp, JSON.stringify(record2, null, 2), { flag: "wx" });
|
|
50887
|
+
(0, import_node_fs39.renameSync)(temp, path2);
|
|
50494
50888
|
} catch (e) {
|
|
50495
50889
|
throw new Error(`suite cut record write failed at ${path2}: ${e.message}; stop, verify the file before retrying`);
|
|
50496
50890
|
}
|
|
@@ -50539,25 +50933,25 @@ async function suiteMarkAbandoned(deps, ownerGo = false) {
|
|
|
50539
50933
|
return tags.map((t) => `${t.repo}@${t.tag}`);
|
|
50540
50934
|
}
|
|
50541
50935
|
async function retireFailedLeadRcLedger(deps, ownerGo) {
|
|
50542
|
-
if (!(0,
|
|
50936
|
+
if (!(0, import_node_fs39.existsSync)(deps.ledgerPath)) return void 0;
|
|
50543
50937
|
return withLedgerLock(deps.ledgerPath, async (fence) => {
|
|
50544
|
-
const ledger = parseReleaseLedger((0,
|
|
50938
|
+
const ledger = parseReleaseLedger((0, import_node_fs39.readFileSync)(deps.ledgerPath, "utf8"));
|
|
50545
50939
|
const states = Object.values(ledger.phases).map((p) => p.state);
|
|
50546
50940
|
if (ledger.repo !== SUITE_REPO || ledger.lane !== "rcand" || ledger.phases.promotion.state === "complete" || !states.includes("failed")) return void 0;
|
|
50547
50941
|
if (!ownerGo) throw new Error(`the lead's failed rcand ledger for ${ledger.tag} blocks every train; retire it with suite abandon --owner-go`);
|
|
50548
50942
|
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`);
|
|
50549
|
-
const archive = (0,
|
|
50550
|
-
if ((0,
|
|
50943
|
+
const archive = (0, import_node_path36.join)((0, import_node_path36.dirname)(deps.ledgerPath), `phases.archive-${ledger.tag}.json`);
|
|
50944
|
+
if ((0, import_node_fs39.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
|
|
50551
50945
|
await deps.markAbandoned(SUITE_REPO, ledger.tag, ledger.tagSha);
|
|
50552
|
-
await fence(() => (0,
|
|
50946
|
+
await fence(() => (0, import_node_fs39.renameSync)(deps.ledgerPath, archive));
|
|
50553
50947
|
return `${ledger.tag} (ledger archived to ${archive})`;
|
|
50554
50948
|
});
|
|
50555
50949
|
}
|
|
50556
50950
|
function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Date()) {
|
|
50557
50951
|
const record2 = abandonableCut(recordPath, ownerGo);
|
|
50558
50952
|
const archive = `${recordPath}.abandoned-${record2.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
|
|
50559
|
-
if ((0,
|
|
50560
|
-
(0,
|
|
50953
|
+
if ((0, import_node_fs39.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
|
|
50954
|
+
(0, import_node_fs39.renameSync)(recordPath, archive);
|
|
50561
50955
|
return { archive, orphanTags: record2.members.filter((m) => m.tag).map((m) => `${m.repo}@${m.tag}`) };
|
|
50562
50956
|
}
|
|
50563
50957
|
async function suiteRestoreMainPins(deps) {
|
|
@@ -50644,12 +51038,12 @@ async function suiteRcandRun(deps, opts) {
|
|
|
50644
51038
|
}
|
|
50645
51039
|
if ((record2?.announcement === "sent" || record2?.announcement === "skipped") && record2.leadReleased) {
|
|
50646
51040
|
const archive = `${deps.recordPath}.${record2.leadRcTag}.json`;
|
|
50647
|
-
if ((0,
|
|
50648
|
-
(0,
|
|
51041
|
+
if ((0, import_node_fs39.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
|
|
51042
|
+
(0, import_node_fs39.renameSync)(deps.recordPath, archive);
|
|
50649
51043
|
record2 = void 0;
|
|
50650
51044
|
}
|
|
50651
51045
|
if (record2 && !record2.pinPr && !record2.leadRcTag && record2.members.every((m) => !m.tag)) {
|
|
50652
|
-
(0,
|
|
51046
|
+
(0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
|
|
50653
51047
|
record2 = void 0;
|
|
50654
51048
|
}
|
|
50655
51049
|
if (!record2) {
|
|
@@ -50703,7 +51097,7 @@ async function suiteRcandRun(deps, opts) {
|
|
|
50703
51097
|
quiet.push(member);
|
|
50704
51098
|
}
|
|
50705
51099
|
if (record2.members.every((m) => m.carried || quiet.includes(m)) && await deps.contains(SUITE_REPO, record2.leadDevelopment, record2.leadPreviousRc)) {
|
|
50706
|
-
(0,
|
|
51100
|
+
(0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
|
|
50707
51101
|
throw new Error("nothing to promote: neither MM-Work nor any module has new development commits since its rc; no suite cut started");
|
|
50708
51102
|
}
|
|
50709
51103
|
for (const member of quiet) {
|
|
@@ -50721,7 +51115,7 @@ async function suiteRcandRun(deps, opts) {
|
|
|
50721
51115
|
}
|
|
50722
51116
|
if (refusals.length) {
|
|
50723
51117
|
const untouched = record2.members.every((m) => !m.tag) && !record2.pinPr;
|
|
50724
|
-
if (untouched) (0,
|
|
51118
|
+
if (untouched) (0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
|
|
50725
51119
|
throw new Error(`module rc serving is not ready; ${untouched ? "no suite cut started" : "no new tag was cut"}:
|
|
50726
51120
|
${refusals.join("\n")}
|
|
50727
51121
|
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).`);
|
|
@@ -50913,11 +51307,11 @@ Each member's live release must serve <data>/main/module.js at its main pin path
|
|
|
50913
51307
|
}
|
|
50914
51308
|
return record2;
|
|
50915
51309
|
}
|
|
50916
|
-
var hotfixRecordPath = (deps) => (0,
|
|
51310
|
+
var hotfixRecordPath = (deps) => (0, import_node_path36.join)((0, import_node_path36.dirname)(deps.recordPath), "hotfix.json");
|
|
50917
51311
|
function readSuiteHotfix(path2) {
|
|
50918
51312
|
let raw;
|
|
50919
51313
|
try {
|
|
50920
|
-
raw = (0,
|
|
51314
|
+
raw = (0, import_node_fs39.readFileSync)(path2, "utf8");
|
|
50921
51315
|
} catch (e) {
|
|
50922
51316
|
if (e.code === "ENOENT") return void 0;
|
|
50923
51317
|
throw e;
|
|
@@ -50960,7 +51354,7 @@ async function suiteHotfix(deps, opts) {
|
|
|
50960
51354
|
if (completed) record2 = void 0;
|
|
50961
51355
|
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`);
|
|
50962
51356
|
if (!record2?.tag) await deps.validateHotfixSummary();
|
|
50963
|
-
if (completed) (0,
|
|
51357
|
+
if (completed) (0, import_node_fs39.renameSync)(path2, `${path2}.${completed.leadTag}.json`);
|
|
50964
51358
|
const rec = record2 ?? { schema: "mmi-suite-hotfix/v1", member: found.id, repo: found.repo, from: opts.from, ...opts.ack?.length ? { ack: opts.ack } : {} };
|
|
50965
51359
|
const save = () => writeSuiteHotfix(path2, rec);
|
|
50966
51360
|
save();
|
|
@@ -51228,9 +51622,9 @@ async function runRcDataSwitch(deps, opts) {
|
|
|
51228
51622
|
|
|
51229
51623
|
// src/suite-train-io.ts
|
|
51230
51624
|
var import_node_child_process18 = require("node:child_process");
|
|
51231
|
-
var import_node_path48 = require("node:path");
|
|
51232
|
-
var import_node_fs51 = require("node:fs");
|
|
51233
51625
|
var import_node_path49 = require("node:path");
|
|
51626
|
+
var import_node_fs52 = require("node:fs");
|
|
51627
|
+
var import_node_path50 = require("node:path");
|
|
51234
51628
|
init_cli_shared();
|
|
51235
51629
|
|
|
51236
51630
|
// src/command-register-train-operations.ts
|
|
@@ -51583,9 +51977,9 @@ A hotfix branch is cut from origin/main and carries ONLY the fixes --from names,
|
|
|
51583
51977
|
async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
|
|
51584
51978
|
if (sources.length === 0) return { failures: [], skipped: [] };
|
|
51585
51979
|
const { mkdtemp: mkdtemp2, rm: rm2 } = await import("node:fs/promises");
|
|
51586
|
-
const { tmpdir:
|
|
51980
|
+
const { tmpdir: tmpdir13 } = await import("node:os");
|
|
51587
51981
|
const { join: join54 } = await import("node:path");
|
|
51588
|
-
const dir = await mkdtemp2(join54(
|
|
51982
|
+
const dir = await mkdtemp2(join54(tmpdir13(), "mmi-hotfix-preflight-"));
|
|
51589
51983
|
const wt = { run: (file, args) => deps.run(file, file === "git" ? ["-C", dir, ...args] : args) };
|
|
51590
51984
|
const failures = [];
|
|
51591
51985
|
const skipped = [];
|
|
@@ -52922,12 +53316,75 @@ function checkHotfixCarries(options) {
|
|
|
52922
53316
|
}
|
|
52923
53317
|
|
|
52924
53318
|
// src/merge-cleanup.ts
|
|
52925
|
-
var
|
|
52926
|
-
var
|
|
52927
|
-
var
|
|
53319
|
+
var import_node_fs40 = require("node:fs");
|
|
53320
|
+
var import_node_path37 = require("node:path");
|
|
53321
|
+
var import_node_os22 = require("node:os");
|
|
52928
53322
|
init_cli_shared();
|
|
52929
53323
|
init_github_client();
|
|
52930
53324
|
|
|
53325
|
+
// src/post-merge-app-route.ts
|
|
53326
|
+
init_cli_shared();
|
|
53327
|
+
init_github_client();
|
|
53328
|
+
var PostMergeCredentialError = class extends Error {
|
|
53329
|
+
constructor(label, why) {
|
|
53330
|
+
super(`post-merge credential refused (${label}): ${why}`);
|
|
53331
|
+
this.name = "PostMergeCredentialError";
|
|
53332
|
+
}
|
|
53333
|
+
};
|
|
53334
|
+
function isUnauthorized(e) {
|
|
53335
|
+
if (e instanceof GitHubApiError) return e.status === 401;
|
|
53336
|
+
const text = `${e?.stderr ?? ""} ${e?.message ?? ""}`;
|
|
53337
|
+
return /HTTP 401|Bad credentials|gh auth login|set the GH_TOKEN environment variable/i.test(text);
|
|
53338
|
+
}
|
|
53339
|
+
async function appTokenFor(label, first) {
|
|
53340
|
+
let token;
|
|
53341
|
+
try {
|
|
53342
|
+
token = await refreshPollToken();
|
|
53343
|
+
} catch (e) {
|
|
53344
|
+
throw new PostMergeCredentialError(label, `HTTP 401 on the gh login and the App installation token could not be minted (${e.message})`);
|
|
53345
|
+
}
|
|
53346
|
+
if (!token) throw new PostMergeCredentialError(label, `HTTP 401 on the gh login (${first.message.trim()}) and this host has no App installation token to retry on`);
|
|
53347
|
+
return token;
|
|
53348
|
+
}
|
|
53349
|
+
async function ghWithAppOn401(label, args, options) {
|
|
53350
|
+
try {
|
|
53351
|
+
return await execFileP("gh", args, options);
|
|
53352
|
+
} catch (e) {
|
|
53353
|
+
if (!isUnauthorized(e)) throw e;
|
|
53354
|
+
const token = await appTokenFor(label, e);
|
|
53355
|
+
try {
|
|
53356
|
+
return await execFileP("gh", args, { ...options, env: { ...process.env, GH_TOKEN: token, MMI_GH_POOL: "app" } });
|
|
53357
|
+
} catch (e2) {
|
|
53358
|
+
if (!isUnauthorized(e2)) throw e2;
|
|
53359
|
+
throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
|
|
53360
|
+
}
|
|
53361
|
+
}
|
|
53362
|
+
}
|
|
53363
|
+
function withAppOn401(client, label = "post-merge") {
|
|
53364
|
+
let app;
|
|
53365
|
+
const run = async (call) => {
|
|
53366
|
+
try {
|
|
53367
|
+
return await call(app ?? client);
|
|
53368
|
+
} catch (e) {
|
|
53369
|
+
if (!isUnauthorized(e)) throw e;
|
|
53370
|
+
if (app) throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
|
|
53371
|
+
const token = await appTokenFor(label, e);
|
|
53372
|
+
app = createGitHubClient({ token: async () => token });
|
|
53373
|
+
try {
|
|
53374
|
+
return await call(app);
|
|
53375
|
+
} catch (e2) {
|
|
53376
|
+
if (isUnauthorized(e2)) throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
|
|
53377
|
+
throw e2;
|
|
53378
|
+
}
|
|
53379
|
+
}
|
|
53380
|
+
};
|
|
53381
|
+
return {
|
|
53382
|
+
rest: (method, path2, init) => run((c) => c.rest(method, path2, init)),
|
|
53383
|
+
restPaginate: (path2, init) => run((c) => c.restPaginate(path2, init)),
|
|
53384
|
+
graphql: (query, variables, init) => run((c) => c.graphql(query, variables, init))
|
|
53385
|
+
};
|
|
53386
|
+
}
|
|
53387
|
+
|
|
52931
53388
|
// src/board-advance.ts
|
|
52932
53389
|
function repoOf2(ref) {
|
|
52933
53390
|
if (!ref || typeof ref !== "object") return void 0;
|
|
@@ -53148,10 +53605,10 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
53148
53605
|
// only for PRs into the default branch, so a PR into rc/main (a train or hotfix PR) never falls back.
|
|
53149
53606
|
fallback: {
|
|
53150
53607
|
fetchClaimed: async () => {
|
|
53151
|
-
const { stdout } = await
|
|
53608
|
+
const { stdout } = await ghWithAppOn401("PR body read", ["pr", "view", prNumber, ...repoArgs, "--json", "body,baseRefName"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53152
53609
|
const { body, baseRefName } = JSON.parse(stdout || "null") ?? {};
|
|
53153
53610
|
if (typeof body !== "string" || typeof baseRefName !== "string") throw new Error("gh returned no PR body or base branch");
|
|
53154
|
-
const repoView = await
|
|
53611
|
+
const repoView = await ghWithAppOn401("default branch read", ["repo", "view", repo, "--json", "defaultBranchRef"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53155
53612
|
const defaultBranch = JSON.parse(repoView.stdout || "null")?.defaultBranchRef?.name;
|
|
53156
53613
|
if (typeof defaultBranch !== "string" || !defaultBranch) throw new Error("gh returned no default branch");
|
|
53157
53614
|
if (baseRefName !== defaultBranch) return [];
|
|
@@ -53159,7 +53616,7 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
53159
53616
|
},
|
|
53160
53617
|
closeIssue: async (n) => {
|
|
53161
53618
|
try {
|
|
53162
|
-
const view = await
|
|
53619
|
+
const view = await ghWithAppOn401("issue state read", ["issue", "view", String(n), "--repo", repo, "--json", "state"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53163
53620
|
if (JSON.parse(view.stdout || "null")?.state !== "OPEN") return { state: "already-closed" };
|
|
53164
53621
|
await execFileP("gh", [
|
|
53165
53622
|
"issue",
|
|
@@ -53179,7 +53636,7 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
53179
53636
|
}
|
|
53180
53637
|
},
|
|
53181
53638
|
fetchClosingIssues: async () => {
|
|
53182
|
-
const { stdout } = await
|
|
53639
|
+
const { stdout } = await ghWithAppOn401("closingIssuesReferences read", ["pr", "view", prNumber, ...repoArgs, "--json", "closingIssuesReferences"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53183
53640
|
const parsed = JSON.parse(stdout || "null");
|
|
53184
53641
|
if (!parsed || typeof parsed !== "object" || !("closingIssuesReferences" in parsed)) {
|
|
53185
53642
|
throw new Error("gh returned no closingIssuesReferences field for this PR");
|
|
@@ -53268,13 +53725,13 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
|
|
|
53268
53725
|
return rewritten === base ? null : rewritten;
|
|
53269
53726
|
}
|
|
53270
53727
|
function writeSquashBodyFile(body) {
|
|
53271
|
-
const dir = (0,
|
|
53272
|
-
const path2 = (0,
|
|
53273
|
-
(0,
|
|
53728
|
+
const dir = (0, import_node_fs40.mkdtempSync)((0, import_node_path37.join)((0, import_node_os22.tmpdir)(), "mmi-squash-body-"));
|
|
53729
|
+
const path2 = (0, import_node_path37.join)(dir, "body.txt");
|
|
53730
|
+
(0, import_node_fs40.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
|
|
53274
53731
|
`, "utf8");
|
|
53275
53732
|
return { path: path2, cleanup: () => {
|
|
53276
53733
|
try {
|
|
53277
|
-
(0,
|
|
53734
|
+
(0, import_node_fs40.rmSync)(dir, { recursive: true, force: true });
|
|
53278
53735
|
} catch {
|
|
53279
53736
|
}
|
|
53280
53737
|
} };
|
|
@@ -53541,9 +53998,9 @@ function hubReleaseCiRun(env) {
|
|
|
53541
53998
|
}
|
|
53542
53999
|
|
|
53543
54000
|
// src/release-custody.ts
|
|
53544
|
-
var
|
|
53545
|
-
var
|
|
53546
|
-
var
|
|
54001
|
+
var import_node_crypto18 = require("node:crypto");
|
|
54002
|
+
var import_node_fs41 = require("node:fs");
|
|
54003
|
+
var import_node_path38 = require("node:path");
|
|
53547
54004
|
init_github_client();
|
|
53548
54005
|
|
|
53549
54006
|
// src/release-custody-bootstrap.ts
|
|
@@ -53764,7 +54221,7 @@ var ReleaseCustodyError = class extends Error {
|
|
|
53764
54221
|
this.name = "ReleaseCustodyError";
|
|
53765
54222
|
}
|
|
53766
54223
|
};
|
|
53767
|
-
var custodyDigest = (value) => (0,
|
|
54224
|
+
var custodyDigest = (value) => (0, import_node_crypto18.createHash)("sha256").update(value).digest("hex");
|
|
53768
54225
|
function parseCustody(raw, repo) {
|
|
53769
54226
|
const s = JSON.parse(raw);
|
|
53770
54227
|
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) {
|
|
@@ -53852,7 +54309,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
|
|
|
53852
54309
|
throw new ReleaseCustodyError(`previous controller run ${found.state.controller.runId} attempt ${found.state.controller.runAttempt} is not proven terminal`);
|
|
53853
54310
|
}
|
|
53854
54311
|
let state = {
|
|
53855
|
-
...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0,
|
|
54312
|
+
...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0, import_node_crypto18.randomUUID)(), effects: {}, ...await initialize() },
|
|
53856
54313
|
revision: (found.state?.revision ?? 0) + 1,
|
|
53857
54314
|
controller
|
|
53858
54315
|
};
|
|
@@ -53913,7 +54370,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
|
|
|
53913
54370
|
proof: { kind: "ledger-lifecycle", controller: state.controller }
|
|
53914
54371
|
};
|
|
53915
54372
|
}
|
|
53916
|
-
return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0,
|
|
54373
|
+
return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0, import_node_crypto18.randomUUID)() : state.precutId };
|
|
53917
54374
|
});
|
|
53918
54375
|
}
|
|
53919
54376
|
async preserveLocal(raw) {
|
|
@@ -54013,7 +54470,7 @@ function reclassifySupersededArchives(state) {
|
|
|
54013
54470
|
}
|
|
54014
54471
|
function localRaw(path2) {
|
|
54015
54472
|
try {
|
|
54016
|
-
return (0,
|
|
54473
|
+
return (0, import_node_fs41.readFileSync)(path2, "utf8");
|
|
54017
54474
|
} catch (e) {
|
|
54018
54475
|
if (e.code === "ENOENT") return null;
|
|
54019
54476
|
throw e;
|
|
@@ -54022,14 +54479,14 @@ function localRaw(path2) {
|
|
|
54022
54479
|
function hydrateLocal(path2, raw) {
|
|
54023
54480
|
const previous = localRaw(path2);
|
|
54024
54481
|
if (previous === raw) return;
|
|
54025
|
-
(0,
|
|
54482
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path38.dirname)(path2), { recursive: true });
|
|
54026
54483
|
if (previous !== null) {
|
|
54027
54484
|
const ledger = parseReleaseLedger(previous);
|
|
54028
|
-
const archive = (0,
|
|
54029
|
-
if (!(0,
|
|
54030
|
-
if (raw === null) (0,
|
|
54485
|
+
const archive = (0, import_node_path38.join)((0, import_node_path38.dirname)(path2), `phases.local-${ledger.tagSha}-${custodyDigest(previous)}.json`);
|
|
54486
|
+
if (!(0, import_node_fs41.existsSync)(archive)) (0, import_node_fs41.writeFileSync)(archive, previous, { flag: "wx" });
|
|
54487
|
+
if (raw === null) (0, import_node_fs41.renameSync)(path2, `${path2}.retired-${custodyDigest(previous)}`);
|
|
54031
54488
|
}
|
|
54032
|
-
if (raw !== null) (0,
|
|
54489
|
+
if (raw !== null) (0, import_node_fs41.writeFileSync)(path2, raw, "utf8");
|
|
54033
54490
|
}
|
|
54034
54491
|
async function commandIdentity(file, args, run, repo) {
|
|
54035
54492
|
if (file === "git" && args[0] === "push") {
|
|
@@ -54139,7 +54596,7 @@ function withHubReleaseCustody(deps) {
|
|
|
54139
54596
|
if (!authority.ok || !authority.train || authority.authenticatedDelegation !== true) {
|
|
54140
54597
|
throw new ReleaseCustodyError("repository custody requires the server-verified owner-triggered train session");
|
|
54141
54598
|
}
|
|
54142
|
-
const controller = { ...runIdentity, token: (0,
|
|
54599
|
+
const controller = { ...runIdentity, token: (0, import_node_crypto18.randomUUID)() };
|
|
54143
54600
|
const raw = localRaw(path2);
|
|
54144
54601
|
const requiredContexts = () => discoverRequiredCheckContexts(
|
|
54145
54602
|
deps,
|
|
@@ -54219,8 +54676,8 @@ function withHubReleaseCustody(deps) {
|
|
|
54219
54676
|
}
|
|
54220
54677
|
|
|
54221
54678
|
// src/project-info-sync.ts
|
|
54222
|
-
var
|
|
54223
|
-
var
|
|
54679
|
+
var import_node_fs42 = require("node:fs");
|
|
54680
|
+
var import_node_path39 = require("node:path");
|
|
54224
54681
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
54225
54682
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
54226
54683
|
projectV2 { id }
|
|
@@ -54267,14 +54724,14 @@ function sharedName(entries, fallback) {
|
|
|
54267
54724
|
}
|
|
54268
54725
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
54269
54726
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
54270
|
-
const readmePath = (0,
|
|
54271
|
-
if (!(0,
|
|
54727
|
+
const readmePath = (0, import_node_path39.join)(repoRoot2, "README.md");
|
|
54728
|
+
if (!(0, import_node_fs42.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
54272
54729
|
const entries = entriesFor(project2, projects);
|
|
54273
54730
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
54274
54731
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
54275
54732
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
54276
54733
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
54277
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
54734
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs42.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
54278
54735
|
const lines2 = [
|
|
54279
54736
|
`# ${projectName}`,
|
|
54280
54737
|
"",
|
|
@@ -54293,8 +54750,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
54293
54750
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
54294
54751
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
54295
54752
|
const orgDocs = [
|
|
54296
|
-
(0,
|
|
54297
|
-
(0,
|
|
54753
|
+
(0, import_node_fs42.existsSync)((0, import_node_path39.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
|
|
54754
|
+
(0, import_node_fs42.existsSync)((0, import_node_path39.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
|
|
54298
54755
|
].filter(Boolean);
|
|
54299
54756
|
if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
|
|
54300
54757
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
|
|
@@ -54316,8 +54773,8 @@ async function syncProjectInfo(plan, client, apply) {
|
|
|
54316
54773
|
}
|
|
54317
54774
|
|
|
54318
54775
|
// src/train-commands.ts
|
|
54319
|
-
var
|
|
54320
|
-
var
|
|
54776
|
+
var import_node_fs43 = require("node:fs");
|
|
54777
|
+
var import_node_path40 = require("node:path");
|
|
54321
54778
|
init_cli_shared();
|
|
54322
54779
|
init_clean_exit();
|
|
54323
54780
|
init_client_version();
|
|
@@ -54332,7 +54789,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
54332
54789
|
}
|
|
54333
54790
|
function readRepoVersion() {
|
|
54334
54791
|
try {
|
|
54335
|
-
return JSON.parse((0,
|
|
54792
|
+
return JSON.parse((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
54336
54793
|
} catch {
|
|
54337
54794
|
return void 0;
|
|
54338
54795
|
}
|
|
@@ -54443,7 +54900,7 @@ function releaseProgressVerdict(status, subject = "Release") {
|
|
|
54443
54900
|
|
|
54444
54901
|
// src/suite-pin.ts
|
|
54445
54902
|
var import_promises6 = require("node:fs/promises");
|
|
54446
|
-
var
|
|
54903
|
+
var import_node_path41 = require("node:path");
|
|
54447
54904
|
var SuitePinError = class _SuitePinError extends Error {
|
|
54448
54905
|
diagnostic;
|
|
54449
54906
|
constructor(repo, tag, error) {
|
|
@@ -54511,8 +54968,8 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
54511
54968
|
if (input.carriedOnly) return { status: "unchanged" };
|
|
54512
54969
|
throw new Error("pins already match without a merged cut PR \u2014 provenance unverified");
|
|
54513
54970
|
}
|
|
54514
|
-
await (0, import_promises6.mkdir)((0,
|
|
54515
|
-
scratch = await (0, import_promises6.mkdtemp)((0,
|
|
54971
|
+
await (0, import_promises6.mkdir)((0, import_node_path41.join)(deps.root, "tmp"), { recursive: true });
|
|
54972
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path41.join)(deps.root, "tmp", "suite-pin-"));
|
|
54516
54973
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
54517
54974
|
await run("git", ["clone", "--quiet", "--branch", prs.length ? branch : "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
54518
54975
|
if (prs.length) {
|
|
@@ -54524,7 +54981,7 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
54524
54981
|
await run("git", ["switch", "-c", branch]);
|
|
54525
54982
|
}
|
|
54526
54983
|
for (const stage of ["rc", "main"]) {
|
|
54527
|
-
const path2 = (0,
|
|
54984
|
+
const path2 = (0, import_node_path41.join)(scratch, `${stage}-pins.json`);
|
|
54528
54985
|
await (0, import_promises6.writeFile)(path2, JSON.stringify(input.pins.map((p) => ({ module: p.module, url: p[stage] }))));
|
|
54529
54986
|
await run("node", ["scripts/pin-modules.mjs", "--stage", stage, "--pins", path2]);
|
|
54530
54987
|
}
|
|
@@ -54577,12 +55034,12 @@ async function openSuiteRestorePinPr(deps, input) {
|
|
|
54577
55034
|
}
|
|
54578
55035
|
});
|
|
54579
55036
|
if (!todo.length) return void 0;
|
|
54580
|
-
await (0, import_promises6.mkdir)((0,
|
|
54581
|
-
scratch = await (0, import_promises6.mkdtemp)((0,
|
|
55037
|
+
await (0, import_promises6.mkdir)((0, import_node_path41.join)(deps.root, "tmp"), { recursive: true });
|
|
55038
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path41.join)(deps.root, "tmp", "suite-pin-"));
|
|
54582
55039
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
54583
55040
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
54584
55041
|
await run("git", ["switch", "-c", branch]);
|
|
54585
|
-
const path2 = (0,
|
|
55042
|
+
const path2 = (0, import_node_path41.join)(scratch, "main-pins.json");
|
|
54586
55043
|
await (0, import_promises6.writeFile)(path2, JSON.stringify(todo.map((p) => ({ module: p.module, url: p.prior }))));
|
|
54587
55044
|
await run("node", ["scripts/pin-modules.mjs", "--stage", "main", "--pins", path2]);
|
|
54588
55045
|
await run("git", ["add", "seed/modules.json"]);
|
|
@@ -54656,8 +55113,8 @@ async function openSuitePinPr(deps, input) {
|
|
|
54656
55113
|
if (newer.length) return { status: "superseded", pr: newer[0].url, url: pin };
|
|
54657
55114
|
const open2 = onBranch.find((p) => p.state === "OPEN");
|
|
54658
55115
|
if (open2 && tagOf(open2.title ?? "") === tag) return { status: "pending", pr: open2.url, url: pin };
|
|
54659
|
-
await (0, import_promises6.mkdir)((0,
|
|
54660
|
-
scratch = await (0, import_promises6.mkdtemp)((0,
|
|
55116
|
+
await (0, import_promises6.mkdir)((0, import_node_path41.join)(deps.root, "tmp"), { recursive: true });
|
|
55117
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path41.join)(deps.root, "tmp", "suite-pin-"));
|
|
54661
55118
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
54662
55119
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
54663
55120
|
await run("git", ["switch", "-c", branch]);
|
|
@@ -54695,6 +55152,9 @@ async function openSuitePinPr(deps, input) {
|
|
|
54695
55152
|
}
|
|
54696
55153
|
}
|
|
54697
55154
|
|
|
55155
|
+
// src/command-register-collaboration.ts
|
|
55156
|
+
init_board_write();
|
|
55157
|
+
|
|
54698
55158
|
// src/pr-title-check.ts
|
|
54699
55159
|
var CONVENTIONAL_PR_TITLE = /^(feat|fix|docs|chore|refactor|perf|test|build|ci|revert|style)(\([^)]+\))?!?: .+/;
|
|
54700
55160
|
var EXEMPT_PR_TITLE = /^Merge pull request /;
|
|
@@ -54706,7 +55166,7 @@ function prTitleRefusal(title) {
|
|
|
54706
55166
|
|
|
54707
55167
|
// src/command-register-collaboration.ts
|
|
54708
55168
|
var import_node_child_process17 = require("node:child_process");
|
|
54709
|
-
var
|
|
55169
|
+
var import_node_fs51 = require("node:fs");
|
|
54710
55170
|
var import_promises8 = require("node:fs/promises");
|
|
54711
55171
|
init_clean_exit();
|
|
54712
55172
|
init_cli_shared();
|
|
@@ -54730,11 +55190,11 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
54730
55190
|
}
|
|
54731
55191
|
|
|
54732
55192
|
// src/command-register-collaboration.ts
|
|
54733
|
-
var
|
|
55193
|
+
var import_node_path48 = require("node:path");
|
|
54734
55194
|
init_house_map();
|
|
54735
55195
|
|
|
54736
55196
|
// src/issue-body.ts
|
|
54737
|
-
var
|
|
55197
|
+
var import_node_os23 = require("node:os");
|
|
54738
55198
|
init_error_codes();
|
|
54739
55199
|
var TextArgError = class extends Error {
|
|
54740
55200
|
constructor(message2, code, offendingFlag) {
|
|
@@ -54747,7 +55207,7 @@ var TextArgError = class extends Error {
|
|
|
54747
55207
|
offendingFlag;
|
|
54748
55208
|
};
|
|
54749
55209
|
function emptyStdinMessage(fileFlag) {
|
|
54750
|
-
if ((0,
|
|
55210
|
+
if ((0, import_node_os23.platform)() === "win32") {
|
|
54751
55211
|
return `${fileFlag} - read empty stdin (on Windows, ${fileFlag} - is unreliable through the npm .cmd shim \u2014 use ${fileFlag} <path>, or pipe to \`node cli/dist/index.cjs\` directly)`;
|
|
54752
55212
|
}
|
|
54753
55213
|
return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
|
|
@@ -54847,8 +55307,8 @@ init_github_client();
|
|
|
54847
55307
|
init_github_quota();
|
|
54848
55308
|
|
|
54849
55309
|
// src/issue-commands.ts
|
|
54850
|
-
var
|
|
54851
|
-
var
|
|
55310
|
+
var import_node_fs44 = require("node:fs");
|
|
55311
|
+
var import_node_crypto19 = require("node:crypto");
|
|
54852
55312
|
init_cli_shared();
|
|
54853
55313
|
init_clean_exit();
|
|
54854
55314
|
init_error_codes();
|
|
@@ -55040,7 +55500,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
55040
55500
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
55041
55501
|
const patch = {};
|
|
55042
55502
|
let bodyChanged = false;
|
|
55043
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
55503
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs44.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
55044
55504
|
if (options.titleFile !== void 0) {
|
|
55045
55505
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
55046
55506
|
} else if (options.title !== void 0) {
|
|
@@ -55149,8 +55609,11 @@ async function closeIssue(client, options, deps = {}) {
|
|
|
55149
55609
|
}
|
|
55150
55610
|
const appWriter = await selectClaimWriteClient(client, deps.appClient);
|
|
55151
55611
|
if (reason === "completed" && options.evidence !== void 0) {
|
|
55152
|
-
|
|
55153
|
-
|
|
55612
|
+
try {
|
|
55613
|
+
await appWriter.rest("POST", `repos/${repo}/labels`, { body: { name: CLOSED_MERGED_LOOP_STATE_LABEL, color: "ededed" } });
|
|
55614
|
+
} catch (e) {
|
|
55615
|
+
if (!(e instanceof GitHubApiError && e.status === 422)) throw e;
|
|
55616
|
+
}
|
|
55154
55617
|
await appWriter.rest("POST", `repos/${repo}/issues/${parsed.number}/labels`, {
|
|
55155
55618
|
body: { labels: [CLOSED_MERGED_LOOP_STATE_LABEL] }
|
|
55156
55619
|
});
|
|
@@ -55322,7 +55785,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
55322
55785
|
const identity = `${spec.type}
|
|
55323
55786
|
${spec.title.trim()}
|
|
55324
55787
|
${spec.body ?? ""}`;
|
|
55325
|
-
const hash = (0,
|
|
55788
|
+
const hash = (0, import_node_crypto19.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
55326
55789
|
return `${batchKey}:${hash}`;
|
|
55327
55790
|
}
|
|
55328
55791
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -55704,7 +56167,7 @@ function extendCreateCommand(issue, batchAttach) {
|
|
|
55704
56167
|
if (opts.batch) {
|
|
55705
56168
|
let specs;
|
|
55706
56169
|
try {
|
|
55707
|
-
const raw = (0,
|
|
56170
|
+
const raw = (0, import_node_fs44.readFileSync)(opts.batch, "utf8");
|
|
55708
56171
|
specs = JSON.parse(raw);
|
|
55709
56172
|
if (!Array.isArray(specs)) {
|
|
55710
56173
|
const top = specs === null ? "null" : typeof specs === "object" ? `an object with keys: ${Object.keys(specs).join(", ") || "(none)"}` : `a ${typeof specs}`;
|
|
@@ -55781,8 +56244,8 @@ ${lines2}`, {
|
|
|
55781
56244
|
}
|
|
55782
56245
|
|
|
55783
56246
|
// src/post-merge-recon.ts
|
|
55784
|
-
var
|
|
55785
|
-
var
|
|
56247
|
+
var import_node_fs45 = require("node:fs");
|
|
56248
|
+
var import_node_path42 = require("node:path");
|
|
55786
56249
|
|
|
55787
56250
|
// src/cross-repo-filing-issue.ts
|
|
55788
56251
|
init_github_client();
|
|
@@ -55842,7 +56305,8 @@ function explicitSameOrgIssueUrls(text, prRepo, closingNumbers) {
|
|
|
55842
56305
|
}
|
|
55843
56306
|
return [...found.values()];
|
|
55844
56307
|
}
|
|
55845
|
-
async function reconcileCrossRepoFilingIssue(
|
|
56308
|
+
async function reconcileCrossRepoFilingIssue(rawClient, prRepo, prNumber) {
|
|
56309
|
+
const client = withAppOn401(rawClient, `cross-repo filing reconcile ${prRepo}#${prNumber}`);
|
|
55846
56310
|
try {
|
|
55847
56311
|
const pr = await client.rest("GET", `repos/${prRepo}/pulls/${prNumber}`);
|
|
55848
56312
|
if (!pr.merged_at) return { status: "not-applicable" };
|
|
@@ -55874,7 +56338,7 @@ ${pr.body ?? ""}`;
|
|
|
55874
56338
|
const issue = `${target.repo}#${target.number}`;
|
|
55875
56339
|
if (target.state === "CLOSED") return { status: "already-closed", issue, url: target.url };
|
|
55876
56340
|
const prUrl = pr.html_url ?? `https://github.com/${prRepo}/pull/${prNumber}`;
|
|
55877
|
-
const comment = await client.rest("POST", `repos/${target.repo}/issues/${target.number}/comments`, {
|
|
56341
|
+
const comment = await (await selectClaimWriteClient(client)).rest("POST", `repos/${target.repo}/issues/${target.number}/comments`, {
|
|
55878
56342
|
body: { body: `Resolved by merged PR ${prUrl}.
|
|
55879
56343
|
|
|
55880
56344
|
<!-- mmi-cross-repo-close:${prRepo}#${prNumber} -->` }
|
|
@@ -55951,16 +56415,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
55951
56415
|
}
|
|
55952
56416
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
55953
56417
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
55954
|
-
(0,
|
|
55955
|
-
(0,
|
|
56418
|
+
(0, import_node_fs45.mkdirSync)((0, import_node_path42.dirname)(path2), { recursive: true });
|
|
56419
|
+
(0, import_node_fs45.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
55956
56420
|
`, "utf8");
|
|
55957
56421
|
return path2;
|
|
55958
56422
|
}
|
|
55959
56423
|
function clearPostMergeReconRecovery(cwd, repo, pr) {
|
|
55960
56424
|
const path2 = postMergeReconStatePath(cwd, repo, pr);
|
|
55961
|
-
if (!(0,
|
|
56425
|
+
if (!(0, import_node_fs45.existsSync)(path2)) return;
|
|
55962
56426
|
try {
|
|
55963
|
-
(0,
|
|
56427
|
+
(0, import_node_fs45.unlinkSync)(path2);
|
|
55964
56428
|
} catch {
|
|
55965
56429
|
}
|
|
55966
56430
|
}
|
|
@@ -56436,9 +56900,9 @@ function registerPrLifecycleCommands(program3) {
|
|
|
56436
56900
|
|
|
56437
56901
|
// src/review-verdict.ts
|
|
56438
56902
|
var import_node_child_process14 = require("node:child_process");
|
|
56439
|
-
var
|
|
56440
|
-
var
|
|
56441
|
-
var
|
|
56903
|
+
var import_node_fs46 = require("node:fs");
|
|
56904
|
+
var import_node_os24 = require("node:os");
|
|
56905
|
+
var import_node_path43 = require("node:path");
|
|
56442
56906
|
init_cli_shared();
|
|
56443
56907
|
var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
|
|
56444
56908
|
var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
|
|
@@ -56603,15 +57067,15 @@ async function readPrIssueComments(number, repo) {
|
|
|
56603
57067
|
return comments;
|
|
56604
57068
|
}
|
|
56605
57069
|
async function postPrCommentFromFile(number, repo, body) {
|
|
56606
|
-
const dir = (0,
|
|
56607
|
-
const path2 = (0,
|
|
57070
|
+
const dir = (0, import_node_fs46.mkdtempSync)((0, import_node_path43.join)((0, import_node_os24.tmpdir)(), "mmi-review-verdict-"));
|
|
57071
|
+
const path2 = (0, import_node_path43.join)(dir, "body.md");
|
|
56608
57072
|
try {
|
|
56609
|
-
(0,
|
|
57073
|
+
(0, import_node_fs46.writeFileSync)(path2, body, "utf8");
|
|
56610
57074
|
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
56611
57075
|
return stdout.trim();
|
|
56612
57076
|
} finally {
|
|
56613
57077
|
try {
|
|
56614
|
-
(0,
|
|
57078
|
+
(0, import_node_fs46.rmSync)(dir, { recursive: true, force: true });
|
|
56615
57079
|
} catch {
|
|
56616
57080
|
}
|
|
56617
57081
|
}
|
|
@@ -57024,18 +57488,18 @@ function createPreparationDeps(input, io = {}) {
|
|
|
57024
57488
|
}
|
|
57025
57489
|
|
|
57026
57490
|
// src/worktree-residue-sweep.ts
|
|
57027
|
-
var
|
|
57028
|
-
var
|
|
57491
|
+
var import_node_fs50 = require("node:fs");
|
|
57492
|
+
var import_node_path47 = require("node:path");
|
|
57029
57493
|
|
|
57030
57494
|
// src/worktree-merge-cleanup.ts
|
|
57031
|
-
var
|
|
57032
|
-
var
|
|
57495
|
+
var import_node_fs49 = require("node:fs");
|
|
57496
|
+
var import_node_path46 = require("node:path");
|
|
57033
57497
|
init_cli_shared();
|
|
57034
57498
|
|
|
57035
57499
|
// src/stage-runner.ts
|
|
57036
57500
|
var import_node_child_process16 = require("node:child_process");
|
|
57037
|
-
var
|
|
57038
|
-
var
|
|
57501
|
+
var import_node_fs48 = require("node:fs");
|
|
57502
|
+
var import_node_path45 = require("node:path");
|
|
57039
57503
|
var import_node_net = require("node:net");
|
|
57040
57504
|
var import_node_util5 = require("node:util");
|
|
57041
57505
|
|
|
@@ -57045,8 +57509,8 @@ function normalizeEol2(s) {
|
|
|
57045
57509
|
}
|
|
57046
57510
|
|
|
57047
57511
|
// src/env-detection.ts
|
|
57048
|
-
var
|
|
57049
|
-
var
|
|
57512
|
+
var import_node_fs47 = require("node:fs");
|
|
57513
|
+
var import_node_path44 = require("node:path");
|
|
57050
57514
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
|
|
57051
57515
|
function isEnvBaseName(name) {
|
|
57052
57516
|
return name.toLowerCase() === ".env";
|
|
@@ -57056,7 +57520,7 @@ function findEnvFiles(root) {
|
|
|
57056
57520
|
const walk2 = (dir, rel) => {
|
|
57057
57521
|
let entries;
|
|
57058
57522
|
try {
|
|
57059
|
-
entries = (0,
|
|
57523
|
+
entries = (0, import_node_fs47.readdirSync)(dir, { withFileTypes: true });
|
|
57060
57524
|
} catch {
|
|
57061
57525
|
return;
|
|
57062
57526
|
}
|
|
@@ -57064,13 +57528,13 @@ function findEnvFiles(root) {
|
|
|
57064
57528
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
57065
57529
|
if (entry.isDirectory()) {
|
|
57066
57530
|
if (SKIPPED_DIRS.has(entry.name)) continue;
|
|
57067
|
-
walk2((0,
|
|
57531
|
+
walk2((0, import_node_path44.join)(dir, entry.name), relPath);
|
|
57068
57532
|
continue;
|
|
57069
57533
|
}
|
|
57070
57534
|
if (isEnvBaseName(entry.name)) {
|
|
57071
57535
|
let isDir = false;
|
|
57072
57536
|
try {
|
|
57073
|
-
isDir = (0,
|
|
57537
|
+
isDir = (0, import_node_fs47.lstatSync)((0, import_node_path44.join)(dir, entry.name)).isDirectory();
|
|
57074
57538
|
} catch {
|
|
57075
57539
|
}
|
|
57076
57540
|
if (!isDir) found.push(relPath);
|
|
@@ -57209,12 +57673,12 @@ var DOCKER_TIMEOUT_MS = 15e3;
|
|
|
57209
57673
|
function stageBash() {
|
|
57210
57674
|
if (process.platform !== "win32") return "bash";
|
|
57211
57675
|
const configured = process.env.SHELL;
|
|
57212
|
-
if (configured && (0,
|
|
57676
|
+
if (configured && (0, import_node_path45.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs48.existsSync)(configured)) return configured;
|
|
57213
57677
|
const candidates = [
|
|
57214
|
-
(0,
|
|
57215
|
-
...process.env.LOCALAPPDATA ? [(0,
|
|
57678
|
+
(0, import_node_path45.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
|
|
57679
|
+
...process.env.LOCALAPPDATA ? [(0, import_node_path45.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
|
|
57216
57680
|
];
|
|
57217
|
-
const bash = candidates.find((path2) => (0,
|
|
57681
|
+
const bash = candidates.find((path2) => (0, import_node_fs48.existsSync)(path2));
|
|
57218
57682
|
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.");
|
|
57219
57683
|
return bash;
|
|
57220
57684
|
}
|
|
@@ -57352,7 +57816,7 @@ function appendForceRecreate(up) {
|
|
|
57352
57816
|
return `${up.trimEnd()} --force-recreate`;
|
|
57353
57817
|
}
|
|
57354
57818
|
function stageStatePath(cwd = process.cwd()) {
|
|
57355
|
-
return (0,
|
|
57819
|
+
return (0, import_node_path45.join)(cwd, "tmp", "stage", "state.json");
|
|
57356
57820
|
}
|
|
57357
57821
|
function normPath(path2) {
|
|
57358
57822
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -57569,8 +58033,8 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
57569
58033
|
}
|
|
57570
58034
|
function composeResolvesPort(cwd) {
|
|
57571
58035
|
if (process.env.PORT) return true;
|
|
57572
|
-
const envFile = (0,
|
|
57573
|
-
return (0,
|
|
58036
|
+
const envFile = (0, import_node_path45.join)(cwd, ".env");
|
|
58037
|
+
return (0, import_node_fs48.existsSync)(envFile) && envFileKeys((0, import_node_fs48.readFileSync)(envFile, "utf8")).has("PORT");
|
|
57574
58038
|
}
|
|
57575
58039
|
function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
57576
58040
|
const composeProject = config.env?.COMPOSE_PROJECT_NAME ?? vaultEnvMerge?.COMPOSE_PROJECT_NAME ?? stageComposeProjectName(cwd);
|
|
@@ -57585,13 +58049,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
|
57585
58049
|
function stageComposeFiles(cwd) {
|
|
57586
58050
|
const files = ["docker-compose.yml"];
|
|
57587
58051
|
for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
|
|
57588
|
-
if ((0,
|
|
58052
|
+
if ((0, import_node_fs48.existsSync)((0, import_node_path45.join)(cwd, conventional))) files.push(conventional);
|
|
57589
58053
|
}
|
|
57590
58054
|
return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
|
|
57591
58055
|
}
|
|
57592
58056
|
async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
57593
58057
|
const keys = Object.keys(vaultEnvMerge ?? {});
|
|
57594
|
-
if (!keys.length || !(0,
|
|
58058
|
+
if (!keys.length || !(0, import_node_fs48.existsSync)((0, import_node_path45.join)(cwd, "docker-compose.yml"))) return {};
|
|
57595
58059
|
let configJson;
|
|
57596
58060
|
try {
|
|
57597
58061
|
const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
|
|
@@ -57607,21 +58071,21 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
|
57607
58071
|
}
|
|
57608
58072
|
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson), runtimeEnvSelects(configJson));
|
|
57609
58073
|
if (!body) return {};
|
|
57610
|
-
const overridePath = (0,
|
|
57611
|
-
(0,
|
|
57612
|
-
(0,
|
|
58074
|
+
const overridePath = (0, import_node_path45.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
|
|
58075
|
+
(0, import_node_fs48.mkdirSync)((0, import_node_path45.join)(cwd, "tmp", "stage"), { recursive: true });
|
|
58076
|
+
(0, import_node_fs48.writeFileSync)(overridePath, body, "utf8");
|
|
57613
58077
|
return stageComposeFileEnv(stageComposeFiles(cwd));
|
|
57614
58078
|
}
|
|
57615
58079
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
57616
58080
|
if (!config.ensureEnv) return;
|
|
57617
|
-
const target = (0,
|
|
57618
|
-
const example = (0,
|
|
57619
|
-
if (!(0,
|
|
57620
|
-
(0,
|
|
57621
|
-
} else if ((0,
|
|
57622
|
-
const stale = detectStaleEnvFile((0,
|
|
57623
|
-
exampleMtimeMs: (0,
|
|
57624
|
-
targetMtimeMs: (0,
|
|
58081
|
+
const target = (0, import_node_path45.join)(cwd, config.ensureEnv.target);
|
|
58082
|
+
const example = (0, import_node_path45.join)(cwd, config.ensureEnv.example);
|
|
58083
|
+
if (!(0, import_node_fs48.existsSync)(target) && (0, import_node_fs48.existsSync)(example)) {
|
|
58084
|
+
(0, import_node_fs48.copyFileSync)(example, target);
|
|
58085
|
+
} else if ((0, import_node_fs48.existsSync)(target) && (0, import_node_fs48.existsSync)(example)) {
|
|
58086
|
+
const stale = detectStaleEnvFile((0, import_node_fs48.readFileSync)(example, "utf8"), (0, import_node_fs48.readFileSync)(target, "utf8"), {
|
|
58087
|
+
exampleMtimeMs: (0, import_node_fs48.statSync)(example).mtimeMs,
|
|
58088
|
+
targetMtimeMs: (0, import_node_fs48.statSync)(target).mtimeMs
|
|
57625
58089
|
});
|
|
57626
58090
|
if (stale) {
|
|
57627
58091
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running mmi-cli stage`;
|
|
@@ -57629,8 +58093,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
57629
58093
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
57630
58094
|
}
|
|
57631
58095
|
}
|
|
57632
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
57633
|
-
(0,
|
|
58096
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs48.existsSync)(target)) {
|
|
58097
|
+
(0, import_node_fs48.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs48.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
57634
58098
|
}
|
|
57635
58099
|
}
|
|
57636
58100
|
async function gitText(cwd, args) {
|
|
@@ -57660,20 +58124,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
57660
58124
|
return void 0;
|
|
57661
58125
|
}
|
|
57662
58126
|
function readState(path2) {
|
|
57663
|
-
if (!(0,
|
|
58127
|
+
if (!(0, import_node_fs48.existsSync)(path2)) return null;
|
|
57664
58128
|
try {
|
|
57665
|
-
return JSON.parse((0,
|
|
58129
|
+
return JSON.parse((0, import_node_fs48.readFileSync)(path2, "utf8"));
|
|
57666
58130
|
} catch {
|
|
57667
58131
|
return null;
|
|
57668
58132
|
}
|
|
57669
58133
|
}
|
|
57670
58134
|
function mkdirFor(path2) {
|
|
57671
58135
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
57672
|
-
(0,
|
|
58136
|
+
(0, import_node_fs48.mkdirSync)(dir, { recursive: true });
|
|
57673
58137
|
}
|
|
57674
58138
|
function writeState(path2, state) {
|
|
57675
58139
|
mkdirFor(path2);
|
|
57676
|
-
(0,
|
|
58140
|
+
(0, import_node_fs48.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
57677
58141
|
}
|
|
57678
58142
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
57679
58143
|
const reservation = {
|
|
@@ -57702,7 +58166,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd, currentEn
|
|
|
57702
58166
|
);
|
|
57703
58167
|
}
|
|
57704
58168
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
57705
|
-
(0,
|
|
58169
|
+
(0, import_node_fs48.rmSync)(path2, { force: true });
|
|
57706
58170
|
}
|
|
57707
58171
|
}
|
|
57708
58172
|
async function killTree(pid) {
|
|
@@ -57949,8 +58413,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
57949
58413
|
});
|
|
57950
58414
|
}
|
|
57951
58415
|
} catch (e) {
|
|
57952
|
-
(0,
|
|
57953
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
58416
|
+
(0, import_node_fs48.rmSync)(statePath, { force: true });
|
|
58417
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs48.rmSync)(globalStatePath, { force: true });
|
|
57954
58418
|
throw e;
|
|
57955
58419
|
}
|
|
57956
58420
|
const started = await startStage(config, {
|
|
@@ -57969,9 +58433,9 @@ function normPath2(p) {
|
|
|
57969
58433
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
57970
58434
|
}
|
|
57971
58435
|
function unlinkNodeModulesJunction(wtPath) {
|
|
57972
|
-
const nm = (0,
|
|
58436
|
+
const nm = (0, import_node_path46.join)(wtPath, "node_modules");
|
|
57973
58437
|
try {
|
|
57974
|
-
if ((0,
|
|
58438
|
+
if ((0, import_node_fs49.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs49.rmdirSync)(nm);
|
|
57975
58439
|
return { ok: true };
|
|
57976
58440
|
} catch (e) {
|
|
57977
58441
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -57982,8 +58446,8 @@ function defaultSleep2(ms) {
|
|
|
57982
58446
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
57983
58447
|
}
|
|
57984
58448
|
async function removeResidueDirectory(wtPath, options = {}) {
|
|
57985
|
-
const { sleep: sleep2 = defaultSleep2, removeEmptyDir =
|
|
57986
|
-
const removeRecursive = options.removeRecursive ?? ((path2) => (0,
|
|
58449
|
+
const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs49.rmdirSync, exists = import_node_fs49.existsSync } = options;
|
|
58450
|
+
const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs49.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
|
|
57987
58451
|
const junction = unlinkNodeModulesJunction(wtPath);
|
|
57988
58452
|
if (!junction.ok) return junction;
|
|
57989
58453
|
try {
|
|
@@ -58106,28 +58570,28 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
|
|
|
58106
58570
|
}
|
|
58107
58571
|
async function listNestedIgnoredNodeModules(wtPath, execGit) {
|
|
58108
58572
|
const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
|
|
58109
|
-
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0,
|
|
58573
|
+
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path46.join)(wtPath, line.slice(0, -1)));
|
|
58110
58574
|
}
|
|
58111
58575
|
function safeRemoveTree(path2) {
|
|
58112
|
-
const stat4 = (0,
|
|
58576
|
+
const stat4 = (0, import_node_fs49.lstatSync)(path2);
|
|
58113
58577
|
if (stat4.isSymbolicLink()) {
|
|
58114
58578
|
try {
|
|
58115
|
-
(0,
|
|
58579
|
+
(0, import_node_fs49.rmdirSync)(path2);
|
|
58116
58580
|
} catch {
|
|
58117
|
-
(0,
|
|
58581
|
+
(0, import_node_fs49.unlinkSync)(path2);
|
|
58118
58582
|
}
|
|
58119
58583
|
return;
|
|
58120
58584
|
}
|
|
58121
58585
|
if (stat4.isDirectory()) {
|
|
58122
|
-
for (const entry of (0,
|
|
58123
|
-
(0,
|
|
58586
|
+
for (const entry of (0, import_node_fs49.readdirSync)(path2)) safeRemoveTree((0, import_node_path46.join)(path2, entry));
|
|
58587
|
+
(0, import_node_fs49.rmdirSync)(path2);
|
|
58124
58588
|
return;
|
|
58125
58589
|
}
|
|
58126
|
-
(0,
|
|
58590
|
+
(0, import_node_fs49.unlinkSync)(path2);
|
|
58127
58591
|
}
|
|
58128
58592
|
async function removeWorktreeNodeModules(wtPath) {
|
|
58129
58593
|
try {
|
|
58130
|
-
safeRemoveTree((0,
|
|
58594
|
+
safeRemoveTree((0, import_node_path46.join)(wtPath, "node_modules"));
|
|
58131
58595
|
return { ok: true };
|
|
58132
58596
|
} catch (e) {
|
|
58133
58597
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -58139,7 +58603,7 @@ function errorMessage(e) {
|
|
|
58139
58603
|
}
|
|
58140
58604
|
function resolvedOrRaw(path2) {
|
|
58141
58605
|
try {
|
|
58142
|
-
return normPath2((0,
|
|
58606
|
+
return normPath2((0, import_node_fs49.realpathSync)(path2));
|
|
58143
58607
|
} catch {
|
|
58144
58608
|
return normPath2(path2);
|
|
58145
58609
|
}
|
|
@@ -58147,10 +58611,10 @@ function resolvedOrRaw(path2) {
|
|
|
58147
58611
|
function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
58148
58612
|
let realRoot;
|
|
58149
58613
|
try {
|
|
58150
|
-
if ((0,
|
|
58614
|
+
if ((0, import_node_fs49.lstatSync)(root).isSymbolicLink()) {
|
|
58151
58615
|
return { ok: false, error: `delete root ${normPath2(root)} is a reparse point resolving to ${resolvedOrRaw(root)}` };
|
|
58152
58616
|
}
|
|
58153
|
-
realRoot = normPath2((0,
|
|
58617
|
+
realRoot = normPath2((0, import_node_fs49.realpathSync)(root));
|
|
58154
58618
|
} catch (e) {
|
|
58155
58619
|
if (e.code === "ENOENT") return { ok: true, unlinked: [] };
|
|
58156
58620
|
return { ok: false, error: `cannot resolve delete root ${normPath2(root)}: ${errorMessage(e)}` };
|
|
@@ -58166,17 +58630,17 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
58166
58630
|
const dir = stack.pop();
|
|
58167
58631
|
let entries;
|
|
58168
58632
|
try {
|
|
58169
|
-
entries = (0,
|
|
58633
|
+
entries = (0, import_node_fs49.readdirSync)(dir, { withFileTypes: true });
|
|
58170
58634
|
} catch (e) {
|
|
58171
58635
|
if (e.code === "ENOENT") continue;
|
|
58172
58636
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
58173
58637
|
}
|
|
58174
58638
|
for (const entry of entries) {
|
|
58175
|
-
const child2 = (0,
|
|
58639
|
+
const child2 = (0, import_node_path46.join)(dir, entry.name);
|
|
58176
58640
|
if (entry.isSymbolicLink()) {
|
|
58177
58641
|
let target = "";
|
|
58178
58642
|
try {
|
|
58179
|
-
target = normPath2((0,
|
|
58643
|
+
target = normPath2((0, import_node_fs49.realpathSync)(child2));
|
|
58180
58644
|
} catch {
|
|
58181
58645
|
target = "";
|
|
58182
58646
|
}
|
|
@@ -58359,7 +58823,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
58359
58823
|
return report;
|
|
58360
58824
|
}
|
|
58361
58825
|
const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
58362
|
-
const pathExists = options.pathExists ??
|
|
58826
|
+
const pathExists = options.pathExists ?? import_node_fs49.existsSync;
|
|
58363
58827
|
let afterWorktrees = [];
|
|
58364
58828
|
try {
|
|
58365
58829
|
afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
|
|
@@ -58499,7 +58963,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
58499
58963
|
const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
|
|
58500
58964
|
if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
|
|
58501
58965
|
unlinkedReparsePoints.push(...preHelperGuard.unlinked);
|
|
58502
|
-
if (pathExists((0,
|
|
58966
|
+
if (pathExists((0, import_node_path46.join)(wtPath, "node_modules"))) {
|
|
58503
58967
|
const nmRemoved = await (options.removeRealNodeModules ?? removeWorktreeNodeModules)(wtPath);
|
|
58504
58968
|
if (!nmRemoved.ok) {
|
|
58505
58969
|
report.worktree = {
|
|
@@ -58635,18 +59099,18 @@ function renderPrMergeCleanupLines(cleanup) {
|
|
|
58635
59099
|
// src/worktree-residue-sweep.ts
|
|
58636
59100
|
var RESIDUE_LEDGER_FILE = "mmi-worktree-residue.json";
|
|
58637
59101
|
function canonicalWorktreeRoot(primary) {
|
|
58638
|
-
return (0,
|
|
59102
|
+
return (0, import_node_path47.join)((0, import_node_path47.dirname)(primary), "mmi-worktrees", (0, import_node_path47.basename)(primary));
|
|
58639
59103
|
}
|
|
58640
59104
|
function norm(p) {
|
|
58641
59105
|
const s = p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
58642
59106
|
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
58643
59107
|
}
|
|
58644
59108
|
function residueLedgerPath(gitCommonDir) {
|
|
58645
|
-
return (0,
|
|
59109
|
+
return (0, import_node_path47.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
|
|
58646
59110
|
}
|
|
58647
59111
|
function readPaths(file, deps) {
|
|
58648
59112
|
try {
|
|
58649
|
-
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0,
|
|
59113
|
+
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs50.readFileSync)(f, "utf8")))(file));
|
|
58650
59114
|
return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
|
|
58651
59115
|
} catch {
|
|
58652
59116
|
return [];
|
|
@@ -58655,8 +59119,8 @@ function readPaths(file, deps) {
|
|
|
58655
59119
|
function writePaths(file, paths, deps) {
|
|
58656
59120
|
try {
|
|
58657
59121
|
const write = deps.writeLedger ?? ((f, t) => {
|
|
58658
|
-
(0,
|
|
58659
|
-
(0,
|
|
59122
|
+
(0, import_node_fs50.mkdirSync)((0, import_node_path47.dirname)(f), { recursive: true });
|
|
59123
|
+
(0, import_node_fs50.writeFileSync)(f, t, "utf8");
|
|
58660
59124
|
});
|
|
58661
59125
|
write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
|
|
58662
59126
|
`);
|
|
@@ -58670,17 +59134,17 @@ function recordResidue(gitCommonDir, path2, deps = {}) {
|
|
|
58670
59134
|
writePaths(file, paths, deps);
|
|
58671
59135
|
}
|
|
58672
59136
|
async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
58673
|
-
const exists = deps.exists ??
|
|
58674
|
-
const readDir = deps.readDir ?? ((p) => (0,
|
|
59137
|
+
const exists = deps.exists ?? import_node_fs50.existsSync;
|
|
59138
|
+
const readDir = deps.readDir ?? ((p) => (0, import_node_fs50.readdirSync)(p));
|
|
58675
59139
|
const isDir = deps.isDir ?? ((p) => {
|
|
58676
59140
|
try {
|
|
58677
|
-
return (0,
|
|
59141
|
+
return (0, import_node_fs50.statSync)(p).isDirectory();
|
|
58678
59142
|
} catch {
|
|
58679
59143
|
return false;
|
|
58680
59144
|
}
|
|
58681
59145
|
});
|
|
58682
59146
|
const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
|
|
58683
|
-
const removeEmpty = deps.removeEmpty ?? ((p) => (0,
|
|
59147
|
+
const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs50.rmdirSync)(p));
|
|
58684
59148
|
const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
|
|
58685
59149
|
const live = new Set(registered.map(norm));
|
|
58686
59150
|
const primary = registered[0] ? norm(registered[0]) : void 0;
|
|
@@ -58715,7 +59179,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
|
58715
59179
|
} catch {
|
|
58716
59180
|
}
|
|
58717
59181
|
for (const name of children) {
|
|
58718
|
-
const child2 = (0,
|
|
59182
|
+
const child2 = (0, import_node_path47.join)(root, name);
|
|
58719
59183
|
const key = norm(child2);
|
|
58720
59184
|
if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
|
|
58721
59185
|
let entries;
|
|
@@ -58758,10 +59222,10 @@ function argvWantsJson2() {
|
|
|
58758
59222
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
58759
59223
|
}
|
|
58760
59224
|
function hubRoot() {
|
|
58761
|
-
const fromPkg = (0,
|
|
59225
|
+
const fromPkg = (0, import_node_path48.join)(__dirname, "..", "..");
|
|
58762
59226
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
58763
|
-
if ((0,
|
|
58764
|
-
if ((0,
|
|
59227
|
+
if ((0, import_node_fs51.existsSync)((0, import_node_path48.join)(fromPkg, marker))) return fromPkg;
|
|
59228
|
+
if ((0, import_node_fs51.existsSync)((0, import_node_path48.join)(process.cwd(), marker))) return process.cwd();
|
|
58765
59229
|
return null;
|
|
58766
59230
|
}
|
|
58767
59231
|
function ciAuditDeps() {
|
|
@@ -58773,12 +59237,12 @@ function ciAuditDeps() {
|
|
|
58773
59237
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
58774
59238
|
readSeedFile: (path2) => {
|
|
58775
59239
|
if (!root) return null;
|
|
58776
|
-
const fullPath = (0,
|
|
58777
|
-
return (0,
|
|
59240
|
+
const fullPath = (0, import_node_path48.join)(root, path2);
|
|
59241
|
+
return (0, import_node_fs51.existsSync)(fullPath) ? (0, import_node_fs51.readFileSync)(fullPath, "utf8") : null;
|
|
58778
59242
|
}
|
|
58779
59243
|
};
|
|
58780
59244
|
}
|
|
58781
|
-
async function
|
|
59245
|
+
async function ghJson2(args, timeout = 1e4) {
|
|
58782
59246
|
const { stdout } = await execFileP("gh", args, { timeout });
|
|
58783
59247
|
return JSON.parse(stdout);
|
|
58784
59248
|
}
|
|
@@ -58852,6 +59316,24 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
58852
59316
|
}
|
|
58853
59317
|
return { onBoard: true };
|
|
58854
59318
|
}
|
|
59319
|
+
const viaHub = contentId ? await attachOnIntegrationRefusal(detail, cfg, contentId) : void 0;
|
|
59320
|
+
if (viaHub && "projectItemId" in viaHub) {
|
|
59321
|
+
if (priority) {
|
|
59322
|
+
try {
|
|
59323
|
+
await setBoardItemPriority(defaultGitHubClient(), cfg, viaHub.projectItemId, priority);
|
|
59324
|
+
} catch (pe) {
|
|
59325
|
+
const perr = pe;
|
|
59326
|
+
process.stderr.write(`warning: issue #${issueNumber} board Priority not set: ${(perr.stderr || perr.message || String(pe)).trim()}
|
|
59327
|
+
`);
|
|
59328
|
+
}
|
|
59329
|
+
}
|
|
59330
|
+
return { projectItemId: viaHub.projectItemId, onBoard: true, credential: "app_installation" };
|
|
59331
|
+
}
|
|
59332
|
+
if (viaHub) {
|
|
59333
|
+
process.stderr.write(`warning: issue #${issueNumber} created but NOT added to the project board: ${viaHub.warning}
|
|
59334
|
+
`);
|
|
59335
|
+
return { onBoard: false };
|
|
59336
|
+
}
|
|
58855
59337
|
if (isRateLimitText(detail)) {
|
|
58856
59338
|
if (targetRepo2) {
|
|
58857
59339
|
try {
|
|
@@ -59079,8 +59561,25 @@ function registerCollaborationCommands(program3) {
|
|
|
59079
59561
|
}
|
|
59080
59562
|
if (refusal && !surfaceWaived()) return fail(refusal.message, refusal.payload);
|
|
59081
59563
|
}
|
|
59082
|
-
|
|
59083
|
-
|
|
59564
|
+
let created;
|
|
59565
|
+
try {
|
|
59566
|
+
await ensureLabelsExist(extraLabels, targetRepo2);
|
|
59567
|
+
created = await ghCreate(args);
|
|
59568
|
+
} catch (error) {
|
|
59569
|
+
if (o.parent !== void 0 || !/Could not resolve to a Repository|HTTP 404|Not Found/i.test(error.message)) throw error;
|
|
59570
|
+
const filed = await createBoardIssueViaHub({ repo: targetRepo2, title, body, type: issueType, priority, labels: extraLabels });
|
|
59571
|
+
const nextStep = filed.onBoard ? void 0 : `mmi-cli oracle board attach ${targetRepo2}#${filed.number}`;
|
|
59572
|
+
console.log(JSON.stringify({
|
|
59573
|
+
status: filed.onBoard ? "created" : "partial",
|
|
59574
|
+
...filed,
|
|
59575
|
+
label: issueType,
|
|
59576
|
+
priority,
|
|
59577
|
+
credential: "app",
|
|
59578
|
+
...nextStep ? { boardAttach: "failed", nextStep, nextSteps: [nextStep] } : {},
|
|
59579
|
+
...surfaceInferred ? { surface_inferred: true, surface: surfaceInferred.label, surface_reason: surfaceInferred.reason } : {}
|
|
59580
|
+
}));
|
|
59581
|
+
return;
|
|
59582
|
+
}
|
|
59084
59583
|
if (isGhCreateRateLimited(created)) {
|
|
59085
59584
|
console.log(JSON.stringify(created));
|
|
59086
59585
|
process.exitCode = 1;
|
|
@@ -59146,7 +59645,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59146
59645
|
async function readParentField(number, repo) {
|
|
59147
59646
|
let payload;
|
|
59148
59647
|
try {
|
|
59149
|
-
payload = await
|
|
59648
|
+
payload = await ghJson2(["api", `repos/${repo}/issues/${number}`]);
|
|
59150
59649
|
} catch (e) {
|
|
59151
59650
|
const err = e;
|
|
59152
59651
|
return { parentReadError: (err.stderr || err.message || String(e)).trim() };
|
|
@@ -59163,7 +59662,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59163
59662
|
}
|
|
59164
59663
|
async function readIssueTypeField(number, repo) {
|
|
59165
59664
|
try {
|
|
59166
|
-
return { issueType: resolveIssueType(await
|
|
59665
|
+
return { issueType: resolveIssueType(await ghJson2(["api", `repos/${repo}/issues/${number}`])) };
|
|
59167
59666
|
} catch (e) {
|
|
59168
59667
|
const err = e;
|
|
59169
59668
|
return { issueTypeReadError: (err.stderr || err.message || String(e)).trim() };
|
|
@@ -59209,7 +59708,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59209
59708
|
const decisions = parseDecisionMarkers(data2.comments);
|
|
59210
59709
|
return { data: { ...data2, decisions }, parent: await readParentField(targetNumber, targetRepo2) };
|
|
59211
59710
|
}
|
|
59212
|
-
const data = await
|
|
59711
|
+
const data = await ghJson2(["issue", "view", String(targetNumber), "--repo", targetRepo2, "--json", gh2.ghFields]);
|
|
59213
59712
|
return { data, parent: await readParentField(targetNumber, targetRepo2) };
|
|
59214
59713
|
};
|
|
59215
59714
|
const readClosedBy = async (targetRepo2, targetNumber) => ({
|
|
@@ -59244,7 +59743,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59244
59743
|
const repo = await resolveRepo(o.repo);
|
|
59245
59744
|
if (!repo) return fail("issue discover-related: could not resolve repo");
|
|
59246
59745
|
try {
|
|
59247
|
-
const issues = await
|
|
59746
|
+
const issues = await ghJson2([
|
|
59248
59747
|
"issue",
|
|
59249
59748
|
"list",
|
|
59250
59749
|
"--repo",
|
|
@@ -59259,7 +59758,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59259
59758
|
const candidates = findRelatedIssues({ number, title: o.title, body: o.body }, issues);
|
|
59260
59759
|
if (o.json) return console.log(JSON.stringify({ number, repo, candidates }, null, 2));
|
|
59261
59760
|
if (!candidates.length) return;
|
|
59262
|
-
const viewed = await
|
|
59761
|
+
const viewed = await ghJson2([
|
|
59263
59762
|
"issue",
|
|
59264
59763
|
"view",
|
|
59265
59764
|
String(number),
|
|
@@ -59360,7 +59859,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59360
59859
|
const checked = o.off !== true;
|
|
59361
59860
|
let body;
|
|
59362
59861
|
try {
|
|
59363
|
-
const viewed = await
|
|
59862
|
+
const viewed = await ghJson2(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
|
|
59364
59863
|
body = viewed.body ?? "";
|
|
59365
59864
|
} catch (e) {
|
|
59366
59865
|
return fail(`issue check: could not read ${repo}#${parsed.number}: ${e.message}`);
|
|
@@ -59461,7 +59960,7 @@ ${list}`);
|
|
|
59461
59960
|
if (!o.force) {
|
|
59462
59961
|
let openLessons = [];
|
|
59463
59962
|
try {
|
|
59464
|
-
openLessons = await
|
|
59963
|
+
openLessons = await ghJson2([
|
|
59465
59964
|
"issue",
|
|
59466
59965
|
"list",
|
|
59467
59966
|
"--repo",
|
|
@@ -59598,7 +60097,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59598
60097
|
console.log(JSON.stringify(data2));
|
|
59599
60098
|
return;
|
|
59600
60099
|
}
|
|
59601
|
-
const data = await
|
|
60100
|
+
const data = await ghJson2(["pr", "view", String(n), "--repo", repo, "--json", effective]);
|
|
59602
60101
|
console.log(JSON.stringify(data));
|
|
59603
60102
|
} catch (e) {
|
|
59604
60103
|
const err = e;
|
|
@@ -59606,11 +60105,11 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59606
60105
|
}
|
|
59607
60106
|
});
|
|
59608
60107
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
59609
|
-
const wfDir = (0,
|
|
59610
|
-
if (!(0,
|
|
59611
|
-
return (0,
|
|
60108
|
+
const wfDir = (0, import_node_path48.join)(cwd, ".github", "workflows");
|
|
60109
|
+
if (!(0, import_node_fs51.existsSync)(wfDir)) return [];
|
|
60110
|
+
return (0, import_node_fs51.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
59612
60111
|
try {
|
|
59613
|
-
return workflowReportsPrChecks((0,
|
|
60112
|
+
return workflowReportsPrChecks((0, import_node_fs51.readFileSync)((0, import_node_path48.join)(wfDir, name), "utf8"));
|
|
59614
60113
|
} catch {
|
|
59615
60114
|
return true;
|
|
59616
60115
|
}
|
|
@@ -59815,7 +60314,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59815
60314
|
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) => {
|
|
59816
60315
|
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
59817
60316
|
const verdict = o.verdict;
|
|
59818
|
-
const findings = o.findingsFile ? (0,
|
|
60317
|
+
const findings = o.findingsFile ? (0, import_node_fs51.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
59819
60318
|
let patch;
|
|
59820
60319
|
let head;
|
|
59821
60320
|
try {
|
|
@@ -59896,7 +60395,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59896
60395
|
await readClosingGuardInput(number, repoArgs, landRepoForGuard, "pr land"),
|
|
59897
60396
|
async (n) => {
|
|
59898
60397
|
if (!landRepoForGuard) return void 0;
|
|
59899
|
-
const viewed = await
|
|
60398
|
+
const viewed = await ghJson2(["issue", "view", String(n), "--repo", landRepoForGuard, "--json", "state"]);
|
|
59900
60399
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
59901
60400
|
}
|
|
59902
60401
|
);
|
|
@@ -60093,7 +60592,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60093
60592
|
const cleanupMerged = async (r) => {
|
|
60094
60593
|
if (opts.preserveWorktree) return "cleanup skipped (--preserve-worktree)";
|
|
60095
60594
|
const ran = await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(["pr", "merge", r.pr, "--repo", r.repo])], {
|
|
60096
|
-
cwd: (0,
|
|
60595
|
+
cwd: (0, import_node_fs51.existsSync)(cwd0) ? cwd0 : primaryRoot0,
|
|
60097
60596
|
timeout: GH_MUTATION_TIMEOUT_MS * 3
|
|
60098
60597
|
}).then((x) => ({ stdout: x.stdout, stderr: x.stderr }), (e) => ({ stdout: e.stdout ?? "", stderr: e.stderr ?? "" }));
|
|
60099
60598
|
const json = ran.stdout.split("\n").reverse().find((l) => l.startsWith("{"));
|
|
@@ -60154,7 +60653,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60154
60653
|
await readClosingGuardInput(number, repoArgs, repoForPostCleanup, "pr merge"),
|
|
60155
60654
|
async (n) => {
|
|
60156
60655
|
if (!repoForPostCleanup) return void 0;
|
|
60157
|
-
const viewed = await
|
|
60656
|
+
const viewed = await ghJson2(["issue", "view", String(n), "--repo", repoForPostCleanup, "--json", "state"]);
|
|
60158
60657
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
60159
60658
|
}
|
|
60160
60659
|
);
|
|
@@ -60164,7 +60663,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60164
60663
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
60165
60664
|
closingGuardInput,
|
|
60166
60665
|
method === "--squash",
|
|
60167
|
-
o.squashBodyFile ? (0,
|
|
60666
|
+
o.squashBodyFile ? (0, import_node_fs51.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
60168
60667
|
);
|
|
60169
60668
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
60170
60669
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -60196,7 +60695,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60196
60695
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
60197
60696
|
let foreignCheckout;
|
|
60198
60697
|
if (foreignCwd) {
|
|
60199
|
-
const sibling = (0,
|
|
60698
|
+
const sibling = (0, import_node_path48.join)((0, import_node_path48.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
|
|
60200
60699
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
60201
60700
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
60202
60701
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -60315,7 +60814,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60315
60814
|
}
|
|
60316
60815
|
if (!repoForPostCleanup) throw e;
|
|
60317
60816
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
60318
|
-
const commitMessage = bodyFile ? (0,
|
|
60817
|
+
const commitMessage = bodyFile ? (0, import_node_fs51.readFileSync)(bodyFile, "utf8") : void 0;
|
|
60319
60818
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
60320
60819
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
60321
60820
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -60430,7 +60929,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60430
60929
|
let residueSweep;
|
|
60431
60930
|
if (!(foreignCwd && !foreignCheckout)) {
|
|
60432
60931
|
try {
|
|
60433
|
-
gitCommonDir = (0,
|
|
60932
|
+
gitCommonDir = (0, import_node_path48.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
|
|
60434
60933
|
residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
|
|
60435
60934
|
} catch {
|
|
60436
60935
|
}
|
|
@@ -60448,7 +60947,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60448
60947
|
preserveWorktree: o.preserveWorktree,
|
|
60449
60948
|
gcAcknowledged: o.gc,
|
|
60450
60949
|
expectedHeadOid: headRefOid,
|
|
60451
|
-
pathExists: (p) => (0,
|
|
60950
|
+
pathExists: (p) => (0, import_node_fs51.existsSync)(p),
|
|
60452
60951
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
60453
60952
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
60454
60953
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -61711,7 +62210,7 @@ function rcRunCauseFromLog(log) {
|
|
|
61711
62210
|
function memberAnnounceSummaryFile(repo, summaryFile, memberSummaryFiles) {
|
|
61712
62211
|
return repo === SUITE_REPO ? summaryFile : memberSummaryFiles[repo];
|
|
61713
62212
|
}
|
|
61714
|
-
var suiteStateDir = (root) => (0,
|
|
62213
|
+
var suiteStateDir = (root) => (0, import_node_path50.join)(resolvePrimaryGitDir(root), "mmi-runtime", "suite");
|
|
61715
62214
|
async function runSuiteMemberRcand(repo, previousRc, rcWasAbsent) {
|
|
61716
62215
|
const deps = trainApplyDeps();
|
|
61717
62216
|
deps.suiteCut = suiteCutContext(repo, SUITE_REPO);
|
|
@@ -61758,16 +62257,16 @@ async function rcandInCheckout(repo, deps, previousRc, rcWasAbsent) {
|
|
|
61758
62257
|
}
|
|
61759
62258
|
}
|
|
61760
62259
|
function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr, memberSummaryFilesTr = {}) {
|
|
61761
|
-
const recordPath = (0,
|
|
61762
|
-
const base = (0,
|
|
62260
|
+
const recordPath = (0, import_node_path50.join)(suiteStateDir(root), "cut.json");
|
|
62261
|
+
const base = (0, import_node_path50.join)(suiteStateDir(root), "checkouts");
|
|
61763
62262
|
const checkout = async (repo) => {
|
|
61764
62263
|
if (repo === SUITE_REPO) return root;
|
|
61765
|
-
const target = (0,
|
|
61766
|
-
if (!(0,
|
|
61767
|
-
(0,
|
|
62264
|
+
const target = (0, import_node_path50.join)(base, repo.split("/")[1]);
|
|
62265
|
+
if (!(0, import_node_fs52.existsSync)(target)) {
|
|
62266
|
+
(0, import_node_fs52.mkdirSync)(base, { recursive: true });
|
|
61768
62267
|
await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
|
|
61769
62268
|
}
|
|
61770
|
-
if (!(0,
|
|
62269
|
+
if (!(0, import_node_fs52.existsSync)((0, import_node_path50.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
|
|
61771
62270
|
const origin = await exec("git", ["remote", "get-url", "origin"], target);
|
|
61772
62271
|
if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
|
|
61773
62272
|
throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
|
|
@@ -61831,7 +62330,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
61831
62330
|
if (repo === SUITE_REPO) return inTrain(repo, (deps) => rcandInCheckout(repo, deps, previousRc, rcWasAbsent));
|
|
61832
62331
|
const target = await checkout(repo);
|
|
61833
62332
|
return new Promise((resolveResult, reject2) => {
|
|
61834
|
-
const child2 = (0, import_node_child_process18.fork)((0,
|
|
62333
|
+
const child2 = (0, import_node_child_process18.fork)((0, import_node_path49.resolve)(process.argv[1]), ["devops", "suite", "member-rcand-internal", repo, previousRc ?? "", ...rcWasAbsent ? ["--rc-was-absent"] : []], {
|
|
61835
62334
|
cwd: target,
|
|
61836
62335
|
stdio: ["ignore", "inherit", "inherit", "ipc"],
|
|
61837
62336
|
execArgv: []
|
|
@@ -62074,9 +62573,9 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
62074
62573
|
const body = await gh(["release", "view", leadTag, "--repo", SUITE_REPO, "--json", "body", "--jq", ".body"]);
|
|
62075
62574
|
if (parseSuiteMapping(body)) return;
|
|
62076
62575
|
const dir = suiteStateDir(root);
|
|
62077
|
-
(0,
|
|
62078
|
-
const file = (0,
|
|
62079
|
-
(0,
|
|
62576
|
+
(0, import_node_fs52.mkdirSync)(dir, { recursive: true });
|
|
62577
|
+
const file = (0, import_node_path50.join)(dir, `notes-${leadTag}.md`);
|
|
62578
|
+
(0, import_node_fs52.writeFileSync)(file, `${body}
|
|
62080
62579
|
|
|
62081
62580
|
${suiteMappingBlock(leadTag, mapping)}`);
|
|
62082
62581
|
await gh(["release", "edit", leadTag, "--repo", SUITE_REPO, "--notes-file", file]);
|
|
@@ -62117,7 +62616,7 @@ ${suiteMappingBlock(leadTag, mapping)}`);
|
|
|
62117
62616
|
|
|
62118
62617
|
// src/suite-commands.ts
|
|
62119
62618
|
init_cli_shared();
|
|
62120
|
-
var
|
|
62619
|
+
var import_node_fs53 = require("node:fs");
|
|
62121
62620
|
function parseMemberSummaryFiles(entries) {
|
|
62122
62621
|
const memberSummaryFiles = {};
|
|
62123
62622
|
for (const entry of entries) {
|
|
@@ -62211,7 +62710,7 @@ function registerSuiteCommands(program3) {
|
|
|
62211
62710
|
try {
|
|
62212
62711
|
const io = suiteTrainIo(root);
|
|
62213
62712
|
retired = await retireFailedLeadRcLedger({ ledgerPath: releaseLedgerPath(root), rcHead: () => io.head(SUITE_REPO, "rc").then((h) => h ?? void 0), markAbandoned: io.markAbandoned }, o.ownerGo === true);
|
|
62214
|
-
if (retired && !(0,
|
|
62713
|
+
if (retired && !(0, import_node_fs53.existsSync)(io.recordPath)) {
|
|
62215
62714
|
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)`);
|
|
62216
62715
|
return;
|
|
62217
62716
|
}
|
|
@@ -62451,9 +62950,9 @@ function registerJevCommands(program3) {
|
|
|
62451
62950
|
|
|
62452
62951
|
// src/packages-adopt-export.ts
|
|
62453
62952
|
var import_node_child_process19 = require("node:child_process");
|
|
62454
|
-
var
|
|
62455
|
-
var
|
|
62456
|
-
var
|
|
62953
|
+
var import_node_fs54 = require("node:fs");
|
|
62954
|
+
var import_node_os25 = require("node:os");
|
|
62955
|
+
var import_node_path51 = require("node:path");
|
|
62457
62956
|
var import_node_util6 = require("node:util");
|
|
62458
62957
|
init_cli_shared();
|
|
62459
62958
|
|
|
@@ -63073,38 +63572,38 @@ function expectedFromRun(run, opts) {
|
|
|
63073
63572
|
}
|
|
63074
63573
|
async function adoptExport(opts, deps) {
|
|
63075
63574
|
if (!/^sha256:[0-9a-f]{64}$/.test(opts.digest)) throw new Error("--digest must be sha256:<64 hex>, from the export run summary");
|
|
63076
|
-
const packagePath = (0,
|
|
63077
|
-
const lockPath = (0,
|
|
63078
|
-
const packageText = (0,
|
|
63079
|
-
const lockText = (0,
|
|
63080
|
-
const work = (0,
|
|
63575
|
+
const packagePath = (0, import_node_path51.join)(opts.dir, "package.json");
|
|
63576
|
+
const lockPath = (0, import_node_path51.join)(opts.dir, "package-lock.json");
|
|
63577
|
+
const packageText = (0, import_node_fs54.readFileSync)(packagePath, "utf8");
|
|
63578
|
+
const lockText = (0, import_node_fs54.readFileSync)(lockPath, "utf8");
|
|
63579
|
+
const work = (0, import_node_fs54.mkdtempSync)((0, import_node_path51.join)((0, import_node_os25.tmpdir)(), "mmi-adopt-export-"));
|
|
63081
63580
|
try {
|
|
63082
63581
|
const run = JSON.parse((await deps.ghApi(`repos/${EXPORT_REPO}/actions/runs/${opts.run}`)).toString("utf8"));
|
|
63083
|
-
(0,
|
|
63084
|
-
(0,
|
|
63085
|
-
(0,
|
|
63086
|
-
const script = (0,
|
|
63087
|
-
(0,
|
|
63582
|
+
(0, import_node_fs54.writeFileSync)((0, import_node_path51.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
|
|
63583
|
+
(0, import_node_fs54.writeFileSync)((0, import_node_path51.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
|
|
63584
|
+
(0, import_node_fs54.writeFileSync)((0, import_node_path51.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
|
|
63585
|
+
const script = (0, import_node_path51.join)(work, "package_export.py");
|
|
63586
|
+
(0, import_node_fs54.writeFileSync)(script, package_export_default);
|
|
63088
63587
|
await deps.python(script, ["verify", work, opts.ds, opts.screens]);
|
|
63089
|
-
const verified = (0,
|
|
63090
|
-
const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0,
|
|
63588
|
+
const verified = (0, import_node_path51.join)(work, "verified");
|
|
63589
|
+
const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0, import_node_fs54.readFileSync)((0, import_node_path51.join)(verified, `${short3}.metadata.json`), "utf8"))]));
|
|
63091
63590
|
const plan = planAdoption(packageText, lockText, metadata);
|
|
63092
63591
|
for (const { name } of plan.result.adopted) {
|
|
63093
63592
|
const short3 = PACKAGES.find((p) => p.name === name).short;
|
|
63094
|
-
await deps.npm(["cache", "add", (0,
|
|
63593
|
+
await deps.npm(["cache", "add", (0, import_node_path51.join)(verified, `${short3}.tgz`)], opts.dir);
|
|
63095
63594
|
}
|
|
63096
|
-
(0,
|
|
63097
|
-
(0,
|
|
63595
|
+
(0, import_node_fs54.writeFileSync)(packagePath, plan.packageText);
|
|
63596
|
+
(0, import_node_fs54.writeFileSync)(lockPath, plan.lockText);
|
|
63098
63597
|
try {
|
|
63099
63598
|
await deps.npm(["ci", "--offline", "--ignore-scripts", "--no-audit", "--no-fund"], opts.dir);
|
|
63100
63599
|
} catch (error) {
|
|
63101
|
-
(0,
|
|
63102
|
-
(0,
|
|
63600
|
+
(0, import_node_fs54.writeFileSync)(packagePath, packageText);
|
|
63601
|
+
(0, import_node_fs54.writeFileSync)(lockPath, lockText);
|
|
63103
63602
|
throw new Error(`npm ci --offline failed, package.json and package-lock.json restored: ${error.message}`);
|
|
63104
63603
|
}
|
|
63105
63604
|
return plan.result;
|
|
63106
63605
|
} finally {
|
|
63107
|
-
(0,
|
|
63606
|
+
(0, import_node_fs54.rmSync)(work, { recursive: true, force: true });
|
|
63108
63607
|
}
|
|
63109
63608
|
}
|
|
63110
63609
|
var execFileBuffer = (0, import_node_util6.promisify)(import_node_child_process19.execFile);
|
|
@@ -63208,12 +63707,12 @@ function registerPackagesCommands(program3) {
|
|
|
63208
63707
|
init_cli_shared();
|
|
63209
63708
|
|
|
63210
63709
|
// src/wave-status.ts
|
|
63211
|
-
var
|
|
63710
|
+
var import_node_fs55 = require("node:fs");
|
|
63212
63711
|
function observeStage(worktreePath) {
|
|
63213
63712
|
const statePath = stageStatePath(worktreePath);
|
|
63214
63713
|
let raw;
|
|
63215
63714
|
try {
|
|
63216
|
-
raw = (0,
|
|
63715
|
+
raw = (0, import_node_fs55.readFileSync)(statePath, "utf8");
|
|
63217
63716
|
} catch (error) {
|
|
63218
63717
|
if (error.code === "ENOENT") return { state: "ok", value: { running: false } };
|
|
63219
63718
|
return { state: "unavailable", reason: "stage state could not be read" };
|
|
@@ -63280,9 +63779,9 @@ async function collectWaveStatus(deps) {
|
|
|
63280
63779
|
// src/discovery-commands.ts
|
|
63281
63780
|
init_github_client();
|
|
63282
63781
|
init_cli_shared();
|
|
63283
|
-
var
|
|
63284
|
-
var
|
|
63285
|
-
var
|
|
63782
|
+
var import_node_fs56 = require("node:fs");
|
|
63783
|
+
var import_node_os26 = require("node:os");
|
|
63784
|
+
var import_node_path52 = require("node:path");
|
|
63286
63785
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
63287
63786
|
var defaultStatusDeps = {
|
|
63288
63787
|
repo: resolveRepo,
|
|
@@ -63501,10 +64000,10 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
63501
64000
|
else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
|
|
63502
64001
|
else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
|
|
63503
64002
|
}
|
|
63504
|
-
const home = (0,
|
|
64003
|
+
const home = (0, import_node_os26.homedir)();
|
|
63505
64004
|
const plugin = onboardPluginGate({
|
|
63506
|
-
readKnown: () => readFileSyncSafe((0,
|
|
63507
|
-
readSettings: () => readFileSyncSafe((0,
|
|
64005
|
+
readKnown: () => readFileSyncSafe((0, import_node_path52.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs56.readFileSync),
|
|
64006
|
+
readSettings: () => readFileSyncSafe((0, import_node_path52.join)(home, ".claude", "settings.json"), import_node_fs56.readFileSync)
|
|
63508
64007
|
});
|
|
63509
64008
|
return {
|
|
63510
64009
|
guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
|
|
@@ -65690,7 +66189,7 @@ function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
|
|
|
65690
66189
|
}
|
|
65691
66190
|
|
|
65692
66191
|
// src/api-read.ts
|
|
65693
|
-
var
|
|
66192
|
+
var import_node_fs57 = require("node:fs");
|
|
65694
66193
|
|
|
65695
66194
|
// src/run-log-read.ts
|
|
65696
66195
|
init_cli_shared();
|
|
@@ -65887,7 +66386,7 @@ var BROKER_MAX_BUFFER = 64 * 1024 * 1024;
|
|
|
65887
66386
|
var PR_QUEUE_QUERY = "query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){mergeQueueEntry{state position enqueuedAt estimatedTimeToMerge headCommit{oid}}}}}";
|
|
65888
66387
|
function missingBrokerPrRepoMessage(subcommand, number) {
|
|
65889
66388
|
const example = subcommand === "list" ? "mmi-cli devops read pr list --repo <owner/repo> --state open --json number,title,headRefName" : subcommand === "diff" ? `mmi-cli devops read pr diff ${number ?? "<number>"} --repo <owner/repo> --name-only` : `mmi-cli devops read pr queue ${number ?? "<number>"} --repo <owner/repo> --json`;
|
|
65890
|
-
return `read pr ${subcommand}: required option '--repo <owner/repo>' not specified \u2014 broker PR ${subcommand} reads
|
|
66389
|
+
return `read pr ${subcommand}: required option '--repo <owner/repo>' not specified \u2014 broker PR ${subcommand} reads could not resolve the repo from git origin; pass --repo <owner/repo> explicitly (e.g. \`${example}\`)`;
|
|
65891
66390
|
}
|
|
65892
66391
|
function buildPrQueueSpec(number, o) {
|
|
65893
66392
|
const repo = parseBrokerRepo(o.repo, "--repo");
|
|
@@ -66282,25 +66781,38 @@ function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
|
|
|
66282
66781
|
}
|
|
66283
66782
|
await dispatchGh(spec, "read run watch");
|
|
66284
66783
|
});
|
|
66784
|
+
const resolvePrRepo = async (raw, subcommand, number) => {
|
|
66785
|
+
if (raw) return raw;
|
|
66786
|
+
try {
|
|
66787
|
+
const resolved = await resolveRepo2(void 0);
|
|
66788
|
+
if (resolved) return parseBrokerRepo(resolved, "--repo");
|
|
66789
|
+
} catch {
|
|
66790
|
+
}
|
|
66791
|
+
fail(missingBrokerPrRepoMessage(subcommand, number));
|
|
66792
|
+
return void 0;
|
|
66793
|
+
};
|
|
66285
66794
|
const pr = read.command("pr").description("pull-request reads \u2014 view/list/diff ride gh; queue uses fixed GraphQL; checks is REST-backed (deterministic closed-custody adapter; native was EMPTY only on the historical #3035 fixture)");
|
|
66286
|
-
pr.command("list").description("list viewer-free PRs (author/viewer filters stay personal)").option("--repo <owner/repo>", "target repo (
|
|
66287
|
-
|
|
66795
|
+
pr.command("list").description("list viewer-free PRs (author/viewer filters stay personal)").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--json [fields]", "native gh JSON fields", jsonOpt).addOption(new Option("--state <state>", "open|closed|merged|all").choices(["open", "closed", "merged", "all"])).option("--base <branch>", "base branch", branchOpt).option("--head <branch>", "head branch", branchOpt).option("--label <label>", "filter by label", labelOpt).option("--search <query>", "viewer-free search", (v) => parseBrokerPrSearch(v)).option("--assignee <login>", "explicit assignee (viewer refused)", assigneeOpt).option("--limit <n>", "maximum PRs (1-100; larger values clamp to 100)", limitOpt).option("--jq <expr>", JQ_HELP, jqOpt).action(async (o) => {
|
|
66796
|
+
const repo = await resolvePrRepo(o.repo, "list");
|
|
66797
|
+
if (!repo) return;
|
|
66288
66798
|
let spec;
|
|
66289
66799
|
try {
|
|
66290
66800
|
requireJqJson(o.jq, o.json);
|
|
66291
|
-
spec = buildPrListSpec(o);
|
|
66801
|
+
spec = buildPrListSpec({ ...o, repo });
|
|
66292
66802
|
} catch (e) {
|
|
66293
66803
|
return fail(e.message);
|
|
66294
66804
|
}
|
|
66295
66805
|
await dispatchGh(spec, "read pr list", o.jq);
|
|
66296
66806
|
});
|
|
66297
|
-
pr.command("diff").description("read a bounded PR diff on the App pool").option("--repo <owner/repo>", "target repo (
|
|
66298
|
-
|
|
66299
|
-
|
|
66807
|
+
pr.command("diff").description("read a bounded PR diff on the App pool").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--name-only", "only changed file names").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
|
|
66808
|
+
const repo = await resolvePrRepo(o.repo, "diff", number);
|
|
66809
|
+
if (!repo) return;
|
|
66810
|
+
await dispatchGh(buildPrDiffSpec(number, { ...o, repo }), "read pr diff");
|
|
66300
66811
|
});
|
|
66301
|
-
pr.command("queue").description("read one PR mergeQueueEntry (fixed GraphQL query, no permission widening)").option("--repo <owner/repo>", "target repo (
|
|
66302
|
-
|
|
66303
|
-
|
|
66812
|
+
pr.command("queue").description("read one PR mergeQueueEntry (fixed GraphQL query, no permission widening)").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--json", "machine-readable mergeQueueEntry (null when not queued)").argument("<number>", "PR number", idOpt("PR number")).action(async (number, o) => {
|
|
66813
|
+
const repo = await resolvePrRepo(o.repo, "queue", number);
|
|
66814
|
+
if (!repo) return;
|
|
66815
|
+
const spec = buildPrQueueSpec(number, { ...o, repo });
|
|
66304
66816
|
requireBrokerApp("read pr queue");
|
|
66305
66817
|
try {
|
|
66306
66818
|
process.exitCode = await runBrokerGh(async (args, opts) => {
|
|
@@ -66581,7 +67093,7 @@ function registerApiReadCommands(program3, overrides) {
|
|
|
66581
67093
|
);
|
|
66582
67094
|
if (o.jq !== void 0) body = await jqRun(o.jq, body, localJqEnv());
|
|
66583
67095
|
if (zip) {
|
|
66584
|
-
(0,
|
|
67096
|
+
(0, import_node_fs57.writeFileSync)(o.out, body, "latin1");
|
|
66585
67097
|
return;
|
|
66586
67098
|
}
|
|
66587
67099
|
} catch (e) {
|
|
@@ -67497,8 +68009,8 @@ function registerSessionReport(program3) {
|
|
|
67497
68009
|
}
|
|
67498
68010
|
|
|
67499
68011
|
// src/stage-commands.ts
|
|
67500
|
-
var
|
|
67501
|
-
var
|
|
68012
|
+
var import_node_fs58 = require("node:fs");
|
|
68013
|
+
var import_node_path53 = require("node:path");
|
|
67502
68014
|
init_cli_shared();
|
|
67503
68015
|
init_clean_exit();
|
|
67504
68016
|
|
|
@@ -67746,8 +68258,8 @@ function registerStageCommands(program3) {
|
|
|
67746
68258
|
const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
|
|
67747
68259
|
const resolution = decideStage({
|
|
67748
68260
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
67749
|
-
hasCompose: (0,
|
|
67750
|
-
hasEnvExample: (0,
|
|
68261
|
+
hasCompose: (0, import_node_fs58.existsSync)((0, import_node_path53.join)(process.cwd(), "docker-compose.yml")),
|
|
68262
|
+
hasEnvExample: (0, import_node_fs58.existsSync)((0, import_node_path53.join)(process.cwd(), ".env.example")),
|
|
67751
68263
|
repo
|
|
67752
68264
|
});
|
|
67753
68265
|
if (resolution.source === "derived" && resolution.derived) {
|
|
@@ -68039,20 +68551,20 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
68039
68551
|
}
|
|
68040
68552
|
|
|
68041
68553
|
// src/tenant-artifact.ts
|
|
68042
|
-
var
|
|
68043
|
-
var
|
|
68554
|
+
var import_node_crypto20 = require("node:crypto");
|
|
68555
|
+
var import_node_fs59 = require("node:fs");
|
|
68044
68556
|
var import_promises11 = require("node:fs/promises");
|
|
68045
|
-
var
|
|
68557
|
+
var import_node_path54 = require("node:path");
|
|
68046
68558
|
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}$/;
|
|
68047
68559
|
var MAX_BYTES2 = 5 * 1024 * 1024 * 1024;
|
|
68048
68560
|
async function sha256File(path2) {
|
|
68049
|
-
const hash = (0,
|
|
68050
|
-
for await (const chunk of (0,
|
|
68561
|
+
const hash = (0, import_node_crypto20.createHash)("sha256");
|
|
68562
|
+
for await (const chunk of (0, import_node_fs59.createReadStream)(path2)) hash.update(chunk);
|
|
68051
68563
|
return hash.digest("hex");
|
|
68052
68564
|
}
|
|
68053
68565
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
68054
68566
|
if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
|
|
68055
|
-
const path2 = (0,
|
|
68567
|
+
const path2 = (0, import_node_path54.resolve)(inputPath);
|
|
68056
68568
|
const info = await (0, import_promises11.stat)(path2);
|
|
68057
68569
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
68058
68570
|
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`);
|
|
@@ -68070,7 +68582,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
68070
68582
|
return [key, value];
|
|
68071
68583
|
}));
|
|
68072
68584
|
headers["content-length"] = String(info.size);
|
|
68073
|
-
const stream = (0,
|
|
68585
|
+
const stream = (0, import_node_fs59.createReadStream)(path2);
|
|
68074
68586
|
let uploaded;
|
|
68075
68587
|
try {
|
|
68076
68588
|
uploaded = await fetch(body.uploadUrl, {
|
|
@@ -68424,7 +68936,7 @@ function renderVerifySecrets(body) {
|
|
|
68424
68936
|
}
|
|
68425
68937
|
|
|
68426
68938
|
// src/command-register-developer.ts
|
|
68427
|
-
var
|
|
68939
|
+
var import_node_fs64 = require("node:fs");
|
|
68428
68940
|
init_clean_exit();
|
|
68429
68941
|
init_cli_shared();
|
|
68430
68942
|
init_error_codes();
|
|
@@ -68457,12 +68969,12 @@ async function resolveWhoami(deps) {
|
|
|
68457
68969
|
}
|
|
68458
68970
|
|
|
68459
68971
|
// src/command-register-developer.ts
|
|
68460
|
-
var
|
|
68972
|
+
var import_node_path61 = require("node:path");
|
|
68461
68973
|
|
|
68462
68974
|
// src/doc-refs-command-source.ts
|
|
68463
68975
|
var import_node_child_process21 = require("node:child_process");
|
|
68464
|
-
var
|
|
68465
|
-
var
|
|
68976
|
+
var import_node_fs60 = require("node:fs");
|
|
68977
|
+
var import_node_path55 = require("node:path");
|
|
68466
68978
|
var BUNDLE_MANIFEST_TIMEOUT_MS = 12e4;
|
|
68467
68979
|
var BUNDLE_MANIFEST_MAX_BUFFER = 64 * 1024 * 1024;
|
|
68468
68980
|
var flagsOf = (options) => new Set((options ?? []).flatMap((option) => String(option?.flags ?? "").match(/--[\w-]+/g) ?? []));
|
|
@@ -68483,7 +68995,7 @@ function inventoryFromManifest(manifest, source) {
|
|
|
68483
68995
|
function readBundleManifest(bundle, exec3 = import_node_child_process21.execFileSync) {
|
|
68484
68996
|
try {
|
|
68485
68997
|
const text = exec3(process.execPath, [bundle, "commands", "--all", "--json"], {
|
|
68486
|
-
cwd: (0,
|
|
68998
|
+
cwd: (0, import_node_path55.dirname)(bundle),
|
|
68487
68999
|
encoding: "utf8",
|
|
68488
69000
|
windowsHide: true,
|
|
68489
69001
|
timeout: BUNDLE_MANIFEST_TIMEOUT_MS,
|
|
@@ -68499,19 +69011,19 @@ function readBundleManifest(bundle, exec3 = import_node_child_process21.execFile
|
|
|
68499
69011
|
function isRunningBundle(bundle, argv1 = process.argv[1], platform2 = process.platform) {
|
|
68500
69012
|
if (!argv1) return false;
|
|
68501
69013
|
const norm2 = (path2) => {
|
|
68502
|
-
const absolute = (0,
|
|
69014
|
+
const absolute = (0, import_node_path55.resolve)(path2);
|
|
68503
69015
|
return platform2 === "win32" ? absolute.toLowerCase() : absolute;
|
|
68504
69016
|
};
|
|
68505
69017
|
const running = norm2(argv1);
|
|
68506
|
-
if (!["main.cjs", "index.cjs"].includes((0,
|
|
68507
|
-
return (0,
|
|
69018
|
+
if (!["main.cjs", "index.cjs"].includes((0, import_node_path55.basename)(running))) return false;
|
|
69019
|
+
return (0, import_node_path55.dirname)(running) === (0, import_node_path55.dirname)(norm2(bundle));
|
|
68508
69020
|
}
|
|
68509
69021
|
function resolveCommandInventory(deps) {
|
|
68510
|
-
const exists = deps.exists ??
|
|
69022
|
+
const exists = deps.exists ?? import_node_fs60.existsSync;
|
|
68511
69023
|
const read = deps.read ?? ((bundle) => readBundleManifest(bundle));
|
|
68512
69024
|
const warnings = [];
|
|
68513
69025
|
if (deps.from) {
|
|
68514
|
-
const bundle = (0,
|
|
69026
|
+
const bundle = (0, import_node_path55.isAbsolute)(deps.from) ? deps.from : (0, import_node_path55.resolve)(deps.root, deps.from);
|
|
68515
69027
|
if (!exists(bundle)) {
|
|
68516
69028
|
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)`);
|
|
68517
69029
|
}
|
|
@@ -68521,7 +69033,7 @@ function resolveCommandInventory(deps) {
|
|
|
68521
69033
|
}
|
|
68522
69034
|
return { inventory: inventory2, warnings };
|
|
68523
69035
|
}
|
|
68524
|
-
const treeBundle = (0,
|
|
69036
|
+
const treeBundle = (0, import_node_path55.join)(deps.root, "cli", "dist", "main.cjs");
|
|
68525
69037
|
if (!exists(treeBundle) || isRunningBundle(treeBundle, deps.argv1, deps.platform)) {
|
|
68526
69038
|
return { inventory: deps.inProcess(), warnings };
|
|
68527
69039
|
}
|
|
@@ -68638,28 +69150,28 @@ async function executeWaveLand(plan, deps) {
|
|
|
68638
69150
|
|
|
68639
69151
|
// src/dist-drift.ts
|
|
68640
69152
|
var import_node_child_process22 = require("node:child_process");
|
|
69153
|
+
var import_node_crypto22 = require("node:crypto");
|
|
69154
|
+
var import_node_fs62 = require("node:fs");
|
|
69155
|
+
var import_node_os27 = require("node:os");
|
|
69156
|
+
var import_node_path57 = require("node:path");
|
|
69157
|
+
|
|
69158
|
+
// ../scripts/distribution-digest.mjs
|
|
68641
69159
|
var import_node_crypto21 = require("node:crypto");
|
|
68642
69160
|
var import_node_fs61 = require("node:fs");
|
|
68643
|
-
var import_node_os26 = require("node:os");
|
|
68644
69161
|
var import_node_path56 = require("node:path");
|
|
68645
|
-
|
|
68646
|
-
// ../scripts/distribution-digest.mjs
|
|
68647
|
-
var import_node_crypto20 = require("node:crypto");
|
|
68648
|
-
var import_node_fs60 = require("node:fs");
|
|
68649
|
-
var import_node_path55 = require("node:path");
|
|
68650
69162
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
68651
69163
|
function repoPath(root, declaredPath, label) {
|
|
68652
|
-
const absoluteRoot = (0,
|
|
68653
|
-
const target = (0,
|
|
68654
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
69164
|
+
const absoluteRoot = (0, import_node_path56.resolve)(root);
|
|
69165
|
+
const target = (0, import_node_path56.resolve)(root, declaredPath);
|
|
69166
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path56.sep}`)) {
|
|
68655
69167
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
68656
69168
|
}
|
|
68657
69169
|
return target;
|
|
68658
69170
|
}
|
|
68659
69171
|
function digestFiles(files) {
|
|
68660
|
-
const hash = (0,
|
|
69172
|
+
const hash = (0, import_node_crypto21.createHash)("sha256");
|
|
68661
69173
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
68662
|
-
const content = file.stat.isSymbolicLink() ? Buffer.from((0,
|
|
69174
|
+
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs61.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs61.readFileSync)(file.absolute);
|
|
68663
69175
|
hash.update(file.relative, "utf8");
|
|
68664
69176
|
hash.update("\0");
|
|
68665
69177
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -68674,8 +69186,8 @@ function digestFiles(files) {
|
|
|
68674
69186
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
68675
69187
|
return digestFiles(packedFiles.map((path2) => {
|
|
68676
69188
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
68677
|
-
if (!(0,
|
|
68678
|
-
return { absolute, relative: slash(path2), stat: (0,
|
|
69189
|
+
if (!(0, import_node_fs61.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
|
|
69190
|
+
return { absolute, relative: slash(path2), stat: (0, import_node_fs61.lstatSync)(absolute) };
|
|
68679
69191
|
}));
|
|
68680
69192
|
}
|
|
68681
69193
|
|
|
@@ -68688,7 +69200,7 @@ var DIST_ARTIFACTS = [
|
|
|
68688
69200
|
];
|
|
68689
69201
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
68690
69202
|
var ABSENT = "absent";
|
|
68691
|
-
var sha2562 = (bytes) => `sha256:${(0,
|
|
69203
|
+
var sha2562 = (bytes) => `sha256:${(0, import_node_crypto22.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
68692
69204
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
68693
69205
|
const committed = committedBytes ? sha2562(committedBytes) : ABSENT;
|
|
68694
69206
|
const rebuiltExpected = rebuiltBytes ? sha2562(rebuiltBytes) : ABSENT;
|
|
@@ -68760,13 +69272,13 @@ function renderDistDriftReceipt(receipt) {
|
|
|
68760
69272
|
return lines2;
|
|
68761
69273
|
}
|
|
68762
69274
|
function readOrNull(path2) {
|
|
68763
|
-
return (0,
|
|
69275
|
+
return (0, import_node_fs62.existsSync)(path2) ? (0, import_node_fs62.readFileSync)(path2) : null;
|
|
68764
69276
|
}
|
|
68765
69277
|
function walkFiles(root) {
|
|
68766
69278
|
const files = [];
|
|
68767
69279
|
const walk2 = (directory) => {
|
|
68768
|
-
for (const entry of (0,
|
|
68769
|
-
const child2 = (0,
|
|
69280
|
+
for (const entry of (0, import_node_fs62.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
69281
|
+
const child2 = (0, import_node_path57.join)(directory, entry.name);
|
|
68770
69282
|
if (entry.isDirectory()) walk2(child2);
|
|
68771
69283
|
else files.push(child2);
|
|
68772
69284
|
}
|
|
@@ -68776,10 +69288,10 @@ function walkFiles(root) {
|
|
|
68776
69288
|
}
|
|
68777
69289
|
function bomPathFor(root) {
|
|
68778
69290
|
try {
|
|
68779
|
-
const registry2 = JSON.parse((0,
|
|
68780
|
-
return (0,
|
|
69291
|
+
const registry2 = JSON.parse((0, import_node_fs62.readFileSync)((0, import_node_path57.join)(root, "surfaces.json"), "utf8"));
|
|
69292
|
+
return (0, import_node_path57.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
68781
69293
|
} catch {
|
|
68782
|
-
return (0,
|
|
69294
|
+
return (0, import_node_path57.join)(root, "distribution-bom.json");
|
|
68783
69295
|
}
|
|
68784
69296
|
}
|
|
68785
69297
|
function rebuildTo(packageRoot, outDir) {
|
|
@@ -68792,40 +69304,40 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
68792
69304
|
});
|
|
68793
69305
|
}
|
|
68794
69306
|
function runDistStatus(root) {
|
|
68795
|
-
const stage = (0,
|
|
69307
|
+
const stage = (0, import_node_fs62.mkdtempSync)((0, import_node_path57.join)((0, import_node_os27.tmpdir)(), "mmi-dist-drift-"));
|
|
68796
69308
|
let overlayCount = 0;
|
|
68797
69309
|
try {
|
|
68798
|
-
const cliOut = (0,
|
|
68799
|
-
const hubOut = (0,
|
|
68800
|
-
rebuildTo((0,
|
|
68801
|
-
rebuildTo((0,
|
|
69310
|
+
const cliOut = (0, import_node_path57.join)(stage, "cli-dist");
|
|
69311
|
+
const hubOut = (0, import_node_path57.join)(stage, "hub-dist");
|
|
69312
|
+
rebuildTo((0, import_node_path57.join)(root, "cli"), cliOut);
|
|
69313
|
+
rebuildTo((0, import_node_path57.join)(root, "updater"), hubOut);
|
|
68802
69314
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
68803
69315
|
const rebuilt = (path2) => {
|
|
68804
69316
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
68805
|
-
return spec ? readOrNull((0,
|
|
69317
|
+
return spec ? readOrNull((0, import_node_path57.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
68806
69318
|
};
|
|
68807
|
-
const committed = (path2) => readOrNull((0,
|
|
68808
|
-
const tree = (path2) => readOrNull((0,
|
|
68809
|
-
const distRoot = (0,
|
|
68810
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
68811
|
-
const bom = JSON.parse((0,
|
|
69319
|
+
const committed = (path2) => readOrNull((0, import_node_path57.join)(root, path2));
|
|
69320
|
+
const tree = (path2) => readOrNull((0, import_node_path57.join)(root, path2));
|
|
69321
|
+
const distRoot = (0, import_node_path57.join)(root, "cli", "dist");
|
|
69322
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path57.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
69323
|
+
const bom = JSON.parse((0, import_node_fs62.readFileSync)(bomPathFor(root), "utf8"));
|
|
68812
69324
|
const digest = (entries) => {
|
|
68813
|
-
const overlay = (0,
|
|
69325
|
+
const overlay = (0, import_node_path57.join)(stage, `overlay-${overlayCount++}`);
|
|
68814
69326
|
for (const entry of entries) {
|
|
68815
|
-
const target = (0,
|
|
68816
|
-
(0,
|
|
68817
|
-
(0,
|
|
69327
|
+
const target = (0, import_node_path57.join)(overlay, entry.path);
|
|
69328
|
+
(0, import_node_fs62.mkdirSync)((0, import_node_path57.dirname)(target), { recursive: true });
|
|
69329
|
+
(0, import_node_fs62.writeFileSync)(target, entry.bytes);
|
|
68818
69330
|
}
|
|
68819
69331
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
68820
69332
|
};
|
|
68821
69333
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
68822
69334
|
} finally {
|
|
68823
|
-
(0,
|
|
69335
|
+
(0, import_node_fs62.rmSync)(stage, { recursive: true, force: true });
|
|
68824
69336
|
}
|
|
68825
69337
|
}
|
|
68826
69338
|
|
|
68827
69339
|
// src/doc-refs-live.ts
|
|
68828
|
-
var
|
|
69340
|
+
var import_node_path58 = require("node:path");
|
|
68829
69341
|
var concrete = (value) => !!value && !/[<>{}$]/.test(value);
|
|
68830
69342
|
var liveOption = (tokens2, flag) => {
|
|
68831
69343
|
const index = tokens2.indexOf(flag);
|
|
@@ -68898,7 +69410,7 @@ async function runLiveDocRefs(root, deps, live) {
|
|
|
68898
69410
|
const offline = runDocRefs(root, { ...deps, runbooksOnly: true });
|
|
68899
69411
|
const read = deps.readFile ?? readFileOrNull;
|
|
68900
69412
|
const docs = Object.fromEntries((deps.listDocs ?? defaultListDocs)(root).flatMap((doc) => {
|
|
68901
|
-
const body = read((0,
|
|
69413
|
+
const body = read((0, import_node_path58.join)(root, doc));
|
|
68902
69414
|
return body !== null && isRunbook(doc, body) && !deps.isIgnored?.([doc]).has(doc) ? [[doc, body]] : [];
|
|
68903
69415
|
}));
|
|
68904
69416
|
const remote = await checkRunbookLive(docs, live);
|
|
@@ -69109,7 +69621,7 @@ async function diagnoseHub(deps) {
|
|
|
69109
69621
|
|
|
69110
69622
|
// src/schedules-lift-command.ts
|
|
69111
69623
|
var import_promises12 = require("node:fs/promises");
|
|
69112
|
-
var
|
|
69624
|
+
var import_node_path59 = require("node:path");
|
|
69113
69625
|
init_clean_exit();
|
|
69114
69626
|
init_cli_shared();
|
|
69115
69627
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -69138,7 +69650,7 @@ async function readWorkflowFiles(dir) {
|
|
|
69138
69650
|
const files = [];
|
|
69139
69651
|
for (const name of names.sort()) {
|
|
69140
69652
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
69141
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0,
|
|
69653
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0, import_node_path59.join)(dir, name), "utf8") });
|
|
69142
69654
|
}
|
|
69143
69655
|
return files;
|
|
69144
69656
|
}
|
|
@@ -69221,8 +69733,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
69221
69733
|
|
|
69222
69734
|
// src/spawn-policy-core.ts
|
|
69223
69735
|
var import_node_child_process24 = require("node:child_process");
|
|
69224
|
-
var
|
|
69225
|
-
var
|
|
69736
|
+
var import_node_fs63 = require("node:fs");
|
|
69737
|
+
var import_node_path60 = require("node:path");
|
|
69226
69738
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
69227
69739
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
69228
69740
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -69308,7 +69820,7 @@ function runSpawnPolicy(root) {
|
|
|
69308
69820
|
for (const file of files) {
|
|
69309
69821
|
let raw;
|
|
69310
69822
|
try {
|
|
69311
|
-
raw = (0,
|
|
69823
|
+
raw = (0, import_node_fs63.readFileSync)((0, import_node_path60.join)(root, file), "utf8");
|
|
69312
69824
|
} catch {
|
|
69313
69825
|
continue;
|
|
69314
69826
|
}
|
|
@@ -69328,19 +69840,19 @@ function runSpawnPolicy(root) {
|
|
|
69328
69840
|
function registerDeveloperCommands(program3) {
|
|
69329
69841
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
69330
69842
|
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) => {
|
|
69331
|
-
const path2 = (0,
|
|
69332
|
-
const current = (0,
|
|
69843
|
+
const path2 = (0, import_node_path61.join)(process.cwd(), ".gitignore");
|
|
69844
|
+
const current = (0, import_node_fs64.existsSync)(path2) ? (0, import_node_fs64.readFileSync)(path2, "utf8") : null;
|
|
69333
69845
|
const plan = planManagedGitignore(current);
|
|
69334
69846
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
69335
69847
|
if (opts.json) {
|
|
69336
|
-
if (opts.write && plan.changed) (0,
|
|
69848
|
+
if (opts.write && plan.changed) (0, import_node_fs64.writeFileSync)(path2, plan.content, "utf8");
|
|
69337
69849
|
console.log(JSON.stringify(plan, null, 2));
|
|
69338
69850
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
69339
69851
|
return;
|
|
69340
69852
|
}
|
|
69341
69853
|
if (opts.write) {
|
|
69342
69854
|
if (plan.changed) {
|
|
69343
|
-
(0,
|
|
69855
|
+
(0, import_node_fs64.writeFileSync)(path2, plan.content, "utf8");
|
|
69344
69856
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
69345
69857
|
} else {
|
|
69346
69858
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -69657,12 +70169,12 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
69657
70169
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
69658
70170
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
69659
70171
|
function envHealLockPath(home) {
|
|
69660
|
-
return (0,
|
|
70172
|
+
return (0, import_node_path62.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
69661
70173
|
}
|
|
69662
70174
|
async function withEnvHealLock(what, run) {
|
|
69663
70175
|
try {
|
|
69664
70176
|
return await withFileLock(
|
|
69665
|
-
envHealLockPath((0,
|
|
70177
|
+
envHealLockPath((0, import_node_os28.homedir)()),
|
|
69666
70178
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
69667
70179
|
run
|
|
69668
70180
|
);
|
|
@@ -69760,7 +70272,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69760
70272
|
const configRoot = surfaceConfigRoot(surface);
|
|
69761
70273
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
69762
70274
|
const plan = buildPluginCachePlan(
|
|
69763
|
-
(0,
|
|
70275
|
+
(0, import_node_os28.homedir)(),
|
|
69764
70276
|
running,
|
|
69765
70277
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
69766
70278
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -69788,14 +70300,14 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69788
70300
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
69789
70301
|
const installed = installedActivePluginVersion(surface);
|
|
69790
70302
|
const plan = buildPluginCachePlan(
|
|
69791
|
-
(0,
|
|
70303
|
+
(0, import_node_os28.homedir)(),
|
|
69792
70304
|
running,
|
|
69793
70305
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
69794
70306
|
{ configRoot, includeStaging: surface !== "codex", installedVersion: installed }
|
|
69795
70307
|
);
|
|
69796
70308
|
const result = applyPluginCachePlan(
|
|
69797
70309
|
plan,
|
|
69798
|
-
(p) => (0,
|
|
70310
|
+
(p) => (0, import_node_fs65.rmSync)(p, { recursive: true }),
|
|
69799
70311
|
stagingApplyFsGuard(configRoot)
|
|
69800
70312
|
);
|
|
69801
70313
|
return {
|
|
@@ -69830,7 +70342,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69830
70342
|
// has no generated routing index would
|
|
69831
70343
|
// get a permanent — demanding an artifact it never asked for.
|
|
69832
70344
|
docsIndexState: (root) => {
|
|
69833
|
-
if (!(0,
|
|
70345
|
+
if (!(0, import_node_fs65.existsSync)((0, import_node_path62.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
69834
70346
|
const real = createDocsIndexDeps(root);
|
|
69835
70347
|
let docs;
|
|
69836
70348
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -69839,7 +70351,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69839
70351
|
},
|
|
69840
70352
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
69841
70353
|
healDocsIndex: (root) => {
|
|
69842
|
-
if (!(0,
|
|
70354
|
+
if (!(0, import_node_fs65.existsSync)((0, import_node_path62.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
69843
70355
|
const real = createDocsIndexDeps(root);
|
|
69844
70356
|
let docs;
|
|
69845
70357
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -70209,7 +70721,7 @@ function appActorDeps() {
|
|
|
70209
70721
|
fetch: (url, init) => fetch(url, init)
|
|
70210
70722
|
};
|
|
70211
70723
|
}
|
|
70212
|
-
setPollTokenProvider(async () => activeAppToken() ?? pollTokenOrUndefined(appActorDeps()));
|
|
70724
|
+
setPollTokenProvider(async (o) => (o?.fresh ? void 0 : activeAppToken()) ?? pollTokenOrUndefined(appActorDeps()));
|
|
70213
70725
|
setGhReadTokenProvider(pollToken);
|
|
70214
70726
|
setGitHubReadTokenProvider(pollToken);
|
|
70215
70727
|
var filingToken;
|
|
@@ -70217,7 +70729,10 @@ setGhWriteTokenProvider(() => {
|
|
|
70217
70729
|
if (process.env.MMI_ACTOR?.trim() === "app") {
|
|
70218
70730
|
return mintFilingWriteToken(appActorDeps()).then((t) => t.token);
|
|
70219
70731
|
}
|
|
70220
|
-
return filingToken ??= mintFilingWriteToken(appActorDeps()).then((t) => t.token, () =>
|
|
70732
|
+
return filingToken ??= mintFilingWriteToken(appActorDeps()).then((t) => t.token, (e) => {
|
|
70733
|
+
if (e instanceof AppActorError && e.code === "vault-denied") return void 0;
|
|
70734
|
+
throw e;
|
|
70735
|
+
});
|
|
70221
70736
|
});
|
|
70222
70737
|
setClaimWriteTokenProvider(() => mintFilingWriteToken(appActorDeps()).then((t) => t.token));
|
|
70223
70738
|
setStatusWriteTokenProvider(() => mintStatusWriteToken(appActorDeps()));
|
|
@@ -70624,7 +71139,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
70624
71139
|
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`);
|
|
70625
71140
|
if (o.secretsFile) {
|
|
70626
71141
|
try {
|
|
70627
|
-
vars.push(`secrets=${(0,
|
|
71142
|
+
vars.push(`secrets=${(0, import_node_fs65.readFileSync)(o.secretsFile, "utf8")}`);
|
|
70628
71143
|
} catch (e) {
|
|
70629
71144
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
70630
71145
|
}
|
|
@@ -71016,16 +71531,16 @@ function ciAuditDeps2() {
|
|
|
71016
71531
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
71017
71532
|
readSeedFile: (path2) => {
|
|
71018
71533
|
if (!root) return null;
|
|
71019
|
-
const fullPath = (0,
|
|
71020
|
-
return (0,
|
|
71534
|
+
const fullPath = (0, import_node_path62.join)(root, path2);
|
|
71535
|
+
return (0, import_node_fs65.existsSync)(fullPath) ? (0, import_node_fs65.readFileSync)(fullPath, "utf8") : null;
|
|
71021
71536
|
}
|
|
71022
71537
|
};
|
|
71023
71538
|
}
|
|
71024
71539
|
function hubRoot2() {
|
|
71025
|
-
const fromPkg = (0,
|
|
71540
|
+
const fromPkg = (0, import_node_path62.join)(__dirname, "..", "..");
|
|
71026
71541
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
71027
|
-
if ((0,
|
|
71028
|
-
if ((0,
|
|
71542
|
+
if ((0, import_node_fs65.existsSync)((0, import_node_path62.join)(fromPkg, marker))) return fromPkg;
|
|
71543
|
+
if ((0, import_node_fs65.existsSync)((0, import_node_path62.join)(process.cwd(), marker))) return process.cwd();
|
|
71029
71544
|
return null;
|
|
71030
71545
|
}
|
|
71031
71546
|
registerQueryCommands(program2);
|
|
@@ -71166,10 +71681,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
71166
71681
|
targets = resolution.targets;
|
|
71167
71682
|
}
|
|
71168
71683
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
71169
|
-
const fileMatrix = (0,
|
|
71684
|
+
const fileMatrix = (0, import_node_fs65.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs65.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
71170
71685
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
71171
71686
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
71172
|
-
const sanctioned = (0,
|
|
71687
|
+
const sanctioned = (0, import_node_fs65.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs65.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
71173
71688
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
71174
71689
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
71175
71690
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -71200,16 +71715,16 @@ function directoryBytes(path2) {
|
|
|
71200
71715
|
let total = 0;
|
|
71201
71716
|
let entries;
|
|
71202
71717
|
try {
|
|
71203
|
-
entries = (0,
|
|
71718
|
+
entries = (0, import_node_fs65.readdirSync)(path2, { withFileTypes: true });
|
|
71204
71719
|
} catch {
|
|
71205
71720
|
return 0;
|
|
71206
71721
|
}
|
|
71207
71722
|
for (const entry of entries) {
|
|
71208
|
-
const child2 = (0,
|
|
71723
|
+
const child2 = (0, import_node_path62.join)(path2, entry.name);
|
|
71209
71724
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
71210
71725
|
else {
|
|
71211
71726
|
try {
|
|
71212
|
-
total += (0,
|
|
71727
|
+
total += (0, import_node_fs65.statSync)(child2).size;
|
|
71213
71728
|
} catch {
|
|
71214
71729
|
}
|
|
71215
71730
|
}
|
|
@@ -71217,25 +71732,25 @@ function directoryBytes(path2) {
|
|
|
71217
71732
|
return total;
|
|
71218
71733
|
}
|
|
71219
71734
|
function listDirEntries(dir) {
|
|
71220
|
-
return (0,
|
|
71735
|
+
return (0, import_node_fs65.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
71221
71736
|
}
|
|
71222
71737
|
function readInstalledPluginRefs(configRoot) {
|
|
71223
71738
|
const p = installedPluginsPathForConfig(configRoot);
|
|
71224
|
-
if (!(0,
|
|
71739
|
+
if (!(0, import_node_fs65.existsSync)(p)) return [];
|
|
71225
71740
|
try {
|
|
71226
|
-
return installedPluginPaths((0,
|
|
71741
|
+
return installedPluginPaths((0, import_node_fs65.readFileSync)(p, "utf8"));
|
|
71227
71742
|
} catch {
|
|
71228
71743
|
return null;
|
|
71229
71744
|
}
|
|
71230
71745
|
}
|
|
71231
71746
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
71232
71747
|
return {
|
|
71233
|
-
exists: (p) => (0,
|
|
71234
|
-
listVersionDirs: (root) => (0,
|
|
71748
|
+
exists: (p) => (0, import_node_fs65.existsSync)(p),
|
|
71749
|
+
listVersionDirs: (root) => (0, import_node_fs65.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
71235
71750
|
dirBytes,
|
|
71236
|
-
listStagingDirs: (root) => (0,
|
|
71751
|
+
listStagingDirs: (root) => (0, import_node_fs65.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
71237
71752
|
try {
|
|
71238
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
71753
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path62.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs65.statSync)(p).mtimeMs) };
|
|
71239
71754
|
} catch {
|
|
71240
71755
|
return { name: d.name, mtimeMs: Date.now() };
|
|
71241
71756
|
}
|
|
@@ -71249,10 +71764,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
71249
71764
|
return {
|
|
71250
71765
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
71251
71766
|
mtimeMs: (name) => {
|
|
71252
|
-
const p = (0,
|
|
71253
|
-
if (!(0,
|
|
71767
|
+
const p = (0, import_node_path62.join)(stagingRoot, name);
|
|
71768
|
+
if (!(0, import_node_fs65.existsSync)(p)) return null;
|
|
71254
71769
|
try {
|
|
71255
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
71770
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs65.statSync)(q).mtimeMs);
|
|
71256
71771
|
} catch {
|
|
71257
71772
|
return null;
|
|
71258
71773
|
}
|
|
@@ -71272,13 +71787,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
71272
71787
|
return;
|
|
71273
71788
|
}
|
|
71274
71789
|
const plan = buildPluginCachePlan(
|
|
71275
|
-
(0,
|
|
71790
|
+
(0, import_node_os28.homedir)(),
|
|
71276
71791
|
running,
|
|
71277
71792
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
71278
71793
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
71279
71794
|
);
|
|
71280
71795
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
71281
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
71796
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs65.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
71282
71797
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
71283
71798
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
71284
71799
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -71288,7 +71803,7 @@ program2.command("archive-prune").description("inventory worktree evidence archi
|
|
|
71288
71803
|
let primary = o.repoRoot;
|
|
71289
71804
|
if (!primary) {
|
|
71290
71805
|
const common = await gitOut(["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
71291
|
-
primary = common ? (0,
|
|
71806
|
+
primary = common ? (0, import_node_path62.dirname)(common) : "";
|
|
71292
71807
|
}
|
|
71293
71808
|
if (!primary) {
|
|
71294
71809
|
const detail = "archive-prune: not inside a git checkout (pass --repo-root <primary checkout>)";
|