@mutmutco/claude-plugin 4.6.11 → 4.6.12
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/cli/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
|
};
|
|
@@ -13512,7 +13506,7 @@ function useColor() {
|
|
|
13512
13506
|
var program = new Command();
|
|
13513
13507
|
|
|
13514
13508
|
// src/command-composition.ts
|
|
13515
|
-
var
|
|
13509
|
+
var import_node_fs65 = require("node:fs");
|
|
13516
13510
|
init_clean_exit();
|
|
13517
13511
|
init_cli_shared();
|
|
13518
13512
|
|
|
@@ -15129,8 +15123,8 @@ function resolveVerbAliasShim(argv) {
|
|
|
15129
15123
|
}
|
|
15130
15124
|
|
|
15131
15125
|
// src/command-composition.ts
|
|
15132
|
-
var
|
|
15133
|
-
var
|
|
15126
|
+
var import_node_os28 = require("node:os");
|
|
15127
|
+
var import_node_path62 = require("node:path");
|
|
15134
15128
|
|
|
15135
15129
|
// src/board-read.ts
|
|
15136
15130
|
var import_node_fs18 = require("node:fs");
|
|
@@ -16084,6 +16078,14 @@ async function pollToken() {
|
|
|
16084
16078
|
});
|
|
16085
16079
|
return pollTokenOnce;
|
|
16086
16080
|
}
|
|
16081
|
+
async function refreshPollToken() {
|
|
16082
|
+
if (process.env.MMI_ACTOR?.trim() === "personal" || !pollTokenProvider) return void 0;
|
|
16083
|
+
pollTokenOnce = pollTokenProvider({ fresh: true }).catch((e) => {
|
|
16084
|
+
if (e instanceof AppActorError && e.code === "mint-failed") throw e;
|
|
16085
|
+
return void 0;
|
|
16086
|
+
});
|
|
16087
|
+
return pollTokenOnce;
|
|
16088
|
+
}
|
|
16087
16089
|
async function ghReadEnv() {
|
|
16088
16090
|
const appToken = await pollToken();
|
|
16089
16091
|
return appToken ? { ...process.env, GH_TOKEN: appToken, MMI_GH_POOL: "app" } : void 0;
|
|
@@ -25439,11 +25441,25 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
25439
25441
|
}
|
|
25440
25442
|
|
|
25441
25443
|
// src/gate-budget.ts
|
|
25442
|
-
var
|
|
25444
|
+
var gate_budget_exports = {};
|
|
25445
|
+
__export(gate_budget_exports, {
|
|
25446
|
+
BLESSED_JEV_FAILURES_SHA: () => BLESSED_JEV_FAILURES_SHA,
|
|
25447
|
+
BLESSED_KIT_AGENT_FILES_SHA: () => BLESSED_KIT_AGENT_FILES_SHA,
|
|
25448
|
+
BLESSED_RUNBOOK_GUARD_SHA: () => BLESSED_RUNBOOK_GUARD_SHA,
|
|
25449
|
+
BLESSED_RUNNER_CHOICE_SHA: () => BLESSED_RUNNER_CHOICE_SHA,
|
|
25450
|
+
BLESSED_RUNNER_GATE_SHA: () => BLESSED_RUNNER_GATE_SHA,
|
|
25451
|
+
BLESSED_RUN_WITH_BUDGET_SHA: () => BLESSED_RUN_WITH_BUDGET_SHA,
|
|
25452
|
+
BLESSED_VERDICT_RECEIPT_SHA: () => BLESSED_VERDICT_RECEIPT_SHA,
|
|
25453
|
+
MAX_SECONDS_CEILING: () => MAX_SECONDS_CEILING,
|
|
25454
|
+
checkGateBudget: () => checkGateBudget,
|
|
25455
|
+
isGateWorkflowPath: () => isGateWorkflowPath
|
|
25456
|
+
});
|
|
25457
|
+
var BLESSED_RUN_WITH_BUDGET_SHA = "cb31a94c89729beac83500ad4d301132dcfe2e4c";
|
|
25443
25458
|
var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
25444
25459
|
var BLESSED_KIT_AGENT_FILES_SHA = "e976df208d891b215844386c1cac0f161946b6fa";
|
|
25445
25460
|
var BLESSED_JEV_FAILURES_SHA = "d5278c6133318b33727483ddfdfddc8f841e3ef6";
|
|
25446
|
-
var
|
|
25461
|
+
var BLESSED_VERDICT_RECEIPT_SHA = "";
|
|
25462
|
+
var BLESSED_RUNNER_CHOICE_SHA = "f6b691f966611ec2a3b688c74c92a92f23133f41";
|
|
25447
25463
|
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
25448
25464
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
25449
25465
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
@@ -25828,7 +25844,10 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
25828
25844
|
GATE_SERVICES_YAML: "",
|
|
25829
25845
|
GATE_EXTRA_STEPS_YAML: "",
|
|
25830
25846
|
// #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)
|
|
25847
|
+
...gateJevStepsYaml(BLESSED_JEV_FAILURES_SHA, runtime),
|
|
25848
|
+
// Org CI H1: exact-SHA verdict receipt from the blessed verdict-receipt pin (an empty pin renders the legacy #8206 rc-tag step).
|
|
25849
|
+
GATE_VERDICT_SHA: BLESSED_VERDICT_RECEIPT_SHA,
|
|
25850
|
+
GATE_VERDICT_STEP_YAML: gateVerdictStepYaml(BLESSED_VERDICT_RECEIPT_SHA)
|
|
25832
25851
|
};
|
|
25833
25852
|
const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
|
|
25834
25853
|
const trackBranches = track === "trunk" ? ["main"] : track === "single" ? ["development"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
|
|
@@ -25977,6 +25996,47 @@ function gateServicesYaml(services) {
|
|
|
25977
25996
|
}
|
|
25978
25997
|
return out;
|
|
25979
25998
|
}
|
|
25999
|
+
var LEGACY_RC_REUSE_STEP_YAML = String.raw`
|
|
26000
|
+
# #8206: keep a fresh successful tag gate; promote the exact development verdict, not a ref.
|
|
26001
|
+
- name: Reuse development gate for rc tag
|
|
26002
|
+
id: development-gate
|
|
26003
|
+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
|
26004
|
+
with:
|
|
26005
|
+
script: |
|
|
26006
|
+
core.setOutput('reuse', 'false');
|
|
26007
|
+
if (context.eventName !== 'push' || !/^refs\/tags\/v.+-rc\.\d+$/.test(context.ref) || !/^[0-9a-f]{40}$/.test(context.sha)) return;
|
|
26008
|
+
try {
|
|
26009
|
+
const { data: current } = await github.rest.actions.getWorkflowRun({ ...context.repo, run_id: context.runId });
|
|
26010
|
+
const { data } = await github.rest.actions.listWorkflowRuns({
|
|
26011
|
+
...context.repo, workflow_id: current.workflow_id, branch: 'development',
|
|
26012
|
+
event: 'push', head_sha: context.sha, per_page: 100
|
|
26013
|
+
});
|
|
26014
|
+
// API returns newest first: a newer red/pending rerun must not reuse an older green.
|
|
26015
|
+
const run = data.workflow_runs[0];
|
|
26016
|
+
if (!run || run.head_sha !== context.sha || run.head_branch !== 'development' ||
|
|
26017
|
+
run.event !== 'push' || run.status !== 'completed' || run.conclusion !== 'success') return;
|
|
26018
|
+
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
|
|
26019
|
+
...context.repo, run_id: run.id, filter: 'latest', per_page: 100
|
|
26020
|
+
});
|
|
26021
|
+
if (!jobs.some(job => job.name === 'gate' && job.status === 'completed' && job.conclusion === 'success')) return;
|
|
26022
|
+
core.setOutput('reuse', 'true');
|
|
26023
|
+
core.info('Reusing successful development gate run ' + run.id + ' for exact SHA ' + context.sha);
|
|
26024
|
+
} catch (error) {
|
|
26025
|
+
core.warning('Development gate proof unavailable; running normal gate: ' + error.message);
|
|
26026
|
+
}`;
|
|
26027
|
+
function gateVerdictStepYaml(sha) {
|
|
26028
|
+
if (!/^[0-9a-f]{40}$/.test(sha)) return LEGACY_RC_REUSE_STEP_YAML;
|
|
26029
|
+
return [
|
|
26030
|
+
"\n # Org CI H1: exact-SHA verdict receipt. Any missing or partial proof reports reuse=false and the gate runs.",
|
|
26031
|
+
"\n - name: Reuse the exact-SHA gate verdict",
|
|
26032
|
+
"\n id: development-gate",
|
|
26033
|
+
"\n if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/{{GATE_FULL_RUN_BRANCH}}' || (startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '-rc.'))) }}",
|
|
26034
|
+
"\n uses: mutmutco/MMI-Hub/.github/actions/verdict-receipt@{{GATE_VERDICT_SHA}}",
|
|
26035
|
+
"\n with:",
|
|
26036
|
+
"\n source: ${{ github.ref_type == 'tag' && 'push' || 'merge_group' }}",
|
|
26037
|
+
"\n queue-is-full-verdict: 'true'"
|
|
26038
|
+
].join("");
|
|
26039
|
+
}
|
|
25980
26040
|
function gateJevStepsYaml(sha, runtime = "node") {
|
|
25981
26041
|
if (!/^[0-9a-f]{40}$/.test(sha)) return { GATE_JEV_FETCH_YAML: "", GATE_JEV_REPORT_YAML: "" };
|
|
25982
26042
|
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`;
|
|
@@ -27605,10 +27665,10 @@ var rollout_plan_default = {
|
|
|
27605
27665
|
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
27666
|
},
|
|
27607
27667
|
baseline: {
|
|
27608
|
-
version: "4.6.
|
|
27609
|
-
tag: "v4.6.
|
|
27610
|
-
commit: "
|
|
27611
|
-
npm: "@mutmutco/cli@4.6.
|
|
27668
|
+
version: "4.6.12",
|
|
27669
|
+
tag: "v4.6.12",
|
|
27670
|
+
commit: "9d7baf6ad8c7",
|
|
27671
|
+
npm: "@mutmutco/cli@4.6.12"
|
|
27612
27672
|
},
|
|
27613
27673
|
exitCriterion: "fleet-n-of-n",
|
|
27614
27674
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27625,14 +27685,14 @@ var rollout_plan_default = {
|
|
|
27625
27685
|
repo: "mutmutco/mmi-hub",
|
|
27626
27686
|
role: "canary",
|
|
27627
27687
|
schedule: "train",
|
|
27628
|
-
v3Target: "v4.6.
|
|
27688
|
+
v3Target: "v4.6.12"
|
|
27629
27689
|
}
|
|
27630
27690
|
],
|
|
27631
27691
|
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
27692
|
rollback: {
|
|
27633
27693
|
independent: true,
|
|
27634
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
27635
|
-
v3Target: "v4.6.
|
|
27694
|
+
mechanism: "npm dist-tag latest -> 4.6.12 and redeploy the Hub Lambda from tag v4.6.12 (9d7baf6ad8c7); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
27695
|
+
v3Target: "v4.6.12 (@mutmutco/cli@4.6.12, tag commit 9d7baf6ad8c7 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
27636
27696
|
}
|
|
27637
27697
|
},
|
|
27638
27698
|
{
|
|
@@ -38671,6 +38731,385 @@ async function runPipelineWithBump(bump, mode, input) {
|
|
|
38671
38731
|
return { ...await runTrainApplyPipeline(mode, input), bump };
|
|
38672
38732
|
}
|
|
38673
38733
|
|
|
38734
|
+
// src/hub-action-pins.ts
|
|
38735
|
+
var import_node_crypto11 = require("node:crypto");
|
|
38736
|
+
var import_node_fs32 = require("node:fs");
|
|
38737
|
+
var import_node_os17 = require("node:os");
|
|
38738
|
+
var import_node_path29 = require("node:path");
|
|
38739
|
+
|
|
38740
|
+
// src/bootstrap-propagate.ts
|
|
38741
|
+
function archivedWaiver(repoMeta) {
|
|
38742
|
+
return repoMeta?.archived === true ? "archived repository (read-only)" : void 0;
|
|
38743
|
+
}
|
|
38744
|
+
function assertPropagationCoverage(rosterCount, independentRegistryCount) {
|
|
38745
|
+
if (rosterCount === 0) {
|
|
38746
|
+
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)");
|
|
38747
|
+
}
|
|
38748
|
+
if (rosterCount < independentRegistryCount) {
|
|
38749
|
+
throw new Error(
|
|
38750
|
+
`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)`
|
|
38751
|
+
);
|
|
38752
|
+
}
|
|
38753
|
+
}
|
|
38754
|
+
function assignWaves(repos, canarySlug) {
|
|
38755
|
+
const waves = /* @__PURE__ */ new Map();
|
|
38756
|
+
if (!canarySlug) return waves;
|
|
38757
|
+
const rest = repos.filter((r) => r.slug !== canarySlug).slice().sort((a, b) => a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
|
|
38758
|
+
const canary = repos.find((r) => r.slug === canarySlug);
|
|
38759
|
+
if (canary) waves.set(canary.repo, 0);
|
|
38760
|
+
const wave1Count = Math.ceil(rest.length * 0.25);
|
|
38761
|
+
rest.forEach((r, i) => waves.set(r.repo, i < wave1Count ? 1 : 2));
|
|
38762
|
+
return waves;
|
|
38763
|
+
}
|
|
38764
|
+
function statusFor(read, currentSha) {
|
|
38765
|
+
if (!read) return { status: "pending", record: {} };
|
|
38766
|
+
if (read.drift === "match") return { status: "match", record: { mergeSha: read.pr?.mergeSha } };
|
|
38767
|
+
if (read.prUnread) return { status: "red", record: {} };
|
|
38768
|
+
const pr = read.pr;
|
|
38769
|
+
if (!pr) return { status: "pending", record: {} };
|
|
38770
|
+
if (pr.sourceSha && pr.sourceSha !== currentSha) return { status: "pending", record: {} };
|
|
38771
|
+
if (pr.state === "closed") return { status: "closed-unmerged", record: { prNumber: pr.number, prUrl: pr.url } };
|
|
38772
|
+
if (pr.checks === "red") return { status: "red", record: { prNumber: pr.number, prUrl: pr.url } };
|
|
38773
|
+
return { status: "open-pending", record: { prNumber: pr.number, prUrl: pr.url, mergeSha: pr.mergeSha } };
|
|
38774
|
+
}
|
|
38775
|
+
var HALTING_STATUSES = /* @__PURE__ */ new Set(["red", "closed-unmerged"]);
|
|
38776
|
+
function planPropagationTick(input) {
|
|
38777
|
+
const { target, repos, canarySlug, reads, isWorkflowSeed, functionGateSatisfied, currentSha } = input;
|
|
38778
|
+
const sourcePilot = input.sourcePilot === true;
|
|
38779
|
+
const readByRepo = new Map(reads.map((r) => [r.repo, r]));
|
|
38780
|
+
const records = [];
|
|
38781
|
+
const opened = [];
|
|
38782
|
+
const waived = repos.filter((r) => r.waiver);
|
|
38783
|
+
for (const r of waived) {
|
|
38784
|
+
records.push({ repo: r.repo, target, wave: null, status: "skipped-waived", action: "none", detail: `waived: ${r.waiver}` });
|
|
38785
|
+
}
|
|
38786
|
+
const eligible = repos.filter((r) => !r.waiver);
|
|
38787
|
+
const refusedNoCanary = !canarySlug || !sourcePilot && !eligible.some((r) => r.slug === canarySlug);
|
|
38788
|
+
if (refusedNoCanary) {
|
|
38789
|
+
for (const r of eligible) {
|
|
38790
|
+
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)" });
|
|
38791
|
+
}
|
|
38792
|
+
return { target, reposInScope: repos.length, canary: null, refusedNoCanary: true, halted: false, haltReason: null, opened, records };
|
|
38793
|
+
}
|
|
38794
|
+
const waveOf = assignWaves(eligible, canarySlug);
|
|
38795
|
+
const byWave = { 0: [], 1: [], 2: [] };
|
|
38796
|
+
for (const r of eligible) byWave[waveOf.get(r.repo) ?? 2].push(r);
|
|
38797
|
+
let halted = false;
|
|
38798
|
+
let haltReason = null;
|
|
38799
|
+
let waveGateOpen = !(sourcePilot && isWorkflowSeed && !functionGateSatisfied);
|
|
38800
|
+
for (const waveNum of [0, 1, 2]) {
|
|
38801
|
+
const waveRepos = byWave[waveNum];
|
|
38802
|
+
if (!waveRepos.length) continue;
|
|
38803
|
+
if (!waveGateOpen || halted) {
|
|
38804
|
+
for (const r of waveRepos) {
|
|
38805
|
+
records.push({ repo: r.repo, target, wave: waveNum, status: "not-yet-reached", action: "none", detail: "prior wave not yet converged" });
|
|
38806
|
+
}
|
|
38807
|
+
continue;
|
|
38808
|
+
}
|
|
38809
|
+
let waveAllMatch = true;
|
|
38810
|
+
let waveHasRed = false;
|
|
38811
|
+
let waveHasClosed = false;
|
|
38812
|
+
for (const r of waveRepos) {
|
|
38813
|
+
const { status, record: record2 } = statusFor(readByRepo.get(r.repo), currentSha);
|
|
38814
|
+
const shouldOpen = status === "pending" || status === "closed-unmerged";
|
|
38815
|
+
if (shouldOpen) opened.push(r.repo);
|
|
38816
|
+
records.push({
|
|
38817
|
+
repo: r.repo,
|
|
38818
|
+
target,
|
|
38819
|
+
wave: waveNum,
|
|
38820
|
+
status,
|
|
38821
|
+
action: shouldOpen ? "open-pr" : "none",
|
|
38822
|
+
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,
|
|
38823
|
+
...record2
|
|
38824
|
+
});
|
|
38825
|
+
if (status !== "match") waveAllMatch = false;
|
|
38826
|
+
if (HALTING_STATUSES.has(status)) waveHasRed = true;
|
|
38827
|
+
if (status === "closed-unmerged") waveHasClosed = true;
|
|
38828
|
+
}
|
|
38829
|
+
if (waveHasRed) {
|
|
38830
|
+
halted = true;
|
|
38831
|
+
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" : "");
|
|
38832
|
+
waveGateOpen = false;
|
|
38833
|
+
continue;
|
|
38834
|
+
}
|
|
38835
|
+
if (waveNum === 0 && isWorkflowSeed && !functionGateSatisfied) {
|
|
38836
|
+
waveGateOpen = false;
|
|
38837
|
+
continue;
|
|
38838
|
+
}
|
|
38839
|
+
waveGateOpen = waveAllMatch;
|
|
38840
|
+
}
|
|
38841
|
+
return { target, reposInScope: repos.length, canary: canarySlug, refusedNoCanary: false, halted, haltReason, opened, records };
|
|
38842
|
+
}
|
|
38843
|
+
function summarizePropagationTick(plan) {
|
|
38844
|
+
const parts = [];
|
|
38845
|
+
parts.push(plan.opened.length ? `opened ${plan.opened.length} PR(s) this tick: ${plan.opened.join(", ")}` : "opened no PR this tick");
|
|
38846
|
+
const stillNotCurrent = plan.records.filter((r) => r.status !== "match" && r.status !== "skipped-waived" && r.action !== "open-pr");
|
|
38847
|
+
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");
|
|
38848
|
+
if (plan.halted) parts.push(`HALTED \u2014 ${plan.haltReason}`);
|
|
38849
|
+
return parts.join("; ");
|
|
38850
|
+
}
|
|
38851
|
+
function renderPropagationReport(plan) {
|
|
38852
|
+
const lines2 = [`bootstrap propagate \u2014 target ${plan.target}: ${plan.reposInScope} repo(s) in scope, canary=${plan.canary ?? "NONE"}`];
|
|
38853
|
+
if (plan.refusedNoCanary) {
|
|
38854
|
+
lines2.push(" REFUSED \u2014 no canary declared for this target; plan every repo not-yet-reached");
|
|
38855
|
+
lines2.push(` ${summarizePropagationTick(plan)}`);
|
|
38856
|
+
return lines2.join("\n");
|
|
38857
|
+
}
|
|
38858
|
+
for (const r of plan.records) {
|
|
38859
|
+
const wave = r.wave == null ? "-" : String(r.wave);
|
|
38860
|
+
lines2.push(` wave${wave.padEnd(2)} ${r.status.padEnd(16)} ${r.repo}${r.prNumber ? ` PR#${r.prNumber}` : ""} \u2014 ${r.detail}`);
|
|
38861
|
+
}
|
|
38862
|
+
lines2.push(` ${summarizePropagationTick(plan)}`);
|
|
38863
|
+
return lines2.join("\n");
|
|
38864
|
+
}
|
|
38865
|
+
|
|
38866
|
+
// src/bootstrap-propagation-identity.ts
|
|
38867
|
+
var import_node_crypto10 = require("node:crypto");
|
|
38868
|
+
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
38869
|
+
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
38870
|
+
function safeBranchPart(value, maxLength, fallback) {
|
|
38871
|
+
const safe = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength).replace(/-+$/g, "");
|
|
38872
|
+
return safe || fallback;
|
|
38873
|
+
}
|
|
38874
|
+
function repoSlug2(repo) {
|
|
38875
|
+
return repo.trim().split("/").pop()?.toLowerCase() || "repo";
|
|
38876
|
+
}
|
|
38877
|
+
function propagationBranch(repo, target) {
|
|
38878
|
+
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
38879
|
+
const targetPart = safeBranchPart(target, 48, "target");
|
|
38880
|
+
const hash = (0, import_node_crypto10.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
38881
|
+
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
38882
|
+
}
|
|
38883
|
+
function legacyPropagationBranch(repo) {
|
|
38884
|
+
return `${PROPAGATION_BRANCH_PREFIX}${repoSlug2(repo)}`;
|
|
38885
|
+
}
|
|
38886
|
+
function renderPropagationTargetMarker(target) {
|
|
38887
|
+
return `<!-- ${TARGET_MARKER_NAME}: ${JSON.stringify(target)} -->`;
|
|
38888
|
+
}
|
|
38889
|
+
function targetMarkers(body) {
|
|
38890
|
+
const present = body.includes(`${TARGET_MARKER_NAME}:`);
|
|
38891
|
+
const targets = [];
|
|
38892
|
+
const marker = new RegExp(`<!--\\s*${TARGET_MARKER_NAME}:\\s*(.*?)\\s*-->`, "g");
|
|
38893
|
+
for (const match of body.matchAll(marker)) {
|
|
38894
|
+
try {
|
|
38895
|
+
const parsed = JSON.parse(match[1]);
|
|
38896
|
+
if (typeof parsed === "string") targets.push(parsed);
|
|
38897
|
+
} catch {
|
|
38898
|
+
}
|
|
38899
|
+
}
|
|
38900
|
+
return { present, targets };
|
|
38901
|
+
}
|
|
38902
|
+
function canonicalProseTargets(body) {
|
|
38903
|
+
const targets = [];
|
|
38904
|
+
const command = /bootstrap propagate --target (.+?) --execute`/g;
|
|
38905
|
+
const prose = /(?:org-owned copy of|declared marker-bounded block inside repo-owned) `([^`]+)` — the file this PR carries and nothing else/g;
|
|
38906
|
+
for (const match of body.matchAll(command)) targets.push(match[1]);
|
|
38907
|
+
for (const match of body.matchAll(prose)) targets.push(match[1]);
|
|
38908
|
+
return targets;
|
|
38909
|
+
}
|
|
38910
|
+
function propagationPrMatchesTarget(pr, target, identity) {
|
|
38911
|
+
const body = pr.body ?? "";
|
|
38912
|
+
const markers = targetMarkers(body);
|
|
38913
|
+
if (markers.present) {
|
|
38914
|
+
if (markers.targets.length !== 1 || markers.targets[0] !== target) return false;
|
|
38915
|
+
return !pr.files?.length || pr.files.length === 1 && pr.files[0] === target;
|
|
38916
|
+
}
|
|
38917
|
+
if (identity === "target-specific") return false;
|
|
38918
|
+
const proseTargets = canonicalProseTargets(body);
|
|
38919
|
+
if (proseTargets.some((candidate) => candidate !== target)) return false;
|
|
38920
|
+
if (pr.files?.length) {
|
|
38921
|
+
return pr.files.length === 1 && pr.files[0] === target;
|
|
38922
|
+
}
|
|
38923
|
+
return proseTargets.length > 0;
|
|
38924
|
+
}
|
|
38925
|
+
function propagationPrCandidatesForTarget(target, targetSpecificPrs, legacyPrs) {
|
|
38926
|
+
const targetSpecific = targetSpecificPrs.filter((pr) => propagationPrMatchesTarget(pr, target, "target-specific"));
|
|
38927
|
+
if (targetSpecific.length) return targetSpecific;
|
|
38928
|
+
return legacyPrs.filter((pr) => propagationPrMatchesTarget(pr, target, "legacy"));
|
|
38929
|
+
}
|
|
38930
|
+
|
|
38931
|
+
// src/hub-action-pins.ts
|
|
38932
|
+
var PIN_TARGET = "hub-action-pins";
|
|
38933
|
+
var HUB_REPO4 = "mutmutco/MMI-Hub";
|
|
38934
|
+
var FULL_SHA2 = /^[0-9a-f]{40}$/;
|
|
38935
|
+
var PIN_ACTION_ALIASES = {
|
|
38936
|
+
BLESSED_RUNNER_GATE_SHA: ["runner-checkout", "runner-node-toolchain"],
|
|
38937
|
+
BLESSED_KIT_AGENT_FILES_SHA: ["check-kit-agent-files"]
|
|
38938
|
+
};
|
|
38939
|
+
function blessedHubActionPins(constants = gate_budget_exports) {
|
|
38940
|
+
const pins = /* @__PURE__ */ new Map();
|
|
38941
|
+
for (const [name, sha] of Object.entries(constants)) {
|
|
38942
|
+
const m = /^BLESSED_([A-Z0-9_]+)_SHA$/.exec(name);
|
|
38943
|
+
if (!m || typeof sha !== "string" || !FULL_SHA2.test(sha)) continue;
|
|
38944
|
+
for (const action of PIN_ACTION_ALIASES[name] ?? [m[1].toLowerCase().replace(/_/g, "-")]) pins.set(action, sha);
|
|
38945
|
+
}
|
|
38946
|
+
return pins;
|
|
38947
|
+
}
|
|
38948
|
+
function pinTableDigest(pins) {
|
|
38949
|
+
return (0, import_node_crypto11.createHash)("sha1").update([...pins].map(([a, s]) => `${a}@${s}`).sort().join("\n")).digest("hex");
|
|
38950
|
+
}
|
|
38951
|
+
var HUB_ACTION_USE = /^(\s*-?\s*uses:\s*['"]?mutmutco\/mmi-hub\/\.github\/actions\/)([\w.-]+)@([^\s'"#]+)([^\n]*)$/i;
|
|
38952
|
+
function staleHubActionPins(body, pins = blessedHubActionPins()) {
|
|
38953
|
+
const stale = [];
|
|
38954
|
+
body.split("\n").forEach((text, i) => {
|
|
38955
|
+
const m = HUB_ACTION_USE.exec(text);
|
|
38956
|
+
const to = m ? pins.get(m[2]) : void 0;
|
|
38957
|
+
if (!m || !to || m[3] === to || !(m[3] === "development" || FULL_SHA2.test(m[3]))) return;
|
|
38958
|
+
stale.push({ line: i + 1, action: m[2], from: m[3], to });
|
|
38959
|
+
});
|
|
38960
|
+
return stale;
|
|
38961
|
+
}
|
|
38962
|
+
function rewriteHubActionPins(body, pins = blessedHubActionPins(), keep = () => false) {
|
|
38963
|
+
const changes = staleHubActionPins(body, pins).filter((use) => !keep(use));
|
|
38964
|
+
const lines2 = body.split("\n");
|
|
38965
|
+
for (const c of changes) {
|
|
38966
|
+
lines2[c.line - 1] = lines2[c.line - 1].replace(HUB_ACTION_USE, (_all, pre, action, _ref, rest) => `${pre}${action}@${c.to}${rest}`);
|
|
38967
|
+
}
|
|
38968
|
+
return { body: lines2.join("\n"), changes };
|
|
38969
|
+
}
|
|
38970
|
+
var ghJson = async (gh2, args) => JSON.parse((await gh2(args)).stdout || "null");
|
|
38971
|
+
var pinPathEnc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
38972
|
+
async function readPinFile(gh2, repo, path2, ref) {
|
|
38973
|
+
const f = await ghJson(gh2, ["api", `repos/${repo}/contents/${pinPathEnc(path2)}?ref=${encodeURIComponent(ref)}`]);
|
|
38974
|
+
return f?.encoding === "base64" && typeof f.content === "string" ? { body: decodeGitHubContents(f.content), sha: f.sha } : null;
|
|
38975
|
+
}
|
|
38976
|
+
async function runPinTick(input) {
|
|
38977
|
+
const { gh: gh2, repos } = input;
|
|
38978
|
+
const pins = input.pins ?? blessedHubActionPins();
|
|
38979
|
+
const digest = pinTableDigest(pins);
|
|
38980
|
+
const ahead = /* @__PURE__ */ new Map();
|
|
38981
|
+
const isAhead = (use) => {
|
|
38982
|
+
if (!FULL_SHA2.test(use.from)) return Promise.resolve(false);
|
|
38983
|
+
const key = `${use.to}...${use.from}`;
|
|
38984
|
+
if (!ahead.has(key)) ahead.set(key, gh2(["api", `repos/${HUB_REPO4}/compare/${key}`, "--jq", ".status"]).then((o) => o.stdout.trim() === "ahead", () => false));
|
|
38985
|
+
return ahead.get(key);
|
|
38986
|
+
};
|
|
38987
|
+
const keepSet = /* @__PURE__ */ new Set();
|
|
38988
|
+
const keep = (use) => keepSet.has(`${use.to}...${use.from}`);
|
|
38989
|
+
const reads = [];
|
|
38990
|
+
const diffs = [];
|
|
38991
|
+
for (const r of repos) {
|
|
38992
|
+
if (r.waiver) continue;
|
|
38993
|
+
const base = input.baseBranchOf(r.repo);
|
|
38994
|
+
let listing = [];
|
|
38995
|
+
try {
|
|
38996
|
+
listing = await ghJson(gh2, ["api", `repos/${r.repo}/contents/.github/workflows?ref=${encodeURIComponent(base)}`]) ?? [];
|
|
38997
|
+
} catch (e) {
|
|
38998
|
+
if (!/HTTP 404|Not Found/i.test(e.message)) {
|
|
38999
|
+
reads.push({ repo: r.repo, drift: "drift", prUnread: true });
|
|
39000
|
+
continue;
|
|
39001
|
+
}
|
|
39002
|
+
}
|
|
39003
|
+
const files = [];
|
|
39004
|
+
for (const entry of listing) {
|
|
39005
|
+
if (entry.type !== "file" || !entry.path || !/\.ya?ml$/.test(entry.path)) continue;
|
|
39006
|
+
const file = await readPinFile(gh2, r.repo, entry.path, base);
|
|
39007
|
+
if (!file) continue;
|
|
39008
|
+
for (const use of staleHubActionPins(file.body, pins)) if (await isAhead(use)) keepSet.add(`${use.to}...${use.from}`);
|
|
39009
|
+
const { changes } = rewriteHubActionPins(file.body, pins, keep);
|
|
39010
|
+
if (changes.length) files.push({ path: entry.path, changes });
|
|
39011
|
+
}
|
|
39012
|
+
if (files.length) diffs.push({ repo: r.repo, files });
|
|
39013
|
+
let pr;
|
|
39014
|
+
let prUnread = false;
|
|
39015
|
+
try {
|
|
39016
|
+
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"]) ?? [];
|
|
39017
|
+
const p = propagationPrCandidatesForTarget(PIN_TARGET, listed, [])[0];
|
|
39018
|
+
if (p) {
|
|
39019
|
+
const rollup = p.statusCheckRollup ?? [];
|
|
39020
|
+
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";
|
|
39021
|
+
pr = {
|
|
39022
|
+
number: p.number,
|
|
39023
|
+
url: p.url,
|
|
39024
|
+
checks,
|
|
39025
|
+
mergeSha: p.mergeCommit?.oid,
|
|
39026
|
+
state: p.state === "MERGED" ? "merged" : p.state === "CLOSED" ? "closed" : "open",
|
|
39027
|
+
sourceSha: p.body?.match(/Hub action pins ([0-9a-f]{40})/)?.[1]
|
|
39028
|
+
};
|
|
39029
|
+
}
|
|
39030
|
+
} catch {
|
|
39031
|
+
prUnread = true;
|
|
39032
|
+
}
|
|
39033
|
+
reads.push({ repo: r.repo, drift: files.length ? "drift" : "match", pr, prUnread });
|
|
39034
|
+
}
|
|
39035
|
+
const plan = planPropagationTick({
|
|
39036
|
+
target: PIN_TARGET,
|
|
39037
|
+
repos,
|
|
39038
|
+
canarySlug: input.canarySlug,
|
|
39039
|
+
reads,
|
|
39040
|
+
isWorkflowSeed: false,
|
|
39041
|
+
functionGateSatisfied: true,
|
|
39042
|
+
currentSha: digest,
|
|
39043
|
+
sourcePilot: input.sourcePilot
|
|
39044
|
+
});
|
|
39045
|
+
if (!input.apply || plan.refusedNoCanary) return { plan, digest, diffs };
|
|
39046
|
+
for (const rec of plan.records) {
|
|
39047
|
+
if (rec.action !== "open-pr") continue;
|
|
39048
|
+
const diff = diffs.find((d) => d.repo === rec.repo);
|
|
39049
|
+
if (!diff) continue;
|
|
39050
|
+
const base = input.baseBranchOf(rec.repo);
|
|
39051
|
+
const branch = propagationBranch(rec.repo, PIN_TARGET);
|
|
39052
|
+
const baseSha = (await gh2(["api", `repos/${rec.repo}/git/ref/heads/${base}`, "--jq", ".object.sha"])).stdout.trim();
|
|
39053
|
+
let branchExists = true;
|
|
39054
|
+
try {
|
|
39055
|
+
await gh2(["api", `repos/${rec.repo}/git/ref/heads/${branch}`]);
|
|
39056
|
+
} catch {
|
|
39057
|
+
branchExists = false;
|
|
39058
|
+
}
|
|
39059
|
+
if (!branchExists) await gh2(["api", `repos/${rec.repo}/git/refs`, "-f", `ref=refs/heads/${branch}`, "-f", `sha=${baseSha}`]);
|
|
39060
|
+
else await gh2(["api", "-X", "PATCH", `repos/${rec.repo}/git/refs/heads/${branch}`, "-f", `sha=${baseSha}`, "-F", "force=true"]);
|
|
39061
|
+
const lines2 = [];
|
|
39062
|
+
for (const { path: path2 } of diff.files) {
|
|
39063
|
+
const file = await readPinFile(gh2, rec.repo, path2, branch);
|
|
39064
|
+
if (!file) continue;
|
|
39065
|
+
const next = rewriteHubActionPins(file.body, pins, keep);
|
|
39066
|
+
if (!next.changes.length) continue;
|
|
39067
|
+
lines2.push(...next.changes.map((c) => `- \`${path2}:${c.line}\` ${c.action}: \`${c.from.slice(0, 12)}\` \u2192 \`${c.to.slice(0, 12)}\``));
|
|
39068
|
+
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`);
|
|
39069
|
+
const put = { message: `ci: pin MMI-Hub actions in ${path2} to the blessed SHAs`, content: encodeGitHubContents(next.body), branch };
|
|
39070
|
+
if (file.sha) put.sha = file.sha;
|
|
39071
|
+
(0, import_node_fs32.writeFileSync)(tmp, JSON.stringify(put), "utf8");
|
|
39072
|
+
try {
|
|
39073
|
+
await gh2(["api", "-X", "PUT", `repos/${rec.repo}/contents/${pinPathEnc(path2)}`, "--input", tmp]);
|
|
39074
|
+
} finally {
|
|
39075
|
+
try {
|
|
39076
|
+
(0, import_node_fs32.unlinkSync)(tmp);
|
|
39077
|
+
} catch {
|
|
39078
|
+
}
|
|
39079
|
+
}
|
|
39080
|
+
}
|
|
39081
|
+
if (!lines2.length) continue;
|
|
39082
|
+
const body = `Auto-opened by \`mmi-cli devops bootstrap propagate --pins --apply\` (org CI plan H4), wave ${rec.wave}.
|
|
39083
|
+
|
|
39084
|
+
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.
|
|
39085
|
+
|
|
39086
|
+
${lines2.join("\n")}
|
|
39087
|
+
|
|
39088
|
+
Hub action pins ${digest}
|
|
39089
|
+
${renderPropagationTargetMarker(PIN_TARGET)}`;
|
|
39090
|
+
const open2 = await ghJson(gh2, ["pr", "list", "--repo", rec.repo, "--head", branch, "--base", base, "--state", "open", "--json", "number,url,body"]) ?? [];
|
|
39091
|
+
const decision = decideSeedPrAction(propagationPrCandidatesForTarget(PIN_TARGET, open2, []));
|
|
39092
|
+
let prUrl;
|
|
39093
|
+
if (decision.action === "reuse") {
|
|
39094
|
+
prUrl = decision.url;
|
|
39095
|
+
await gh2(["pr", "edit", prUrl, "--repo", rec.repo, "--body", body]);
|
|
39096
|
+
} else {
|
|
39097
|
+
if (!input.createPr) throw new Error("runPinTick: apply needs createPr");
|
|
39098
|
+
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]);
|
|
39099
|
+
}
|
|
39100
|
+
if (rec.wave !== 0 && input.mayAutoMerge && await input.mayAutoMerge(rec.repo)) {
|
|
39101
|
+
await gh2(["pr", "merge", prUrl, "--repo", rec.repo, "--auto", "--squash"]).catch(() => {
|
|
39102
|
+
});
|
|
39103
|
+
}
|
|
39104
|
+
rec.prUrl = prUrl;
|
|
39105
|
+
}
|
|
39106
|
+
return { plan, digest, diffs };
|
|
39107
|
+
}
|
|
39108
|
+
function renderPinDiffs(tick) {
|
|
39109
|
+
if (!tick.diffs.length) return "hub action pins: every in-scope repo already pins the blessed SHAs";
|
|
39110
|
+
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");
|
|
39111
|
+
}
|
|
39112
|
+
|
|
38674
39113
|
// src/registry-repositories.ts
|
|
38675
39114
|
function qualifyRegistryRepo(raw) {
|
|
38676
39115
|
return raw.includes("/") ? raw : `mutmutco/${raw}`;
|
|
@@ -38681,7 +39120,7 @@ function registryRepoKey(repo) {
|
|
|
38681
39120
|
|
|
38682
39121
|
// src/ci-audit.ts
|
|
38683
39122
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
38684
|
-
var
|
|
39123
|
+
var HUB_REPO5 = "mutmutco/MMI-Hub";
|
|
38685
39124
|
var HUB_GATE_CONTEXTS = ["cli", "infra", "docs"];
|
|
38686
39125
|
var PRODUCT_GATE_PATH = ".github/workflows/gate.yml";
|
|
38687
39126
|
var PRODUCT_RULESET_REF = ".github/rulesets/mmi-product-required-checks.json";
|
|
@@ -38868,6 +39307,46 @@ function auditJevOutcomes(files) {
|
|
|
38868
39307
|
}
|
|
38869
39308
|
return checks;
|
|
38870
39309
|
}
|
|
39310
|
+
var VERDICT_RECEIPT = /(?:^|\/)\.github\/actions\/verdict-receipt(?:@|$)/;
|
|
39311
|
+
function auditVerdictReceipt(files) {
|
|
39312
|
+
const checks = [];
|
|
39313
|
+
for (const { path: path2, body } of files) {
|
|
39314
|
+
if (!isGateWorkflowPath(path2)) continue;
|
|
39315
|
+
let doc;
|
|
39316
|
+
try {
|
|
39317
|
+
doc = (0, import_yaml2.parse)(body);
|
|
39318
|
+
} catch {
|
|
39319
|
+
continue;
|
|
39320
|
+
}
|
|
39321
|
+
if (!plainRecord(doc)) continue;
|
|
39322
|
+
const on = doc.on;
|
|
39323
|
+
const events = typeof on === "string" ? [on] : Array.isArray(on) ? on : plainRecord(on) ? Object.keys(on) : [];
|
|
39324
|
+
if (!events.includes("merge_group") || !events.includes("push")) continue;
|
|
39325
|
+
const push = plainRecord(on) ? on.push : void 0;
|
|
39326
|
+
const branches = plainRecord(push) ? push.branches : void 0;
|
|
39327
|
+
if (branches !== void 0 && !(Array.isArray(branches) ? branches : [branches]).includes("development")) continue;
|
|
39328
|
+
const steps = Object.values(plainRecord(doc.jobs) ? doc.jobs : {}).flatMap((job) => plainRecord(job) && Array.isArray(job.steps) ? job.steps.filter(plainRecord) : []);
|
|
39329
|
+
if (steps.some((s) => typeof s.uses === "string" && VERDICT_RECEIPT.test(s.uses))) continue;
|
|
39330
|
+
if (!steps.some((s) => typeof s.uses === "string" && s.uses.includes("run-with-budget") || typeof s.run === "string" && RUNS_TESTS.test(s.run))) continue;
|
|
39331
|
+
checks.push({
|
|
39332
|
+
ok: true,
|
|
39333
|
+
severity: "info",
|
|
39334
|
+
label: `development push reuses the merge-queue verdict (org CI H1): ${path2}`,
|
|
39335
|
+
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",
|
|
39336
|
+
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"
|
|
39337
|
+
});
|
|
39338
|
+
}
|
|
39339
|
+
return checks;
|
|
39340
|
+
}
|
|
39341
|
+
function auditHubActionPins(files) {
|
|
39342
|
+
return files.flatMap(({ path: path2, body }) => staleHubActionPins(body).map((use) => ({
|
|
39343
|
+
ok: true,
|
|
39344
|
+
severity: "info",
|
|
39345
|
+
label: `Hub action pin is the blessed SHA (H4): ${path2}:${use.line}`,
|
|
39346
|
+
detail: `${use.action} pins ${use.from === "development" ? "@development (floating)" : use.from.slice(0, 12)}, blessed is ${use.to.slice(0, 12)}`,
|
|
39347
|
+
remediation: "mmi-cli devops bootstrap propagate --pins --apply (from MMI-Hub), or re-pin by hand to the blessed SHA"
|
|
39348
|
+
})));
|
|
39349
|
+
}
|
|
38871
39350
|
var PENDING_LANDING_LABEL = "pending PRs landing (#8783)";
|
|
38872
39351
|
var PENDING_LANDING_LIST_CAP = 50;
|
|
38873
39352
|
var PENDING_LANDING_STALL_MS = 15 * 60 * 1e3;
|
|
@@ -38935,7 +39414,7 @@ function slugFromRepo(repo) {
|
|
|
38935
39414
|
return (repo.includes("/") ? repo.split("/")[1] : repo).toLowerCase();
|
|
38936
39415
|
}
|
|
38937
39416
|
function classifyRepo(repo, meta) {
|
|
38938
|
-
if (repo.toLowerCase() ===
|
|
39417
|
+
if (repo.toLowerCase() === HUB_REPO5.toLowerCase()) return "hub";
|
|
38939
39418
|
if (meta?.class === "content") return "content";
|
|
38940
39419
|
if (meta?.class === "deployable" || meta?.deployModel && meta.deployModel !== "content" && meta.deployModel !== "none") return "deployable";
|
|
38941
39420
|
if (meta) return "deployable";
|
|
@@ -38964,7 +39443,7 @@ function pinContainsFloor(deps, floor, pin) {
|
|
|
38964
39443
|
const key = `${floor}...${pin}`;
|
|
38965
39444
|
let comparison = comparisons.get(key);
|
|
38966
39445
|
if (!comparison) {
|
|
38967
|
-
comparison = deps.client.rest("GET", `repos/${
|
|
39446
|
+
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
39447
|
comparisons.set(key, comparison);
|
|
38969
39448
|
}
|
|
38970
39449
|
return comparison;
|
|
@@ -39237,7 +39716,7 @@ function collectRegistryRepos(projects) {
|
|
|
39237
39716
|
if (!seen.has(key)) seen.set(key, repo);
|
|
39238
39717
|
}
|
|
39239
39718
|
}
|
|
39240
|
-
if (!seen.has(
|
|
39719
|
+
if (!seen.has(HUB_REPO5.toLowerCase())) seen.set(HUB_REPO5.toLowerCase(), HUB_REPO5);
|
|
39241
39720
|
return [...seen.values()].sort((a, b) => a.localeCompare(b));
|
|
39242
39721
|
}
|
|
39243
39722
|
async function prTriggeredWorkflowsOnRef(deps, repo, branch, repoClass) {
|
|
@@ -39663,6 +40142,8 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
39663
40142
|
}
|
|
39664
40143
|
checks.push(...auditSelfHostedRunnerHygiene(workflows));
|
|
39665
40144
|
checks.push(...auditJevOutcomes(workflows));
|
|
40145
|
+
checks.push(...auditVerdictReceipt(workflows));
|
|
40146
|
+
if (repoClass !== "hub") checks.push(...auditHubActionPins(workflows));
|
|
39666
40147
|
if (repoClass !== "hub") checks.push(...await auditSharedActionPins(workflows, deps));
|
|
39667
40148
|
} else {
|
|
39668
40149
|
checks.push({ ok: false, label: "runner workflow source is readable (#8958)", detail: "workflow source inventory unreadable" });
|
|
@@ -41226,10 +41707,10 @@ init_github_client();
|
|
|
41226
41707
|
init_cli_shared();
|
|
41227
41708
|
|
|
41228
41709
|
// src/pr-merge-queue.ts
|
|
41229
|
-
var
|
|
41230
|
-
var
|
|
41231
|
-
var
|
|
41232
|
-
var
|
|
41710
|
+
var import_node_crypto12 = require("node:crypto");
|
|
41711
|
+
var import_node_fs34 = require("node:fs");
|
|
41712
|
+
var import_node_os18 = require("node:os");
|
|
41713
|
+
var import_node_path31 = require("node:path");
|
|
41233
41714
|
init_cli_shared();
|
|
41234
41715
|
|
|
41235
41716
|
// src/pr-land.ts
|
|
@@ -41668,8 +42149,8 @@ var import_yaml3 = __toESM(require_dist(), 1);
|
|
|
41668
42149
|
|
|
41669
42150
|
// src/test-policy-core.ts
|
|
41670
42151
|
var import_node_child_process11 = require("node:child_process");
|
|
41671
|
-
var
|
|
41672
|
-
var
|
|
42152
|
+
var import_node_fs33 = require("node:fs");
|
|
42153
|
+
var import_node_path30 = require("node:path");
|
|
41673
42154
|
|
|
41674
42155
|
// src/test-command-policy-shared.mjs
|
|
41675
42156
|
var import_node_child_process10 = require("node:child_process");
|
|
@@ -42225,7 +42706,7 @@ function isMeaningfulRow(file) {
|
|
|
42225
42706
|
return file.meaningful !== false;
|
|
42226
42707
|
}
|
|
42227
42708
|
function loadPolicy(root, readFile10 = readFileOrNull2) {
|
|
42228
|
-
const raw = readFile10((0,
|
|
42709
|
+
const raw = readFile10((0, import_node_path30.join)(root, POLICY_FILE));
|
|
42229
42710
|
if (raw == null) return { mandatory: [], declared: false };
|
|
42230
42711
|
return parsePolicy(raw, POLICY_FILE);
|
|
42231
42712
|
}
|
|
@@ -42309,7 +42790,7 @@ function loadPolicySource(root, explicitRef, diffBase) {
|
|
|
42309
42790
|
}
|
|
42310
42791
|
function readFileOrNull2(path2) {
|
|
42311
42792
|
try {
|
|
42312
|
-
return (0,
|
|
42793
|
+
return (0, import_node_fs33.readFileSync)(path2, "utf8");
|
|
42313
42794
|
} catch {
|
|
42314
42795
|
return null;
|
|
42315
42796
|
}
|
|
@@ -42337,12 +42818,12 @@ function classify(changed, policy, present = () => false) {
|
|
|
42337
42818
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
42338
42819
|
return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
|
|
42339
42820
|
}
|
|
42340
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
42341
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
42821
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs33.existsSync)(path2)) {
|
|
42822
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
42342
42823
|
}
|
|
42343
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
42824
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs33.existsSync)(path2)) {
|
|
42344
42825
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
42345
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
42826
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
42346
42827
|
}
|
|
42347
42828
|
function evaluate(changed, policy, present = () => false) {
|
|
42348
42829
|
const { mandatoryHits, untestedHits, meaningfulTestChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -42516,20 +42997,20 @@ function runTestPolicy(root, deps = {}) {
|
|
|
42516
42997
|
if (deps.policyRef && (deps.policy !== void 0 || deps.changed !== void 0 || deps.override !== void 0 || deps.exists !== void 0)) {
|
|
42517
42998
|
throw new Error("--policy-ref cannot be combined with injected policy, changed rows, override, or filesystem evidence");
|
|
42518
42999
|
}
|
|
42519
|
-
const candidateTree = candidate ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", candidate.head], root).split("\0").filter(Boolean).map((p) => (0,
|
|
43000
|
+
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
43001
|
const loaded = candidate ? {
|
|
42521
|
-
policy: candidateTree.has((0,
|
|
43002
|
+
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
43003
|
source: { ref: candidate.head, sha: candidate.head, path: POLICY_FILE }
|
|
42523
43004
|
} : deps.policy ? { policy: deps.policy, source: { ref: null, sha: null, path: POLICY_FILE } } : loadPolicySource(root, deps.policyRef, deps.base);
|
|
42524
43005
|
const { policy, source: policySource } = loaded;
|
|
42525
|
-
const exists = candidateTree ? (path2) => candidateTree.has(path2) : deps.exists ?? ((path2) => (0,
|
|
43006
|
+
const exists = candidateTree ? (path2) => candidateTree.has(path2) : deps.exists ?? ((path2) => (0, import_node_fs33.existsSync)(path2));
|
|
42526
43007
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
42527
43008
|
const base = candidate ? git2(["merge-base", candidate.head, candidate.base], root).trim() : deps.changed ? "(injected)" : deps.policyRef ? resolveHotfixBase(root) : resolveBase(root, deps.base);
|
|
42528
43009
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
42529
43010
|
const raw = deps.changed ?? (refusal ? [] : changedFilesSince(base, root, candidate?.head));
|
|
42530
43011
|
const changed = deps.changed ? raw : annotateChangeMeaning(raw, policy, (path2) => ({
|
|
42531
43012
|
before: blobAt(base, path2, root),
|
|
42532
|
-
after: candidate ? blobAt(candidate.head, path2, root) : readFileOrNull2((0,
|
|
43013
|
+
after: candidate ? blobAt(candidate.head, path2, root) : readFileOrNull2((0, import_node_path30.join)(root, path2))
|
|
42533
43014
|
}));
|
|
42534
43015
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root, candidate?.head) : { override: null, refusals: [] };
|
|
42535
43016
|
const policyRefusals = policy.declared === false ? [{
|
|
@@ -42539,8 +43020,8 @@ function runTestPolicy(root, deps = {}) {
|
|
|
42539
43020
|
A gate with no policy cannot decide which test commands are required or permitted.
|
|
42540
43021
|
` + (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
43022
|
}] : [];
|
|
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,
|
|
43023
|
+
const present = (path2) => exists((0, import_node_path30.join)(root, path2));
|
|
43024
|
+
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
43025
|
const policyTreeExists = policyTree ? (abs) => policyTree.has(abs) : exists;
|
|
42545
43026
|
const where = policySource.sha ? `${policySource.ref}@${policySource.sha}` : "this worktree";
|
|
42546
43027
|
const removedByThisDiff = removedPaths(changed);
|
|
@@ -42660,16 +43141,16 @@ var autoMergeApi = async (pr, repo, head) => {
|
|
|
42660
43141
|
var ASYNC_HEADERS = ["-H", "Accept: application/vnd.github+json", "-H", "X-GitHub-Api-Version: 2026-03-10"];
|
|
42661
43142
|
var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
|
42662
43143
|
function admissionJournal(repo, pr, root) {
|
|
42663
|
-
const stateRoot = root ?? (process.platform === "win32" ? (0,
|
|
42664
|
-
const directory = (0,
|
|
42665
|
-
(0,
|
|
42666
|
-
const attempts = (0,
|
|
43144
|
+
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"));
|
|
43145
|
+
const directory = (0, import_node_path31.join)(stateRoot, (0, import_node_crypto12.createHash)("sha256").update(`${repo.toLowerCase()}#${pr}`).digest("hex"));
|
|
43146
|
+
(0, import_node_fs34.mkdirSync)(directory, { recursive: true, mode: 448 });
|
|
43147
|
+
const attempts = (0, import_node_fs34.readdirSync)(directory).map((name) => {
|
|
42667
43148
|
if (!/^[1-9][0-9]*\.jsonl$/.test(name)) throw new Error("queue admission receipt directory contains an unrecognized record");
|
|
42668
43149
|
return Number(name.slice(0, -6));
|
|
42669
43150
|
}).sort((a, b) => a - b);
|
|
42670
43151
|
if (attempts.some((number, index) => number !== index + 1)) throw new Error("queue admission receipt sequence is incomplete");
|
|
42671
43152
|
const attempt = attempts.length;
|
|
42672
|
-
const path2 = (number) => (0,
|
|
43153
|
+
const path2 = (number) => (0, import_node_path31.join)(directory, `${number}.jsonl`);
|
|
42673
43154
|
const validate = (value, previous) => {
|
|
42674
43155
|
const row = value;
|
|
42675
43156
|
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 +43165,7 @@ function admissionJournal(repo, pr, root) {
|
|
|
42684
43165
|
};
|
|
42685
43166
|
const load = (number = attempt) => {
|
|
42686
43167
|
let previous;
|
|
42687
|
-
for (const line of (0,
|
|
43168
|
+
for (const line of (0, import_node_fs34.readFileSync)(path2(number), "utf8").trimEnd().split("\n")) previous = validate(JSON.parse(line), previous);
|
|
42688
43169
|
return previous;
|
|
42689
43170
|
};
|
|
42690
43171
|
return {
|
|
@@ -42699,9 +43180,9 @@ function admissionJournal(repo, pr, root) {
|
|
|
42699
43180
|
},
|
|
42700
43181
|
create(head) {
|
|
42701
43182
|
const createdAt = Date.now();
|
|
42702
|
-
const initial = { version: 1, repo: repo.toLowerCase(), pr, head, intentId: (0,
|
|
43183
|
+
const initial = { version: 1, repo: repo.toLowerCase(), pr, head, intentId: (0, import_node_crypto12.randomUUID)(), createdAt, observedAt: createdAt, state: "dispatching" };
|
|
42703
43184
|
try {
|
|
42704
|
-
(0,
|
|
43185
|
+
(0, import_node_fs34.writeFileSync)(path2(attempt + 1), JSON.stringify(initial) + "\n", { flag: "wx", mode: 384, flush: true });
|
|
42705
43186
|
} catch (error) {
|
|
42706
43187
|
if (error.code === "EEXIST") return false;
|
|
42707
43188
|
throw error;
|
|
@@ -42721,7 +43202,7 @@ function admissionJournal(repo, pr, root) {
|
|
|
42721
43202
|
...queueWitnessed !== void 0 ? { queueWitnessed } : {},
|
|
42722
43203
|
...credential && previous.credential === void 0 ? { credential } : {}
|
|
42723
43204
|
}, previous);
|
|
42724
|
-
(0,
|
|
43205
|
+
(0, import_node_fs34.appendFileSync)(path2(attempt), JSON.stringify(receipt) + "\n", { flush: true });
|
|
42725
43206
|
load();
|
|
42726
43207
|
}
|
|
42727
43208
|
};
|
|
@@ -43614,6 +44095,15 @@ async function attachViaHubApp(cfg, contentNodeId) {
|
|
|
43614
44095
|
const { addIssueToProjectViaHub: addIssueToProjectViaHub2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
|
|
43615
44096
|
return addIssueToProjectViaHub2(cfg, contentNodeId);
|
|
43616
44097
|
}
|
|
44098
|
+
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";
|
|
44099
|
+
async function attachOnIntegrationRefusal(detail, cfg, contentNodeId, attach = attachViaHubApp) {
|
|
44100
|
+
if (!/Resource not accessible by integration/i.test(detail)) return void 0;
|
|
44101
|
+
try {
|
|
44102
|
+
return { projectItemId: await attach(cfg, contentNodeId) };
|
|
44103
|
+
} catch (e) {
|
|
44104
|
+
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}` };
|
|
44105
|
+
}
|
|
44106
|
+
}
|
|
43617
44107
|
async function issueNodeIdForAttach(repo, number, ghView, client) {
|
|
43618
44108
|
try {
|
|
43619
44109
|
return (await ghView()).trim();
|
|
@@ -44223,9 +44713,9 @@ function registerBoardCommands(program3) {
|
|
|
44223
44713
|
}
|
|
44224
44714
|
|
|
44225
44715
|
// src/bootstrap-commands.ts
|
|
44226
|
-
var
|
|
44227
|
-
var
|
|
44228
|
-
var
|
|
44716
|
+
var import_node_fs38 = require("node:fs");
|
|
44717
|
+
var import_node_os21 = require("node:os");
|
|
44718
|
+
var import_node_path35 = require("node:path");
|
|
44229
44719
|
init_cli_shared();
|
|
44230
44720
|
init_clean_exit();
|
|
44231
44721
|
|
|
@@ -44410,8 +44900,8 @@ init_github_client();
|
|
|
44410
44900
|
init_cli_shared();
|
|
44411
44901
|
|
|
44412
44902
|
// src/port-registry.ts
|
|
44413
|
-
var
|
|
44414
|
-
var
|
|
44903
|
+
var import_node_fs35 = require("node:fs");
|
|
44904
|
+
var import_node_path32 = require("node:path");
|
|
44415
44905
|
|
|
44416
44906
|
// ../infra/port-geometry.mjs
|
|
44417
44907
|
var PORT_BLOCK = 100;
|
|
@@ -44425,8 +44915,8 @@ function nextPortBlock(registry2) {
|
|
|
44425
44915
|
return [base, base + PORT_SPAN];
|
|
44426
44916
|
}
|
|
44427
44917
|
function loadPortRegistry(path2) {
|
|
44428
|
-
if (!(0,
|
|
44429
|
-
const raw = JSON.parse((0,
|
|
44918
|
+
if (!(0, import_node_fs35.existsSync)(path2)) return {};
|
|
44919
|
+
const raw = JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8"));
|
|
44430
44920
|
const out = {};
|
|
44431
44921
|
for (const [key, value] of Object.entries(raw)) {
|
|
44432
44922
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -44454,22 +44944,22 @@ function existingPortRange(repo, registry2) {
|
|
|
44454
44944
|
return registry2[repo] ?? null;
|
|
44455
44945
|
}
|
|
44456
44946
|
function portRangeInfraAt(root, source) {
|
|
44457
|
-
const registryPath = (0,
|
|
44458
|
-
const ddbScriptPath = (0,
|
|
44459
|
-
if (!(0,
|
|
44947
|
+
const registryPath = (0, import_node_path32.join)(root, "infra", "port-ranges.json");
|
|
44948
|
+
const ddbScriptPath = (0, import_node_path32.join)(root, "infra", "port-ddb.mjs");
|
|
44949
|
+
if (!(0, import_node_fs35.existsSync)(registryPath) || !(0, import_node_fs35.existsSync)(ddbScriptPath)) return null;
|
|
44460
44950
|
return { root, source, registryPath, ddbScriptPath };
|
|
44461
44951
|
}
|
|
44462
44952
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
44463
44953
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
44464
44954
|
if (direct) return direct;
|
|
44465
|
-
for (let dir = cwd; ; dir = (0,
|
|
44466
|
-
const sibling = portRangeInfraAt((0,
|
|
44955
|
+
for (let dir = cwd; ; dir = (0, import_node_path32.dirname)(dir)) {
|
|
44956
|
+
const sibling = portRangeInfraAt((0, import_node_path32.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
44467
44957
|
if (sibling) return sibling;
|
|
44468
|
-
const parent = (0,
|
|
44958
|
+
const parent = (0, import_node_path32.dirname)(dir);
|
|
44469
44959
|
if (parent === dir) break;
|
|
44470
44960
|
}
|
|
44471
44961
|
if (packageDir) {
|
|
44472
|
-
const pkgRoot = (0,
|
|
44962
|
+
const pkgRoot = (0, import_node_path32.join)(packageDir, "..", "..");
|
|
44473
44963
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
44474
44964
|
if (pkgFrom) return pkgFrom;
|
|
44475
44965
|
}
|
|
@@ -44486,7 +44976,7 @@ async function ensurePortRangeAtomic(repo, path2, allocate) {
|
|
|
44486
44976
|
} catch (e) {
|
|
44487
44977
|
const why = e.message;
|
|
44488
44978
|
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,
|
|
44979
|
+
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
44980
|
}
|
|
44491
44981
|
}
|
|
44492
44982
|
|
|
@@ -44538,13 +45028,13 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
|
|
|
44538
45028
|
}
|
|
44539
45029
|
|
|
44540
45030
|
// src/box-commands.ts
|
|
44541
|
-
var
|
|
45031
|
+
var import_node_fs37 = require("node:fs");
|
|
44542
45032
|
var import_node_child_process12 = require("node:child_process");
|
|
44543
|
-
var
|
|
44544
|
-
var
|
|
45033
|
+
var import_node_os20 = require("node:os");
|
|
45034
|
+
var import_node_path34 = require("node:path");
|
|
44545
45035
|
|
|
44546
45036
|
// src/box-control.ts
|
|
44547
|
-
var
|
|
45037
|
+
var import_node_crypto13 = require("node:crypto");
|
|
44548
45038
|
var MANIFEST_PATH = "infra/box-control/manifest.json";
|
|
44549
45039
|
var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
|
|
44550
45040
|
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 +45138,7 @@ function laneCoverageAction(files, box, op) {
|
|
|
44648
45138
|
return { notice: `LANE_COVERAGE: ${mismatch}` };
|
|
44649
45139
|
}
|
|
44650
45140
|
function md5(content) {
|
|
44651
|
-
return (0,
|
|
45141
|
+
return (0, import_node_crypto13.createHash)("md5").update(content).digest("hex");
|
|
44652
45142
|
}
|
|
44653
45143
|
function planRemoteScript(dests) {
|
|
44654
45144
|
const lines2 = ["set -u"];
|
|
@@ -44766,9 +45256,9 @@ init_clean_exit();
|
|
|
44766
45256
|
init_cli_shared();
|
|
44767
45257
|
|
|
44768
45258
|
// src/secrets-commands.ts
|
|
44769
|
-
var
|
|
44770
|
-
var
|
|
44771
|
-
var
|
|
45259
|
+
var import_node_fs36 = require("node:fs");
|
|
45260
|
+
var import_node_path33 = require("node:path");
|
|
45261
|
+
var import_node_os19 = require("node:os");
|
|
44772
45262
|
init_cli_shared();
|
|
44773
45263
|
init_hub_auth();
|
|
44774
45264
|
init_github_client();
|
|
@@ -45964,18 +46454,18 @@ function collectMap(value, previous = []) {
|
|
|
45964
46454
|
return [...previous, value];
|
|
45965
46455
|
}
|
|
45966
46456
|
async function decryptRailsCredentials(input) {
|
|
45967
|
-
const appDir = (0,
|
|
46457
|
+
const appDir = (0, import_node_path33.resolve)(input.appDir ?? process.cwd());
|
|
45968
46458
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
45969
46459
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
45970
|
-
const credentialsPath = (0,
|
|
45971
|
-
const masterKeyPath = (0,
|
|
46460
|
+
const credentialsPath = (0, import_node_path33.resolve)(appDir, credentialsFile);
|
|
46461
|
+
const masterKeyPath = (0, import_node_path33.resolve)(appDir, masterKeyFile);
|
|
45972
46462
|
const env = {
|
|
45973
46463
|
...process.env,
|
|
45974
46464
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
45975
46465
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
45976
46466
|
};
|
|
45977
|
-
if ((0,
|
|
45978
|
-
env.RAILS_MASTER_KEY = (0,
|
|
46467
|
+
if ((0, import_node_fs36.existsSync)(masterKeyPath)) {
|
|
46468
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs36.readFileSync)(masterKeyPath, "utf8").trim();
|
|
45979
46469
|
}
|
|
45980
46470
|
const script = [
|
|
45981
46471
|
'require "json"',
|
|
@@ -45985,9 +46475,9 @@ async function decryptRailsCredentials(input) {
|
|
|
45985
46475
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
45986
46476
|
"puts JSON.generate(config.config)"
|
|
45987
46477
|
].join("\n");
|
|
45988
|
-
const scriptDir = (0,
|
|
45989
|
-
const scriptPath = (0,
|
|
45990
|
-
(0,
|
|
46478
|
+
const scriptDir = (0, import_node_fs36.mkdtempSync)((0, import_node_path33.join)((0, import_node_os19.tmpdir)(), "mmi-rails-decrypt-"));
|
|
46479
|
+
const scriptPath = (0, import_node_path33.join)(scriptDir, "decrypt.rb");
|
|
46480
|
+
(0, import_node_fs36.writeFileSync)(scriptPath, script, "utf8");
|
|
45991
46481
|
try {
|
|
45992
46482
|
const args = ["exec", "ruby", scriptPath];
|
|
45993
46483
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -45999,7 +46489,7 @@ async function decryptRailsCredentials(input) {
|
|
|
45999
46489
|
});
|
|
46000
46490
|
return JSON.parse(stdout);
|
|
46001
46491
|
} finally {
|
|
46002
|
-
(0,
|
|
46492
|
+
(0, import_node_fs36.rmSync)(scriptDir, { recursive: true, force: true });
|
|
46003
46493
|
}
|
|
46004
46494
|
}
|
|
46005
46495
|
async function readSecretStdin() {
|
|
@@ -46089,7 +46579,7 @@ function registerSecretsCommands(program3) {
|
|
|
46089
46579
|
let body;
|
|
46090
46580
|
if (o.file) {
|
|
46091
46581
|
try {
|
|
46092
|
-
body = (0,
|
|
46582
|
+
body = (0, import_node_fs36.readFileSync)((0, import_node_path33.resolve)(o.file), "utf8");
|
|
46093
46583
|
} catch (e) {
|
|
46094
46584
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
46095
46585
|
}
|
|
@@ -46225,7 +46715,7 @@ function registerSecretsCommands(program3) {
|
|
|
46225
46715
|
{
|
|
46226
46716
|
...d,
|
|
46227
46717
|
decryptRailsCredentials,
|
|
46228
|
-
removeFile: (path2) => (0,
|
|
46718
|
+
removeFile: (path2) => (0, import_node_fs36.unlinkSync)((0, import_node_path33.resolve)(o.appDir ?? process.cwd(), path2))
|
|
46229
46719
|
},
|
|
46230
46720
|
{
|
|
46231
46721
|
repo: o.repo,
|
|
@@ -46496,7 +46986,7 @@ function registerBoxCommands(program3) {
|
|
|
46496
46986
|
}
|
|
46497
46987
|
const remote = command && command.length ? command.join(" ") : void 0;
|
|
46498
46988
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
46499
|
-
if (wroteScript) (0,
|
|
46989
|
+
if (wroteScript) (0, import_node_fs37.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
|
|
46500
46990
|
if (o.json) {
|
|
46501
46991
|
console.log(JSON.stringify({
|
|
46502
46992
|
box: found,
|
|
@@ -46623,17 +47113,17 @@ function hubGitShow(ref) {
|
|
|
46623
47113
|
return (path2) => git3(["show", `${ref}:${path2}`]);
|
|
46624
47114
|
}
|
|
46625
47115
|
function runOnBox(found, script) {
|
|
46626
|
-
const dir = (0,
|
|
47116
|
+
const dir = (0, import_node_fs37.mkdtempSync)((0, import_node_path34.join)((0, import_node_os20.tmpdir)(), "mmi-box-control-"));
|
|
46627
47117
|
try {
|
|
46628
|
-
const recipe = (0,
|
|
46629
|
-
(0,
|
|
47118
|
+
const recipe = (0, import_node_path34.join)(dir, "recipe.sh");
|
|
47119
|
+
(0, import_node_fs37.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
|
|
46630
47120
|
const r = (0, import_node_child_process12.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
|
|
46631
47121
|
if (r.status !== 0) {
|
|
46632
47122
|
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
47123
|
}
|
|
46634
47124
|
return r.stdout;
|
|
46635
47125
|
} finally {
|
|
46636
|
-
(0,
|
|
47126
|
+
(0, import_node_fs37.rmSync)(dir, { recursive: true, force: true });
|
|
46637
47127
|
}
|
|
46638
47128
|
}
|
|
46639
47129
|
function formatControl(mode, box, rows) {
|
|
@@ -46761,7 +47251,7 @@ function renderDeployPortDoctor(report) {
|
|
|
46761
47251
|
}
|
|
46762
47252
|
|
|
46763
47253
|
// src/bootstrap-drift.ts
|
|
46764
|
-
var
|
|
47254
|
+
var import_node_crypto14 = require("node:crypto");
|
|
46765
47255
|
function byteComparableSeeds(manifest, cls) {
|
|
46766
47256
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
46767
47257
|
}
|
|
@@ -46781,7 +47271,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
46781
47271
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
46782
47272
|
}
|
|
46783
47273
|
function seedContentHash(content) {
|
|
46784
|
-
return (0,
|
|
47274
|
+
return (0, import_node_crypto14.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
46785
47275
|
}
|
|
46786
47276
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
46787
47277
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -46831,197 +47321,6 @@ function renderSeedDriftReport(findings, reposAudited, seedsPerRepo) {
|
|
|
46831
47321
|
return lines2.join("\n");
|
|
46832
47322
|
}
|
|
46833
47323
|
|
|
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
47324
|
// src/bootstrap-rollback.ts
|
|
47026
47325
|
function resolveRollbackRecord(records, repo, target) {
|
|
47027
47326
|
const candidates = records.filter((r) => r.repo === repo && r.target === target && r.mergeSha);
|
|
@@ -47466,7 +47765,7 @@ var requiredIssueTemplates = [
|
|
|
47466
47765
|
];
|
|
47467
47766
|
var requiredProductWorkflows = [".github/workflows/gate.yml"];
|
|
47468
47767
|
var requiredProductRulesetRef = ".github/rulesets/mmi-product-required-checks.json";
|
|
47469
|
-
var
|
|
47768
|
+
var HUB_REPO6 = "mutmutco/MMI-Hub";
|
|
47470
47769
|
var requiredLabels = ["bug", "feature", "task"];
|
|
47471
47770
|
var requiredPriorityOptions = ["Urgent", "High", "Medium", "Low"];
|
|
47472
47771
|
var strayDefaultLabels = ["documentation", "duplicate", "enhancement", "good first issue", "help wanted", "invalid", "question", "wontfix"];
|
|
@@ -47880,7 +48179,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
47880
48179
|
checks.push({ ok: await contentExists2(deps, repo, baseBranch, path2), label: `issue template exists: ${path2}` });
|
|
47881
48180
|
}
|
|
47882
48181
|
let productContexts = [PRODUCT_GATE_CONTEXT];
|
|
47883
|
-
if (repo !==
|
|
48182
|
+
if (repo !== HUB_REPO6 && repoClass === "deployable") {
|
|
47884
48183
|
const rulesetRaw = await contentText(deps, repo, baseBranch, requiredProductRulesetRef);
|
|
47885
48184
|
productContexts = committedRequiredContexts(rulesetRaw);
|
|
47886
48185
|
const workflowCandidates = [.../* @__PURE__ */ new Set([
|
|
@@ -48124,7 +48423,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
48124
48423
|
detail: coverage.ok ? void 0 : `${coverage.reason} (branches: ${coverage.branches.join(", ")})`
|
|
48125
48424
|
});
|
|
48126
48425
|
}
|
|
48127
|
-
if (repo ===
|
|
48426
|
+
if (repo === HUB_REPO6) {
|
|
48128
48427
|
const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
|
|
48129
48428
|
const missing = requiredHubStatusChecks.filter((check) => !statusChecks.has(check));
|
|
48130
48429
|
checks.push({
|
|
@@ -48222,7 +48521,7 @@ function renderBootstrapVerifyReport(report) {
|
|
|
48222
48521
|
}
|
|
48223
48522
|
|
|
48224
48523
|
// src/bootstrap-seed-delivery.ts
|
|
48225
|
-
var
|
|
48524
|
+
var import_node_crypto15 = require("node:crypto");
|
|
48226
48525
|
init_github_client();
|
|
48227
48526
|
var SHA = /^[a-f0-9]{40}$/;
|
|
48228
48527
|
async function readSeedFile(repo, target, ref, client) {
|
|
@@ -48255,7 +48554,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
|
|
|
48255
48554
|
}
|
|
48256
48555
|
const baseSha = base?.object?.sha;
|
|
48257
48556
|
if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
|
|
48258
|
-
const identity = (0,
|
|
48557
|
+
const identity = (0, import_node_crypto15.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
|
|
48259
48558
|
const delivery = planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`);
|
|
48260
48559
|
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
48560
|
return { ...scoped, baseSha, baseBranch };
|
|
@@ -48325,12 +48624,12 @@ var declaredProjectAdmins = (meta) => {
|
|
|
48325
48624
|
};
|
|
48326
48625
|
var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
48327
48626
|
async function gateRenderDiff(current, rendered) {
|
|
48328
|
-
const dir = (0,
|
|
48329
|
-
const before = (0,
|
|
48330
|
-
const after = (0,
|
|
48627
|
+
const dir = (0, import_node_fs38.mkdtempSync)((0, import_node_path35.join)((0, import_node_os21.tmpdir)(), "mmi-gate-diff-"));
|
|
48628
|
+
const before = (0, import_node_path35.join)(dir, "committed-gate.yml");
|
|
48629
|
+
const after = (0, import_node_path35.join)(dir, "registry-render.yml");
|
|
48331
48630
|
try {
|
|
48332
|
-
(0,
|
|
48333
|
-
(0,
|
|
48631
|
+
(0, import_node_fs38.writeFileSync)(before, current);
|
|
48632
|
+
(0, import_node_fs38.writeFileSync)(after, rendered);
|
|
48334
48633
|
try {
|
|
48335
48634
|
return (await execFileP("git", ["diff", "--no-index", "--", before, after], { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
48336
48635
|
} catch (e) {
|
|
@@ -48339,7 +48638,7 @@ async function gateRenderDiff(current, rendered) {
|
|
|
48339
48638
|
throw e;
|
|
48340
48639
|
}
|
|
48341
48640
|
} finally {
|
|
48342
|
-
(0,
|
|
48641
|
+
(0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
|
|
48343
48642
|
}
|
|
48344
48643
|
}
|
|
48345
48644
|
function bootstrapTrainDoctorDeps() {
|
|
@@ -48352,6 +48651,90 @@ function bootstrapTrainDoctorDeps() {
|
|
|
48352
48651
|
}
|
|
48353
48652
|
};
|
|
48354
48653
|
}
|
|
48654
|
+
async function propagationScope(classes, what) {
|
|
48655
|
+
const cfg = await loadConfig();
|
|
48656
|
+
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
48657
|
+
if (!projects || projects.length === 0) {
|
|
48658
|
+
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)");
|
|
48659
|
+
}
|
|
48660
|
+
const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
|
|
48661
|
+
let independentCount = rosterRepos.length;
|
|
48662
|
+
if ((0, import_node_fs38.existsSync)("projects.json")) {
|
|
48663
|
+
try {
|
|
48664
|
+
const local = JSON.parse((0, import_node_fs38.readFileSync)("projects.json", "utf8"));
|
|
48665
|
+
const localRepos = /* @__PURE__ */ new Set();
|
|
48666
|
+
for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
|
|
48667
|
+
const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
48668
|
+
if (full !== "mutmutco/mmi-hub") localRepos.add(full);
|
|
48669
|
+
}
|
|
48670
|
+
if (localRepos.size > 0) independentCount = localRepos.size;
|
|
48671
|
+
} catch {
|
|
48672
|
+
}
|
|
48673
|
+
}
|
|
48674
|
+
try {
|
|
48675
|
+
assertPropagationCoverage(rosterRepos.length, independentCount);
|
|
48676
|
+
} catch (e) {
|
|
48677
|
+
return fail(e.message);
|
|
48678
|
+
}
|
|
48679
|
+
const bySlugMeta = new Map(projects.flatMap((p) => (p.repos ?? []).map((r) => [(r.includes("/") ? r : `mutmutco/${r}`).toLowerCase(), p])));
|
|
48680
|
+
const classOf = (repo) => bySlugMeta.get(repo.toLowerCase())?.class ?? "deployable";
|
|
48681
|
+
const seedClasses = classes ?? [...new Set(rosterRepos.map(classOf))];
|
|
48682
|
+
const inClassRepos = rosterRepos.filter((repo) => seedClasses.includes(classOf(repo)));
|
|
48683
|
+
const classPriority = [.../* @__PURE__ */ new Set(["deployable", ...seedClasses])].filter((c) => seedClasses.includes(c));
|
|
48684
|
+
let canaryProject = null;
|
|
48685
|
+
for (const cls of classPriority) {
|
|
48686
|
+
const inClass = projects.filter((p) => p.seedCanary === true && (p.class ?? "deployable") === cls);
|
|
48687
|
+
if (inClass.length === 0) continue;
|
|
48688
|
+
if (inClass.length > 1) {
|
|
48689
|
+
const slugs = inClass.map((p) => (p.repos ?? [])[0]?.split("/").pop() ?? "?").join(", ");
|
|
48690
|
+
return fail(
|
|
48691
|
+
`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`
|
|
48692
|
+
);
|
|
48693
|
+
}
|
|
48694
|
+
canaryProject = inClass[0];
|
|
48695
|
+
break;
|
|
48696
|
+
}
|
|
48697
|
+
if (!canaryProject) {
|
|
48698
|
+
return fail(
|
|
48699
|
+
`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)`
|
|
48700
|
+
);
|
|
48701
|
+
}
|
|
48702
|
+
const canarySlug = (canaryProject.repos ?? [])[0]?.split("/").pop()?.toLowerCase() ?? null;
|
|
48703
|
+
const canaryFullRepo = (canaryProject.repos ?? [])[0];
|
|
48704
|
+
const sourcePilot = !!canaryFullRepo && (canaryFullRepo.includes("/") ? canaryFullRepo : `mutmutco/${canaryFullRepo}`).toLowerCase() === "mutmutco/mmi-hub";
|
|
48705
|
+
return { projects, classOf, inClassRepos, canarySlug, sourcePilot };
|
|
48706
|
+
}
|
|
48707
|
+
async function propagatePins(apply, json) {
|
|
48708
|
+
const source = await resolveHubSeedSource(execGitForSeedSource);
|
|
48709
|
+
if (apply && !source.ok) return fail(`bootstrap propagate --pins --apply: ${source.reason}`);
|
|
48710
|
+
const { classOf, inClassRepos, canarySlug, sourcePilot } = await propagationScope(null, "hub action pins");
|
|
48711
|
+
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
48712
|
+
const repos = [];
|
|
48713
|
+
for (const repo of inClassRepos) {
|
|
48714
|
+
let waiver;
|
|
48715
|
+
try {
|
|
48716
|
+
waiver = archivedWaiver(JSON.parse((await gh2(["api", `repos/${repo}`])).stdout));
|
|
48717
|
+
} catch {
|
|
48718
|
+
}
|
|
48719
|
+
repos.push({ repo, slug: repo.split("/").pop().toLowerCase(), waiver });
|
|
48720
|
+
}
|
|
48721
|
+
const tick = await runPinTick({
|
|
48722
|
+
repos,
|
|
48723
|
+
canarySlug,
|
|
48724
|
+
sourcePilot,
|
|
48725
|
+
gh: gh2,
|
|
48726
|
+
apply,
|
|
48727
|
+
baseBranchOf: (repo) => classOf(repo) === "content" ? "main" : "development",
|
|
48728
|
+
createPr: async (args) => requireGhCreateOk(await ghCreate(args), "bootstrap propagate --pins pr create").url,
|
|
48729
|
+
mayAutoMerge: (repo) => propagateMayAutoMerge(repo, classOf(repo), async (path2) => JSON.parse((await gh2(["api", path2])).stdout || "[]"))
|
|
48730
|
+
});
|
|
48731
|
+
if (json) console.log(JSON.stringify({ ...tick.plan, digest: tick.digest, diffs: tick.diffs, summary: summarizePropagationTick(tick.plan) }, null, 2));
|
|
48732
|
+
else console.log(`${renderPinDiffs(tick)}
|
|
48733
|
+
|
|
48734
|
+
${renderPropagationReport(tick.plan)}
|
|
48735
|
+
pins: ${tick.digest}${source.ok ? ` from MMI-Hub@${source.sha}` : ""}`);
|
|
48736
|
+
if (tick.plan.halted) process.exitCode = 1;
|
|
48737
|
+
}
|
|
48355
48738
|
function registerBootstrapCommands(program3) {
|
|
48356
48739
|
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
48740
|
if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
|
|
@@ -48399,13 +48782,13 @@ function registerBootstrapCommands(program3) {
|
|
|
48399
48782
|
})),
|
|
48400
48783
|
pushAllowlistOwners: pushAllowlistAuthority(verifyOwners, declaredProjectAdmins(meta)),
|
|
48401
48784
|
pushAllowlistOwnersRead: verifyOwnersRead,
|
|
48402
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
48785
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null,
|
|
48403
48786
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
48404
48787
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
48405
48788
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
48406
48789
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
48407
48790
|
// sanction, which is the pre-#3664 behaviour.
|
|
48408
|
-
sanctionedAdmins: (0,
|
|
48791
|
+
sanctionedAdmins: (0, import_node_fs38.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs38.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
48409
48792
|
requiredGcpApis: (() => {
|
|
48410
48793
|
const v = meta?.requiredGcpApis;
|
|
48411
48794
|
if (Array.isArray(v)) return v;
|
|
@@ -48483,14 +48866,14 @@ function registerBootstrapCommands(program3) {
|
|
|
48483
48866
|
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
48867
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
48485
48868
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
48486
|
-
if (!(0,
|
|
48869
|
+
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
48870
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
48488
48871
|
if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
|
|
48489
|
-
const manifest = loadBootstrapSeeds((0,
|
|
48872
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
48490
48873
|
const hubContents = /* @__PURE__ */ new Map();
|
|
48491
48874
|
for (const s of manifest.seeds) {
|
|
48492
48875
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
48493
|
-
hubContents.set(s.target, (0,
|
|
48876
|
+
hubContents.set(s.target, (0, import_node_fs38.existsSync)(s.target) ? (0, import_node_fs38.readFileSync)(s.target, "utf8") : null);
|
|
48494
48877
|
}
|
|
48495
48878
|
let targets;
|
|
48496
48879
|
let classOf = (_repo) => "deployable";
|
|
@@ -48747,24 +49130,24 @@ function registerBootstrapCommands(program3) {
|
|
|
48747
49130
|
return;
|
|
48748
49131
|
}
|
|
48749
49132
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
48750
|
-
if (!(0,
|
|
49133
|
+
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
49134
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
48752
49135
|
if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
|
|
48753
|
-
const manifest = loadBootstrapSeeds((0,
|
|
49136
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
48754
49137
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
48755
49138
|
const slug = parsedRepo.slug;
|
|
48756
49139
|
const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
|
|
48757
49140
|
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
48758
|
-
const readFile10 = (p) => (0,
|
|
49141
|
+
const readFile10 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
|
|
48759
49142
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
48760
49143
|
const putSeed = async (target, content, ref, sha) => {
|
|
48761
|
-
const tmp = (0,
|
|
48762
|
-
(0,
|
|
49144
|
+
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`);
|
|
49145
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
48763
49146
|
try {
|
|
48764
49147
|
await gh2(contentPutInputArgs(repo, target, tmp));
|
|
48765
49148
|
} finally {
|
|
48766
49149
|
try {
|
|
48767
|
-
(0,
|
|
49150
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
48768
49151
|
} catch {
|
|
48769
49152
|
}
|
|
48770
49153
|
}
|
|
@@ -49398,13 +49781,14 @@ LIVE apply to ${repo}:
|
|
|
49398
49781
|
if (deferredFailures.length) return failGraceful(`bootstrap apply: incomplete \u2014 every other step ran:
|
|
49399
49782
|
${deferredFailures.join("\n ")}`);
|
|
49400
49783
|
});
|
|
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 () => {
|
|
49784
|
+
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 () => {
|
|
49785
|
+
if (rawFlag("--pins")) return propagatePins(rawFlag("--apply"), rawFlag("--json"));
|
|
49402
49786
|
const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
|
|
49403
49787
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
49404
|
-
if (!(0,
|
|
49788
|
+
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
49789
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
49406
49790
|
if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
49407
|
-
const manifest = loadBootstrapSeeds((0,
|
|
49791
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
49408
49792
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
49409
49793
|
if (!o.target) {
|
|
49410
49794
|
return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
|
|
@@ -49413,59 +49797,11 @@ LIVE apply to ${repo}:
|
|
|
49413
49797
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
49414
49798
|
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
|
|
49415
49799
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
49416
|
-
if (!seed.managedBlock && !(0,
|
|
49417
|
-
const hubContent = seed.managedBlock ? null : (0,
|
|
49418
|
-
const readSeedFile2 = (path2) => (0,
|
|
49800
|
+
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`);
|
|
49801
|
+
const hubContent = seed.managedBlock ? null : (0, import_node_fs38.readFileSync)(seed.target, "utf8");
|
|
49802
|
+
const readSeedFile2 = (path2) => (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null;
|
|
49419
49803
|
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";
|
|
49804
|
+
const { projects, classOf, inClassRepos, canarySlug, sourcePilot } = await propagationScope(seed.classes, `target '${seed.target}'`);
|
|
49469
49805
|
const repos = inClassRepos.map((repo) => {
|
|
49470
49806
|
const slug = repo.split("/").pop().toLowerCase();
|
|
49471
49807
|
const waiver = seed.waivers?.[slug];
|
|
@@ -49582,7 +49918,7 @@ LIVE apply to ${repo}:
|
|
|
49582
49918
|
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
49919
|
const headSha = seedSource.sha;
|
|
49584
49920
|
const sourceBlob = (await execGitForSeedSource(["rev-parse", `${headSha}:${seedSourcePath(seed)}`])).trim();
|
|
49585
|
-
const runbookUnaffected = (0,
|
|
49921
|
+
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
49922
|
for (const rec of plan.records) {
|
|
49587
49923
|
if (rec.action !== "open-pr") continue;
|
|
49588
49924
|
const baseBranch = classOf(rec.repo) === "content" ? "main" : "development";
|
|
@@ -49604,15 +49940,15 @@ LIVE apply to ${repo}:
|
|
|
49604
49940
|
} catch {
|
|
49605
49941
|
existingSha = void 0;
|
|
49606
49942
|
}
|
|
49607
|
-
const tmp = (0,
|
|
49943
|
+
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
49944
|
const desiredContent = desiredByRepo.get(rec.repo);
|
|
49609
49945
|
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
49610
|
-
(0,
|
|
49946
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha, runbookUnaffected)), "utf8");
|
|
49611
49947
|
try {
|
|
49612
49948
|
await gh2(contentPutInputArgs(rec.repo, seed.target, tmp));
|
|
49613
49949
|
} finally {
|
|
49614
49950
|
try {
|
|
49615
|
-
(0,
|
|
49951
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
49616
49952
|
} catch {
|
|
49617
49953
|
}
|
|
49618
49954
|
}
|
|
@@ -49681,10 +50017,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
49681
50017
|
return fail(`bootstrap rollback: ${e.message}`);
|
|
49682
50018
|
}
|
|
49683
50019
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
49684
|
-
if (!(0,
|
|
50020
|
+
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
50021
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
49686
50022
|
if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
|
|
49687
|
-
const manifest = loadBootstrapSeeds((0,
|
|
50023
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
49688
50024
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
49689
50025
|
if (!o.target) {
|
|
49690
50026
|
return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
|
|
@@ -49701,10 +50037,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
49701
50037
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
49702
50038
|
let candidates;
|
|
49703
50039
|
if (o.record) {
|
|
49704
|
-
if (!(0,
|
|
50040
|
+
if (!(0, import_node_fs38.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
|
|
49705
50041
|
let parsed;
|
|
49706
50042
|
try {
|
|
49707
|
-
parsed = JSON.parse((0,
|
|
50043
|
+
parsed = JSON.parse((0, import_node_fs38.readFileSync)(o.record, "utf8"));
|
|
49708
50044
|
} catch (e) {
|
|
49709
50045
|
return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
|
|
49710
50046
|
}
|
|
@@ -49781,13 +50117,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
49781
50117
|
} catch {
|
|
49782
50118
|
existingSha = void 0;
|
|
49783
50119
|
}
|
|
49784
|
-
const tmp = (0,
|
|
49785
|
-
(0,
|
|
50120
|
+
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`);
|
|
50121
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
|
|
49786
50122
|
try {
|
|
49787
50123
|
await gh2(contentPutInputArgs(repo, seed.target, tmp));
|
|
49788
50124
|
} finally {
|
|
49789
50125
|
try {
|
|
49790
|
-
(0,
|
|
50126
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
49791
50127
|
} catch {
|
|
49792
50128
|
}
|
|
49793
50129
|
}
|
|
@@ -50100,7 +50436,7 @@ init_github_client();
|
|
|
50100
50436
|
init_cli_shared();
|
|
50101
50437
|
|
|
50102
50438
|
// src/suite-status.ts
|
|
50103
|
-
var
|
|
50439
|
+
var import_node_crypto16 = require("node:crypto");
|
|
50104
50440
|
var SUITE_REPO = "mutmutco/MM-Work";
|
|
50105
50441
|
var REPOS = {
|
|
50106
50442
|
"mmi-scout": "mutmutco/MMI-Scout",
|
|
@@ -50128,7 +50464,7 @@ function parseSuiteMapping(body) {
|
|
|
50128
50464
|
return void 0;
|
|
50129
50465
|
}
|
|
50130
50466
|
}
|
|
50131
|
-
var sha256 = (b) => (0,
|
|
50467
|
+
var sha256 = (b) => (0, import_node_crypto16.createHash)("sha256").update(b).digest("hex");
|
|
50132
50468
|
async function proveBundle(url, tag, fetchUrl) {
|
|
50133
50469
|
const get = async (u) => {
|
|
50134
50470
|
try {
|
|
@@ -50383,9 +50719,9 @@ async function waitSuiteDev(deps, opts) {
|
|
|
50383
50719
|
}
|
|
50384
50720
|
|
|
50385
50721
|
// src/suite-train.ts
|
|
50386
|
-
var
|
|
50387
|
-
var
|
|
50388
|
-
var
|
|
50722
|
+
var import_node_fs39 = require("node:fs");
|
|
50723
|
+
var import_node_path36 = require("node:path");
|
|
50724
|
+
var import_node_crypto17 = require("node:crypto");
|
|
50389
50725
|
|
|
50390
50726
|
// src/kit-version.ts
|
|
50391
50727
|
var parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(typeof v === "string" ? v.trim() : "");
|
|
@@ -50470,7 +50806,7 @@ function suiteReleaseTag(rc) {
|
|
|
50470
50806
|
function readSuiteCut(path2) {
|
|
50471
50807
|
let raw;
|
|
50472
50808
|
try {
|
|
50473
|
-
raw = (0,
|
|
50809
|
+
raw = (0, import_node_fs39.readFileSync)(path2, "utf8");
|
|
50474
50810
|
} catch (e) {
|
|
50475
50811
|
if (e.code === "ENOENT") return void 0;
|
|
50476
50812
|
throw e;
|
|
@@ -50486,11 +50822,11 @@ function readSuiteCut(path2) {
|
|
|
50486
50822
|
return record2;
|
|
50487
50823
|
}
|
|
50488
50824
|
function writeSuiteCut(path2, record2) {
|
|
50489
|
-
(0,
|
|
50490
|
-
const temp = `${path2}.${(0,
|
|
50825
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path36.dirname)(path2), { recursive: true });
|
|
50826
|
+
const temp = `${path2}.${(0, import_node_crypto17.randomBytes)(8).toString("hex")}.tmp`;
|
|
50491
50827
|
try {
|
|
50492
|
-
(0,
|
|
50493
|
-
(0,
|
|
50828
|
+
(0, import_node_fs39.writeFileSync)(temp, JSON.stringify(record2, null, 2), { flag: "wx" });
|
|
50829
|
+
(0, import_node_fs39.renameSync)(temp, path2);
|
|
50494
50830
|
} catch (e) {
|
|
50495
50831
|
throw new Error(`suite cut record write failed at ${path2}: ${e.message}; stop, verify the file before retrying`);
|
|
50496
50832
|
}
|
|
@@ -50539,25 +50875,25 @@ async function suiteMarkAbandoned(deps, ownerGo = false) {
|
|
|
50539
50875
|
return tags.map((t) => `${t.repo}@${t.tag}`);
|
|
50540
50876
|
}
|
|
50541
50877
|
async function retireFailedLeadRcLedger(deps, ownerGo) {
|
|
50542
|
-
if (!(0,
|
|
50878
|
+
if (!(0, import_node_fs39.existsSync)(deps.ledgerPath)) return void 0;
|
|
50543
50879
|
return withLedgerLock(deps.ledgerPath, async (fence) => {
|
|
50544
|
-
const ledger = parseReleaseLedger((0,
|
|
50880
|
+
const ledger = parseReleaseLedger((0, import_node_fs39.readFileSync)(deps.ledgerPath, "utf8"));
|
|
50545
50881
|
const states = Object.values(ledger.phases).map((p) => p.state);
|
|
50546
50882
|
if (ledger.repo !== SUITE_REPO || ledger.lane !== "rcand" || ledger.phases.promotion.state === "complete" || !states.includes("failed")) return void 0;
|
|
50547
50883
|
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
50884
|
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,
|
|
50885
|
+
const archive = (0, import_node_path36.join)((0, import_node_path36.dirname)(deps.ledgerPath), `phases.archive-${ledger.tag}.json`);
|
|
50886
|
+
if ((0, import_node_fs39.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
|
|
50551
50887
|
await deps.markAbandoned(SUITE_REPO, ledger.tag, ledger.tagSha);
|
|
50552
|
-
await fence(() => (0,
|
|
50888
|
+
await fence(() => (0, import_node_fs39.renameSync)(deps.ledgerPath, archive));
|
|
50553
50889
|
return `${ledger.tag} (ledger archived to ${archive})`;
|
|
50554
50890
|
});
|
|
50555
50891
|
}
|
|
50556
50892
|
function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Date()) {
|
|
50557
50893
|
const record2 = abandonableCut(recordPath, ownerGo);
|
|
50558
50894
|
const archive = `${recordPath}.abandoned-${record2.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
|
|
50559
|
-
if ((0,
|
|
50560
|
-
(0,
|
|
50895
|
+
if ((0, import_node_fs39.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
|
|
50896
|
+
(0, import_node_fs39.renameSync)(recordPath, archive);
|
|
50561
50897
|
return { archive, orphanTags: record2.members.filter((m) => m.tag).map((m) => `${m.repo}@${m.tag}`) };
|
|
50562
50898
|
}
|
|
50563
50899
|
async function suiteRestoreMainPins(deps) {
|
|
@@ -50644,12 +50980,12 @@ async function suiteRcandRun(deps, opts) {
|
|
|
50644
50980
|
}
|
|
50645
50981
|
if ((record2?.announcement === "sent" || record2?.announcement === "skipped") && record2.leadReleased) {
|
|
50646
50982
|
const archive = `${deps.recordPath}.${record2.leadRcTag}.json`;
|
|
50647
|
-
if ((0,
|
|
50648
|
-
(0,
|
|
50983
|
+
if ((0, import_node_fs39.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
|
|
50984
|
+
(0, import_node_fs39.renameSync)(deps.recordPath, archive);
|
|
50649
50985
|
record2 = void 0;
|
|
50650
50986
|
}
|
|
50651
50987
|
if (record2 && !record2.pinPr && !record2.leadRcTag && record2.members.every((m) => !m.tag)) {
|
|
50652
|
-
(0,
|
|
50988
|
+
(0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
|
|
50653
50989
|
record2 = void 0;
|
|
50654
50990
|
}
|
|
50655
50991
|
if (!record2) {
|
|
@@ -50703,7 +51039,7 @@ async function suiteRcandRun(deps, opts) {
|
|
|
50703
51039
|
quiet.push(member);
|
|
50704
51040
|
}
|
|
50705
51041
|
if (record2.members.every((m) => m.carried || quiet.includes(m)) && await deps.contains(SUITE_REPO, record2.leadDevelopment, record2.leadPreviousRc)) {
|
|
50706
|
-
(0,
|
|
51042
|
+
(0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
|
|
50707
51043
|
throw new Error("nothing to promote: neither MM-Work nor any module has new development commits since its rc; no suite cut started");
|
|
50708
51044
|
}
|
|
50709
51045
|
for (const member of quiet) {
|
|
@@ -50721,7 +51057,7 @@ async function suiteRcandRun(deps, opts) {
|
|
|
50721
51057
|
}
|
|
50722
51058
|
if (refusals.length) {
|
|
50723
51059
|
const untouched = record2.members.every((m) => !m.tag) && !record2.pinPr;
|
|
50724
|
-
if (untouched) (0,
|
|
51060
|
+
if (untouched) (0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
|
|
50725
51061
|
throw new Error(`module rc serving is not ready; ${untouched ? "no suite cut started" : "no new tag was cut"}:
|
|
50726
51062
|
${refusals.join("\n")}
|
|
50727
51063
|
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 +51249,11 @@ Each member's live release must serve <data>/main/module.js at its main pin path
|
|
|
50913
51249
|
}
|
|
50914
51250
|
return record2;
|
|
50915
51251
|
}
|
|
50916
|
-
var hotfixRecordPath = (deps) => (0,
|
|
51252
|
+
var hotfixRecordPath = (deps) => (0, import_node_path36.join)((0, import_node_path36.dirname)(deps.recordPath), "hotfix.json");
|
|
50917
51253
|
function readSuiteHotfix(path2) {
|
|
50918
51254
|
let raw;
|
|
50919
51255
|
try {
|
|
50920
|
-
raw = (0,
|
|
51256
|
+
raw = (0, import_node_fs39.readFileSync)(path2, "utf8");
|
|
50921
51257
|
} catch (e) {
|
|
50922
51258
|
if (e.code === "ENOENT") return void 0;
|
|
50923
51259
|
throw e;
|
|
@@ -50960,7 +51296,7 @@ async function suiteHotfix(deps, opts) {
|
|
|
50960
51296
|
if (completed) record2 = void 0;
|
|
50961
51297
|
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
51298
|
if (!record2?.tag) await deps.validateHotfixSummary();
|
|
50963
|
-
if (completed) (0,
|
|
51299
|
+
if (completed) (0, import_node_fs39.renameSync)(path2, `${path2}.${completed.leadTag}.json`);
|
|
50964
51300
|
const rec = record2 ?? { schema: "mmi-suite-hotfix/v1", member: found.id, repo: found.repo, from: opts.from, ...opts.ack?.length ? { ack: opts.ack } : {} };
|
|
50965
51301
|
const save = () => writeSuiteHotfix(path2, rec);
|
|
50966
51302
|
save();
|
|
@@ -51228,9 +51564,9 @@ async function runRcDataSwitch(deps, opts) {
|
|
|
51228
51564
|
|
|
51229
51565
|
// src/suite-train-io.ts
|
|
51230
51566
|
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
51567
|
var import_node_path49 = require("node:path");
|
|
51568
|
+
var import_node_fs52 = require("node:fs");
|
|
51569
|
+
var import_node_path50 = require("node:path");
|
|
51234
51570
|
init_cli_shared();
|
|
51235
51571
|
|
|
51236
51572
|
// src/command-register-train-operations.ts
|
|
@@ -51583,9 +51919,9 @@ A hotfix branch is cut from origin/main and carries ONLY the fixes --from names,
|
|
|
51583
51919
|
async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
|
|
51584
51920
|
if (sources.length === 0) return { failures: [], skipped: [] };
|
|
51585
51921
|
const { mkdtemp: mkdtemp2, rm: rm2 } = await import("node:fs/promises");
|
|
51586
|
-
const { tmpdir:
|
|
51922
|
+
const { tmpdir: tmpdir13 } = await import("node:os");
|
|
51587
51923
|
const { join: join54 } = await import("node:path");
|
|
51588
|
-
const dir = await mkdtemp2(join54(
|
|
51924
|
+
const dir = await mkdtemp2(join54(tmpdir13(), "mmi-hotfix-preflight-"));
|
|
51589
51925
|
const wt = { run: (file, args) => deps.run(file, file === "git" ? ["-C", dir, ...args] : args) };
|
|
51590
51926
|
const failures = [];
|
|
51591
51927
|
const skipped = [];
|
|
@@ -52922,12 +53258,75 @@ function checkHotfixCarries(options) {
|
|
|
52922
53258
|
}
|
|
52923
53259
|
|
|
52924
53260
|
// src/merge-cleanup.ts
|
|
52925
|
-
var
|
|
52926
|
-
var
|
|
52927
|
-
var
|
|
53261
|
+
var import_node_fs40 = require("node:fs");
|
|
53262
|
+
var import_node_path37 = require("node:path");
|
|
53263
|
+
var import_node_os22 = require("node:os");
|
|
52928
53264
|
init_cli_shared();
|
|
52929
53265
|
init_github_client();
|
|
52930
53266
|
|
|
53267
|
+
// src/post-merge-app-route.ts
|
|
53268
|
+
init_cli_shared();
|
|
53269
|
+
init_github_client();
|
|
53270
|
+
var PostMergeCredentialError = class extends Error {
|
|
53271
|
+
constructor(label, why) {
|
|
53272
|
+
super(`post-merge credential refused (${label}): ${why}`);
|
|
53273
|
+
this.name = "PostMergeCredentialError";
|
|
53274
|
+
}
|
|
53275
|
+
};
|
|
53276
|
+
function isUnauthorized(e) {
|
|
53277
|
+
if (e instanceof GitHubApiError) return e.status === 401;
|
|
53278
|
+
const text = `${e?.stderr ?? ""} ${e?.message ?? ""}`;
|
|
53279
|
+
return /HTTP 401|Bad credentials|gh auth login|set the GH_TOKEN environment variable/i.test(text);
|
|
53280
|
+
}
|
|
53281
|
+
async function appTokenFor(label, first) {
|
|
53282
|
+
let token;
|
|
53283
|
+
try {
|
|
53284
|
+
token = await refreshPollToken();
|
|
53285
|
+
} catch (e) {
|
|
53286
|
+
throw new PostMergeCredentialError(label, `HTTP 401 on the gh login and the App installation token could not be minted (${e.message})`);
|
|
53287
|
+
}
|
|
53288
|
+
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`);
|
|
53289
|
+
return token;
|
|
53290
|
+
}
|
|
53291
|
+
async function ghWithAppOn401(label, args, options) {
|
|
53292
|
+
try {
|
|
53293
|
+
return await execFileP("gh", args, options);
|
|
53294
|
+
} catch (e) {
|
|
53295
|
+
if (!isUnauthorized(e)) throw e;
|
|
53296
|
+
const token = await appTokenFor(label, e);
|
|
53297
|
+
try {
|
|
53298
|
+
return await execFileP("gh", args, { ...options, env: { ...process.env, GH_TOKEN: token, MMI_GH_POOL: "app" } });
|
|
53299
|
+
} catch (e2) {
|
|
53300
|
+
if (!isUnauthorized(e2)) throw e2;
|
|
53301
|
+
throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
|
|
53302
|
+
}
|
|
53303
|
+
}
|
|
53304
|
+
}
|
|
53305
|
+
function withAppOn401(client, label = "post-merge") {
|
|
53306
|
+
let app;
|
|
53307
|
+
const run = async (call) => {
|
|
53308
|
+
try {
|
|
53309
|
+
return await call(app ?? client);
|
|
53310
|
+
} catch (e) {
|
|
53311
|
+
if (!isUnauthorized(e)) throw e;
|
|
53312
|
+
if (app) throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
|
|
53313
|
+
const token = await appTokenFor(label, e);
|
|
53314
|
+
app = createGitHubClient({ token: async () => token });
|
|
53315
|
+
try {
|
|
53316
|
+
return await call(app);
|
|
53317
|
+
} catch (e2) {
|
|
53318
|
+
if (isUnauthorized(e2)) throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
|
|
53319
|
+
throw e2;
|
|
53320
|
+
}
|
|
53321
|
+
}
|
|
53322
|
+
};
|
|
53323
|
+
return {
|
|
53324
|
+
rest: (method, path2, init) => run((c) => c.rest(method, path2, init)),
|
|
53325
|
+
restPaginate: (path2, init) => run((c) => c.restPaginate(path2, init)),
|
|
53326
|
+
graphql: (query, variables, init) => run((c) => c.graphql(query, variables, init))
|
|
53327
|
+
};
|
|
53328
|
+
}
|
|
53329
|
+
|
|
52931
53330
|
// src/board-advance.ts
|
|
52932
53331
|
function repoOf2(ref) {
|
|
52933
53332
|
if (!ref || typeof ref !== "object") return void 0;
|
|
@@ -53148,10 +53547,10 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
53148
53547
|
// only for PRs into the default branch, so a PR into rc/main (a train or hotfix PR) never falls back.
|
|
53149
53548
|
fallback: {
|
|
53150
53549
|
fetchClaimed: async () => {
|
|
53151
|
-
const { stdout } = await
|
|
53550
|
+
const { stdout } = await ghWithAppOn401("PR body read", ["pr", "view", prNumber, ...repoArgs, "--json", "body,baseRefName"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53152
53551
|
const { body, baseRefName } = JSON.parse(stdout || "null") ?? {};
|
|
53153
53552
|
if (typeof body !== "string" || typeof baseRefName !== "string") throw new Error("gh returned no PR body or base branch");
|
|
53154
|
-
const repoView = await
|
|
53553
|
+
const repoView = await ghWithAppOn401("default branch read", ["repo", "view", repo, "--json", "defaultBranchRef"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53155
53554
|
const defaultBranch = JSON.parse(repoView.stdout || "null")?.defaultBranchRef?.name;
|
|
53156
53555
|
if (typeof defaultBranch !== "string" || !defaultBranch) throw new Error("gh returned no default branch");
|
|
53157
53556
|
if (baseRefName !== defaultBranch) return [];
|
|
@@ -53159,7 +53558,7 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
53159
53558
|
},
|
|
53160
53559
|
closeIssue: async (n) => {
|
|
53161
53560
|
try {
|
|
53162
|
-
const view = await
|
|
53561
|
+
const view = await ghWithAppOn401("issue state read", ["issue", "view", String(n), "--repo", repo, "--json", "state"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53163
53562
|
if (JSON.parse(view.stdout || "null")?.state !== "OPEN") return { state: "already-closed" };
|
|
53164
53563
|
await execFileP("gh", [
|
|
53165
53564
|
"issue",
|
|
@@ -53179,7 +53578,7 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
53179
53578
|
}
|
|
53180
53579
|
},
|
|
53181
53580
|
fetchClosingIssues: async () => {
|
|
53182
|
-
const { stdout } = await
|
|
53581
|
+
const { stdout } = await ghWithAppOn401("closingIssuesReferences read", ["pr", "view", prNumber, ...repoArgs, "--json", "closingIssuesReferences"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53183
53582
|
const parsed = JSON.parse(stdout || "null");
|
|
53184
53583
|
if (!parsed || typeof parsed !== "object" || !("closingIssuesReferences" in parsed)) {
|
|
53185
53584
|
throw new Error("gh returned no closingIssuesReferences field for this PR");
|
|
@@ -53268,13 +53667,13 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
|
|
|
53268
53667
|
return rewritten === base ? null : rewritten;
|
|
53269
53668
|
}
|
|
53270
53669
|
function writeSquashBodyFile(body) {
|
|
53271
|
-
const dir = (0,
|
|
53272
|
-
const path2 = (0,
|
|
53273
|
-
(0,
|
|
53670
|
+
const dir = (0, import_node_fs40.mkdtempSync)((0, import_node_path37.join)((0, import_node_os22.tmpdir)(), "mmi-squash-body-"));
|
|
53671
|
+
const path2 = (0, import_node_path37.join)(dir, "body.txt");
|
|
53672
|
+
(0, import_node_fs40.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
|
|
53274
53673
|
`, "utf8");
|
|
53275
53674
|
return { path: path2, cleanup: () => {
|
|
53276
53675
|
try {
|
|
53277
|
-
(0,
|
|
53676
|
+
(0, import_node_fs40.rmSync)(dir, { recursive: true, force: true });
|
|
53278
53677
|
} catch {
|
|
53279
53678
|
}
|
|
53280
53679
|
} };
|
|
@@ -53541,9 +53940,9 @@ function hubReleaseCiRun(env) {
|
|
|
53541
53940
|
}
|
|
53542
53941
|
|
|
53543
53942
|
// src/release-custody.ts
|
|
53544
|
-
var
|
|
53545
|
-
var
|
|
53546
|
-
var
|
|
53943
|
+
var import_node_crypto18 = require("node:crypto");
|
|
53944
|
+
var import_node_fs41 = require("node:fs");
|
|
53945
|
+
var import_node_path38 = require("node:path");
|
|
53547
53946
|
init_github_client();
|
|
53548
53947
|
|
|
53549
53948
|
// src/release-custody-bootstrap.ts
|
|
@@ -53764,7 +54163,7 @@ var ReleaseCustodyError = class extends Error {
|
|
|
53764
54163
|
this.name = "ReleaseCustodyError";
|
|
53765
54164
|
}
|
|
53766
54165
|
};
|
|
53767
|
-
var custodyDigest = (value) => (0,
|
|
54166
|
+
var custodyDigest = (value) => (0, import_node_crypto18.createHash)("sha256").update(value).digest("hex");
|
|
53768
54167
|
function parseCustody(raw, repo) {
|
|
53769
54168
|
const s = JSON.parse(raw);
|
|
53770
54169
|
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 +54251,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
|
|
|
53852
54251
|
throw new ReleaseCustodyError(`previous controller run ${found.state.controller.runId} attempt ${found.state.controller.runAttempt} is not proven terminal`);
|
|
53853
54252
|
}
|
|
53854
54253
|
let state = {
|
|
53855
|
-
...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0,
|
|
54254
|
+
...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0, import_node_crypto18.randomUUID)(), effects: {}, ...await initialize() },
|
|
53856
54255
|
revision: (found.state?.revision ?? 0) + 1,
|
|
53857
54256
|
controller
|
|
53858
54257
|
};
|
|
@@ -53913,7 +54312,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
|
|
|
53913
54312
|
proof: { kind: "ledger-lifecycle", controller: state.controller }
|
|
53914
54313
|
};
|
|
53915
54314
|
}
|
|
53916
|
-
return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0,
|
|
54315
|
+
return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0, import_node_crypto18.randomUUID)() : state.precutId };
|
|
53917
54316
|
});
|
|
53918
54317
|
}
|
|
53919
54318
|
async preserveLocal(raw) {
|
|
@@ -54013,7 +54412,7 @@ function reclassifySupersededArchives(state) {
|
|
|
54013
54412
|
}
|
|
54014
54413
|
function localRaw(path2) {
|
|
54015
54414
|
try {
|
|
54016
|
-
return (0,
|
|
54415
|
+
return (0, import_node_fs41.readFileSync)(path2, "utf8");
|
|
54017
54416
|
} catch (e) {
|
|
54018
54417
|
if (e.code === "ENOENT") return null;
|
|
54019
54418
|
throw e;
|
|
@@ -54022,14 +54421,14 @@ function localRaw(path2) {
|
|
|
54022
54421
|
function hydrateLocal(path2, raw) {
|
|
54023
54422
|
const previous = localRaw(path2);
|
|
54024
54423
|
if (previous === raw) return;
|
|
54025
|
-
(0,
|
|
54424
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path38.dirname)(path2), { recursive: true });
|
|
54026
54425
|
if (previous !== null) {
|
|
54027
54426
|
const ledger = parseReleaseLedger(previous);
|
|
54028
|
-
const archive = (0,
|
|
54029
|
-
if (!(0,
|
|
54030
|
-
if (raw === null) (0,
|
|
54427
|
+
const archive = (0, import_node_path38.join)((0, import_node_path38.dirname)(path2), `phases.local-${ledger.tagSha}-${custodyDigest(previous)}.json`);
|
|
54428
|
+
if (!(0, import_node_fs41.existsSync)(archive)) (0, import_node_fs41.writeFileSync)(archive, previous, { flag: "wx" });
|
|
54429
|
+
if (raw === null) (0, import_node_fs41.renameSync)(path2, `${path2}.retired-${custodyDigest(previous)}`);
|
|
54031
54430
|
}
|
|
54032
|
-
if (raw !== null) (0,
|
|
54431
|
+
if (raw !== null) (0, import_node_fs41.writeFileSync)(path2, raw, "utf8");
|
|
54033
54432
|
}
|
|
54034
54433
|
async function commandIdentity(file, args, run, repo) {
|
|
54035
54434
|
if (file === "git" && args[0] === "push") {
|
|
@@ -54139,7 +54538,7 @@ function withHubReleaseCustody(deps) {
|
|
|
54139
54538
|
if (!authority.ok || !authority.train || authority.authenticatedDelegation !== true) {
|
|
54140
54539
|
throw new ReleaseCustodyError("repository custody requires the server-verified owner-triggered train session");
|
|
54141
54540
|
}
|
|
54142
|
-
const controller = { ...runIdentity, token: (0,
|
|
54541
|
+
const controller = { ...runIdentity, token: (0, import_node_crypto18.randomUUID)() };
|
|
54143
54542
|
const raw = localRaw(path2);
|
|
54144
54543
|
const requiredContexts = () => discoverRequiredCheckContexts(
|
|
54145
54544
|
deps,
|
|
@@ -54219,8 +54618,8 @@ function withHubReleaseCustody(deps) {
|
|
|
54219
54618
|
}
|
|
54220
54619
|
|
|
54221
54620
|
// src/project-info-sync.ts
|
|
54222
|
-
var
|
|
54223
|
-
var
|
|
54621
|
+
var import_node_fs42 = require("node:fs");
|
|
54622
|
+
var import_node_path39 = require("node:path");
|
|
54224
54623
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
54225
54624
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
54226
54625
|
projectV2 { id }
|
|
@@ -54267,14 +54666,14 @@ function sharedName(entries, fallback) {
|
|
|
54267
54666
|
}
|
|
54268
54667
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
54269
54668
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
54270
|
-
const readmePath = (0,
|
|
54271
|
-
if (!(0,
|
|
54669
|
+
const readmePath = (0, import_node_path39.join)(repoRoot2, "README.md");
|
|
54670
|
+
if (!(0, import_node_fs42.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
54272
54671
|
const entries = entriesFor(project2, projects);
|
|
54273
54672
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
54274
54673
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
54275
54674
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
54276
54675
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
54277
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
54676
|
+
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
54677
|
const lines2 = [
|
|
54279
54678
|
`# ${projectName}`,
|
|
54280
54679
|
"",
|
|
@@ -54293,8 +54692,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
54293
54692
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
54294
54693
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
54295
54694
|
const orgDocs = [
|
|
54296
|
-
(0,
|
|
54297
|
-
(0,
|
|
54695
|
+
(0, import_node_fs42.existsSync)((0, import_node_path39.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
|
|
54696
|
+
(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
54697
|
].filter(Boolean);
|
|
54299
54698
|
if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
|
|
54300
54699
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
|
|
@@ -54316,8 +54715,8 @@ async function syncProjectInfo(plan, client, apply) {
|
|
|
54316
54715
|
}
|
|
54317
54716
|
|
|
54318
54717
|
// src/train-commands.ts
|
|
54319
|
-
var
|
|
54320
|
-
var
|
|
54718
|
+
var import_node_fs43 = require("node:fs");
|
|
54719
|
+
var import_node_path40 = require("node:path");
|
|
54321
54720
|
init_cli_shared();
|
|
54322
54721
|
init_clean_exit();
|
|
54323
54722
|
init_client_version();
|
|
@@ -54332,7 +54731,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
54332
54731
|
}
|
|
54333
54732
|
function readRepoVersion() {
|
|
54334
54733
|
try {
|
|
54335
|
-
return JSON.parse((0,
|
|
54734
|
+
return JSON.parse((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
54336
54735
|
} catch {
|
|
54337
54736
|
return void 0;
|
|
54338
54737
|
}
|
|
@@ -54443,7 +54842,7 @@ function releaseProgressVerdict(status, subject = "Release") {
|
|
|
54443
54842
|
|
|
54444
54843
|
// src/suite-pin.ts
|
|
54445
54844
|
var import_promises6 = require("node:fs/promises");
|
|
54446
|
-
var
|
|
54845
|
+
var import_node_path41 = require("node:path");
|
|
54447
54846
|
var SuitePinError = class _SuitePinError extends Error {
|
|
54448
54847
|
diagnostic;
|
|
54449
54848
|
constructor(repo, tag, error) {
|
|
@@ -54511,8 +54910,8 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
54511
54910
|
if (input.carriedOnly) return { status: "unchanged" };
|
|
54512
54911
|
throw new Error("pins already match without a merged cut PR \u2014 provenance unverified");
|
|
54513
54912
|
}
|
|
54514
|
-
await (0, import_promises6.mkdir)((0,
|
|
54515
|
-
scratch = await (0, import_promises6.mkdtemp)((0,
|
|
54913
|
+
await (0, import_promises6.mkdir)((0, import_node_path41.join)(deps.root, "tmp"), { recursive: true });
|
|
54914
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path41.join)(deps.root, "tmp", "suite-pin-"));
|
|
54516
54915
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
54517
54916
|
await run("git", ["clone", "--quiet", "--branch", prs.length ? branch : "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
54518
54917
|
if (prs.length) {
|
|
@@ -54524,7 +54923,7 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
54524
54923
|
await run("git", ["switch", "-c", branch]);
|
|
54525
54924
|
}
|
|
54526
54925
|
for (const stage of ["rc", "main"]) {
|
|
54527
|
-
const path2 = (0,
|
|
54926
|
+
const path2 = (0, import_node_path41.join)(scratch, `${stage}-pins.json`);
|
|
54528
54927
|
await (0, import_promises6.writeFile)(path2, JSON.stringify(input.pins.map((p) => ({ module: p.module, url: p[stage] }))));
|
|
54529
54928
|
await run("node", ["scripts/pin-modules.mjs", "--stage", stage, "--pins", path2]);
|
|
54530
54929
|
}
|
|
@@ -54577,12 +54976,12 @@ async function openSuiteRestorePinPr(deps, input) {
|
|
|
54577
54976
|
}
|
|
54578
54977
|
});
|
|
54579
54978
|
if (!todo.length) return void 0;
|
|
54580
|
-
await (0, import_promises6.mkdir)((0,
|
|
54581
|
-
scratch = await (0, import_promises6.mkdtemp)((0,
|
|
54979
|
+
await (0, import_promises6.mkdir)((0, import_node_path41.join)(deps.root, "tmp"), { recursive: true });
|
|
54980
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path41.join)(deps.root, "tmp", "suite-pin-"));
|
|
54582
54981
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
54583
54982
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
54584
54983
|
await run("git", ["switch", "-c", branch]);
|
|
54585
|
-
const path2 = (0,
|
|
54984
|
+
const path2 = (0, import_node_path41.join)(scratch, "main-pins.json");
|
|
54586
54985
|
await (0, import_promises6.writeFile)(path2, JSON.stringify(todo.map((p) => ({ module: p.module, url: p.prior }))));
|
|
54587
54986
|
await run("node", ["scripts/pin-modules.mjs", "--stage", "main", "--pins", path2]);
|
|
54588
54987
|
await run("git", ["add", "seed/modules.json"]);
|
|
@@ -54656,8 +55055,8 @@ async function openSuitePinPr(deps, input) {
|
|
|
54656
55055
|
if (newer.length) return { status: "superseded", pr: newer[0].url, url: pin };
|
|
54657
55056
|
const open2 = onBranch.find((p) => p.state === "OPEN");
|
|
54658
55057
|
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,
|
|
55058
|
+
await (0, import_promises6.mkdir)((0, import_node_path41.join)(deps.root, "tmp"), { recursive: true });
|
|
55059
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path41.join)(deps.root, "tmp", "suite-pin-"));
|
|
54661
55060
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
54662
55061
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
54663
55062
|
await run("git", ["switch", "-c", branch]);
|
|
@@ -54706,7 +55105,7 @@ function prTitleRefusal(title) {
|
|
|
54706
55105
|
|
|
54707
55106
|
// src/command-register-collaboration.ts
|
|
54708
55107
|
var import_node_child_process17 = require("node:child_process");
|
|
54709
|
-
var
|
|
55108
|
+
var import_node_fs51 = require("node:fs");
|
|
54710
55109
|
var import_promises8 = require("node:fs/promises");
|
|
54711
55110
|
init_clean_exit();
|
|
54712
55111
|
init_cli_shared();
|
|
@@ -54730,11 +55129,11 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
54730
55129
|
}
|
|
54731
55130
|
|
|
54732
55131
|
// src/command-register-collaboration.ts
|
|
54733
|
-
var
|
|
55132
|
+
var import_node_path48 = require("node:path");
|
|
54734
55133
|
init_house_map();
|
|
54735
55134
|
|
|
54736
55135
|
// src/issue-body.ts
|
|
54737
|
-
var
|
|
55136
|
+
var import_node_os23 = require("node:os");
|
|
54738
55137
|
init_error_codes();
|
|
54739
55138
|
var TextArgError = class extends Error {
|
|
54740
55139
|
constructor(message2, code, offendingFlag) {
|
|
@@ -54747,7 +55146,7 @@ var TextArgError = class extends Error {
|
|
|
54747
55146
|
offendingFlag;
|
|
54748
55147
|
};
|
|
54749
55148
|
function emptyStdinMessage(fileFlag) {
|
|
54750
|
-
if ((0,
|
|
55149
|
+
if ((0, import_node_os23.platform)() === "win32") {
|
|
54751
55150
|
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
55151
|
}
|
|
54753
55152
|
return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
|
|
@@ -54847,8 +55246,8 @@ init_github_client();
|
|
|
54847
55246
|
init_github_quota();
|
|
54848
55247
|
|
|
54849
55248
|
// src/issue-commands.ts
|
|
54850
|
-
var
|
|
54851
|
-
var
|
|
55249
|
+
var import_node_fs44 = require("node:fs");
|
|
55250
|
+
var import_node_crypto19 = require("node:crypto");
|
|
54852
55251
|
init_cli_shared();
|
|
54853
55252
|
init_clean_exit();
|
|
54854
55253
|
init_error_codes();
|
|
@@ -55040,7 +55439,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
55040
55439
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
55041
55440
|
const patch = {};
|
|
55042
55441
|
let bodyChanged = false;
|
|
55043
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
55442
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs44.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
55044
55443
|
if (options.titleFile !== void 0) {
|
|
55045
55444
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
55046
55445
|
} else if (options.title !== void 0) {
|
|
@@ -55149,8 +55548,11 @@ async function closeIssue(client, options, deps = {}) {
|
|
|
55149
55548
|
}
|
|
55150
55549
|
const appWriter = await selectClaimWriteClient(client, deps.appClient);
|
|
55151
55550
|
if (reason === "completed" && options.evidence !== void 0) {
|
|
55152
|
-
|
|
55153
|
-
|
|
55551
|
+
try {
|
|
55552
|
+
await appWriter.rest("POST", `repos/${repo}/labels`, { body: { name: CLOSED_MERGED_LOOP_STATE_LABEL, color: "ededed" } });
|
|
55553
|
+
} catch (e) {
|
|
55554
|
+
if (!(e instanceof GitHubApiError && e.status === 422)) throw e;
|
|
55555
|
+
}
|
|
55154
55556
|
await appWriter.rest("POST", `repos/${repo}/issues/${parsed.number}/labels`, {
|
|
55155
55557
|
body: { labels: [CLOSED_MERGED_LOOP_STATE_LABEL] }
|
|
55156
55558
|
});
|
|
@@ -55322,7 +55724,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
55322
55724
|
const identity = `${spec.type}
|
|
55323
55725
|
${spec.title.trim()}
|
|
55324
55726
|
${spec.body ?? ""}`;
|
|
55325
|
-
const hash = (0,
|
|
55727
|
+
const hash = (0, import_node_crypto19.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
55326
55728
|
return `${batchKey}:${hash}`;
|
|
55327
55729
|
}
|
|
55328
55730
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -55704,7 +56106,7 @@ function extendCreateCommand(issue, batchAttach) {
|
|
|
55704
56106
|
if (opts.batch) {
|
|
55705
56107
|
let specs;
|
|
55706
56108
|
try {
|
|
55707
|
-
const raw = (0,
|
|
56109
|
+
const raw = (0, import_node_fs44.readFileSync)(opts.batch, "utf8");
|
|
55708
56110
|
specs = JSON.parse(raw);
|
|
55709
56111
|
if (!Array.isArray(specs)) {
|
|
55710
56112
|
const top = specs === null ? "null" : typeof specs === "object" ? `an object with keys: ${Object.keys(specs).join(", ") || "(none)"}` : `a ${typeof specs}`;
|
|
@@ -55781,8 +56183,8 @@ ${lines2}`, {
|
|
|
55781
56183
|
}
|
|
55782
56184
|
|
|
55783
56185
|
// src/post-merge-recon.ts
|
|
55784
|
-
var
|
|
55785
|
-
var
|
|
56186
|
+
var import_node_fs45 = require("node:fs");
|
|
56187
|
+
var import_node_path42 = require("node:path");
|
|
55786
56188
|
|
|
55787
56189
|
// src/cross-repo-filing-issue.ts
|
|
55788
56190
|
init_github_client();
|
|
@@ -55842,7 +56244,8 @@ function explicitSameOrgIssueUrls(text, prRepo, closingNumbers) {
|
|
|
55842
56244
|
}
|
|
55843
56245
|
return [...found.values()];
|
|
55844
56246
|
}
|
|
55845
|
-
async function reconcileCrossRepoFilingIssue(
|
|
56247
|
+
async function reconcileCrossRepoFilingIssue(rawClient, prRepo, prNumber) {
|
|
56248
|
+
const client = withAppOn401(rawClient, `cross-repo filing reconcile ${prRepo}#${prNumber}`);
|
|
55846
56249
|
try {
|
|
55847
56250
|
const pr = await client.rest("GET", `repos/${prRepo}/pulls/${prNumber}`);
|
|
55848
56251
|
if (!pr.merged_at) return { status: "not-applicable" };
|
|
@@ -55874,7 +56277,7 @@ ${pr.body ?? ""}`;
|
|
|
55874
56277
|
const issue = `${target.repo}#${target.number}`;
|
|
55875
56278
|
if (target.state === "CLOSED") return { status: "already-closed", issue, url: target.url };
|
|
55876
56279
|
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`, {
|
|
56280
|
+
const comment = await (await selectClaimWriteClient(client)).rest("POST", `repos/${target.repo}/issues/${target.number}/comments`, {
|
|
55878
56281
|
body: { body: `Resolved by merged PR ${prUrl}.
|
|
55879
56282
|
|
|
55880
56283
|
<!-- mmi-cross-repo-close:${prRepo}#${prNumber} -->` }
|
|
@@ -55951,16 +56354,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
55951
56354
|
}
|
|
55952
56355
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
55953
56356
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
55954
|
-
(0,
|
|
55955
|
-
(0,
|
|
56357
|
+
(0, import_node_fs45.mkdirSync)((0, import_node_path42.dirname)(path2), { recursive: true });
|
|
56358
|
+
(0, import_node_fs45.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
55956
56359
|
`, "utf8");
|
|
55957
56360
|
return path2;
|
|
55958
56361
|
}
|
|
55959
56362
|
function clearPostMergeReconRecovery(cwd, repo, pr) {
|
|
55960
56363
|
const path2 = postMergeReconStatePath(cwd, repo, pr);
|
|
55961
|
-
if (!(0,
|
|
56364
|
+
if (!(0, import_node_fs45.existsSync)(path2)) return;
|
|
55962
56365
|
try {
|
|
55963
|
-
(0,
|
|
56366
|
+
(0, import_node_fs45.unlinkSync)(path2);
|
|
55964
56367
|
} catch {
|
|
55965
56368
|
}
|
|
55966
56369
|
}
|
|
@@ -56436,9 +56839,9 @@ function registerPrLifecycleCommands(program3) {
|
|
|
56436
56839
|
|
|
56437
56840
|
// src/review-verdict.ts
|
|
56438
56841
|
var import_node_child_process14 = require("node:child_process");
|
|
56439
|
-
var
|
|
56440
|
-
var
|
|
56441
|
-
var
|
|
56842
|
+
var import_node_fs46 = require("node:fs");
|
|
56843
|
+
var import_node_os24 = require("node:os");
|
|
56844
|
+
var import_node_path43 = require("node:path");
|
|
56442
56845
|
init_cli_shared();
|
|
56443
56846
|
var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
|
|
56444
56847
|
var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
|
|
@@ -56603,15 +57006,15 @@ async function readPrIssueComments(number, repo) {
|
|
|
56603
57006
|
return comments;
|
|
56604
57007
|
}
|
|
56605
57008
|
async function postPrCommentFromFile(number, repo, body) {
|
|
56606
|
-
const dir = (0,
|
|
56607
|
-
const path2 = (0,
|
|
57009
|
+
const dir = (0, import_node_fs46.mkdtempSync)((0, import_node_path43.join)((0, import_node_os24.tmpdir)(), "mmi-review-verdict-"));
|
|
57010
|
+
const path2 = (0, import_node_path43.join)(dir, "body.md");
|
|
56608
57011
|
try {
|
|
56609
|
-
(0,
|
|
57012
|
+
(0, import_node_fs46.writeFileSync)(path2, body, "utf8");
|
|
56610
57013
|
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
56611
57014
|
return stdout.trim();
|
|
56612
57015
|
} finally {
|
|
56613
57016
|
try {
|
|
56614
|
-
(0,
|
|
57017
|
+
(0, import_node_fs46.rmSync)(dir, { recursive: true, force: true });
|
|
56615
57018
|
} catch {
|
|
56616
57019
|
}
|
|
56617
57020
|
}
|
|
@@ -57024,18 +57427,18 @@ function createPreparationDeps(input, io = {}) {
|
|
|
57024
57427
|
}
|
|
57025
57428
|
|
|
57026
57429
|
// src/worktree-residue-sweep.ts
|
|
57027
|
-
var
|
|
57028
|
-
var
|
|
57430
|
+
var import_node_fs50 = require("node:fs");
|
|
57431
|
+
var import_node_path47 = require("node:path");
|
|
57029
57432
|
|
|
57030
57433
|
// src/worktree-merge-cleanup.ts
|
|
57031
|
-
var
|
|
57032
|
-
var
|
|
57434
|
+
var import_node_fs49 = require("node:fs");
|
|
57435
|
+
var import_node_path46 = require("node:path");
|
|
57033
57436
|
init_cli_shared();
|
|
57034
57437
|
|
|
57035
57438
|
// src/stage-runner.ts
|
|
57036
57439
|
var import_node_child_process16 = require("node:child_process");
|
|
57037
|
-
var
|
|
57038
|
-
var
|
|
57440
|
+
var import_node_fs48 = require("node:fs");
|
|
57441
|
+
var import_node_path45 = require("node:path");
|
|
57039
57442
|
var import_node_net = require("node:net");
|
|
57040
57443
|
var import_node_util5 = require("node:util");
|
|
57041
57444
|
|
|
@@ -57045,8 +57448,8 @@ function normalizeEol2(s) {
|
|
|
57045
57448
|
}
|
|
57046
57449
|
|
|
57047
57450
|
// src/env-detection.ts
|
|
57048
|
-
var
|
|
57049
|
-
var
|
|
57451
|
+
var import_node_fs47 = require("node:fs");
|
|
57452
|
+
var import_node_path44 = require("node:path");
|
|
57050
57453
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
|
|
57051
57454
|
function isEnvBaseName(name) {
|
|
57052
57455
|
return name.toLowerCase() === ".env";
|
|
@@ -57056,7 +57459,7 @@ function findEnvFiles(root) {
|
|
|
57056
57459
|
const walk2 = (dir, rel) => {
|
|
57057
57460
|
let entries;
|
|
57058
57461
|
try {
|
|
57059
|
-
entries = (0,
|
|
57462
|
+
entries = (0, import_node_fs47.readdirSync)(dir, { withFileTypes: true });
|
|
57060
57463
|
} catch {
|
|
57061
57464
|
return;
|
|
57062
57465
|
}
|
|
@@ -57064,13 +57467,13 @@ function findEnvFiles(root) {
|
|
|
57064
57467
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
57065
57468
|
if (entry.isDirectory()) {
|
|
57066
57469
|
if (SKIPPED_DIRS.has(entry.name)) continue;
|
|
57067
|
-
walk2((0,
|
|
57470
|
+
walk2((0, import_node_path44.join)(dir, entry.name), relPath);
|
|
57068
57471
|
continue;
|
|
57069
57472
|
}
|
|
57070
57473
|
if (isEnvBaseName(entry.name)) {
|
|
57071
57474
|
let isDir = false;
|
|
57072
57475
|
try {
|
|
57073
|
-
isDir = (0,
|
|
57476
|
+
isDir = (0, import_node_fs47.lstatSync)((0, import_node_path44.join)(dir, entry.name)).isDirectory();
|
|
57074
57477
|
} catch {
|
|
57075
57478
|
}
|
|
57076
57479
|
if (!isDir) found.push(relPath);
|
|
@@ -57209,12 +57612,12 @@ var DOCKER_TIMEOUT_MS = 15e3;
|
|
|
57209
57612
|
function stageBash() {
|
|
57210
57613
|
if (process.platform !== "win32") return "bash";
|
|
57211
57614
|
const configured = process.env.SHELL;
|
|
57212
|
-
if (configured && (0,
|
|
57615
|
+
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
57616
|
const candidates = [
|
|
57214
|
-
(0,
|
|
57215
|
-
...process.env.LOCALAPPDATA ? [(0,
|
|
57617
|
+
(0, import_node_path45.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
|
|
57618
|
+
...process.env.LOCALAPPDATA ? [(0, import_node_path45.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
|
|
57216
57619
|
];
|
|
57217
|
-
const bash = candidates.find((path2) => (0,
|
|
57620
|
+
const bash = candidates.find((path2) => (0, import_node_fs48.existsSync)(path2));
|
|
57218
57621
|
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
57622
|
return bash;
|
|
57220
57623
|
}
|
|
@@ -57352,7 +57755,7 @@ function appendForceRecreate(up) {
|
|
|
57352
57755
|
return `${up.trimEnd()} --force-recreate`;
|
|
57353
57756
|
}
|
|
57354
57757
|
function stageStatePath(cwd = process.cwd()) {
|
|
57355
|
-
return (0,
|
|
57758
|
+
return (0, import_node_path45.join)(cwd, "tmp", "stage", "state.json");
|
|
57356
57759
|
}
|
|
57357
57760
|
function normPath(path2) {
|
|
57358
57761
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -57569,8 +57972,8 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
57569
57972
|
}
|
|
57570
57973
|
function composeResolvesPort(cwd) {
|
|
57571
57974
|
if (process.env.PORT) return true;
|
|
57572
|
-
const envFile = (0,
|
|
57573
|
-
return (0,
|
|
57975
|
+
const envFile = (0, import_node_path45.join)(cwd, ".env");
|
|
57976
|
+
return (0, import_node_fs48.existsSync)(envFile) && envFileKeys((0, import_node_fs48.readFileSync)(envFile, "utf8")).has("PORT");
|
|
57574
57977
|
}
|
|
57575
57978
|
function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
57576
57979
|
const composeProject = config.env?.COMPOSE_PROJECT_NAME ?? vaultEnvMerge?.COMPOSE_PROJECT_NAME ?? stageComposeProjectName(cwd);
|
|
@@ -57585,13 +57988,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
|
57585
57988
|
function stageComposeFiles(cwd) {
|
|
57586
57989
|
const files = ["docker-compose.yml"];
|
|
57587
57990
|
for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
|
|
57588
|
-
if ((0,
|
|
57991
|
+
if ((0, import_node_fs48.existsSync)((0, import_node_path45.join)(cwd, conventional))) files.push(conventional);
|
|
57589
57992
|
}
|
|
57590
57993
|
return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
|
|
57591
57994
|
}
|
|
57592
57995
|
async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
57593
57996
|
const keys = Object.keys(vaultEnvMerge ?? {});
|
|
57594
|
-
if (!keys.length || !(0,
|
|
57997
|
+
if (!keys.length || !(0, import_node_fs48.existsSync)((0, import_node_path45.join)(cwd, "docker-compose.yml"))) return {};
|
|
57595
57998
|
let configJson;
|
|
57596
57999
|
try {
|
|
57597
58000
|
const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
|
|
@@ -57607,21 +58010,21 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
|
57607
58010
|
}
|
|
57608
58011
|
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson), runtimeEnvSelects(configJson));
|
|
57609
58012
|
if (!body) return {};
|
|
57610
|
-
const overridePath = (0,
|
|
57611
|
-
(0,
|
|
57612
|
-
(0,
|
|
58013
|
+
const overridePath = (0, import_node_path45.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
|
|
58014
|
+
(0, import_node_fs48.mkdirSync)((0, import_node_path45.join)(cwd, "tmp", "stage"), { recursive: true });
|
|
58015
|
+
(0, import_node_fs48.writeFileSync)(overridePath, body, "utf8");
|
|
57613
58016
|
return stageComposeFileEnv(stageComposeFiles(cwd));
|
|
57614
58017
|
}
|
|
57615
58018
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
57616
58019
|
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,
|
|
58020
|
+
const target = (0, import_node_path45.join)(cwd, config.ensureEnv.target);
|
|
58021
|
+
const example = (0, import_node_path45.join)(cwd, config.ensureEnv.example);
|
|
58022
|
+
if (!(0, import_node_fs48.existsSync)(target) && (0, import_node_fs48.existsSync)(example)) {
|
|
58023
|
+
(0, import_node_fs48.copyFileSync)(example, target);
|
|
58024
|
+
} else if ((0, import_node_fs48.existsSync)(target) && (0, import_node_fs48.existsSync)(example)) {
|
|
58025
|
+
const stale = detectStaleEnvFile((0, import_node_fs48.readFileSync)(example, "utf8"), (0, import_node_fs48.readFileSync)(target, "utf8"), {
|
|
58026
|
+
exampleMtimeMs: (0, import_node_fs48.statSync)(example).mtimeMs,
|
|
58027
|
+
targetMtimeMs: (0, import_node_fs48.statSync)(target).mtimeMs
|
|
57625
58028
|
});
|
|
57626
58029
|
if (stale) {
|
|
57627
58030
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running mmi-cli stage`;
|
|
@@ -57629,8 +58032,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
57629
58032
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
57630
58033
|
}
|
|
57631
58034
|
}
|
|
57632
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
57633
|
-
(0,
|
|
58035
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs48.existsSync)(target)) {
|
|
58036
|
+
(0, import_node_fs48.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs48.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
57634
58037
|
}
|
|
57635
58038
|
}
|
|
57636
58039
|
async function gitText(cwd, args) {
|
|
@@ -57660,20 +58063,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
57660
58063
|
return void 0;
|
|
57661
58064
|
}
|
|
57662
58065
|
function readState(path2) {
|
|
57663
|
-
if (!(0,
|
|
58066
|
+
if (!(0, import_node_fs48.existsSync)(path2)) return null;
|
|
57664
58067
|
try {
|
|
57665
|
-
return JSON.parse((0,
|
|
58068
|
+
return JSON.parse((0, import_node_fs48.readFileSync)(path2, "utf8"));
|
|
57666
58069
|
} catch {
|
|
57667
58070
|
return null;
|
|
57668
58071
|
}
|
|
57669
58072
|
}
|
|
57670
58073
|
function mkdirFor(path2) {
|
|
57671
58074
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
57672
|
-
(0,
|
|
58075
|
+
(0, import_node_fs48.mkdirSync)(dir, { recursive: true });
|
|
57673
58076
|
}
|
|
57674
58077
|
function writeState(path2, state) {
|
|
57675
58078
|
mkdirFor(path2);
|
|
57676
|
-
(0,
|
|
58079
|
+
(0, import_node_fs48.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
57677
58080
|
}
|
|
57678
58081
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
57679
58082
|
const reservation = {
|
|
@@ -57702,7 +58105,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd, currentEn
|
|
|
57702
58105
|
);
|
|
57703
58106
|
}
|
|
57704
58107
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
57705
|
-
(0,
|
|
58108
|
+
(0, import_node_fs48.rmSync)(path2, { force: true });
|
|
57706
58109
|
}
|
|
57707
58110
|
}
|
|
57708
58111
|
async function killTree(pid) {
|
|
@@ -57949,8 +58352,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
57949
58352
|
});
|
|
57950
58353
|
}
|
|
57951
58354
|
} catch (e) {
|
|
57952
|
-
(0,
|
|
57953
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
58355
|
+
(0, import_node_fs48.rmSync)(statePath, { force: true });
|
|
58356
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs48.rmSync)(globalStatePath, { force: true });
|
|
57954
58357
|
throw e;
|
|
57955
58358
|
}
|
|
57956
58359
|
const started = await startStage(config, {
|
|
@@ -57969,9 +58372,9 @@ function normPath2(p) {
|
|
|
57969
58372
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
57970
58373
|
}
|
|
57971
58374
|
function unlinkNodeModulesJunction(wtPath) {
|
|
57972
|
-
const nm = (0,
|
|
58375
|
+
const nm = (0, import_node_path46.join)(wtPath, "node_modules");
|
|
57973
58376
|
try {
|
|
57974
|
-
if ((0,
|
|
58377
|
+
if ((0, import_node_fs49.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs49.rmdirSync)(nm);
|
|
57975
58378
|
return { ok: true };
|
|
57976
58379
|
} catch (e) {
|
|
57977
58380
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -57982,8 +58385,8 @@ function defaultSleep2(ms) {
|
|
|
57982
58385
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
57983
58386
|
}
|
|
57984
58387
|
async function removeResidueDirectory(wtPath, options = {}) {
|
|
57985
|
-
const { sleep: sleep2 = defaultSleep2, removeEmptyDir =
|
|
57986
|
-
const removeRecursive = options.removeRecursive ?? ((path2) => (0,
|
|
58388
|
+
const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs49.rmdirSync, exists = import_node_fs49.existsSync } = options;
|
|
58389
|
+
const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs49.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
|
|
57987
58390
|
const junction = unlinkNodeModulesJunction(wtPath);
|
|
57988
58391
|
if (!junction.ok) return junction;
|
|
57989
58392
|
try {
|
|
@@ -58106,28 +58509,28 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
|
|
|
58106
58509
|
}
|
|
58107
58510
|
async function listNestedIgnoredNodeModules(wtPath, execGit) {
|
|
58108
58511
|
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,
|
|
58512
|
+
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
58513
|
}
|
|
58111
58514
|
function safeRemoveTree(path2) {
|
|
58112
|
-
const stat4 = (0,
|
|
58515
|
+
const stat4 = (0, import_node_fs49.lstatSync)(path2);
|
|
58113
58516
|
if (stat4.isSymbolicLink()) {
|
|
58114
58517
|
try {
|
|
58115
|
-
(0,
|
|
58518
|
+
(0, import_node_fs49.rmdirSync)(path2);
|
|
58116
58519
|
} catch {
|
|
58117
|
-
(0,
|
|
58520
|
+
(0, import_node_fs49.unlinkSync)(path2);
|
|
58118
58521
|
}
|
|
58119
58522
|
return;
|
|
58120
58523
|
}
|
|
58121
58524
|
if (stat4.isDirectory()) {
|
|
58122
|
-
for (const entry of (0,
|
|
58123
|
-
(0,
|
|
58525
|
+
for (const entry of (0, import_node_fs49.readdirSync)(path2)) safeRemoveTree((0, import_node_path46.join)(path2, entry));
|
|
58526
|
+
(0, import_node_fs49.rmdirSync)(path2);
|
|
58124
58527
|
return;
|
|
58125
58528
|
}
|
|
58126
|
-
(0,
|
|
58529
|
+
(0, import_node_fs49.unlinkSync)(path2);
|
|
58127
58530
|
}
|
|
58128
58531
|
async function removeWorktreeNodeModules(wtPath) {
|
|
58129
58532
|
try {
|
|
58130
|
-
safeRemoveTree((0,
|
|
58533
|
+
safeRemoveTree((0, import_node_path46.join)(wtPath, "node_modules"));
|
|
58131
58534
|
return { ok: true };
|
|
58132
58535
|
} catch (e) {
|
|
58133
58536
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -58139,7 +58542,7 @@ function errorMessage(e) {
|
|
|
58139
58542
|
}
|
|
58140
58543
|
function resolvedOrRaw(path2) {
|
|
58141
58544
|
try {
|
|
58142
|
-
return normPath2((0,
|
|
58545
|
+
return normPath2((0, import_node_fs49.realpathSync)(path2));
|
|
58143
58546
|
} catch {
|
|
58144
58547
|
return normPath2(path2);
|
|
58145
58548
|
}
|
|
@@ -58147,10 +58550,10 @@ function resolvedOrRaw(path2) {
|
|
|
58147
58550
|
function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
58148
58551
|
let realRoot;
|
|
58149
58552
|
try {
|
|
58150
|
-
if ((0,
|
|
58553
|
+
if ((0, import_node_fs49.lstatSync)(root).isSymbolicLink()) {
|
|
58151
58554
|
return { ok: false, error: `delete root ${normPath2(root)} is a reparse point resolving to ${resolvedOrRaw(root)}` };
|
|
58152
58555
|
}
|
|
58153
|
-
realRoot = normPath2((0,
|
|
58556
|
+
realRoot = normPath2((0, import_node_fs49.realpathSync)(root));
|
|
58154
58557
|
} catch (e) {
|
|
58155
58558
|
if (e.code === "ENOENT") return { ok: true, unlinked: [] };
|
|
58156
58559
|
return { ok: false, error: `cannot resolve delete root ${normPath2(root)}: ${errorMessage(e)}` };
|
|
@@ -58166,17 +58569,17 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
58166
58569
|
const dir = stack.pop();
|
|
58167
58570
|
let entries;
|
|
58168
58571
|
try {
|
|
58169
|
-
entries = (0,
|
|
58572
|
+
entries = (0, import_node_fs49.readdirSync)(dir, { withFileTypes: true });
|
|
58170
58573
|
} catch (e) {
|
|
58171
58574
|
if (e.code === "ENOENT") continue;
|
|
58172
58575
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
58173
58576
|
}
|
|
58174
58577
|
for (const entry of entries) {
|
|
58175
|
-
const child2 = (0,
|
|
58578
|
+
const child2 = (0, import_node_path46.join)(dir, entry.name);
|
|
58176
58579
|
if (entry.isSymbolicLink()) {
|
|
58177
58580
|
let target = "";
|
|
58178
58581
|
try {
|
|
58179
|
-
target = normPath2((0,
|
|
58582
|
+
target = normPath2((0, import_node_fs49.realpathSync)(child2));
|
|
58180
58583
|
} catch {
|
|
58181
58584
|
target = "";
|
|
58182
58585
|
}
|
|
@@ -58359,7 +58762,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
58359
58762
|
return report;
|
|
58360
58763
|
}
|
|
58361
58764
|
const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
58362
|
-
const pathExists = options.pathExists ??
|
|
58765
|
+
const pathExists = options.pathExists ?? import_node_fs49.existsSync;
|
|
58363
58766
|
let afterWorktrees = [];
|
|
58364
58767
|
try {
|
|
58365
58768
|
afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
|
|
@@ -58499,7 +58902,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
58499
58902
|
const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
|
|
58500
58903
|
if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
|
|
58501
58904
|
unlinkedReparsePoints.push(...preHelperGuard.unlinked);
|
|
58502
|
-
if (pathExists((0,
|
|
58905
|
+
if (pathExists((0, import_node_path46.join)(wtPath, "node_modules"))) {
|
|
58503
58906
|
const nmRemoved = await (options.removeRealNodeModules ?? removeWorktreeNodeModules)(wtPath);
|
|
58504
58907
|
if (!nmRemoved.ok) {
|
|
58505
58908
|
report.worktree = {
|
|
@@ -58635,18 +59038,18 @@ function renderPrMergeCleanupLines(cleanup) {
|
|
|
58635
59038
|
// src/worktree-residue-sweep.ts
|
|
58636
59039
|
var RESIDUE_LEDGER_FILE = "mmi-worktree-residue.json";
|
|
58637
59040
|
function canonicalWorktreeRoot(primary) {
|
|
58638
|
-
return (0,
|
|
59041
|
+
return (0, import_node_path47.join)((0, import_node_path47.dirname)(primary), "mmi-worktrees", (0, import_node_path47.basename)(primary));
|
|
58639
59042
|
}
|
|
58640
59043
|
function norm(p) {
|
|
58641
59044
|
const s = p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
58642
59045
|
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
58643
59046
|
}
|
|
58644
59047
|
function residueLedgerPath(gitCommonDir) {
|
|
58645
|
-
return (0,
|
|
59048
|
+
return (0, import_node_path47.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
|
|
58646
59049
|
}
|
|
58647
59050
|
function readPaths(file, deps) {
|
|
58648
59051
|
try {
|
|
58649
|
-
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0,
|
|
59052
|
+
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs50.readFileSync)(f, "utf8")))(file));
|
|
58650
59053
|
return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
|
|
58651
59054
|
} catch {
|
|
58652
59055
|
return [];
|
|
@@ -58655,8 +59058,8 @@ function readPaths(file, deps) {
|
|
|
58655
59058
|
function writePaths(file, paths, deps) {
|
|
58656
59059
|
try {
|
|
58657
59060
|
const write = deps.writeLedger ?? ((f, t) => {
|
|
58658
|
-
(0,
|
|
58659
|
-
(0,
|
|
59061
|
+
(0, import_node_fs50.mkdirSync)((0, import_node_path47.dirname)(f), { recursive: true });
|
|
59062
|
+
(0, import_node_fs50.writeFileSync)(f, t, "utf8");
|
|
58660
59063
|
});
|
|
58661
59064
|
write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
|
|
58662
59065
|
`);
|
|
@@ -58670,17 +59073,17 @@ function recordResidue(gitCommonDir, path2, deps = {}) {
|
|
|
58670
59073
|
writePaths(file, paths, deps);
|
|
58671
59074
|
}
|
|
58672
59075
|
async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
58673
|
-
const exists = deps.exists ??
|
|
58674
|
-
const readDir = deps.readDir ?? ((p) => (0,
|
|
59076
|
+
const exists = deps.exists ?? import_node_fs50.existsSync;
|
|
59077
|
+
const readDir = deps.readDir ?? ((p) => (0, import_node_fs50.readdirSync)(p));
|
|
58675
59078
|
const isDir = deps.isDir ?? ((p) => {
|
|
58676
59079
|
try {
|
|
58677
|
-
return (0,
|
|
59080
|
+
return (0, import_node_fs50.statSync)(p).isDirectory();
|
|
58678
59081
|
} catch {
|
|
58679
59082
|
return false;
|
|
58680
59083
|
}
|
|
58681
59084
|
});
|
|
58682
59085
|
const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
|
|
58683
|
-
const removeEmpty = deps.removeEmpty ?? ((p) => (0,
|
|
59086
|
+
const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs50.rmdirSync)(p));
|
|
58684
59087
|
const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
|
|
58685
59088
|
const live = new Set(registered.map(norm));
|
|
58686
59089
|
const primary = registered[0] ? norm(registered[0]) : void 0;
|
|
@@ -58715,7 +59118,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
|
58715
59118
|
} catch {
|
|
58716
59119
|
}
|
|
58717
59120
|
for (const name of children) {
|
|
58718
|
-
const child2 = (0,
|
|
59121
|
+
const child2 = (0, import_node_path47.join)(root, name);
|
|
58719
59122
|
const key = norm(child2);
|
|
58720
59123
|
if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
|
|
58721
59124
|
let entries;
|
|
@@ -58758,10 +59161,10 @@ function argvWantsJson2() {
|
|
|
58758
59161
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
58759
59162
|
}
|
|
58760
59163
|
function hubRoot() {
|
|
58761
|
-
const fromPkg = (0,
|
|
59164
|
+
const fromPkg = (0, import_node_path48.join)(__dirname, "..", "..");
|
|
58762
59165
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
58763
|
-
if ((0,
|
|
58764
|
-
if ((0,
|
|
59166
|
+
if ((0, import_node_fs51.existsSync)((0, import_node_path48.join)(fromPkg, marker))) return fromPkg;
|
|
59167
|
+
if ((0, import_node_fs51.existsSync)((0, import_node_path48.join)(process.cwd(), marker))) return process.cwd();
|
|
58765
59168
|
return null;
|
|
58766
59169
|
}
|
|
58767
59170
|
function ciAuditDeps() {
|
|
@@ -58773,12 +59176,12 @@ function ciAuditDeps() {
|
|
|
58773
59176
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
58774
59177
|
readSeedFile: (path2) => {
|
|
58775
59178
|
if (!root) return null;
|
|
58776
|
-
const fullPath = (0,
|
|
58777
|
-
return (0,
|
|
59179
|
+
const fullPath = (0, import_node_path48.join)(root, path2);
|
|
59180
|
+
return (0, import_node_fs51.existsSync)(fullPath) ? (0, import_node_fs51.readFileSync)(fullPath, "utf8") : null;
|
|
58778
59181
|
}
|
|
58779
59182
|
};
|
|
58780
59183
|
}
|
|
58781
|
-
async function
|
|
59184
|
+
async function ghJson2(args, timeout = 1e4) {
|
|
58782
59185
|
const { stdout } = await execFileP("gh", args, { timeout });
|
|
58783
59186
|
return JSON.parse(stdout);
|
|
58784
59187
|
}
|
|
@@ -58852,6 +59255,24 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
58852
59255
|
}
|
|
58853
59256
|
return { onBoard: true };
|
|
58854
59257
|
}
|
|
59258
|
+
const viaHub = contentId ? await attachOnIntegrationRefusal(detail, cfg, contentId) : void 0;
|
|
59259
|
+
if (viaHub && "projectItemId" in viaHub) {
|
|
59260
|
+
if (priority) {
|
|
59261
|
+
try {
|
|
59262
|
+
await setBoardItemPriority(defaultGitHubClient(), cfg, viaHub.projectItemId, priority);
|
|
59263
|
+
} catch (pe) {
|
|
59264
|
+
const perr = pe;
|
|
59265
|
+
process.stderr.write(`warning: issue #${issueNumber} board Priority not set: ${(perr.stderr || perr.message || String(pe)).trim()}
|
|
59266
|
+
`);
|
|
59267
|
+
}
|
|
59268
|
+
}
|
|
59269
|
+
return { projectItemId: viaHub.projectItemId, onBoard: true, credential: "app_installation" };
|
|
59270
|
+
}
|
|
59271
|
+
if (viaHub) {
|
|
59272
|
+
process.stderr.write(`warning: issue #${issueNumber} created but NOT added to the project board: ${viaHub.warning}
|
|
59273
|
+
`);
|
|
59274
|
+
return { onBoard: false };
|
|
59275
|
+
}
|
|
58855
59276
|
if (isRateLimitText(detail)) {
|
|
58856
59277
|
if (targetRepo2) {
|
|
58857
59278
|
try {
|
|
@@ -59146,7 +59567,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59146
59567
|
async function readParentField(number, repo) {
|
|
59147
59568
|
let payload;
|
|
59148
59569
|
try {
|
|
59149
|
-
payload = await
|
|
59570
|
+
payload = await ghJson2(["api", `repos/${repo}/issues/${number}`]);
|
|
59150
59571
|
} catch (e) {
|
|
59151
59572
|
const err = e;
|
|
59152
59573
|
return { parentReadError: (err.stderr || err.message || String(e)).trim() };
|
|
@@ -59163,7 +59584,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59163
59584
|
}
|
|
59164
59585
|
async function readIssueTypeField(number, repo) {
|
|
59165
59586
|
try {
|
|
59166
|
-
return { issueType: resolveIssueType(await
|
|
59587
|
+
return { issueType: resolveIssueType(await ghJson2(["api", `repos/${repo}/issues/${number}`])) };
|
|
59167
59588
|
} catch (e) {
|
|
59168
59589
|
const err = e;
|
|
59169
59590
|
return { issueTypeReadError: (err.stderr || err.message || String(e)).trim() };
|
|
@@ -59209,7 +59630,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59209
59630
|
const decisions = parseDecisionMarkers(data2.comments);
|
|
59210
59631
|
return { data: { ...data2, decisions }, parent: await readParentField(targetNumber, targetRepo2) };
|
|
59211
59632
|
}
|
|
59212
|
-
const data = await
|
|
59633
|
+
const data = await ghJson2(["issue", "view", String(targetNumber), "--repo", targetRepo2, "--json", gh2.ghFields]);
|
|
59213
59634
|
return { data, parent: await readParentField(targetNumber, targetRepo2) };
|
|
59214
59635
|
};
|
|
59215
59636
|
const readClosedBy = async (targetRepo2, targetNumber) => ({
|
|
@@ -59244,7 +59665,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59244
59665
|
const repo = await resolveRepo(o.repo);
|
|
59245
59666
|
if (!repo) return fail("issue discover-related: could not resolve repo");
|
|
59246
59667
|
try {
|
|
59247
|
-
const issues = await
|
|
59668
|
+
const issues = await ghJson2([
|
|
59248
59669
|
"issue",
|
|
59249
59670
|
"list",
|
|
59250
59671
|
"--repo",
|
|
@@ -59259,7 +59680,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59259
59680
|
const candidates = findRelatedIssues({ number, title: o.title, body: o.body }, issues);
|
|
59260
59681
|
if (o.json) return console.log(JSON.stringify({ number, repo, candidates }, null, 2));
|
|
59261
59682
|
if (!candidates.length) return;
|
|
59262
|
-
const viewed = await
|
|
59683
|
+
const viewed = await ghJson2([
|
|
59263
59684
|
"issue",
|
|
59264
59685
|
"view",
|
|
59265
59686
|
String(number),
|
|
@@ -59360,7 +59781,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59360
59781
|
const checked = o.off !== true;
|
|
59361
59782
|
let body;
|
|
59362
59783
|
try {
|
|
59363
|
-
const viewed = await
|
|
59784
|
+
const viewed = await ghJson2(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
|
|
59364
59785
|
body = viewed.body ?? "";
|
|
59365
59786
|
} catch (e) {
|
|
59366
59787
|
return fail(`issue check: could not read ${repo}#${parsed.number}: ${e.message}`);
|
|
@@ -59461,7 +59882,7 @@ ${list}`);
|
|
|
59461
59882
|
if (!o.force) {
|
|
59462
59883
|
let openLessons = [];
|
|
59463
59884
|
try {
|
|
59464
|
-
openLessons = await
|
|
59885
|
+
openLessons = await ghJson2([
|
|
59465
59886
|
"issue",
|
|
59466
59887
|
"list",
|
|
59467
59888
|
"--repo",
|
|
@@ -59598,7 +60019,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59598
60019
|
console.log(JSON.stringify(data2));
|
|
59599
60020
|
return;
|
|
59600
60021
|
}
|
|
59601
|
-
const data = await
|
|
60022
|
+
const data = await ghJson2(["pr", "view", String(n), "--repo", repo, "--json", effective]);
|
|
59602
60023
|
console.log(JSON.stringify(data));
|
|
59603
60024
|
} catch (e) {
|
|
59604
60025
|
const err = e;
|
|
@@ -59606,11 +60027,11 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59606
60027
|
}
|
|
59607
60028
|
});
|
|
59608
60029
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
59609
|
-
const wfDir = (0,
|
|
59610
|
-
if (!(0,
|
|
59611
|
-
return (0,
|
|
60030
|
+
const wfDir = (0, import_node_path48.join)(cwd, ".github", "workflows");
|
|
60031
|
+
if (!(0, import_node_fs51.existsSync)(wfDir)) return [];
|
|
60032
|
+
return (0, import_node_fs51.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
59612
60033
|
try {
|
|
59613
|
-
return workflowReportsPrChecks((0,
|
|
60034
|
+
return workflowReportsPrChecks((0, import_node_fs51.readFileSync)((0, import_node_path48.join)(wfDir, name), "utf8"));
|
|
59614
60035
|
} catch {
|
|
59615
60036
|
return true;
|
|
59616
60037
|
}
|
|
@@ -59815,7 +60236,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59815
60236
|
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
60237
|
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
59817
60238
|
const verdict = o.verdict;
|
|
59818
|
-
const findings = o.findingsFile ? (0,
|
|
60239
|
+
const findings = o.findingsFile ? (0, import_node_fs51.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
59819
60240
|
let patch;
|
|
59820
60241
|
let head;
|
|
59821
60242
|
try {
|
|
@@ -59896,7 +60317,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59896
60317
|
await readClosingGuardInput(number, repoArgs, landRepoForGuard, "pr land"),
|
|
59897
60318
|
async (n) => {
|
|
59898
60319
|
if (!landRepoForGuard) return void 0;
|
|
59899
|
-
const viewed = await
|
|
60320
|
+
const viewed = await ghJson2(["issue", "view", String(n), "--repo", landRepoForGuard, "--json", "state"]);
|
|
59900
60321
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
59901
60322
|
}
|
|
59902
60323
|
);
|
|
@@ -60093,7 +60514,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60093
60514
|
const cleanupMerged = async (r) => {
|
|
60094
60515
|
if (opts.preserveWorktree) return "cleanup skipped (--preserve-worktree)";
|
|
60095
60516
|
const ran = await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(["pr", "merge", r.pr, "--repo", r.repo])], {
|
|
60096
|
-
cwd: (0,
|
|
60517
|
+
cwd: (0, import_node_fs51.existsSync)(cwd0) ? cwd0 : primaryRoot0,
|
|
60097
60518
|
timeout: GH_MUTATION_TIMEOUT_MS * 3
|
|
60098
60519
|
}).then((x) => ({ stdout: x.stdout, stderr: x.stderr }), (e) => ({ stdout: e.stdout ?? "", stderr: e.stderr ?? "" }));
|
|
60099
60520
|
const json = ran.stdout.split("\n").reverse().find((l) => l.startsWith("{"));
|
|
@@ -60154,7 +60575,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60154
60575
|
await readClosingGuardInput(number, repoArgs, repoForPostCleanup, "pr merge"),
|
|
60155
60576
|
async (n) => {
|
|
60156
60577
|
if (!repoForPostCleanup) return void 0;
|
|
60157
|
-
const viewed = await
|
|
60578
|
+
const viewed = await ghJson2(["issue", "view", String(n), "--repo", repoForPostCleanup, "--json", "state"]);
|
|
60158
60579
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
60159
60580
|
}
|
|
60160
60581
|
);
|
|
@@ -60164,7 +60585,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60164
60585
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
60165
60586
|
closingGuardInput,
|
|
60166
60587
|
method === "--squash",
|
|
60167
|
-
o.squashBodyFile ? (0,
|
|
60588
|
+
o.squashBodyFile ? (0, import_node_fs51.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
60168
60589
|
);
|
|
60169
60590
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
60170
60591
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -60196,7 +60617,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60196
60617
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
60197
60618
|
let foreignCheckout;
|
|
60198
60619
|
if (foreignCwd) {
|
|
60199
|
-
const sibling = (0,
|
|
60620
|
+
const sibling = (0, import_node_path48.join)((0, import_node_path48.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
|
|
60200
60621
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
60201
60622
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
60202
60623
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -60315,7 +60736,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60315
60736
|
}
|
|
60316
60737
|
if (!repoForPostCleanup) throw e;
|
|
60317
60738
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
60318
|
-
const commitMessage = bodyFile ? (0,
|
|
60739
|
+
const commitMessage = bodyFile ? (0, import_node_fs51.readFileSync)(bodyFile, "utf8") : void 0;
|
|
60319
60740
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
60320
60741
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
60321
60742
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -60430,7 +60851,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60430
60851
|
let residueSweep;
|
|
60431
60852
|
if (!(foreignCwd && !foreignCheckout)) {
|
|
60432
60853
|
try {
|
|
60433
|
-
gitCommonDir = (0,
|
|
60854
|
+
gitCommonDir = (0, import_node_path48.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
|
|
60434
60855
|
residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
|
|
60435
60856
|
} catch {
|
|
60436
60857
|
}
|
|
@@ -60448,7 +60869,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60448
60869
|
preserveWorktree: o.preserveWorktree,
|
|
60449
60870
|
gcAcknowledged: o.gc,
|
|
60450
60871
|
expectedHeadOid: headRefOid,
|
|
60451
|
-
pathExists: (p) => (0,
|
|
60872
|
+
pathExists: (p) => (0, import_node_fs51.existsSync)(p),
|
|
60452
60873
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
60453
60874
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
60454
60875
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -61711,7 +62132,7 @@ function rcRunCauseFromLog(log) {
|
|
|
61711
62132
|
function memberAnnounceSummaryFile(repo, summaryFile, memberSummaryFiles) {
|
|
61712
62133
|
return repo === SUITE_REPO ? summaryFile : memberSummaryFiles[repo];
|
|
61713
62134
|
}
|
|
61714
|
-
var suiteStateDir = (root) => (0,
|
|
62135
|
+
var suiteStateDir = (root) => (0, import_node_path50.join)(resolvePrimaryGitDir(root), "mmi-runtime", "suite");
|
|
61715
62136
|
async function runSuiteMemberRcand(repo, previousRc, rcWasAbsent) {
|
|
61716
62137
|
const deps = trainApplyDeps();
|
|
61717
62138
|
deps.suiteCut = suiteCutContext(repo, SUITE_REPO);
|
|
@@ -61758,16 +62179,16 @@ async function rcandInCheckout(repo, deps, previousRc, rcWasAbsent) {
|
|
|
61758
62179
|
}
|
|
61759
62180
|
}
|
|
61760
62181
|
function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr, memberSummaryFilesTr = {}) {
|
|
61761
|
-
const recordPath = (0,
|
|
61762
|
-
const base = (0,
|
|
62182
|
+
const recordPath = (0, import_node_path50.join)(suiteStateDir(root), "cut.json");
|
|
62183
|
+
const base = (0, import_node_path50.join)(suiteStateDir(root), "checkouts");
|
|
61763
62184
|
const checkout = async (repo) => {
|
|
61764
62185
|
if (repo === SUITE_REPO) return root;
|
|
61765
|
-
const target = (0,
|
|
61766
|
-
if (!(0,
|
|
61767
|
-
(0,
|
|
62186
|
+
const target = (0, import_node_path50.join)(base, repo.split("/")[1]);
|
|
62187
|
+
if (!(0, import_node_fs52.existsSync)(target)) {
|
|
62188
|
+
(0, import_node_fs52.mkdirSync)(base, { recursive: true });
|
|
61768
62189
|
await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
|
|
61769
62190
|
}
|
|
61770
|
-
if (!(0,
|
|
62191
|
+
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
62192
|
const origin = await exec("git", ["remote", "get-url", "origin"], target);
|
|
61772
62193
|
if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
|
|
61773
62194
|
throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
|
|
@@ -61831,7 +62252,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
61831
62252
|
if (repo === SUITE_REPO) return inTrain(repo, (deps) => rcandInCheckout(repo, deps, previousRc, rcWasAbsent));
|
|
61832
62253
|
const target = await checkout(repo);
|
|
61833
62254
|
return new Promise((resolveResult, reject2) => {
|
|
61834
|
-
const child2 = (0, import_node_child_process18.fork)((0,
|
|
62255
|
+
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
62256
|
cwd: target,
|
|
61836
62257
|
stdio: ["ignore", "inherit", "inherit", "ipc"],
|
|
61837
62258
|
execArgv: []
|
|
@@ -62074,9 +62495,9 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
62074
62495
|
const body = await gh(["release", "view", leadTag, "--repo", SUITE_REPO, "--json", "body", "--jq", ".body"]);
|
|
62075
62496
|
if (parseSuiteMapping(body)) return;
|
|
62076
62497
|
const dir = suiteStateDir(root);
|
|
62077
|
-
(0,
|
|
62078
|
-
const file = (0,
|
|
62079
|
-
(0,
|
|
62498
|
+
(0, import_node_fs52.mkdirSync)(dir, { recursive: true });
|
|
62499
|
+
const file = (0, import_node_path50.join)(dir, `notes-${leadTag}.md`);
|
|
62500
|
+
(0, import_node_fs52.writeFileSync)(file, `${body}
|
|
62080
62501
|
|
|
62081
62502
|
${suiteMappingBlock(leadTag, mapping)}`);
|
|
62082
62503
|
await gh(["release", "edit", leadTag, "--repo", SUITE_REPO, "--notes-file", file]);
|
|
@@ -62117,7 +62538,7 @@ ${suiteMappingBlock(leadTag, mapping)}`);
|
|
|
62117
62538
|
|
|
62118
62539
|
// src/suite-commands.ts
|
|
62119
62540
|
init_cli_shared();
|
|
62120
|
-
var
|
|
62541
|
+
var import_node_fs53 = require("node:fs");
|
|
62121
62542
|
function parseMemberSummaryFiles(entries) {
|
|
62122
62543
|
const memberSummaryFiles = {};
|
|
62123
62544
|
for (const entry of entries) {
|
|
@@ -62211,7 +62632,7 @@ function registerSuiteCommands(program3) {
|
|
|
62211
62632
|
try {
|
|
62212
62633
|
const io = suiteTrainIo(root);
|
|
62213
62634
|
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,
|
|
62635
|
+
if (retired && !(0, import_node_fs53.existsSync)(io.recordPath)) {
|
|
62215
62636
|
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
62637
|
return;
|
|
62217
62638
|
}
|
|
@@ -62451,9 +62872,9 @@ function registerJevCommands(program3) {
|
|
|
62451
62872
|
|
|
62452
62873
|
// src/packages-adopt-export.ts
|
|
62453
62874
|
var import_node_child_process19 = require("node:child_process");
|
|
62454
|
-
var
|
|
62455
|
-
var
|
|
62456
|
-
var
|
|
62875
|
+
var import_node_fs54 = require("node:fs");
|
|
62876
|
+
var import_node_os25 = require("node:os");
|
|
62877
|
+
var import_node_path51 = require("node:path");
|
|
62457
62878
|
var import_node_util6 = require("node:util");
|
|
62458
62879
|
init_cli_shared();
|
|
62459
62880
|
|
|
@@ -63073,38 +63494,38 @@ function expectedFromRun(run, opts) {
|
|
|
63073
63494
|
}
|
|
63074
63495
|
async function adoptExport(opts, deps) {
|
|
63075
63496
|
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,
|
|
63497
|
+
const packagePath = (0, import_node_path51.join)(opts.dir, "package.json");
|
|
63498
|
+
const lockPath = (0, import_node_path51.join)(opts.dir, "package-lock.json");
|
|
63499
|
+
const packageText = (0, import_node_fs54.readFileSync)(packagePath, "utf8");
|
|
63500
|
+
const lockText = (0, import_node_fs54.readFileSync)(lockPath, "utf8");
|
|
63501
|
+
const work = (0, import_node_fs54.mkdtempSync)((0, import_node_path51.join)((0, import_node_os25.tmpdir)(), "mmi-adopt-export-"));
|
|
63081
63502
|
try {
|
|
63082
63503
|
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,
|
|
63504
|
+
(0, import_node_fs54.writeFileSync)((0, import_node_path51.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
|
|
63505
|
+
(0, import_node_fs54.writeFileSync)((0, import_node_path51.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
|
|
63506
|
+
(0, import_node_fs54.writeFileSync)((0, import_node_path51.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
|
|
63507
|
+
const script = (0, import_node_path51.join)(work, "package_export.py");
|
|
63508
|
+
(0, import_node_fs54.writeFileSync)(script, package_export_default);
|
|
63088
63509
|
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,
|
|
63510
|
+
const verified = (0, import_node_path51.join)(work, "verified");
|
|
63511
|
+
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
63512
|
const plan = planAdoption(packageText, lockText, metadata);
|
|
63092
63513
|
for (const { name } of plan.result.adopted) {
|
|
63093
63514
|
const short3 = PACKAGES.find((p) => p.name === name).short;
|
|
63094
|
-
await deps.npm(["cache", "add", (0,
|
|
63515
|
+
await deps.npm(["cache", "add", (0, import_node_path51.join)(verified, `${short3}.tgz`)], opts.dir);
|
|
63095
63516
|
}
|
|
63096
|
-
(0,
|
|
63097
|
-
(0,
|
|
63517
|
+
(0, import_node_fs54.writeFileSync)(packagePath, plan.packageText);
|
|
63518
|
+
(0, import_node_fs54.writeFileSync)(lockPath, plan.lockText);
|
|
63098
63519
|
try {
|
|
63099
63520
|
await deps.npm(["ci", "--offline", "--ignore-scripts", "--no-audit", "--no-fund"], opts.dir);
|
|
63100
63521
|
} catch (error) {
|
|
63101
|
-
(0,
|
|
63102
|
-
(0,
|
|
63522
|
+
(0, import_node_fs54.writeFileSync)(packagePath, packageText);
|
|
63523
|
+
(0, import_node_fs54.writeFileSync)(lockPath, lockText);
|
|
63103
63524
|
throw new Error(`npm ci --offline failed, package.json and package-lock.json restored: ${error.message}`);
|
|
63104
63525
|
}
|
|
63105
63526
|
return plan.result;
|
|
63106
63527
|
} finally {
|
|
63107
|
-
(0,
|
|
63528
|
+
(0, import_node_fs54.rmSync)(work, { recursive: true, force: true });
|
|
63108
63529
|
}
|
|
63109
63530
|
}
|
|
63110
63531
|
var execFileBuffer = (0, import_node_util6.promisify)(import_node_child_process19.execFile);
|
|
@@ -63208,12 +63629,12 @@ function registerPackagesCommands(program3) {
|
|
|
63208
63629
|
init_cli_shared();
|
|
63209
63630
|
|
|
63210
63631
|
// src/wave-status.ts
|
|
63211
|
-
var
|
|
63632
|
+
var import_node_fs55 = require("node:fs");
|
|
63212
63633
|
function observeStage(worktreePath) {
|
|
63213
63634
|
const statePath = stageStatePath(worktreePath);
|
|
63214
63635
|
let raw;
|
|
63215
63636
|
try {
|
|
63216
|
-
raw = (0,
|
|
63637
|
+
raw = (0, import_node_fs55.readFileSync)(statePath, "utf8");
|
|
63217
63638
|
} catch (error) {
|
|
63218
63639
|
if (error.code === "ENOENT") return { state: "ok", value: { running: false } };
|
|
63219
63640
|
return { state: "unavailable", reason: "stage state could not be read" };
|
|
@@ -63280,9 +63701,9 @@ async function collectWaveStatus(deps) {
|
|
|
63280
63701
|
// src/discovery-commands.ts
|
|
63281
63702
|
init_github_client();
|
|
63282
63703
|
init_cli_shared();
|
|
63283
|
-
var
|
|
63284
|
-
var
|
|
63285
|
-
var
|
|
63704
|
+
var import_node_fs56 = require("node:fs");
|
|
63705
|
+
var import_node_os26 = require("node:os");
|
|
63706
|
+
var import_node_path52 = require("node:path");
|
|
63286
63707
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
63287
63708
|
var defaultStatusDeps = {
|
|
63288
63709
|
repo: resolveRepo,
|
|
@@ -63501,10 +63922,10 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
63501
63922
|
else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
|
|
63502
63923
|
else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
|
|
63503
63924
|
}
|
|
63504
|
-
const home = (0,
|
|
63925
|
+
const home = (0, import_node_os26.homedir)();
|
|
63505
63926
|
const plugin = onboardPluginGate({
|
|
63506
|
-
readKnown: () => readFileSyncSafe((0,
|
|
63507
|
-
readSettings: () => readFileSyncSafe((0,
|
|
63927
|
+
readKnown: () => readFileSyncSafe((0, import_node_path52.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs56.readFileSync),
|
|
63928
|
+
readSettings: () => readFileSyncSafe((0, import_node_path52.join)(home, ".claude", "settings.json"), import_node_fs56.readFileSync)
|
|
63508
63929
|
});
|
|
63509
63930
|
return {
|
|
63510
63931
|
guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
|
|
@@ -65690,7 +66111,7 @@ function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
|
|
|
65690
66111
|
}
|
|
65691
66112
|
|
|
65692
66113
|
// src/api-read.ts
|
|
65693
|
-
var
|
|
66114
|
+
var import_node_fs57 = require("node:fs");
|
|
65694
66115
|
|
|
65695
66116
|
// src/run-log-read.ts
|
|
65696
66117
|
init_cli_shared();
|
|
@@ -65887,7 +66308,7 @@ var BROKER_MAX_BUFFER = 64 * 1024 * 1024;
|
|
|
65887
66308
|
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
66309
|
function missingBrokerPrRepoMessage(subcommand, number) {
|
|
65889
66310
|
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
|
|
66311
|
+
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
66312
|
}
|
|
65892
66313
|
function buildPrQueueSpec(number, o) {
|
|
65893
66314
|
const repo = parseBrokerRepo(o.repo, "--repo");
|
|
@@ -66282,25 +66703,38 @@ function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
|
|
|
66282
66703
|
}
|
|
66283
66704
|
await dispatchGh(spec, "read run watch");
|
|
66284
66705
|
});
|
|
66706
|
+
const resolvePrRepo = async (raw, subcommand, number) => {
|
|
66707
|
+
if (raw) return raw;
|
|
66708
|
+
try {
|
|
66709
|
+
const resolved = await resolveRepo2(void 0);
|
|
66710
|
+
if (resolved) return parseBrokerRepo(resolved, "--repo");
|
|
66711
|
+
} catch {
|
|
66712
|
+
}
|
|
66713
|
+
fail(missingBrokerPrRepoMessage(subcommand, number));
|
|
66714
|
+
return void 0;
|
|
66715
|
+
};
|
|
66285
66716
|
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
|
-
|
|
66717
|
+
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) => {
|
|
66718
|
+
const repo = await resolvePrRepo(o.repo, "list");
|
|
66719
|
+
if (!repo) return;
|
|
66288
66720
|
let spec;
|
|
66289
66721
|
try {
|
|
66290
66722
|
requireJqJson(o.jq, o.json);
|
|
66291
|
-
spec = buildPrListSpec(o);
|
|
66723
|
+
spec = buildPrListSpec({ ...o, repo });
|
|
66292
66724
|
} catch (e) {
|
|
66293
66725
|
return fail(e.message);
|
|
66294
66726
|
}
|
|
66295
66727
|
await dispatchGh(spec, "read pr list", o.jq);
|
|
66296
66728
|
});
|
|
66297
|
-
pr.command("diff").description("read a bounded PR diff on the App pool").option("--repo <owner/repo>", "target repo (
|
|
66298
|
-
|
|
66299
|
-
|
|
66729
|
+
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) => {
|
|
66730
|
+
const repo = await resolvePrRepo(o.repo, "diff", number);
|
|
66731
|
+
if (!repo) return;
|
|
66732
|
+
await dispatchGh(buildPrDiffSpec(number, { ...o, repo }), "read pr diff");
|
|
66300
66733
|
});
|
|
66301
|
-
pr.command("queue").description("read one PR mergeQueueEntry (fixed GraphQL query, no permission widening)").option("--repo <owner/repo>", "target repo (
|
|
66302
|
-
|
|
66303
|
-
|
|
66734
|
+
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) => {
|
|
66735
|
+
const repo = await resolvePrRepo(o.repo, "queue", number);
|
|
66736
|
+
if (!repo) return;
|
|
66737
|
+
const spec = buildPrQueueSpec(number, { ...o, repo });
|
|
66304
66738
|
requireBrokerApp("read pr queue");
|
|
66305
66739
|
try {
|
|
66306
66740
|
process.exitCode = await runBrokerGh(async (args, opts) => {
|
|
@@ -66581,7 +67015,7 @@ function registerApiReadCommands(program3, overrides) {
|
|
|
66581
67015
|
);
|
|
66582
67016
|
if (o.jq !== void 0) body = await jqRun(o.jq, body, localJqEnv());
|
|
66583
67017
|
if (zip) {
|
|
66584
|
-
(0,
|
|
67018
|
+
(0, import_node_fs57.writeFileSync)(o.out, body, "latin1");
|
|
66585
67019
|
return;
|
|
66586
67020
|
}
|
|
66587
67021
|
} catch (e) {
|
|
@@ -67497,8 +67931,8 @@ function registerSessionReport(program3) {
|
|
|
67497
67931
|
}
|
|
67498
67932
|
|
|
67499
67933
|
// src/stage-commands.ts
|
|
67500
|
-
var
|
|
67501
|
-
var
|
|
67934
|
+
var import_node_fs58 = require("node:fs");
|
|
67935
|
+
var import_node_path53 = require("node:path");
|
|
67502
67936
|
init_cli_shared();
|
|
67503
67937
|
init_clean_exit();
|
|
67504
67938
|
|
|
@@ -67746,8 +68180,8 @@ function registerStageCommands(program3) {
|
|
|
67746
68180
|
const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
|
|
67747
68181
|
const resolution = decideStage({
|
|
67748
68182
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
67749
|
-
hasCompose: (0,
|
|
67750
|
-
hasEnvExample: (0,
|
|
68183
|
+
hasCompose: (0, import_node_fs58.existsSync)((0, import_node_path53.join)(process.cwd(), "docker-compose.yml")),
|
|
68184
|
+
hasEnvExample: (0, import_node_fs58.existsSync)((0, import_node_path53.join)(process.cwd(), ".env.example")),
|
|
67751
68185
|
repo
|
|
67752
68186
|
});
|
|
67753
68187
|
if (resolution.source === "derived" && resolution.derived) {
|
|
@@ -68039,20 +68473,20 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
68039
68473
|
}
|
|
68040
68474
|
|
|
68041
68475
|
// src/tenant-artifact.ts
|
|
68042
|
-
var
|
|
68043
|
-
var
|
|
68476
|
+
var import_node_crypto20 = require("node:crypto");
|
|
68477
|
+
var import_node_fs59 = require("node:fs");
|
|
68044
68478
|
var import_promises11 = require("node:fs/promises");
|
|
68045
|
-
var
|
|
68479
|
+
var import_node_path54 = require("node:path");
|
|
68046
68480
|
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
68481
|
var MAX_BYTES2 = 5 * 1024 * 1024 * 1024;
|
|
68048
68482
|
async function sha256File(path2) {
|
|
68049
|
-
const hash = (0,
|
|
68050
|
-
for await (const chunk of (0,
|
|
68483
|
+
const hash = (0, import_node_crypto20.createHash)("sha256");
|
|
68484
|
+
for await (const chunk of (0, import_node_fs59.createReadStream)(path2)) hash.update(chunk);
|
|
68051
68485
|
return hash.digest("hex");
|
|
68052
68486
|
}
|
|
68053
68487
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
68054
68488
|
if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
|
|
68055
|
-
const path2 = (0,
|
|
68489
|
+
const path2 = (0, import_node_path54.resolve)(inputPath);
|
|
68056
68490
|
const info = await (0, import_promises11.stat)(path2);
|
|
68057
68491
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
68058
68492
|
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 +68504,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
68070
68504
|
return [key, value];
|
|
68071
68505
|
}));
|
|
68072
68506
|
headers["content-length"] = String(info.size);
|
|
68073
|
-
const stream = (0,
|
|
68507
|
+
const stream = (0, import_node_fs59.createReadStream)(path2);
|
|
68074
68508
|
let uploaded;
|
|
68075
68509
|
try {
|
|
68076
68510
|
uploaded = await fetch(body.uploadUrl, {
|
|
@@ -68424,7 +68858,7 @@ function renderVerifySecrets(body) {
|
|
|
68424
68858
|
}
|
|
68425
68859
|
|
|
68426
68860
|
// src/command-register-developer.ts
|
|
68427
|
-
var
|
|
68861
|
+
var import_node_fs64 = require("node:fs");
|
|
68428
68862
|
init_clean_exit();
|
|
68429
68863
|
init_cli_shared();
|
|
68430
68864
|
init_error_codes();
|
|
@@ -68457,12 +68891,12 @@ async function resolveWhoami(deps) {
|
|
|
68457
68891
|
}
|
|
68458
68892
|
|
|
68459
68893
|
// src/command-register-developer.ts
|
|
68460
|
-
var
|
|
68894
|
+
var import_node_path61 = require("node:path");
|
|
68461
68895
|
|
|
68462
68896
|
// src/doc-refs-command-source.ts
|
|
68463
68897
|
var import_node_child_process21 = require("node:child_process");
|
|
68464
|
-
var
|
|
68465
|
-
var
|
|
68898
|
+
var import_node_fs60 = require("node:fs");
|
|
68899
|
+
var import_node_path55 = require("node:path");
|
|
68466
68900
|
var BUNDLE_MANIFEST_TIMEOUT_MS = 12e4;
|
|
68467
68901
|
var BUNDLE_MANIFEST_MAX_BUFFER = 64 * 1024 * 1024;
|
|
68468
68902
|
var flagsOf = (options) => new Set((options ?? []).flatMap((option) => String(option?.flags ?? "").match(/--[\w-]+/g) ?? []));
|
|
@@ -68483,7 +68917,7 @@ function inventoryFromManifest(manifest, source) {
|
|
|
68483
68917
|
function readBundleManifest(bundle, exec3 = import_node_child_process21.execFileSync) {
|
|
68484
68918
|
try {
|
|
68485
68919
|
const text = exec3(process.execPath, [bundle, "commands", "--all", "--json"], {
|
|
68486
|
-
cwd: (0,
|
|
68920
|
+
cwd: (0, import_node_path55.dirname)(bundle),
|
|
68487
68921
|
encoding: "utf8",
|
|
68488
68922
|
windowsHide: true,
|
|
68489
68923
|
timeout: BUNDLE_MANIFEST_TIMEOUT_MS,
|
|
@@ -68499,19 +68933,19 @@ function readBundleManifest(bundle, exec3 = import_node_child_process21.execFile
|
|
|
68499
68933
|
function isRunningBundle(bundle, argv1 = process.argv[1], platform2 = process.platform) {
|
|
68500
68934
|
if (!argv1) return false;
|
|
68501
68935
|
const norm2 = (path2) => {
|
|
68502
|
-
const absolute = (0,
|
|
68936
|
+
const absolute = (0, import_node_path55.resolve)(path2);
|
|
68503
68937
|
return platform2 === "win32" ? absolute.toLowerCase() : absolute;
|
|
68504
68938
|
};
|
|
68505
68939
|
const running = norm2(argv1);
|
|
68506
|
-
if (!["main.cjs", "index.cjs"].includes((0,
|
|
68507
|
-
return (0,
|
|
68940
|
+
if (!["main.cjs", "index.cjs"].includes((0, import_node_path55.basename)(running))) return false;
|
|
68941
|
+
return (0, import_node_path55.dirname)(running) === (0, import_node_path55.dirname)(norm2(bundle));
|
|
68508
68942
|
}
|
|
68509
68943
|
function resolveCommandInventory(deps) {
|
|
68510
|
-
const exists = deps.exists ??
|
|
68944
|
+
const exists = deps.exists ?? import_node_fs60.existsSync;
|
|
68511
68945
|
const read = deps.read ?? ((bundle) => readBundleManifest(bundle));
|
|
68512
68946
|
const warnings = [];
|
|
68513
68947
|
if (deps.from) {
|
|
68514
|
-
const bundle = (0,
|
|
68948
|
+
const bundle = (0, import_node_path55.isAbsolute)(deps.from) ? deps.from : (0, import_node_path55.resolve)(deps.root, deps.from);
|
|
68515
68949
|
if (!exists(bundle)) {
|
|
68516
68950
|
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
68951
|
}
|
|
@@ -68521,7 +68955,7 @@ function resolveCommandInventory(deps) {
|
|
|
68521
68955
|
}
|
|
68522
68956
|
return { inventory: inventory2, warnings };
|
|
68523
68957
|
}
|
|
68524
|
-
const treeBundle = (0,
|
|
68958
|
+
const treeBundle = (0, import_node_path55.join)(deps.root, "cli", "dist", "main.cjs");
|
|
68525
68959
|
if (!exists(treeBundle) || isRunningBundle(treeBundle, deps.argv1, deps.platform)) {
|
|
68526
68960
|
return { inventory: deps.inProcess(), warnings };
|
|
68527
68961
|
}
|
|
@@ -68638,28 +69072,28 @@ async function executeWaveLand(plan, deps) {
|
|
|
68638
69072
|
|
|
68639
69073
|
// src/dist-drift.ts
|
|
68640
69074
|
var import_node_child_process22 = require("node:child_process");
|
|
69075
|
+
var import_node_crypto22 = require("node:crypto");
|
|
69076
|
+
var import_node_fs62 = require("node:fs");
|
|
69077
|
+
var import_node_os27 = require("node:os");
|
|
69078
|
+
var import_node_path57 = require("node:path");
|
|
69079
|
+
|
|
69080
|
+
// ../scripts/distribution-digest.mjs
|
|
68641
69081
|
var import_node_crypto21 = require("node:crypto");
|
|
68642
69082
|
var import_node_fs61 = require("node:fs");
|
|
68643
|
-
var import_node_os26 = require("node:os");
|
|
68644
69083
|
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
69084
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
68651
69085
|
function repoPath(root, declaredPath, label) {
|
|
68652
|
-
const absoluteRoot = (0,
|
|
68653
|
-
const target = (0,
|
|
68654
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
69086
|
+
const absoluteRoot = (0, import_node_path56.resolve)(root);
|
|
69087
|
+
const target = (0, import_node_path56.resolve)(root, declaredPath);
|
|
69088
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path56.sep}`)) {
|
|
68655
69089
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
68656
69090
|
}
|
|
68657
69091
|
return target;
|
|
68658
69092
|
}
|
|
68659
69093
|
function digestFiles(files) {
|
|
68660
|
-
const hash = (0,
|
|
69094
|
+
const hash = (0, import_node_crypto21.createHash)("sha256");
|
|
68661
69095
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
68662
|
-
const content = file.stat.isSymbolicLink() ? Buffer.from((0,
|
|
69096
|
+
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs61.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs61.readFileSync)(file.absolute);
|
|
68663
69097
|
hash.update(file.relative, "utf8");
|
|
68664
69098
|
hash.update("\0");
|
|
68665
69099
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -68674,8 +69108,8 @@ function digestFiles(files) {
|
|
|
68674
69108
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
68675
69109
|
return digestFiles(packedFiles.map((path2) => {
|
|
68676
69110
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
68677
|
-
if (!(0,
|
|
68678
|
-
return { absolute, relative: slash(path2), stat: (0,
|
|
69111
|
+
if (!(0, import_node_fs61.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
|
|
69112
|
+
return { absolute, relative: slash(path2), stat: (0, import_node_fs61.lstatSync)(absolute) };
|
|
68679
69113
|
}));
|
|
68680
69114
|
}
|
|
68681
69115
|
|
|
@@ -68688,7 +69122,7 @@ var DIST_ARTIFACTS = [
|
|
|
68688
69122
|
];
|
|
68689
69123
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
68690
69124
|
var ABSENT = "absent";
|
|
68691
|
-
var sha2562 = (bytes) => `sha256:${(0,
|
|
69125
|
+
var sha2562 = (bytes) => `sha256:${(0, import_node_crypto22.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
68692
69126
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
68693
69127
|
const committed = committedBytes ? sha2562(committedBytes) : ABSENT;
|
|
68694
69128
|
const rebuiltExpected = rebuiltBytes ? sha2562(rebuiltBytes) : ABSENT;
|
|
@@ -68760,13 +69194,13 @@ function renderDistDriftReceipt(receipt) {
|
|
|
68760
69194
|
return lines2;
|
|
68761
69195
|
}
|
|
68762
69196
|
function readOrNull(path2) {
|
|
68763
|
-
return (0,
|
|
69197
|
+
return (0, import_node_fs62.existsSync)(path2) ? (0, import_node_fs62.readFileSync)(path2) : null;
|
|
68764
69198
|
}
|
|
68765
69199
|
function walkFiles(root) {
|
|
68766
69200
|
const files = [];
|
|
68767
69201
|
const walk2 = (directory) => {
|
|
68768
|
-
for (const entry of (0,
|
|
68769
|
-
const child2 = (0,
|
|
69202
|
+
for (const entry of (0, import_node_fs62.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
69203
|
+
const child2 = (0, import_node_path57.join)(directory, entry.name);
|
|
68770
69204
|
if (entry.isDirectory()) walk2(child2);
|
|
68771
69205
|
else files.push(child2);
|
|
68772
69206
|
}
|
|
@@ -68776,10 +69210,10 @@ function walkFiles(root) {
|
|
|
68776
69210
|
}
|
|
68777
69211
|
function bomPathFor(root) {
|
|
68778
69212
|
try {
|
|
68779
|
-
const registry2 = JSON.parse((0,
|
|
68780
|
-
return (0,
|
|
69213
|
+
const registry2 = JSON.parse((0, import_node_fs62.readFileSync)((0, import_node_path57.join)(root, "surfaces.json"), "utf8"));
|
|
69214
|
+
return (0, import_node_path57.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
68781
69215
|
} catch {
|
|
68782
|
-
return (0,
|
|
69216
|
+
return (0, import_node_path57.join)(root, "distribution-bom.json");
|
|
68783
69217
|
}
|
|
68784
69218
|
}
|
|
68785
69219
|
function rebuildTo(packageRoot, outDir) {
|
|
@@ -68792,40 +69226,40 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
68792
69226
|
});
|
|
68793
69227
|
}
|
|
68794
69228
|
function runDistStatus(root) {
|
|
68795
|
-
const stage = (0,
|
|
69229
|
+
const stage = (0, import_node_fs62.mkdtempSync)((0, import_node_path57.join)((0, import_node_os27.tmpdir)(), "mmi-dist-drift-"));
|
|
68796
69230
|
let overlayCount = 0;
|
|
68797
69231
|
try {
|
|
68798
|
-
const cliOut = (0,
|
|
68799
|
-
const hubOut = (0,
|
|
68800
|
-
rebuildTo((0,
|
|
68801
|
-
rebuildTo((0,
|
|
69232
|
+
const cliOut = (0, import_node_path57.join)(stage, "cli-dist");
|
|
69233
|
+
const hubOut = (0, import_node_path57.join)(stage, "hub-dist");
|
|
69234
|
+
rebuildTo((0, import_node_path57.join)(root, "cli"), cliOut);
|
|
69235
|
+
rebuildTo((0, import_node_path57.join)(root, "updater"), hubOut);
|
|
68802
69236
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
68803
69237
|
const rebuilt = (path2) => {
|
|
68804
69238
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
68805
|
-
return spec ? readOrNull((0,
|
|
69239
|
+
return spec ? readOrNull((0, import_node_path57.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
68806
69240
|
};
|
|
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,
|
|
69241
|
+
const committed = (path2) => readOrNull((0, import_node_path57.join)(root, path2));
|
|
69242
|
+
const tree = (path2) => readOrNull((0, import_node_path57.join)(root, path2));
|
|
69243
|
+
const distRoot = (0, import_node_path57.join)(root, "cli", "dist");
|
|
69244
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path57.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
69245
|
+
const bom = JSON.parse((0, import_node_fs62.readFileSync)(bomPathFor(root), "utf8"));
|
|
68812
69246
|
const digest = (entries) => {
|
|
68813
|
-
const overlay = (0,
|
|
69247
|
+
const overlay = (0, import_node_path57.join)(stage, `overlay-${overlayCount++}`);
|
|
68814
69248
|
for (const entry of entries) {
|
|
68815
|
-
const target = (0,
|
|
68816
|
-
(0,
|
|
68817
|
-
(0,
|
|
69249
|
+
const target = (0, import_node_path57.join)(overlay, entry.path);
|
|
69250
|
+
(0, import_node_fs62.mkdirSync)((0, import_node_path57.dirname)(target), { recursive: true });
|
|
69251
|
+
(0, import_node_fs62.writeFileSync)(target, entry.bytes);
|
|
68818
69252
|
}
|
|
68819
69253
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
68820
69254
|
};
|
|
68821
69255
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
68822
69256
|
} finally {
|
|
68823
|
-
(0,
|
|
69257
|
+
(0, import_node_fs62.rmSync)(stage, { recursive: true, force: true });
|
|
68824
69258
|
}
|
|
68825
69259
|
}
|
|
68826
69260
|
|
|
68827
69261
|
// src/doc-refs-live.ts
|
|
68828
|
-
var
|
|
69262
|
+
var import_node_path58 = require("node:path");
|
|
68829
69263
|
var concrete = (value) => !!value && !/[<>{}$]/.test(value);
|
|
68830
69264
|
var liveOption = (tokens2, flag) => {
|
|
68831
69265
|
const index = tokens2.indexOf(flag);
|
|
@@ -68898,7 +69332,7 @@ async function runLiveDocRefs(root, deps, live) {
|
|
|
68898
69332
|
const offline = runDocRefs(root, { ...deps, runbooksOnly: true });
|
|
68899
69333
|
const read = deps.readFile ?? readFileOrNull;
|
|
68900
69334
|
const docs = Object.fromEntries((deps.listDocs ?? defaultListDocs)(root).flatMap((doc) => {
|
|
68901
|
-
const body = read((0,
|
|
69335
|
+
const body = read((0, import_node_path58.join)(root, doc));
|
|
68902
69336
|
return body !== null && isRunbook(doc, body) && !deps.isIgnored?.([doc]).has(doc) ? [[doc, body]] : [];
|
|
68903
69337
|
}));
|
|
68904
69338
|
const remote = await checkRunbookLive(docs, live);
|
|
@@ -69109,7 +69543,7 @@ async function diagnoseHub(deps) {
|
|
|
69109
69543
|
|
|
69110
69544
|
// src/schedules-lift-command.ts
|
|
69111
69545
|
var import_promises12 = require("node:fs/promises");
|
|
69112
|
-
var
|
|
69546
|
+
var import_node_path59 = require("node:path");
|
|
69113
69547
|
init_clean_exit();
|
|
69114
69548
|
init_cli_shared();
|
|
69115
69549
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -69138,7 +69572,7 @@ async function readWorkflowFiles(dir) {
|
|
|
69138
69572
|
const files = [];
|
|
69139
69573
|
for (const name of names.sort()) {
|
|
69140
69574
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
69141
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0,
|
|
69575
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0, import_node_path59.join)(dir, name), "utf8") });
|
|
69142
69576
|
}
|
|
69143
69577
|
return files;
|
|
69144
69578
|
}
|
|
@@ -69221,8 +69655,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
69221
69655
|
|
|
69222
69656
|
// src/spawn-policy-core.ts
|
|
69223
69657
|
var import_node_child_process24 = require("node:child_process");
|
|
69224
|
-
var
|
|
69225
|
-
var
|
|
69658
|
+
var import_node_fs63 = require("node:fs");
|
|
69659
|
+
var import_node_path60 = require("node:path");
|
|
69226
69660
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
69227
69661
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
69228
69662
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -69308,7 +69742,7 @@ function runSpawnPolicy(root) {
|
|
|
69308
69742
|
for (const file of files) {
|
|
69309
69743
|
let raw;
|
|
69310
69744
|
try {
|
|
69311
|
-
raw = (0,
|
|
69745
|
+
raw = (0, import_node_fs63.readFileSync)((0, import_node_path60.join)(root, file), "utf8");
|
|
69312
69746
|
} catch {
|
|
69313
69747
|
continue;
|
|
69314
69748
|
}
|
|
@@ -69328,19 +69762,19 @@ function runSpawnPolicy(root) {
|
|
|
69328
69762
|
function registerDeveloperCommands(program3) {
|
|
69329
69763
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
69330
69764
|
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,
|
|
69765
|
+
const path2 = (0, import_node_path61.join)(process.cwd(), ".gitignore");
|
|
69766
|
+
const current = (0, import_node_fs64.existsSync)(path2) ? (0, import_node_fs64.readFileSync)(path2, "utf8") : null;
|
|
69333
69767
|
const plan = planManagedGitignore(current);
|
|
69334
69768
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
69335
69769
|
if (opts.json) {
|
|
69336
|
-
if (opts.write && plan.changed) (0,
|
|
69770
|
+
if (opts.write && plan.changed) (0, import_node_fs64.writeFileSync)(path2, plan.content, "utf8");
|
|
69337
69771
|
console.log(JSON.stringify(plan, null, 2));
|
|
69338
69772
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
69339
69773
|
return;
|
|
69340
69774
|
}
|
|
69341
69775
|
if (opts.write) {
|
|
69342
69776
|
if (plan.changed) {
|
|
69343
|
-
(0,
|
|
69777
|
+
(0, import_node_fs64.writeFileSync)(path2, plan.content, "utf8");
|
|
69344
69778
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
69345
69779
|
} else {
|
|
69346
69780
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -69657,12 +70091,12 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
69657
70091
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
69658
70092
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
69659
70093
|
function envHealLockPath(home) {
|
|
69660
|
-
return (0,
|
|
70094
|
+
return (0, import_node_path62.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
69661
70095
|
}
|
|
69662
70096
|
async function withEnvHealLock(what, run) {
|
|
69663
70097
|
try {
|
|
69664
70098
|
return await withFileLock(
|
|
69665
|
-
envHealLockPath((0,
|
|
70099
|
+
envHealLockPath((0, import_node_os28.homedir)()),
|
|
69666
70100
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
69667
70101
|
run
|
|
69668
70102
|
);
|
|
@@ -69760,7 +70194,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69760
70194
|
const configRoot = surfaceConfigRoot(surface);
|
|
69761
70195
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
69762
70196
|
const plan = buildPluginCachePlan(
|
|
69763
|
-
(0,
|
|
70197
|
+
(0, import_node_os28.homedir)(),
|
|
69764
70198
|
running,
|
|
69765
70199
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
69766
70200
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -69788,14 +70222,14 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69788
70222
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
69789
70223
|
const installed = installedActivePluginVersion(surface);
|
|
69790
70224
|
const plan = buildPluginCachePlan(
|
|
69791
|
-
(0,
|
|
70225
|
+
(0, import_node_os28.homedir)(),
|
|
69792
70226
|
running,
|
|
69793
70227
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
69794
70228
|
{ configRoot, includeStaging: surface !== "codex", installedVersion: installed }
|
|
69795
70229
|
);
|
|
69796
70230
|
const result = applyPluginCachePlan(
|
|
69797
70231
|
plan,
|
|
69798
|
-
(p) => (0,
|
|
70232
|
+
(p) => (0, import_node_fs65.rmSync)(p, { recursive: true }),
|
|
69799
70233
|
stagingApplyFsGuard(configRoot)
|
|
69800
70234
|
);
|
|
69801
70235
|
return {
|
|
@@ -69830,7 +70264,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69830
70264
|
// has no generated routing index would
|
|
69831
70265
|
// get a permanent — demanding an artifact it never asked for.
|
|
69832
70266
|
docsIndexState: (root) => {
|
|
69833
|
-
if (!(0,
|
|
70267
|
+
if (!(0, import_node_fs65.existsSync)((0, import_node_path62.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
69834
70268
|
const real = createDocsIndexDeps(root);
|
|
69835
70269
|
let docs;
|
|
69836
70270
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -69839,7 +70273,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69839
70273
|
},
|
|
69840
70274
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
69841
70275
|
healDocsIndex: (root) => {
|
|
69842
|
-
if (!(0,
|
|
70276
|
+
if (!(0, import_node_fs65.existsSync)((0, import_node_path62.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
69843
70277
|
const real = createDocsIndexDeps(root);
|
|
69844
70278
|
let docs;
|
|
69845
70279
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -70209,7 +70643,7 @@ function appActorDeps() {
|
|
|
70209
70643
|
fetch: (url, init) => fetch(url, init)
|
|
70210
70644
|
};
|
|
70211
70645
|
}
|
|
70212
|
-
setPollTokenProvider(async () => activeAppToken() ?? pollTokenOrUndefined(appActorDeps()));
|
|
70646
|
+
setPollTokenProvider(async (o) => (o?.fresh ? void 0 : activeAppToken()) ?? pollTokenOrUndefined(appActorDeps()));
|
|
70213
70647
|
setGhReadTokenProvider(pollToken);
|
|
70214
70648
|
setGitHubReadTokenProvider(pollToken);
|
|
70215
70649
|
var filingToken;
|
|
@@ -70217,7 +70651,10 @@ setGhWriteTokenProvider(() => {
|
|
|
70217
70651
|
if (process.env.MMI_ACTOR?.trim() === "app") {
|
|
70218
70652
|
return mintFilingWriteToken(appActorDeps()).then((t) => t.token);
|
|
70219
70653
|
}
|
|
70220
|
-
return filingToken ??= mintFilingWriteToken(appActorDeps()).then((t) => t.token, () =>
|
|
70654
|
+
return filingToken ??= mintFilingWriteToken(appActorDeps()).then((t) => t.token, (e) => {
|
|
70655
|
+
if (e instanceof AppActorError && e.code === "vault-denied") return void 0;
|
|
70656
|
+
throw e;
|
|
70657
|
+
});
|
|
70221
70658
|
});
|
|
70222
70659
|
setClaimWriteTokenProvider(() => mintFilingWriteToken(appActorDeps()).then((t) => t.token));
|
|
70223
70660
|
setStatusWriteTokenProvider(() => mintStatusWriteToken(appActorDeps()));
|
|
@@ -70624,7 +71061,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
70624
71061
|
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
71062
|
if (o.secretsFile) {
|
|
70626
71063
|
try {
|
|
70627
|
-
vars.push(`secrets=${(0,
|
|
71064
|
+
vars.push(`secrets=${(0, import_node_fs65.readFileSync)(o.secretsFile, "utf8")}`);
|
|
70628
71065
|
} catch (e) {
|
|
70629
71066
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
70630
71067
|
}
|
|
@@ -71016,16 +71453,16 @@ function ciAuditDeps2() {
|
|
|
71016
71453
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
71017
71454
|
readSeedFile: (path2) => {
|
|
71018
71455
|
if (!root) return null;
|
|
71019
|
-
const fullPath = (0,
|
|
71020
|
-
return (0,
|
|
71456
|
+
const fullPath = (0, import_node_path62.join)(root, path2);
|
|
71457
|
+
return (0, import_node_fs65.existsSync)(fullPath) ? (0, import_node_fs65.readFileSync)(fullPath, "utf8") : null;
|
|
71021
71458
|
}
|
|
71022
71459
|
};
|
|
71023
71460
|
}
|
|
71024
71461
|
function hubRoot2() {
|
|
71025
|
-
const fromPkg = (0,
|
|
71462
|
+
const fromPkg = (0, import_node_path62.join)(__dirname, "..", "..");
|
|
71026
71463
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
71027
|
-
if ((0,
|
|
71028
|
-
if ((0,
|
|
71464
|
+
if ((0, import_node_fs65.existsSync)((0, import_node_path62.join)(fromPkg, marker))) return fromPkg;
|
|
71465
|
+
if ((0, import_node_fs65.existsSync)((0, import_node_path62.join)(process.cwd(), marker))) return process.cwd();
|
|
71029
71466
|
return null;
|
|
71030
71467
|
}
|
|
71031
71468
|
registerQueryCommands(program2);
|
|
@@ -71166,10 +71603,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
71166
71603
|
targets = resolution.targets;
|
|
71167
71604
|
}
|
|
71168
71605
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
71169
|
-
const fileMatrix = (0,
|
|
71606
|
+
const fileMatrix = (0, import_node_fs65.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs65.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
71170
71607
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
71171
71608
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
71172
|
-
const sanctioned = (0,
|
|
71609
|
+
const sanctioned = (0, import_node_fs65.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs65.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
71173
71610
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
71174
71611
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
71175
71612
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -71200,16 +71637,16 @@ function directoryBytes(path2) {
|
|
|
71200
71637
|
let total = 0;
|
|
71201
71638
|
let entries;
|
|
71202
71639
|
try {
|
|
71203
|
-
entries = (0,
|
|
71640
|
+
entries = (0, import_node_fs65.readdirSync)(path2, { withFileTypes: true });
|
|
71204
71641
|
} catch {
|
|
71205
71642
|
return 0;
|
|
71206
71643
|
}
|
|
71207
71644
|
for (const entry of entries) {
|
|
71208
|
-
const child2 = (0,
|
|
71645
|
+
const child2 = (0, import_node_path62.join)(path2, entry.name);
|
|
71209
71646
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
71210
71647
|
else {
|
|
71211
71648
|
try {
|
|
71212
|
-
total += (0,
|
|
71649
|
+
total += (0, import_node_fs65.statSync)(child2).size;
|
|
71213
71650
|
} catch {
|
|
71214
71651
|
}
|
|
71215
71652
|
}
|
|
@@ -71217,25 +71654,25 @@ function directoryBytes(path2) {
|
|
|
71217
71654
|
return total;
|
|
71218
71655
|
}
|
|
71219
71656
|
function listDirEntries(dir) {
|
|
71220
|
-
return (0,
|
|
71657
|
+
return (0, import_node_fs65.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
71221
71658
|
}
|
|
71222
71659
|
function readInstalledPluginRefs(configRoot) {
|
|
71223
71660
|
const p = installedPluginsPathForConfig(configRoot);
|
|
71224
|
-
if (!(0,
|
|
71661
|
+
if (!(0, import_node_fs65.existsSync)(p)) return [];
|
|
71225
71662
|
try {
|
|
71226
|
-
return installedPluginPaths((0,
|
|
71663
|
+
return installedPluginPaths((0, import_node_fs65.readFileSync)(p, "utf8"));
|
|
71227
71664
|
} catch {
|
|
71228
71665
|
return null;
|
|
71229
71666
|
}
|
|
71230
71667
|
}
|
|
71231
71668
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
71232
71669
|
return {
|
|
71233
|
-
exists: (p) => (0,
|
|
71234
|
-
listVersionDirs: (root) => (0,
|
|
71670
|
+
exists: (p) => (0, import_node_fs65.existsSync)(p),
|
|
71671
|
+
listVersionDirs: (root) => (0, import_node_fs65.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
71235
71672
|
dirBytes,
|
|
71236
|
-
listStagingDirs: (root) => (0,
|
|
71673
|
+
listStagingDirs: (root) => (0, import_node_fs65.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
71237
71674
|
try {
|
|
71238
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
71675
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path62.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs65.statSync)(p).mtimeMs) };
|
|
71239
71676
|
} catch {
|
|
71240
71677
|
return { name: d.name, mtimeMs: Date.now() };
|
|
71241
71678
|
}
|
|
@@ -71249,10 +71686,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
71249
71686
|
return {
|
|
71250
71687
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
71251
71688
|
mtimeMs: (name) => {
|
|
71252
|
-
const p = (0,
|
|
71253
|
-
if (!(0,
|
|
71689
|
+
const p = (0, import_node_path62.join)(stagingRoot, name);
|
|
71690
|
+
if (!(0, import_node_fs65.existsSync)(p)) return null;
|
|
71254
71691
|
try {
|
|
71255
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
71692
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs65.statSync)(q).mtimeMs);
|
|
71256
71693
|
} catch {
|
|
71257
71694
|
return null;
|
|
71258
71695
|
}
|
|
@@ -71272,13 +71709,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
71272
71709
|
return;
|
|
71273
71710
|
}
|
|
71274
71711
|
const plan = buildPluginCachePlan(
|
|
71275
|
-
(0,
|
|
71712
|
+
(0, import_node_os28.homedir)(),
|
|
71276
71713
|
running,
|
|
71277
71714
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
71278
71715
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
71279
71716
|
);
|
|
71280
71717
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
71281
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
71718
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs65.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
71282
71719
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
71283
71720
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
71284
71721
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -71288,7 +71725,7 @@ program2.command("archive-prune").description("inventory worktree evidence archi
|
|
|
71288
71725
|
let primary = o.repoRoot;
|
|
71289
71726
|
if (!primary) {
|
|
71290
71727
|
const common = await gitOut(["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
71291
|
-
primary = common ? (0,
|
|
71728
|
+
primary = common ? (0, import_node_path62.dirname)(common) : "";
|
|
71292
71729
|
}
|
|
71293
71730
|
if (!primary) {
|
|
71294
71731
|
const detail = "archive-prune: not inside a git checkout (pass --repo-root <primary checkout>)";
|