@mutmutco/cli 4.6.10 → 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/dist/main.cjs +1201 -733
- package/dist/runbook-sweep.cjs +32 -8
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -1409,13 +1409,7 @@ async function ghFilingAppEnv(file, args, requireApp = false) {
|
|
|
1409
1409
|
if (!ghWriteTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || !requireApp && process.env.MMI_ACTOR?.trim() === "personal") return void 0;
|
|
1410
1410
|
if (!isAppFilingWrite(args)) return void 0;
|
|
1411
1411
|
const explicitApp = requireApp || process.env.MMI_ACTOR?.trim() === "app";
|
|
1412
|
-
|
|
1413
|
-
try {
|
|
1414
|
-
token = await ghWriteTokenProvider();
|
|
1415
|
-
} catch (e) {
|
|
1416
|
-
if (explicitApp) throw e;
|
|
1417
|
-
return void 0;
|
|
1418
|
-
}
|
|
1412
|
+
const token = await ghWriteTokenProvider();
|
|
1419
1413
|
if (!token && explicitApp) {
|
|
1420
1414
|
throw new Error("app actor: filing write token unavailable \u2014 refusing personal-token fallback under MMI_ACTOR=app");
|
|
1421
1415
|
}
|
|
@@ -1642,7 +1636,7 @@ var init_cli_shared = __esm({
|
|
|
1642
1636
|
try {
|
|
1643
1637
|
return await tag(run(appEnv), "app_installation");
|
|
1644
1638
|
} catch (e) {
|
|
1645
|
-
if (shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(), ambient);
|
|
1639
|
+
if (!reportsCredential && shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(), ambient);
|
|
1646
1640
|
throw e;
|
|
1647
1641
|
}
|
|
1648
1642
|
};
|
|
@@ -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
|
|
|
@@ -13912,6 +13906,15 @@ function issueCommentAppendOnlyRecovery(flag, canonicalPath, argv) {
|
|
|
13912
13906
|
corrected
|
|
13913
13907
|
};
|
|
13914
13908
|
}
|
|
13909
|
+
function trainPlanRedundantFlagRecovery(flag, canonicalPath, argv) {
|
|
13910
|
+
if (canonicalPath !== "devops release" && canonicalPath !== "devops rcand") return void 0;
|
|
13911
|
+
if (flag !== "--plan" && !flag.startsWith("--plan=")) return void 0;
|
|
13912
|
+
const kept = argv.filter((tok) => tok !== "--plan" && !tok.startsWith("--plan="));
|
|
13913
|
+
return {
|
|
13914
|
+
message: `unknown option '--plan' \u2014 \`${canonicalPath}\` runs the dry-run plan by default when \`--apply\` is absent; planning needs no flag`,
|
|
13915
|
+
corrected: `mmi-cli ${kept.join(" ")}`
|
|
13916
|
+
};
|
|
13917
|
+
}
|
|
13915
13918
|
var BOARD_CLAIM_OWN_FLAGS = ["--repo", "--for", "--force", "--check", "--allow-partial", "--json"];
|
|
13916
13919
|
function splitInlineSelectors(argv, flags) {
|
|
13917
13920
|
const out = [];
|
|
@@ -15120,8 +15123,8 @@ function resolveVerbAliasShim(argv) {
|
|
|
15120
15123
|
}
|
|
15121
15124
|
|
|
15122
15125
|
// src/command-composition.ts
|
|
15123
|
-
var
|
|
15124
|
-
var
|
|
15126
|
+
var import_node_os28 = require("node:os");
|
|
15127
|
+
var import_node_path62 = require("node:path");
|
|
15125
15128
|
|
|
15126
15129
|
// src/board-read.ts
|
|
15127
15130
|
var import_node_fs18 = require("node:fs");
|
|
@@ -16075,6 +16078,14 @@ async function pollToken() {
|
|
|
16075
16078
|
});
|
|
16076
16079
|
return pollTokenOnce;
|
|
16077
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
|
+
}
|
|
16078
16089
|
async function ghReadEnv() {
|
|
16079
16090
|
const appToken = await pollToken();
|
|
16080
16091
|
return appToken ? { ...process.env, GH_TOKEN: appToken, MMI_GH_POOL: "app" } : void 0;
|
|
@@ -25430,10 +25441,25 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
25430
25441
|
}
|
|
25431
25442
|
|
|
25432
25443
|
// src/gate-budget.ts
|
|
25433
|
-
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";
|
|
25434
25458
|
var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
25435
25459
|
var BLESSED_KIT_AGENT_FILES_SHA = "e976df208d891b215844386c1cac0f161946b6fa";
|
|
25436
25460
|
var BLESSED_JEV_FAILURES_SHA = "d5278c6133318b33727483ddfdfddc8f841e3ef6";
|
|
25461
|
+
var BLESSED_VERDICT_RECEIPT_SHA = "";
|
|
25462
|
+
var BLESSED_RUNNER_CHOICE_SHA = "f6b691f966611ec2a3b688c74c92a92f23133f41";
|
|
25437
25463
|
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
25438
25464
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
25439
25465
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
@@ -25818,7 +25844,10 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
25818
25844
|
GATE_SERVICES_YAML: "",
|
|
25819
25845
|
GATE_EXTRA_STEPS_YAML: "",
|
|
25820
25846
|
// #9293: Jev failure-memory fetch/report steps from the blessed jev-failures pin (an empty pin renders none).
|
|
25821
|
-
...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)
|
|
25822
25851
|
};
|
|
25823
25852
|
const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
|
|
25824
25853
|
const trackBranches = track === "trunk" ? ["main"] : track === "single" ? ["development"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
|
|
@@ -25967,6 +25996,47 @@ function gateServicesYaml(services) {
|
|
|
25967
25996
|
}
|
|
25968
25997
|
return out;
|
|
25969
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
|
+
}
|
|
25970
26040
|
function gateJevStepsYaml(sha, runtime = "node") {
|
|
25971
26041
|
if (!/^[0-9a-f]{40}$/.test(sha)) return { GATE_JEV_FETCH_YAML: "", GATE_JEV_REPORT_YAML: "" };
|
|
25972
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`;
|
|
@@ -27595,10 +27665,10 @@ var rollout_plan_default = {
|
|
|
27595
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)."
|
|
27596
27666
|
},
|
|
27597
27667
|
baseline: {
|
|
27598
|
-
version: "4.6.
|
|
27599
|
-
tag: "v4.6.
|
|
27600
|
-
commit: "
|
|
27601
|
-
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"
|
|
27602
27672
|
},
|
|
27603
27673
|
exitCriterion: "fleet-n-of-n",
|
|
27604
27674
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27615,14 +27685,14 @@ var rollout_plan_default = {
|
|
|
27615
27685
|
repo: "mutmutco/mmi-hub",
|
|
27616
27686
|
role: "canary",
|
|
27617
27687
|
schedule: "train",
|
|
27618
|
-
v3Target: "v4.6.
|
|
27688
|
+
v3Target: "v4.6.12"
|
|
27619
27689
|
}
|
|
27620
27690
|
],
|
|
27621
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.",
|
|
27622
27692
|
rollback: {
|
|
27623
27693
|
independent: true,
|
|
27624
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
27625
|
-
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)"
|
|
27626
27696
|
}
|
|
27627
27697
|
},
|
|
27628
27698
|
{
|
|
@@ -32195,17 +32265,30 @@ function classifyRunnerCost(selector) {
|
|
|
32195
32265
|
}
|
|
32196
32266
|
var STANDARD_HOSTED_ALLOWLIST = [
|
|
32197
32267
|
["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"],
|
|
32198
|
-
["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"]
|
|
32199
|
-
// #9571 (MM-Work#8894 chooser): owned mmi-heavy first, included ubuntu-latest only on a deep backlog.
|
|
32200
|
-
["mutmutco/MMI-Hub", ".github/workflows/gate.yml", "cli-part", "hosted-spill"]
|
|
32268
|
+
["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"]
|
|
32201
32269
|
];
|
|
32202
|
-
var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*'([^']+)'\s*\|\|\s*)?fromJSON\(needs\.[\w-]
|
|
32270
|
+
var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*'([^']+)'\s*\|\|\s*)?fromJSON\(needs\.([\w-]+)\.outputs\.(\w+_runs_on)\s*\|\|\s*('[^']+')\)\s*\}\}$/;
|
|
32271
|
+
var RUNNER_CHOICE_ACTION = "mutmutco/MMI-Hub/.github/actions/runner-choice";
|
|
32272
|
+
function runnerChoiceProduces(job, decider, output, source) {
|
|
32273
|
+
const needs = job.needs;
|
|
32274
|
+
if (!(needs === decider || Array.isArray(needs) && needs.includes(decider))) return false;
|
|
32275
|
+
const producer = source.jobs.get(decider);
|
|
32276
|
+
if (!record(producer) || !record(producer.outputs) || !Array.isArray(producer.steps)) return false;
|
|
32277
|
+
const step = /^\$\{\{\s*steps\.([\w-]+)\.outputs\.runs-on\s*\}\}$/.exec(String(producer.outputs[output] ?? ""))?.[1];
|
|
32278
|
+
const steps = producer.steps.filter((s) => record(s) && s.id === step);
|
|
32279
|
+
if (steps.length !== 1 || !record(steps[0])) return false;
|
|
32280
|
+
const { uses } = steps[0], inputs = steps[0].with ?? {};
|
|
32281
|
+
const pinned = /^[0-9a-f]{40}$/.test(source.blessedSha) && uses === `${RUNNER_CHOICE_ACTION}@${source.blessedSha}`;
|
|
32282
|
+
if (!pinned && !(source.repo === "mutmutco/MMI-Hub" && uses === "./.github/actions/runner-choice")) return false;
|
|
32283
|
+
const hosted = record(inputs) ? inputs.hosted ?? "ubuntu-latest" : void 0;
|
|
32284
|
+
return typeof hosted === "string" && STANDARD_HOSTED_LABEL_RE.test(hosted);
|
|
32285
|
+
}
|
|
32203
32286
|
function standardHostedLabelFor(repo, path2, jobId) {
|
|
32204
32287
|
if (repo === "mutmutco/Jerv-Hub") return ["ubuntu-latest", "windows-latest", "macos-14"];
|
|
32205
32288
|
return STANDARD_HOSTED_ALLOWLIST.find(([r, p, j]) => r === repo && p === path2 && j === jobId)?.[3];
|
|
32206
32289
|
}
|
|
32207
32290
|
var record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
32208
|
-
function classifyJobRunnerCost(job, allowance2) {
|
|
32291
|
+
function classifyJobRunnerCost(job, allowance2, source) {
|
|
32209
32292
|
if (!record(job) || !Object.hasOwn(job, "runs-on")) return "unknown";
|
|
32210
32293
|
const selector = job["runs-on"];
|
|
32211
32294
|
const raw = typeof selector === "string" ? selector.trim() : Array.isArray(selector) && selector.length && selector.every((label) => typeof label === "string") ? JSON.stringify(selector) : "";
|
|
@@ -32214,8 +32297,8 @@ function classifyJobRunnerCost(job, allowance2) {
|
|
|
32214
32297
|
if (approved(raw) || allowance2 === "windows-11-arm" && raw === allowance2) return "standard-hosted";
|
|
32215
32298
|
const fallback = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'([^']+)'\s*\|\|\s*(fromJSON\('[^']+'\))\s*\}\}$/.exec(raw);
|
|
32216
32299
|
if (fallback && approved(fallback[1]) && classifyRunnerCost("${{ " + fallback[2] + " }}") === "self-hosted") return "standard-hosted";
|
|
32217
|
-
const spill =
|
|
32218
|
-
if (spill
|
|
32300
|
+
const spill = HOSTED_SPILL_RE.exec(raw);
|
|
32301
|
+
if (spill && source && (!spill[1] || STANDARD_HOSTED_LABEL_RE.test(spill[1])) && classifyRunnerCost(`\${{ fromJSON(${spill[4]}) }}`) === "self-hosted" && runnerChoiceProduces(job, spill[2], spill[3], source)) return "standard-hosted";
|
|
32219
32302
|
const direct = classifyRunnerCost(raw);
|
|
32220
32303
|
const matrix = /^\$\{\{\s*matrix\.([\w-]+)(?:\.([\w-]+))?\s*\}\}$/.exec(raw);
|
|
32221
32304
|
if (!matrix || direct === "hosted") return direct;
|
|
@@ -32270,16 +32353,24 @@ function parseRunnerJobs(source) {
|
|
|
32270
32353
|
for (const { key, value: job } of jobs.items) {
|
|
32271
32354
|
if (!(0, import_yaml.isScalar)(key) || typeof key.value !== "string" || !literalMap(job)) return void 0;
|
|
32272
32355
|
const projected = /* @__PURE__ */ Object.create(null);
|
|
32273
|
-
for (const field of ["runs-on", "uses", "strategy"]) {
|
|
32356
|
+
for (const field of ["runs-on", "uses", "strategy", "needs", "outputs", "steps"]) {
|
|
32274
32357
|
if (!job.has(field)) continue;
|
|
32358
|
+
const optional = field === "needs" || field === "outputs" || field === "steps";
|
|
32275
32359
|
const value = job.get(field, true);
|
|
32276
|
-
if (value !== null && !(0, import_yaml.isNode)(value))
|
|
32360
|
+
if (value !== null && !(0, import_yaml.isNode)(value)) {
|
|
32361
|
+
if (optional) continue;
|
|
32362
|
+
return void 0;
|
|
32363
|
+
}
|
|
32277
32364
|
(0, import_yaml.visit)(value, (_key, node) => {
|
|
32278
32365
|
if (!literal(node) || (0, import_yaml.isPair)(node) && (0, import_yaml.isScalar)(node.key) && node.key.value === "<<") {
|
|
32279
32366
|
invalid = true;
|
|
32280
32367
|
return import_yaml.visit.BREAK;
|
|
32281
32368
|
}
|
|
32282
32369
|
});
|
|
32370
|
+
if (invalid && optional) {
|
|
32371
|
+
invalid = false;
|
|
32372
|
+
continue;
|
|
32373
|
+
}
|
|
32283
32374
|
if (invalid) return void 0;
|
|
32284
32375
|
projected[field] = value === null ? null : value.toJS(doc, { maxAliasCount: 0 });
|
|
32285
32376
|
}
|
|
@@ -32416,7 +32507,7 @@ function classifyRunnerWorkflows(files) {
|
|
|
32416
32507
|
const uses = hasCallee && typeof job.uses === "string" && !Object.hasOwn(job, "runs-on") ? job.uses : void 0;
|
|
32417
32508
|
const target = uses && resolveCallee(file, uses);
|
|
32418
32509
|
if (target && known.has(locationOf(target)) && resolved(file, /* @__PURE__ */ new Set())) return [];
|
|
32419
|
-
const cost = hasCallee ? "unknown" : classifyJobRunnerCost(job, standardHostedLabelFor(file.repo, file.path, id));
|
|
32510
|
+
const cost = hasCallee ? "unknown" : classifyJobRunnerCost(job, standardHostedLabelFor(file.repo, file.path, id), { repo: file.repo, jobs, blessedSha: BLESSED_RUNNER_CHOICE_SHA });
|
|
32420
32511
|
return [{ repo: file.repo, path: file.path, job: id, cost, callee: hasCallee }];
|
|
32421
32512
|
});
|
|
32422
32513
|
});
|
|
@@ -38640,6 +38731,385 @@ async function runPipelineWithBump(bump, mode, input) {
|
|
|
38640
38731
|
return { ...await runTrainApplyPipeline(mode, input), bump };
|
|
38641
38732
|
}
|
|
38642
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
|
+
|
|
38643
39113
|
// src/registry-repositories.ts
|
|
38644
39114
|
function qualifyRegistryRepo(raw) {
|
|
38645
39115
|
return raw.includes("/") ? raw : `mutmutco/${raw}`;
|
|
@@ -38650,7 +39120,7 @@ function registryRepoKey(repo) {
|
|
|
38650
39120
|
|
|
38651
39121
|
// src/ci-audit.ts
|
|
38652
39122
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
38653
|
-
var
|
|
39123
|
+
var HUB_REPO5 = "mutmutco/MMI-Hub";
|
|
38654
39124
|
var HUB_GATE_CONTEXTS = ["cli", "infra", "docs"];
|
|
38655
39125
|
var PRODUCT_GATE_PATH = ".github/workflows/gate.yml";
|
|
38656
39126
|
var PRODUCT_RULESET_REF = ".github/rulesets/mmi-product-required-checks.json";
|
|
@@ -38837,6 +39307,46 @@ function auditJevOutcomes(files) {
|
|
|
38837
39307
|
}
|
|
38838
39308
|
return checks;
|
|
38839
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
|
+
}
|
|
38840
39350
|
var PENDING_LANDING_LABEL = "pending PRs landing (#8783)";
|
|
38841
39351
|
var PENDING_LANDING_LIST_CAP = 50;
|
|
38842
39352
|
var PENDING_LANDING_STALL_MS = 15 * 60 * 1e3;
|
|
@@ -38904,7 +39414,7 @@ function slugFromRepo(repo) {
|
|
|
38904
39414
|
return (repo.includes("/") ? repo.split("/")[1] : repo).toLowerCase();
|
|
38905
39415
|
}
|
|
38906
39416
|
function classifyRepo(repo, meta) {
|
|
38907
|
-
if (repo.toLowerCase() ===
|
|
39417
|
+
if (repo.toLowerCase() === HUB_REPO5.toLowerCase()) return "hub";
|
|
38908
39418
|
if (meta?.class === "content") return "content";
|
|
38909
39419
|
if (meta?.class === "deployable" || meta?.deployModel && meta.deployModel !== "content" && meta.deployModel !== "none") return "deployable";
|
|
38910
39420
|
if (meta) return "deployable";
|
|
@@ -38933,7 +39443,7 @@ function pinContainsFloor(deps, floor, pin) {
|
|
|
38933
39443
|
const key = `${floor}...${pin}`;
|
|
38934
39444
|
let comparison = comparisons.get(key);
|
|
38935
39445
|
if (!comparison) {
|
|
38936
|
-
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);
|
|
38937
39447
|
comparisons.set(key, comparison);
|
|
38938
39448
|
}
|
|
38939
39449
|
return comparison;
|
|
@@ -39206,7 +39716,7 @@ function collectRegistryRepos(projects) {
|
|
|
39206
39716
|
if (!seen.has(key)) seen.set(key, repo);
|
|
39207
39717
|
}
|
|
39208
39718
|
}
|
|
39209
|
-
if (!seen.has(
|
|
39719
|
+
if (!seen.has(HUB_REPO5.toLowerCase())) seen.set(HUB_REPO5.toLowerCase(), HUB_REPO5);
|
|
39210
39720
|
return [...seen.values()].sort((a, b) => a.localeCompare(b));
|
|
39211
39721
|
}
|
|
39212
39722
|
async function prTriggeredWorkflowsOnRef(deps, repo, branch, repoClass) {
|
|
@@ -39632,6 +40142,8 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
39632
40142
|
}
|
|
39633
40143
|
checks.push(...auditSelfHostedRunnerHygiene(workflows));
|
|
39634
40144
|
checks.push(...auditJevOutcomes(workflows));
|
|
40145
|
+
checks.push(...auditVerdictReceipt(workflows));
|
|
40146
|
+
if (repoClass !== "hub") checks.push(...auditHubActionPins(workflows));
|
|
39635
40147
|
if (repoClass !== "hub") checks.push(...await auditSharedActionPins(workflows, deps));
|
|
39636
40148
|
} else {
|
|
39637
40149
|
checks.push({ ok: false, label: "runner workflow source is readable (#8958)", detail: "workflow source inventory unreadable" });
|
|
@@ -41195,10 +41707,10 @@ init_github_client();
|
|
|
41195
41707
|
init_cli_shared();
|
|
41196
41708
|
|
|
41197
41709
|
// src/pr-merge-queue.ts
|
|
41198
|
-
var
|
|
41199
|
-
var
|
|
41200
|
-
var
|
|
41201
|
-
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");
|
|
41202
41714
|
init_cli_shared();
|
|
41203
41715
|
|
|
41204
41716
|
// src/pr-land.ts
|
|
@@ -41637,8 +42149,8 @@ var import_yaml3 = __toESM(require_dist(), 1);
|
|
|
41637
42149
|
|
|
41638
42150
|
// src/test-policy-core.ts
|
|
41639
42151
|
var import_node_child_process11 = require("node:child_process");
|
|
41640
|
-
var
|
|
41641
|
-
var
|
|
42152
|
+
var import_node_fs33 = require("node:fs");
|
|
42153
|
+
var import_node_path30 = require("node:path");
|
|
41642
42154
|
|
|
41643
42155
|
// src/test-command-policy-shared.mjs
|
|
41644
42156
|
var import_node_child_process10 = require("node:child_process");
|
|
@@ -42194,7 +42706,7 @@ function isMeaningfulRow(file) {
|
|
|
42194
42706
|
return file.meaningful !== false;
|
|
42195
42707
|
}
|
|
42196
42708
|
function loadPolicy(root, readFile10 = readFileOrNull2) {
|
|
42197
|
-
const raw = readFile10((0,
|
|
42709
|
+
const raw = readFile10((0, import_node_path30.join)(root, POLICY_FILE));
|
|
42198
42710
|
if (raw == null) return { mandatory: [], declared: false };
|
|
42199
42711
|
return parsePolicy(raw, POLICY_FILE);
|
|
42200
42712
|
}
|
|
@@ -42278,7 +42790,7 @@ function loadPolicySource(root, explicitRef, diffBase) {
|
|
|
42278
42790
|
}
|
|
42279
42791
|
function readFileOrNull2(path2) {
|
|
42280
42792
|
try {
|
|
42281
|
-
return (0,
|
|
42793
|
+
return (0, import_node_fs33.readFileSync)(path2, "utf8");
|
|
42282
42794
|
} catch {
|
|
42283
42795
|
return null;
|
|
42284
42796
|
}
|
|
@@ -42306,12 +42818,12 @@ function classify(changed, policy, present = () => false) {
|
|
|
42306
42818
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
42307
42819
|
return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
|
|
42308
42820
|
}
|
|
42309
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
42310
|
-
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)));
|
|
42311
42823
|
}
|
|
42312
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
42824
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs33.existsSync)(path2)) {
|
|
42313
42825
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
42314
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
42826
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
42315
42827
|
}
|
|
42316
42828
|
function evaluate(changed, policy, present = () => false) {
|
|
42317
42829
|
const { mandatoryHits, untestedHits, meaningfulTestChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -42485,20 +42997,20 @@ function runTestPolicy(root, deps = {}) {
|
|
|
42485
42997
|
if (deps.policyRef && (deps.policy !== void 0 || deps.changed !== void 0 || deps.override !== void 0 || deps.exists !== void 0)) {
|
|
42486
42998
|
throw new Error("--policy-ref cannot be combined with injected policy, changed rows, override, or filesystem evidence");
|
|
42487
42999
|
}
|
|
42488
|
-
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;
|
|
42489
43001
|
const loaded = candidate ? {
|
|
42490
|
-
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 },
|
|
42491
43003
|
source: { ref: candidate.head, sha: candidate.head, path: POLICY_FILE }
|
|
42492
43004
|
} : deps.policy ? { policy: deps.policy, source: { ref: null, sha: null, path: POLICY_FILE } } : loadPolicySource(root, deps.policyRef, deps.base);
|
|
42493
43005
|
const { policy, source: policySource } = loaded;
|
|
42494
|
-
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));
|
|
42495
43007
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
42496
43008
|
const base = candidate ? git2(["merge-base", candidate.head, candidate.base], root).trim() : deps.changed ? "(injected)" : deps.policyRef ? resolveHotfixBase(root) : resolveBase(root, deps.base);
|
|
42497
43009
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
42498
43010
|
const raw = deps.changed ?? (refusal ? [] : changedFilesSince(base, root, candidate?.head));
|
|
42499
43011
|
const changed = deps.changed ? raw : annotateChangeMeaning(raw, policy, (path2) => ({
|
|
42500
43012
|
before: blobAt(base, path2, root),
|
|
42501
|
-
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))
|
|
42502
43014
|
}));
|
|
42503
43015
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root, candidate?.head) : { override: null, refusals: [] };
|
|
42504
43016
|
const policyRefusals = policy.declared === false ? [{
|
|
@@ -42508,8 +43020,8 @@ function runTestPolicy(root, deps = {}) {
|
|
|
42508
43020
|
A gate with no policy cannot decide which test commands are required or permitted.
|
|
42509
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.`)
|
|
42510
43022
|
}] : [];
|
|
42511
|
-
const present = (path2) => exists((0,
|
|
42512
|
-
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);
|
|
42513
43025
|
const policyTreeExists = policyTree ? (abs) => policyTree.has(abs) : exists;
|
|
42514
43026
|
const where = policySource.sha ? `${policySource.ref}@${policySource.sha}` : "this worktree";
|
|
42515
43027
|
const removedByThisDiff = removedPaths(changed);
|
|
@@ -42629,16 +43141,16 @@ var autoMergeApi = async (pr, repo, head) => {
|
|
|
42629
43141
|
var ASYNC_HEADERS = ["-H", "Accept: application/vnd.github+json", "-H", "X-GitHub-Api-Version: 2026-03-10"];
|
|
42630
43142
|
var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
|
42631
43143
|
function admissionJournal(repo, pr, root) {
|
|
42632
|
-
const stateRoot = root ?? (process.platform === "win32" ? (0,
|
|
42633
|
-
const directory = (0,
|
|
42634
|
-
(0,
|
|
42635
|
-
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) => {
|
|
42636
43148
|
if (!/^[1-9][0-9]*\.jsonl$/.test(name)) throw new Error("queue admission receipt directory contains an unrecognized record");
|
|
42637
43149
|
return Number(name.slice(0, -6));
|
|
42638
43150
|
}).sort((a, b) => a - b);
|
|
42639
43151
|
if (attempts.some((number, index) => number !== index + 1)) throw new Error("queue admission receipt sequence is incomplete");
|
|
42640
43152
|
const attempt = attempts.length;
|
|
42641
|
-
const path2 = (number) => (0,
|
|
43153
|
+
const path2 = (number) => (0, import_node_path31.join)(directory, `${number}.jsonl`);
|
|
42642
43154
|
const validate = (value, previous) => {
|
|
42643
43155
|
const row = value;
|
|
42644
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)))) {
|
|
@@ -42653,7 +43165,7 @@ function admissionJournal(repo, pr, root) {
|
|
|
42653
43165
|
};
|
|
42654
43166
|
const load = (number = attempt) => {
|
|
42655
43167
|
let previous;
|
|
42656
|
-
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);
|
|
42657
43169
|
return previous;
|
|
42658
43170
|
};
|
|
42659
43171
|
return {
|
|
@@ -42668,9 +43180,9 @@ function admissionJournal(repo, pr, root) {
|
|
|
42668
43180
|
},
|
|
42669
43181
|
create(head) {
|
|
42670
43182
|
const createdAt = Date.now();
|
|
42671
|
-
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" };
|
|
42672
43184
|
try {
|
|
42673
|
-
(0,
|
|
43185
|
+
(0, import_node_fs34.writeFileSync)(path2(attempt + 1), JSON.stringify(initial) + "\n", { flag: "wx", mode: 384, flush: true });
|
|
42674
43186
|
} catch (error) {
|
|
42675
43187
|
if (error.code === "EEXIST") return false;
|
|
42676
43188
|
throw error;
|
|
@@ -42690,7 +43202,7 @@ function admissionJournal(repo, pr, root) {
|
|
|
42690
43202
|
...queueWitnessed !== void 0 ? { queueWitnessed } : {},
|
|
42691
43203
|
...credential && previous.credential === void 0 ? { credential } : {}
|
|
42692
43204
|
}, previous);
|
|
42693
|
-
(0,
|
|
43205
|
+
(0, import_node_fs34.appendFileSync)(path2(attempt), JSON.stringify(receipt) + "\n", { flush: true });
|
|
42694
43206
|
load();
|
|
42695
43207
|
}
|
|
42696
43208
|
};
|
|
@@ -43583,6 +44095,15 @@ async function attachViaHubApp(cfg, contentNodeId) {
|
|
|
43583
44095
|
const { addIssueToProjectViaHub: addIssueToProjectViaHub2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
|
|
43584
44096
|
return addIssueToProjectViaHub2(cfg, contentNodeId);
|
|
43585
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
|
+
}
|
|
43586
44107
|
async function issueNodeIdForAttach(repo, number, ghView, client) {
|
|
43587
44108
|
try {
|
|
43588
44109
|
return (await ghView()).trim();
|
|
@@ -44192,9 +44713,9 @@ function registerBoardCommands(program3) {
|
|
|
44192
44713
|
}
|
|
44193
44714
|
|
|
44194
44715
|
// src/bootstrap-commands.ts
|
|
44195
|
-
var
|
|
44196
|
-
var
|
|
44197
|
-
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");
|
|
44198
44719
|
init_cli_shared();
|
|
44199
44720
|
init_clean_exit();
|
|
44200
44721
|
|
|
@@ -44379,8 +44900,8 @@ init_github_client();
|
|
|
44379
44900
|
init_cli_shared();
|
|
44380
44901
|
|
|
44381
44902
|
// src/port-registry.ts
|
|
44382
|
-
var
|
|
44383
|
-
var
|
|
44903
|
+
var import_node_fs35 = require("node:fs");
|
|
44904
|
+
var import_node_path32 = require("node:path");
|
|
44384
44905
|
|
|
44385
44906
|
// ../infra/port-geometry.mjs
|
|
44386
44907
|
var PORT_BLOCK = 100;
|
|
@@ -44394,8 +44915,8 @@ function nextPortBlock(registry2) {
|
|
|
44394
44915
|
return [base, base + PORT_SPAN];
|
|
44395
44916
|
}
|
|
44396
44917
|
function loadPortRegistry(path2) {
|
|
44397
|
-
if (!(0,
|
|
44398
|
-
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"));
|
|
44399
44920
|
const out = {};
|
|
44400
44921
|
for (const [key, value] of Object.entries(raw)) {
|
|
44401
44922
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -44423,22 +44944,22 @@ function existingPortRange(repo, registry2) {
|
|
|
44423
44944
|
return registry2[repo] ?? null;
|
|
44424
44945
|
}
|
|
44425
44946
|
function portRangeInfraAt(root, source) {
|
|
44426
|
-
const registryPath = (0,
|
|
44427
|
-
const ddbScriptPath = (0,
|
|
44428
|
-
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;
|
|
44429
44950
|
return { root, source, registryPath, ddbScriptPath };
|
|
44430
44951
|
}
|
|
44431
44952
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
44432
44953
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
44433
44954
|
if (direct) return direct;
|
|
44434
|
-
for (let dir = cwd; ; dir = (0,
|
|
44435
|
-
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");
|
|
44436
44957
|
if (sibling) return sibling;
|
|
44437
|
-
const parent = (0,
|
|
44958
|
+
const parent = (0, import_node_path32.dirname)(dir);
|
|
44438
44959
|
if (parent === dir) break;
|
|
44439
44960
|
}
|
|
44440
44961
|
if (packageDir) {
|
|
44441
|
-
const pkgRoot = (0,
|
|
44962
|
+
const pkgRoot = (0, import_node_path32.join)(packageDir, "..", "..");
|
|
44442
44963
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
44443
44964
|
if (pkgFrom) return pkgFrom;
|
|
44444
44965
|
}
|
|
@@ -44455,7 +44976,7 @@ async function ensurePortRangeAtomic(repo, path2, allocate) {
|
|
|
44455
44976
|
} catch (e) {
|
|
44456
44977
|
const why = e.message;
|
|
44457
44978
|
const missing = /@aws-sdk\/[\w-]+/.exec(why)?.[0];
|
|
44458
|
-
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}`);
|
|
44459
44980
|
}
|
|
44460
44981
|
}
|
|
44461
44982
|
|
|
@@ -44507,13 +45028,13 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
|
|
|
44507
45028
|
}
|
|
44508
45029
|
|
|
44509
45030
|
// src/box-commands.ts
|
|
44510
|
-
var
|
|
45031
|
+
var import_node_fs37 = require("node:fs");
|
|
44511
45032
|
var import_node_child_process12 = require("node:child_process");
|
|
44512
|
-
var
|
|
44513
|
-
var
|
|
45033
|
+
var import_node_os20 = require("node:os");
|
|
45034
|
+
var import_node_path34 = require("node:path");
|
|
44514
45035
|
|
|
44515
45036
|
// src/box-control.ts
|
|
44516
|
-
var
|
|
45037
|
+
var import_node_crypto13 = require("node:crypto");
|
|
44517
45038
|
var MANIFEST_PATH = "infra/box-control/manifest.json";
|
|
44518
45039
|
var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
|
|
44519
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`;
|
|
@@ -44617,7 +45138,7 @@ function laneCoverageAction(files, box, op) {
|
|
|
44617
45138
|
return { notice: `LANE_COVERAGE: ${mismatch}` };
|
|
44618
45139
|
}
|
|
44619
45140
|
function md5(content) {
|
|
44620
|
-
return (0,
|
|
45141
|
+
return (0, import_node_crypto13.createHash)("md5").update(content).digest("hex");
|
|
44621
45142
|
}
|
|
44622
45143
|
function planRemoteScript(dests) {
|
|
44623
45144
|
const lines2 = ["set -u"];
|
|
@@ -44735,9 +45256,9 @@ init_clean_exit();
|
|
|
44735
45256
|
init_cli_shared();
|
|
44736
45257
|
|
|
44737
45258
|
// src/secrets-commands.ts
|
|
44738
|
-
var
|
|
44739
|
-
var
|
|
44740
|
-
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");
|
|
44741
45262
|
init_cli_shared();
|
|
44742
45263
|
init_hub_auth();
|
|
44743
45264
|
init_github_client();
|
|
@@ -45933,18 +46454,18 @@ function collectMap(value, previous = []) {
|
|
|
45933
46454
|
return [...previous, value];
|
|
45934
46455
|
}
|
|
45935
46456
|
async function decryptRailsCredentials(input) {
|
|
45936
|
-
const appDir = (0,
|
|
46457
|
+
const appDir = (0, import_node_path33.resolve)(input.appDir ?? process.cwd());
|
|
45937
46458
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
45938
46459
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
45939
|
-
const credentialsPath = (0,
|
|
45940
|
-
const masterKeyPath = (0,
|
|
46460
|
+
const credentialsPath = (0, import_node_path33.resolve)(appDir, credentialsFile);
|
|
46461
|
+
const masterKeyPath = (0, import_node_path33.resolve)(appDir, masterKeyFile);
|
|
45941
46462
|
const env = {
|
|
45942
46463
|
...process.env,
|
|
45943
46464
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
45944
46465
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
45945
46466
|
};
|
|
45946
|
-
if ((0,
|
|
45947
|
-
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();
|
|
45948
46469
|
}
|
|
45949
46470
|
const script = [
|
|
45950
46471
|
'require "json"',
|
|
@@ -45954,9 +46475,9 @@ async function decryptRailsCredentials(input) {
|
|
|
45954
46475
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
45955
46476
|
"puts JSON.generate(config.config)"
|
|
45956
46477
|
].join("\n");
|
|
45957
|
-
const scriptDir = (0,
|
|
45958
|
-
const scriptPath = (0,
|
|
45959
|
-
(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");
|
|
45960
46481
|
try {
|
|
45961
46482
|
const args = ["exec", "ruby", scriptPath];
|
|
45962
46483
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -45968,7 +46489,7 @@ async function decryptRailsCredentials(input) {
|
|
|
45968
46489
|
});
|
|
45969
46490
|
return JSON.parse(stdout);
|
|
45970
46491
|
} finally {
|
|
45971
|
-
(0,
|
|
46492
|
+
(0, import_node_fs36.rmSync)(scriptDir, { recursive: true, force: true });
|
|
45972
46493
|
}
|
|
45973
46494
|
}
|
|
45974
46495
|
async function readSecretStdin() {
|
|
@@ -46058,7 +46579,7 @@ function registerSecretsCommands(program3) {
|
|
|
46058
46579
|
let body;
|
|
46059
46580
|
if (o.file) {
|
|
46060
46581
|
try {
|
|
46061
|
-
body = (0,
|
|
46582
|
+
body = (0, import_node_fs36.readFileSync)((0, import_node_path33.resolve)(o.file), "utf8");
|
|
46062
46583
|
} catch (e) {
|
|
46063
46584
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
46064
46585
|
}
|
|
@@ -46194,7 +46715,7 @@ function registerSecretsCommands(program3) {
|
|
|
46194
46715
|
{
|
|
46195
46716
|
...d,
|
|
46196
46717
|
decryptRailsCredentials,
|
|
46197
|
-
removeFile: (path2) => (0,
|
|
46718
|
+
removeFile: (path2) => (0, import_node_fs36.unlinkSync)((0, import_node_path33.resolve)(o.appDir ?? process.cwd(), path2))
|
|
46198
46719
|
},
|
|
46199
46720
|
{
|
|
46200
46721
|
repo: o.repo,
|
|
@@ -46465,7 +46986,7 @@ function registerBoxCommands(program3) {
|
|
|
46465
46986
|
}
|
|
46466
46987
|
const remote = command && command.length ? command.join(" ") : void 0;
|
|
46467
46988
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
46468
|
-
if (wroteScript) (0,
|
|
46989
|
+
if (wroteScript) (0, import_node_fs37.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
|
|
46469
46990
|
if (o.json) {
|
|
46470
46991
|
console.log(JSON.stringify({
|
|
46471
46992
|
box: found,
|
|
@@ -46592,17 +47113,17 @@ function hubGitShow(ref) {
|
|
|
46592
47113
|
return (path2) => git3(["show", `${ref}:${path2}`]);
|
|
46593
47114
|
}
|
|
46594
47115
|
function runOnBox(found, script) {
|
|
46595
|
-
const dir = (0,
|
|
47116
|
+
const dir = (0, import_node_fs37.mkdtempSync)((0, import_node_path34.join)((0, import_node_os20.tmpdir)(), "mmi-box-control-"));
|
|
46596
47117
|
try {
|
|
46597
|
-
const recipe = (0,
|
|
46598
|
-
(0,
|
|
47118
|
+
const recipe = (0, import_node_path34.join)(dir, "recipe.sh");
|
|
47119
|
+
(0, import_node_fs37.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
|
|
46599
47120
|
const r = (0, import_node_child_process12.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
|
|
46600
47121
|
if (r.status !== 0) {
|
|
46601
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(" | ")}`);
|
|
46602
47123
|
}
|
|
46603
47124
|
return r.stdout;
|
|
46604
47125
|
} finally {
|
|
46605
|
-
(0,
|
|
47126
|
+
(0, import_node_fs37.rmSync)(dir, { recursive: true, force: true });
|
|
46606
47127
|
}
|
|
46607
47128
|
}
|
|
46608
47129
|
function formatControl(mode, box, rows) {
|
|
@@ -46730,7 +47251,7 @@ function renderDeployPortDoctor(report) {
|
|
|
46730
47251
|
}
|
|
46731
47252
|
|
|
46732
47253
|
// src/bootstrap-drift.ts
|
|
46733
|
-
var
|
|
47254
|
+
var import_node_crypto14 = require("node:crypto");
|
|
46734
47255
|
function byteComparableSeeds(manifest, cls) {
|
|
46735
47256
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
46736
47257
|
}
|
|
@@ -46750,7 +47271,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
46750
47271
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
46751
47272
|
}
|
|
46752
47273
|
function seedContentHash(content) {
|
|
46753
|
-
return (0,
|
|
47274
|
+
return (0, import_node_crypto14.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
46754
47275
|
}
|
|
46755
47276
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
46756
47277
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -46800,197 +47321,6 @@ function renderSeedDriftReport(findings, reposAudited, seedsPerRepo) {
|
|
|
46800
47321
|
return lines2.join("\n");
|
|
46801
47322
|
}
|
|
46802
47323
|
|
|
46803
|
-
// src/bootstrap-propagate.ts
|
|
46804
|
-
function archivedWaiver(repoMeta) {
|
|
46805
|
-
return repoMeta?.archived === true ? "archived repository (read-only)" : void 0;
|
|
46806
|
-
}
|
|
46807
|
-
function assertPropagationCoverage(rosterCount, independentRegistryCount) {
|
|
46808
|
-
if (rosterCount === 0) {
|
|
46809
|
-
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)");
|
|
46810
|
-
}
|
|
46811
|
-
if (rosterCount < independentRegistryCount) {
|
|
46812
|
-
throw new Error(
|
|
46813
|
-
`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)`
|
|
46814
|
-
);
|
|
46815
|
-
}
|
|
46816
|
-
}
|
|
46817
|
-
function assignWaves(repos, canarySlug) {
|
|
46818
|
-
const waves = /* @__PURE__ */ new Map();
|
|
46819
|
-
if (!canarySlug) return waves;
|
|
46820
|
-
const rest = repos.filter((r) => r.slug !== canarySlug).slice().sort((a, b) => a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
|
|
46821
|
-
const canary = repos.find((r) => r.slug === canarySlug);
|
|
46822
|
-
if (canary) waves.set(canary.repo, 0);
|
|
46823
|
-
const wave1Count = Math.ceil(rest.length * 0.25);
|
|
46824
|
-
rest.forEach((r, i) => waves.set(r.repo, i < wave1Count ? 1 : 2));
|
|
46825
|
-
return waves;
|
|
46826
|
-
}
|
|
46827
|
-
function statusFor(read, currentSha) {
|
|
46828
|
-
if (!read) return { status: "pending", record: {} };
|
|
46829
|
-
if (read.drift === "match") return { status: "match", record: { mergeSha: read.pr?.mergeSha } };
|
|
46830
|
-
if (read.prUnread) return { status: "red", record: {} };
|
|
46831
|
-
const pr = read.pr;
|
|
46832
|
-
if (!pr) return { status: "pending", record: {} };
|
|
46833
|
-
if (pr.sourceSha && pr.sourceSha !== currentSha) return { status: "pending", record: {} };
|
|
46834
|
-
if (pr.state === "closed") return { status: "closed-unmerged", record: { prNumber: pr.number, prUrl: pr.url } };
|
|
46835
|
-
if (pr.checks === "red") return { status: "red", record: { prNumber: pr.number, prUrl: pr.url } };
|
|
46836
|
-
return { status: "open-pending", record: { prNumber: pr.number, prUrl: pr.url, mergeSha: pr.mergeSha } };
|
|
46837
|
-
}
|
|
46838
|
-
var HALTING_STATUSES = /* @__PURE__ */ new Set(["red", "closed-unmerged"]);
|
|
46839
|
-
function planPropagationTick(input) {
|
|
46840
|
-
const { target, repos, canarySlug, reads, isWorkflowSeed, functionGateSatisfied, currentSha } = input;
|
|
46841
|
-
const sourcePilot = input.sourcePilot === true;
|
|
46842
|
-
const readByRepo = new Map(reads.map((r) => [r.repo, r]));
|
|
46843
|
-
const records = [];
|
|
46844
|
-
const opened = [];
|
|
46845
|
-
const waived = repos.filter((r) => r.waiver);
|
|
46846
|
-
for (const r of waived) {
|
|
46847
|
-
records.push({ repo: r.repo, target, wave: null, status: "skipped-waived", action: "none", detail: `waived: ${r.waiver}` });
|
|
46848
|
-
}
|
|
46849
|
-
const eligible = repos.filter((r) => !r.waiver);
|
|
46850
|
-
const refusedNoCanary = !canarySlug || !sourcePilot && !eligible.some((r) => r.slug === canarySlug);
|
|
46851
|
-
if (refusedNoCanary) {
|
|
46852
|
-
for (const r of eligible) {
|
|
46853
|
-
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)" });
|
|
46854
|
-
}
|
|
46855
|
-
return { target, reposInScope: repos.length, canary: null, refusedNoCanary: true, halted: false, haltReason: null, opened, records };
|
|
46856
|
-
}
|
|
46857
|
-
const waveOf = assignWaves(eligible, canarySlug);
|
|
46858
|
-
const byWave = { 0: [], 1: [], 2: [] };
|
|
46859
|
-
for (const r of eligible) byWave[waveOf.get(r.repo) ?? 2].push(r);
|
|
46860
|
-
let halted = false;
|
|
46861
|
-
let haltReason = null;
|
|
46862
|
-
let waveGateOpen = !(sourcePilot && isWorkflowSeed && !functionGateSatisfied);
|
|
46863
|
-
for (const waveNum of [0, 1, 2]) {
|
|
46864
|
-
const waveRepos = byWave[waveNum];
|
|
46865
|
-
if (!waveRepos.length) continue;
|
|
46866
|
-
if (!waveGateOpen || halted) {
|
|
46867
|
-
for (const r of waveRepos) {
|
|
46868
|
-
records.push({ repo: r.repo, target, wave: waveNum, status: "not-yet-reached", action: "none", detail: "prior wave not yet converged" });
|
|
46869
|
-
}
|
|
46870
|
-
continue;
|
|
46871
|
-
}
|
|
46872
|
-
let waveAllMatch = true;
|
|
46873
|
-
let waveHasRed = false;
|
|
46874
|
-
let waveHasClosed = false;
|
|
46875
|
-
for (const r of waveRepos) {
|
|
46876
|
-
const { status, record: record2 } = statusFor(readByRepo.get(r.repo), currentSha);
|
|
46877
|
-
const shouldOpen = status === "pending" || status === "closed-unmerged";
|
|
46878
|
-
if (shouldOpen) opened.push(r.repo);
|
|
46879
|
-
records.push({
|
|
46880
|
-
repo: r.repo,
|
|
46881
|
-
target,
|
|
46882
|
-
wave: waveNum,
|
|
46883
|
-
status,
|
|
46884
|
-
action: shouldOpen ? "open-pr" : "none",
|
|
46885
|
-
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,
|
|
46886
|
-
...record2
|
|
46887
|
-
});
|
|
46888
|
-
if (status !== "match") waveAllMatch = false;
|
|
46889
|
-
if (HALTING_STATUSES.has(status)) waveHasRed = true;
|
|
46890
|
-
if (status === "closed-unmerged") waveHasClosed = true;
|
|
46891
|
-
}
|
|
46892
|
-
if (waveHasRed) {
|
|
46893
|
-
halted = true;
|
|
46894
|
-
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" : "");
|
|
46895
|
-
waveGateOpen = false;
|
|
46896
|
-
continue;
|
|
46897
|
-
}
|
|
46898
|
-
if (waveNum === 0 && isWorkflowSeed && !functionGateSatisfied) {
|
|
46899
|
-
waveGateOpen = false;
|
|
46900
|
-
continue;
|
|
46901
|
-
}
|
|
46902
|
-
waveGateOpen = waveAllMatch;
|
|
46903
|
-
}
|
|
46904
|
-
return { target, reposInScope: repos.length, canary: canarySlug, refusedNoCanary: false, halted, haltReason, opened, records };
|
|
46905
|
-
}
|
|
46906
|
-
function summarizePropagationTick(plan) {
|
|
46907
|
-
const parts = [];
|
|
46908
|
-
parts.push(plan.opened.length ? `opened ${plan.opened.length} PR(s) this tick: ${plan.opened.join(", ")}` : "opened no PR this tick");
|
|
46909
|
-
const stillNotCurrent = plan.records.filter((r) => r.status !== "match" && r.status !== "skipped-waived" && r.action !== "open-pr");
|
|
46910
|
-
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");
|
|
46911
|
-
if (plan.halted) parts.push(`HALTED \u2014 ${plan.haltReason}`);
|
|
46912
|
-
return parts.join("; ");
|
|
46913
|
-
}
|
|
46914
|
-
function renderPropagationReport(plan) {
|
|
46915
|
-
const lines2 = [`bootstrap propagate \u2014 target ${plan.target}: ${plan.reposInScope} repo(s) in scope, canary=${plan.canary ?? "NONE"}`];
|
|
46916
|
-
if (plan.refusedNoCanary) {
|
|
46917
|
-
lines2.push(" REFUSED \u2014 no canary declared for this target; plan every repo not-yet-reached");
|
|
46918
|
-
lines2.push(` ${summarizePropagationTick(plan)}`);
|
|
46919
|
-
return lines2.join("\n");
|
|
46920
|
-
}
|
|
46921
|
-
for (const r of plan.records) {
|
|
46922
|
-
const wave = r.wave == null ? "-" : String(r.wave);
|
|
46923
|
-
lines2.push(` wave${wave.padEnd(2)} ${r.status.padEnd(16)} ${r.repo}${r.prNumber ? ` PR#${r.prNumber}` : ""} \u2014 ${r.detail}`);
|
|
46924
|
-
}
|
|
46925
|
-
lines2.push(` ${summarizePropagationTick(plan)}`);
|
|
46926
|
-
return lines2.join("\n");
|
|
46927
|
-
}
|
|
46928
|
-
|
|
46929
|
-
// src/bootstrap-propagation-identity.ts
|
|
46930
|
-
var import_node_crypto13 = require("node:crypto");
|
|
46931
|
-
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
46932
|
-
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
46933
|
-
function safeBranchPart(value, maxLength, fallback) {
|
|
46934
|
-
const safe = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength).replace(/-+$/g, "");
|
|
46935
|
-
return safe || fallback;
|
|
46936
|
-
}
|
|
46937
|
-
function repoSlug2(repo) {
|
|
46938
|
-
return repo.trim().split("/").pop()?.toLowerCase() || "repo";
|
|
46939
|
-
}
|
|
46940
|
-
function propagationBranch(repo, target) {
|
|
46941
|
-
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
46942
|
-
const targetPart = safeBranchPart(target, 48, "target");
|
|
46943
|
-
const hash = (0, import_node_crypto13.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
46944
|
-
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
46945
|
-
}
|
|
46946
|
-
function legacyPropagationBranch(repo) {
|
|
46947
|
-
return `${PROPAGATION_BRANCH_PREFIX}${repoSlug2(repo)}`;
|
|
46948
|
-
}
|
|
46949
|
-
function renderPropagationTargetMarker(target) {
|
|
46950
|
-
return `<!-- ${TARGET_MARKER_NAME}: ${JSON.stringify(target)} -->`;
|
|
46951
|
-
}
|
|
46952
|
-
function targetMarkers(body) {
|
|
46953
|
-
const present = body.includes(`${TARGET_MARKER_NAME}:`);
|
|
46954
|
-
const targets = [];
|
|
46955
|
-
const marker = new RegExp(`<!--\\s*${TARGET_MARKER_NAME}:\\s*(.*?)\\s*-->`, "g");
|
|
46956
|
-
for (const match of body.matchAll(marker)) {
|
|
46957
|
-
try {
|
|
46958
|
-
const parsed = JSON.parse(match[1]);
|
|
46959
|
-
if (typeof parsed === "string") targets.push(parsed);
|
|
46960
|
-
} catch {
|
|
46961
|
-
}
|
|
46962
|
-
}
|
|
46963
|
-
return { present, targets };
|
|
46964
|
-
}
|
|
46965
|
-
function canonicalProseTargets(body) {
|
|
46966
|
-
const targets = [];
|
|
46967
|
-
const command = /bootstrap propagate --target (.+?) --execute`/g;
|
|
46968
|
-
const prose = /(?:org-owned copy of|declared marker-bounded block inside repo-owned) `([^`]+)` — the file this PR carries and nothing else/g;
|
|
46969
|
-
for (const match of body.matchAll(command)) targets.push(match[1]);
|
|
46970
|
-
for (const match of body.matchAll(prose)) targets.push(match[1]);
|
|
46971
|
-
return targets;
|
|
46972
|
-
}
|
|
46973
|
-
function propagationPrMatchesTarget(pr, target, identity) {
|
|
46974
|
-
const body = pr.body ?? "";
|
|
46975
|
-
const markers = targetMarkers(body);
|
|
46976
|
-
if (markers.present) {
|
|
46977
|
-
if (markers.targets.length !== 1 || markers.targets[0] !== target) return false;
|
|
46978
|
-
return !pr.files?.length || pr.files.length === 1 && pr.files[0] === target;
|
|
46979
|
-
}
|
|
46980
|
-
if (identity === "target-specific") return false;
|
|
46981
|
-
const proseTargets = canonicalProseTargets(body);
|
|
46982
|
-
if (proseTargets.some((candidate) => candidate !== target)) return false;
|
|
46983
|
-
if (pr.files?.length) {
|
|
46984
|
-
return pr.files.length === 1 && pr.files[0] === target;
|
|
46985
|
-
}
|
|
46986
|
-
return proseTargets.length > 0;
|
|
46987
|
-
}
|
|
46988
|
-
function propagationPrCandidatesForTarget(target, targetSpecificPrs, legacyPrs) {
|
|
46989
|
-
const targetSpecific = targetSpecificPrs.filter((pr) => propagationPrMatchesTarget(pr, target, "target-specific"));
|
|
46990
|
-
if (targetSpecific.length) return targetSpecific;
|
|
46991
|
-
return legacyPrs.filter((pr) => propagationPrMatchesTarget(pr, target, "legacy"));
|
|
46992
|
-
}
|
|
46993
|
-
|
|
46994
47324
|
// src/bootstrap-rollback.ts
|
|
46995
47325
|
function resolveRollbackRecord(records, repo, target) {
|
|
46996
47326
|
const candidates = records.filter((r) => r.repo === repo && r.target === target && r.mergeSha);
|
|
@@ -47435,7 +47765,7 @@ var requiredIssueTemplates = [
|
|
|
47435
47765
|
];
|
|
47436
47766
|
var requiredProductWorkflows = [".github/workflows/gate.yml"];
|
|
47437
47767
|
var requiredProductRulesetRef = ".github/rulesets/mmi-product-required-checks.json";
|
|
47438
|
-
var
|
|
47768
|
+
var HUB_REPO6 = "mutmutco/MMI-Hub";
|
|
47439
47769
|
var requiredLabels = ["bug", "feature", "task"];
|
|
47440
47770
|
var requiredPriorityOptions = ["Urgent", "High", "Medium", "Low"];
|
|
47441
47771
|
var strayDefaultLabels = ["documentation", "duplicate", "enhancement", "good first issue", "help wanted", "invalid", "question", "wontfix"];
|
|
@@ -47849,7 +48179,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
47849
48179
|
checks.push({ ok: await contentExists2(deps, repo, baseBranch, path2), label: `issue template exists: ${path2}` });
|
|
47850
48180
|
}
|
|
47851
48181
|
let productContexts = [PRODUCT_GATE_CONTEXT];
|
|
47852
|
-
if (repo !==
|
|
48182
|
+
if (repo !== HUB_REPO6 && repoClass === "deployable") {
|
|
47853
48183
|
const rulesetRaw = await contentText(deps, repo, baseBranch, requiredProductRulesetRef);
|
|
47854
48184
|
productContexts = committedRequiredContexts(rulesetRaw);
|
|
47855
48185
|
const workflowCandidates = [.../* @__PURE__ */ new Set([
|
|
@@ -48093,7 +48423,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
48093
48423
|
detail: coverage.ok ? void 0 : `${coverage.reason} (branches: ${coverage.branches.join(", ")})`
|
|
48094
48424
|
});
|
|
48095
48425
|
}
|
|
48096
|
-
if (repo ===
|
|
48426
|
+
if (repo === HUB_REPO6) {
|
|
48097
48427
|
const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
|
|
48098
48428
|
const missing = requiredHubStatusChecks.filter((check) => !statusChecks.has(check));
|
|
48099
48429
|
checks.push({
|
|
@@ -48191,7 +48521,7 @@ function renderBootstrapVerifyReport(report) {
|
|
|
48191
48521
|
}
|
|
48192
48522
|
|
|
48193
48523
|
// src/bootstrap-seed-delivery.ts
|
|
48194
|
-
var
|
|
48524
|
+
var import_node_crypto15 = require("node:crypto");
|
|
48195
48525
|
init_github_client();
|
|
48196
48526
|
var SHA = /^[a-f0-9]{40}$/;
|
|
48197
48527
|
async function readSeedFile(repo, target, ref, client) {
|
|
@@ -48224,7 +48554,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
|
|
|
48224
48554
|
}
|
|
48225
48555
|
const baseSha = base?.object?.sha;
|
|
48226
48556
|
if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
|
|
48227
|
-
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);
|
|
48228
48558
|
const delivery = planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`);
|
|
48229
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;
|
|
48230
48560
|
return { ...scoped, baseSha, baseBranch };
|
|
@@ -48294,12 +48624,12 @@ var declaredProjectAdmins = (meta) => {
|
|
|
48294
48624
|
};
|
|
48295
48625
|
var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
48296
48626
|
async function gateRenderDiff(current, rendered) {
|
|
48297
|
-
const dir = (0,
|
|
48298
|
-
const before = (0,
|
|
48299
|
-
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");
|
|
48300
48630
|
try {
|
|
48301
|
-
(0,
|
|
48302
|
-
(0,
|
|
48631
|
+
(0, import_node_fs38.writeFileSync)(before, current);
|
|
48632
|
+
(0, import_node_fs38.writeFileSync)(after, rendered);
|
|
48303
48633
|
try {
|
|
48304
48634
|
return (await execFileP("git", ["diff", "--no-index", "--", before, after], { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
48305
48635
|
} catch (e) {
|
|
@@ -48308,7 +48638,7 @@ async function gateRenderDiff(current, rendered) {
|
|
|
48308
48638
|
throw e;
|
|
48309
48639
|
}
|
|
48310
48640
|
} finally {
|
|
48311
|
-
(0,
|
|
48641
|
+
(0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
|
|
48312
48642
|
}
|
|
48313
48643
|
}
|
|
48314
48644
|
function bootstrapTrainDoctorDeps() {
|
|
@@ -48321,6 +48651,90 @@ function bootstrapTrainDoctorDeps() {
|
|
|
48321
48651
|
}
|
|
48322
48652
|
};
|
|
48323
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
|
+
}
|
|
48324
48738
|
function registerBootstrapCommands(program3) {
|
|
48325
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) => {
|
|
48326
48740
|
if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
|
|
@@ -48368,13 +48782,13 @@ function registerBootstrapCommands(program3) {
|
|
|
48368
48782
|
})),
|
|
48369
48783
|
pushAllowlistOwners: pushAllowlistAuthority(verifyOwners, declaredProjectAdmins(meta)),
|
|
48370
48784
|
pushAllowlistOwnersRead: verifyOwnersRead,
|
|
48371
|
-
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,
|
|
48372
48786
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
48373
48787
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
48374
48788
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
48375
48789
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
48376
48790
|
// sanction, which is the pre-#3664 behaviour.
|
|
48377
|
-
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,
|
|
48378
48792
|
requiredGcpApis: (() => {
|
|
48379
48793
|
const v = meta?.requiredGcpApis;
|
|
48380
48794
|
if (Array.isArray(v)) return v;
|
|
@@ -48452,14 +48866,14 @@ function registerBootstrapCommands(program3) {
|
|
|
48452
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 () => {
|
|
48453
48867
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
48454
48868
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
48455
|
-
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`);
|
|
48456
48870
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
48457
48871
|
if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
|
|
48458
|
-
const manifest = loadBootstrapSeeds((0,
|
|
48872
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
48459
48873
|
const hubContents = /* @__PURE__ */ new Map();
|
|
48460
48874
|
for (const s of manifest.seeds) {
|
|
48461
48875
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
48462
|
-
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);
|
|
48463
48877
|
}
|
|
48464
48878
|
let targets;
|
|
48465
48879
|
let classOf = (_repo) => "deployable";
|
|
@@ -48716,24 +49130,24 @@ function registerBootstrapCommands(program3) {
|
|
|
48716
49130
|
return;
|
|
48717
49131
|
}
|
|
48718
49132
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
48719
|
-
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`);
|
|
48720
49134
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
48721
49135
|
if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
|
|
48722
|
-
const manifest = loadBootstrapSeeds((0,
|
|
49136
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
48723
49137
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
48724
49138
|
const slug = parsedRepo.slug;
|
|
48725
49139
|
const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
|
|
48726
49140
|
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
48727
|
-
const readFile10 = (p) => (0,
|
|
49141
|
+
const readFile10 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
|
|
48728
49142
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
48729
49143
|
const putSeed = async (target, content, ref, sha) => {
|
|
48730
|
-
const tmp = (0,
|
|
48731
|
-
(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");
|
|
48732
49146
|
try {
|
|
48733
49147
|
await gh2(contentPutInputArgs(repo, target, tmp));
|
|
48734
49148
|
} finally {
|
|
48735
49149
|
try {
|
|
48736
|
-
(0,
|
|
49150
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
48737
49151
|
} catch {
|
|
48738
49152
|
}
|
|
48739
49153
|
}
|
|
@@ -49367,13 +49781,14 @@ LIVE apply to ${repo}:
|
|
|
49367
49781
|
if (deferredFailures.length) return failGraceful(`bootstrap apply: incomplete \u2014 every other step ran:
|
|
49368
49782
|
${deferredFailures.join("\n ")}`);
|
|
49369
49783
|
});
|
|
49370
|
-
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"));
|
|
49371
49786
|
const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
|
|
49372
49787
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
49373
|
-
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`);
|
|
49374
49789
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
49375
49790
|
if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
49376
|
-
const manifest = loadBootstrapSeeds((0,
|
|
49791
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
49377
49792
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
49378
49793
|
if (!o.target) {
|
|
49379
49794
|
return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
|
|
@@ -49382,59 +49797,11 @@ LIVE apply to ${repo}:
|
|
|
49382
49797
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
49383
49798
|
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
|
|
49384
49799
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
49385
|
-
if (!seed.managedBlock && !(0,
|
|
49386
|
-
const hubContent = seed.managedBlock ? null : (0,
|
|
49387
|
-
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;
|
|
49388
49803
|
const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
|
|
49389
|
-
const
|
|
49390
|
-
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
49391
|
-
if (!projects || projects.length === 0) {
|
|
49392
|
-
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)");
|
|
49393
|
-
}
|
|
49394
|
-
const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
|
|
49395
|
-
let independentCount = rosterRepos.length;
|
|
49396
|
-
if ((0, import_node_fs37.existsSync)("projects.json")) {
|
|
49397
|
-
try {
|
|
49398
|
-
const local = JSON.parse((0, import_node_fs37.readFileSync)("projects.json", "utf8"));
|
|
49399
|
-
const localRepos = /* @__PURE__ */ new Set();
|
|
49400
|
-
for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
|
|
49401
|
-
const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
49402
|
-
if (full !== "mutmutco/mmi-hub") localRepos.add(full);
|
|
49403
|
-
}
|
|
49404
|
-
if (localRepos.size > 0) independentCount = localRepos.size;
|
|
49405
|
-
} catch {
|
|
49406
|
-
}
|
|
49407
|
-
}
|
|
49408
|
-
try {
|
|
49409
|
-
assertPropagationCoverage(rosterRepos.length, independentCount);
|
|
49410
|
-
} catch (e) {
|
|
49411
|
-
return fail(e.message);
|
|
49412
|
-
}
|
|
49413
|
-
const bySlugMeta = new Map(projects.flatMap((p) => (p.repos ?? []).map((r) => [(r.includes("/") ? r : `mutmutco/${r}`).toLowerCase(), p])));
|
|
49414
|
-
const classOf = (repo) => bySlugMeta.get(repo.toLowerCase())?.class ?? "deployable";
|
|
49415
|
-
const inClassRepos = rosterRepos.filter((repo) => seed.classes.includes(classOf(repo)));
|
|
49416
|
-
const classPriority = [.../* @__PURE__ */ new Set(["deployable", ...seed.classes])].filter((c) => seed.classes.includes(c));
|
|
49417
|
-
let canaryProject = null;
|
|
49418
|
-
for (const cls of classPriority) {
|
|
49419
|
-
const inClass = projects.filter((p) => p.seedCanary === true && (p.class ?? "deployable") === cls);
|
|
49420
|
-
if (inClass.length === 0) continue;
|
|
49421
|
-
if (inClass.length > 1) {
|
|
49422
|
-
const slugs = inClass.map((p) => (p.repos ?? [])[0]?.split("/").pop() ?? "?").join(", ");
|
|
49423
|
-
return fail(
|
|
49424
|
-
`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`
|
|
49425
|
-
);
|
|
49426
|
-
}
|
|
49427
|
-
canaryProject = inClass[0];
|
|
49428
|
-
break;
|
|
49429
|
-
}
|
|
49430
|
-
if (!canaryProject) {
|
|
49431
|
-
return fail(
|
|
49432
|
-
`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)`
|
|
49433
|
-
);
|
|
49434
|
-
}
|
|
49435
|
-
const canarySlug = (canaryProject.repos ?? [])[0]?.split("/").pop()?.toLowerCase() ?? null;
|
|
49436
|
-
const canaryFullRepo = (canaryProject.repos ?? [])[0];
|
|
49437
|
-
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}'`);
|
|
49438
49805
|
const repos = inClassRepos.map((repo) => {
|
|
49439
49806
|
const slug = repo.split("/").pop().toLowerCase();
|
|
49440
49807
|
const waiver = seed.waivers?.[slug];
|
|
@@ -49551,7 +49918,7 @@ LIVE apply to ${repo}:
|
|
|
49551
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)");
|
|
49552
49919
|
const headSha = seedSource.sha;
|
|
49553
49920
|
const sourceBlob = (await execGitForSeedSource(["rev-parse", `${headSha}:${seedSourcePath(seed)}`])).trim();
|
|
49554
|
-
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;
|
|
49555
49922
|
for (const rec of plan.records) {
|
|
49556
49923
|
if (rec.action !== "open-pr") continue;
|
|
49557
49924
|
const baseBranch = classOf(rec.repo) === "content" ? "main" : "development";
|
|
@@ -49573,15 +49940,15 @@ LIVE apply to ${repo}:
|
|
|
49573
49940
|
} catch {
|
|
49574
49941
|
existingSha = void 0;
|
|
49575
49942
|
}
|
|
49576
|
-
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`);
|
|
49577
49944
|
const desiredContent = desiredByRepo.get(rec.repo);
|
|
49578
49945
|
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
49579
|
-
(0,
|
|
49946
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha, runbookUnaffected)), "utf8");
|
|
49580
49947
|
try {
|
|
49581
49948
|
await gh2(contentPutInputArgs(rec.repo, seed.target, tmp));
|
|
49582
49949
|
} finally {
|
|
49583
49950
|
try {
|
|
49584
|
-
(0,
|
|
49951
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
49585
49952
|
} catch {
|
|
49586
49953
|
}
|
|
49587
49954
|
}
|
|
@@ -49650,10 +50017,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
49650
50017
|
return fail(`bootstrap rollback: ${e.message}`);
|
|
49651
50018
|
}
|
|
49652
50019
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
49653
|
-
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)`);
|
|
49654
50021
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
49655
50022
|
if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
|
|
49656
|
-
const manifest = loadBootstrapSeeds((0,
|
|
50023
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
49657
50024
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
49658
50025
|
if (!o.target) {
|
|
49659
50026
|
return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
|
|
@@ -49670,10 +50037,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
49670
50037
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
49671
50038
|
let candidates;
|
|
49672
50039
|
if (o.record) {
|
|
49673
|
-
if (!(0,
|
|
50040
|
+
if (!(0, import_node_fs38.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
|
|
49674
50041
|
let parsed;
|
|
49675
50042
|
try {
|
|
49676
|
-
parsed = JSON.parse((0,
|
|
50043
|
+
parsed = JSON.parse((0, import_node_fs38.readFileSync)(o.record, "utf8"));
|
|
49677
50044
|
} catch (e) {
|
|
49678
50045
|
return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
|
|
49679
50046
|
}
|
|
@@ -49750,13 +50117,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
49750
50117
|
} catch {
|
|
49751
50118
|
existingSha = void 0;
|
|
49752
50119
|
}
|
|
49753
|
-
const tmp = (0,
|
|
49754
|
-
(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");
|
|
49755
50122
|
try {
|
|
49756
50123
|
await gh2(contentPutInputArgs(repo, seed.target, tmp));
|
|
49757
50124
|
} finally {
|
|
49758
50125
|
try {
|
|
49759
|
-
(0,
|
|
50126
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
49760
50127
|
} catch {
|
|
49761
50128
|
}
|
|
49762
50129
|
}
|
|
@@ -50069,7 +50436,7 @@ init_github_client();
|
|
|
50069
50436
|
init_cli_shared();
|
|
50070
50437
|
|
|
50071
50438
|
// src/suite-status.ts
|
|
50072
|
-
var
|
|
50439
|
+
var import_node_crypto16 = require("node:crypto");
|
|
50073
50440
|
var SUITE_REPO = "mutmutco/MM-Work";
|
|
50074
50441
|
var REPOS = {
|
|
50075
50442
|
"mmi-scout": "mutmutco/MMI-Scout",
|
|
@@ -50097,7 +50464,7 @@ function parseSuiteMapping(body) {
|
|
|
50097
50464
|
return void 0;
|
|
50098
50465
|
}
|
|
50099
50466
|
}
|
|
50100
|
-
var sha256 = (b) => (0,
|
|
50467
|
+
var sha256 = (b) => (0, import_node_crypto16.createHash)("sha256").update(b).digest("hex");
|
|
50101
50468
|
async function proveBundle(url, tag, fetchUrl) {
|
|
50102
50469
|
const get = async (u) => {
|
|
50103
50470
|
try {
|
|
@@ -50352,9 +50719,9 @@ async function waitSuiteDev(deps, opts) {
|
|
|
50352
50719
|
}
|
|
50353
50720
|
|
|
50354
50721
|
// src/suite-train.ts
|
|
50355
|
-
var
|
|
50356
|
-
var
|
|
50357
|
-
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");
|
|
50358
50725
|
|
|
50359
50726
|
// src/kit-version.ts
|
|
50360
50727
|
var parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(typeof v === "string" ? v.trim() : "");
|
|
@@ -50439,7 +50806,7 @@ function suiteReleaseTag(rc) {
|
|
|
50439
50806
|
function readSuiteCut(path2) {
|
|
50440
50807
|
let raw;
|
|
50441
50808
|
try {
|
|
50442
|
-
raw = (0,
|
|
50809
|
+
raw = (0, import_node_fs39.readFileSync)(path2, "utf8");
|
|
50443
50810
|
} catch (e) {
|
|
50444
50811
|
if (e.code === "ENOENT") return void 0;
|
|
50445
50812
|
throw e;
|
|
@@ -50455,11 +50822,11 @@ function readSuiteCut(path2) {
|
|
|
50455
50822
|
return record2;
|
|
50456
50823
|
}
|
|
50457
50824
|
function writeSuiteCut(path2, record2) {
|
|
50458
|
-
(0,
|
|
50459
|
-
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`;
|
|
50460
50827
|
try {
|
|
50461
|
-
(0,
|
|
50462
|
-
(0,
|
|
50828
|
+
(0, import_node_fs39.writeFileSync)(temp, JSON.stringify(record2, null, 2), { flag: "wx" });
|
|
50829
|
+
(0, import_node_fs39.renameSync)(temp, path2);
|
|
50463
50830
|
} catch (e) {
|
|
50464
50831
|
throw new Error(`suite cut record write failed at ${path2}: ${e.message}; stop, verify the file before retrying`);
|
|
50465
50832
|
}
|
|
@@ -50508,25 +50875,25 @@ async function suiteMarkAbandoned(deps, ownerGo = false) {
|
|
|
50508
50875
|
return tags.map((t) => `${t.repo}@${t.tag}`);
|
|
50509
50876
|
}
|
|
50510
50877
|
async function retireFailedLeadRcLedger(deps, ownerGo) {
|
|
50511
|
-
if (!(0,
|
|
50878
|
+
if (!(0, import_node_fs39.existsSync)(deps.ledgerPath)) return void 0;
|
|
50512
50879
|
return withLedgerLock(deps.ledgerPath, async (fence) => {
|
|
50513
|
-
const ledger = parseReleaseLedger((0,
|
|
50880
|
+
const ledger = parseReleaseLedger((0, import_node_fs39.readFileSync)(deps.ledgerPath, "utf8"));
|
|
50514
50881
|
const states = Object.values(ledger.phases).map((p) => p.state);
|
|
50515
50882
|
if (ledger.repo !== SUITE_REPO || ledger.lane !== "rcand" || ledger.phases.promotion.state === "complete" || !states.includes("failed")) return void 0;
|
|
50516
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`);
|
|
50517
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`);
|
|
50518
|
-
const archive = (0,
|
|
50519
|
-
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`);
|
|
50520
50887
|
await deps.markAbandoned(SUITE_REPO, ledger.tag, ledger.tagSha);
|
|
50521
|
-
await fence(() => (0,
|
|
50888
|
+
await fence(() => (0, import_node_fs39.renameSync)(deps.ledgerPath, archive));
|
|
50522
50889
|
return `${ledger.tag} (ledger archived to ${archive})`;
|
|
50523
50890
|
});
|
|
50524
50891
|
}
|
|
50525
50892
|
function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Date()) {
|
|
50526
50893
|
const record2 = abandonableCut(recordPath, ownerGo);
|
|
50527
50894
|
const archive = `${recordPath}.abandoned-${record2.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
|
|
50528
|
-
if ((0,
|
|
50529
|
-
(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);
|
|
50530
50897
|
return { archive, orphanTags: record2.members.filter((m) => m.tag).map((m) => `${m.repo}@${m.tag}`) };
|
|
50531
50898
|
}
|
|
50532
50899
|
async function suiteRestoreMainPins(deps) {
|
|
@@ -50613,12 +50980,12 @@ async function suiteRcandRun(deps, opts) {
|
|
|
50613
50980
|
}
|
|
50614
50981
|
if ((record2?.announcement === "sent" || record2?.announcement === "skipped") && record2.leadReleased) {
|
|
50615
50982
|
const archive = `${deps.recordPath}.${record2.leadRcTag}.json`;
|
|
50616
|
-
if ((0,
|
|
50617
|
-
(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);
|
|
50618
50985
|
record2 = void 0;
|
|
50619
50986
|
}
|
|
50620
50987
|
if (record2 && !record2.pinPr && !record2.leadRcTag && record2.members.every((m) => !m.tag)) {
|
|
50621
|
-
(0,
|
|
50988
|
+
(0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
|
|
50622
50989
|
record2 = void 0;
|
|
50623
50990
|
}
|
|
50624
50991
|
if (!record2) {
|
|
@@ -50672,7 +51039,7 @@ async function suiteRcandRun(deps, opts) {
|
|
|
50672
51039
|
quiet.push(member);
|
|
50673
51040
|
}
|
|
50674
51041
|
if (record2.members.every((m) => m.carried || quiet.includes(m)) && await deps.contains(SUITE_REPO, record2.leadDevelopment, record2.leadPreviousRc)) {
|
|
50675
|
-
(0,
|
|
51042
|
+
(0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
|
|
50676
51043
|
throw new Error("nothing to promote: neither MM-Work nor any module has new development commits since its rc; no suite cut started");
|
|
50677
51044
|
}
|
|
50678
51045
|
for (const member of quiet) {
|
|
@@ -50690,7 +51057,7 @@ async function suiteRcandRun(deps, opts) {
|
|
|
50690
51057
|
}
|
|
50691
51058
|
if (refusals.length) {
|
|
50692
51059
|
const untouched = record2.members.every((m) => !m.tag) && !record2.pinPr;
|
|
50693
|
-
if (untouched) (0,
|
|
51060
|
+
if (untouched) (0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
|
|
50694
51061
|
throw new Error(`module rc serving is not ready; ${untouched ? "no suite cut started" : "no new tag was cut"}:
|
|
50695
51062
|
${refusals.join("\n")}
|
|
50696
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).`);
|
|
@@ -50882,11 +51249,11 @@ Each member's live release must serve <data>/main/module.js at its main pin path
|
|
|
50882
51249
|
}
|
|
50883
51250
|
return record2;
|
|
50884
51251
|
}
|
|
50885
|
-
var hotfixRecordPath = (deps) => (0,
|
|
51252
|
+
var hotfixRecordPath = (deps) => (0, import_node_path36.join)((0, import_node_path36.dirname)(deps.recordPath), "hotfix.json");
|
|
50886
51253
|
function readSuiteHotfix(path2) {
|
|
50887
51254
|
let raw;
|
|
50888
51255
|
try {
|
|
50889
|
-
raw = (0,
|
|
51256
|
+
raw = (0, import_node_fs39.readFileSync)(path2, "utf8");
|
|
50890
51257
|
} catch (e) {
|
|
50891
51258
|
if (e.code === "ENOENT") return void 0;
|
|
50892
51259
|
throw e;
|
|
@@ -50929,7 +51296,7 @@ async function suiteHotfix(deps, opts) {
|
|
|
50929
51296
|
if (completed) record2 = void 0;
|
|
50930
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`);
|
|
50931
51298
|
if (!record2?.tag) await deps.validateHotfixSummary();
|
|
50932
|
-
if (completed) (0,
|
|
51299
|
+
if (completed) (0, import_node_fs39.renameSync)(path2, `${path2}.${completed.leadTag}.json`);
|
|
50933
51300
|
const rec = record2 ?? { schema: "mmi-suite-hotfix/v1", member: found.id, repo: found.repo, from: opts.from, ...opts.ack?.length ? { ack: opts.ack } : {} };
|
|
50934
51301
|
const save = () => writeSuiteHotfix(path2, rec);
|
|
50935
51302
|
save();
|
|
@@ -51197,9 +51564,9 @@ async function runRcDataSwitch(deps, opts) {
|
|
|
51197
51564
|
|
|
51198
51565
|
// src/suite-train-io.ts
|
|
51199
51566
|
var import_node_child_process18 = require("node:child_process");
|
|
51200
|
-
var import_node_path48 = require("node:path");
|
|
51201
|
-
var import_node_fs51 = require("node:fs");
|
|
51202
51567
|
var import_node_path49 = require("node:path");
|
|
51568
|
+
var import_node_fs52 = require("node:fs");
|
|
51569
|
+
var import_node_path50 = require("node:path");
|
|
51203
51570
|
init_cli_shared();
|
|
51204
51571
|
|
|
51205
51572
|
// src/command-register-train-operations.ts
|
|
@@ -51552,9 +51919,9 @@ A hotfix branch is cut from origin/main and carries ONLY the fixes --from names,
|
|
|
51552
51919
|
async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
|
|
51553
51920
|
if (sources.length === 0) return { failures: [], skipped: [] };
|
|
51554
51921
|
const { mkdtemp: mkdtemp2, rm: rm2 } = await import("node:fs/promises");
|
|
51555
|
-
const { tmpdir:
|
|
51922
|
+
const { tmpdir: tmpdir13 } = await import("node:os");
|
|
51556
51923
|
const { join: join54 } = await import("node:path");
|
|
51557
|
-
const dir = await mkdtemp2(join54(
|
|
51924
|
+
const dir = await mkdtemp2(join54(tmpdir13(), "mmi-hotfix-preflight-"));
|
|
51558
51925
|
const wt = { run: (file, args) => deps.run(file, file === "git" ? ["-C", dir, ...args] : args) };
|
|
51559
51926
|
const failures = [];
|
|
51560
51927
|
const skipped = [];
|
|
@@ -52891,12 +53258,75 @@ function checkHotfixCarries(options) {
|
|
|
52891
53258
|
}
|
|
52892
53259
|
|
|
52893
53260
|
// src/merge-cleanup.ts
|
|
52894
|
-
var
|
|
52895
|
-
var
|
|
52896
|
-
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");
|
|
52897
53264
|
init_cli_shared();
|
|
52898
53265
|
init_github_client();
|
|
52899
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
|
+
|
|
52900
53330
|
// src/board-advance.ts
|
|
52901
53331
|
function repoOf2(ref) {
|
|
52902
53332
|
if (!ref || typeof ref !== "object") return void 0;
|
|
@@ -53117,10 +53547,10 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
53117
53547
|
// only for PRs into the default branch, so a PR into rc/main (a train or hotfix PR) never falls back.
|
|
53118
53548
|
fallback: {
|
|
53119
53549
|
fetchClaimed: async () => {
|
|
53120
|
-
const { stdout } = await
|
|
53550
|
+
const { stdout } = await ghWithAppOn401("PR body read", ["pr", "view", prNumber, ...repoArgs, "--json", "body,baseRefName"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53121
53551
|
const { body, baseRefName } = JSON.parse(stdout || "null") ?? {};
|
|
53122
53552
|
if (typeof body !== "string" || typeof baseRefName !== "string") throw new Error("gh returned no PR body or base branch");
|
|
53123
|
-
const repoView = await
|
|
53553
|
+
const repoView = await ghWithAppOn401("default branch read", ["repo", "view", repo, "--json", "defaultBranchRef"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53124
53554
|
const defaultBranch = JSON.parse(repoView.stdout || "null")?.defaultBranchRef?.name;
|
|
53125
53555
|
if (typeof defaultBranch !== "string" || !defaultBranch) throw new Error("gh returned no default branch");
|
|
53126
53556
|
if (baseRefName !== defaultBranch) return [];
|
|
@@ -53128,7 +53558,7 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
53128
53558
|
},
|
|
53129
53559
|
closeIssue: async (n) => {
|
|
53130
53560
|
try {
|
|
53131
|
-
const view = await
|
|
53561
|
+
const view = await ghWithAppOn401("issue state read", ["issue", "view", String(n), "--repo", repo, "--json", "state"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53132
53562
|
if (JSON.parse(view.stdout || "null")?.state !== "OPEN") return { state: "already-closed" };
|
|
53133
53563
|
await execFileP("gh", [
|
|
53134
53564
|
"issue",
|
|
@@ -53148,7 +53578,7 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
53148
53578
|
}
|
|
53149
53579
|
},
|
|
53150
53580
|
fetchClosingIssues: async () => {
|
|
53151
|
-
const { stdout } = await
|
|
53581
|
+
const { stdout } = await ghWithAppOn401("closingIssuesReferences read", ["pr", "view", prNumber, ...repoArgs, "--json", "closingIssuesReferences"], { timeout: GC_GH_TIMEOUT_MS });
|
|
53152
53582
|
const parsed = JSON.parse(stdout || "null");
|
|
53153
53583
|
if (!parsed || typeof parsed !== "object" || !("closingIssuesReferences" in parsed)) {
|
|
53154
53584
|
throw new Error("gh returned no closingIssuesReferences field for this PR");
|
|
@@ -53237,13 +53667,13 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
|
|
|
53237
53667
|
return rewritten === base ? null : rewritten;
|
|
53238
53668
|
}
|
|
53239
53669
|
function writeSquashBodyFile(body) {
|
|
53240
|
-
const dir = (0,
|
|
53241
|
-
const path2 = (0,
|
|
53242
|
-
(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}
|
|
53243
53673
|
`, "utf8");
|
|
53244
53674
|
return { path: path2, cleanup: () => {
|
|
53245
53675
|
try {
|
|
53246
|
-
(0,
|
|
53676
|
+
(0, import_node_fs40.rmSync)(dir, { recursive: true, force: true });
|
|
53247
53677
|
} catch {
|
|
53248
53678
|
}
|
|
53249
53679
|
} };
|
|
@@ -53510,9 +53940,9 @@ function hubReleaseCiRun(env) {
|
|
|
53510
53940
|
}
|
|
53511
53941
|
|
|
53512
53942
|
// src/release-custody.ts
|
|
53513
|
-
var
|
|
53514
|
-
var
|
|
53515
|
-
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");
|
|
53516
53946
|
init_github_client();
|
|
53517
53947
|
|
|
53518
53948
|
// src/release-custody-bootstrap.ts
|
|
@@ -53733,7 +54163,7 @@ var ReleaseCustodyError = class extends Error {
|
|
|
53733
54163
|
this.name = "ReleaseCustodyError";
|
|
53734
54164
|
}
|
|
53735
54165
|
};
|
|
53736
|
-
var custodyDigest = (value) => (0,
|
|
54166
|
+
var custodyDigest = (value) => (0, import_node_crypto18.createHash)("sha256").update(value).digest("hex");
|
|
53737
54167
|
function parseCustody(raw, repo) {
|
|
53738
54168
|
const s = JSON.parse(raw);
|
|
53739
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) {
|
|
@@ -53821,7 +54251,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
|
|
|
53821
54251
|
throw new ReleaseCustodyError(`previous controller run ${found.state.controller.runId} attempt ${found.state.controller.runAttempt} is not proven terminal`);
|
|
53822
54252
|
}
|
|
53823
54253
|
let state = {
|
|
53824
|
-
...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() },
|
|
53825
54255
|
revision: (found.state?.revision ?? 0) + 1,
|
|
53826
54256
|
controller
|
|
53827
54257
|
};
|
|
@@ -53882,7 +54312,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
|
|
|
53882
54312
|
proof: { kind: "ledger-lifecycle", controller: state.controller }
|
|
53883
54313
|
};
|
|
53884
54314
|
}
|
|
53885
|
-
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 };
|
|
53886
54316
|
});
|
|
53887
54317
|
}
|
|
53888
54318
|
async preserveLocal(raw) {
|
|
@@ -53982,7 +54412,7 @@ function reclassifySupersededArchives(state) {
|
|
|
53982
54412
|
}
|
|
53983
54413
|
function localRaw(path2) {
|
|
53984
54414
|
try {
|
|
53985
|
-
return (0,
|
|
54415
|
+
return (0, import_node_fs41.readFileSync)(path2, "utf8");
|
|
53986
54416
|
} catch (e) {
|
|
53987
54417
|
if (e.code === "ENOENT") return null;
|
|
53988
54418
|
throw e;
|
|
@@ -53991,14 +54421,14 @@ function localRaw(path2) {
|
|
|
53991
54421
|
function hydrateLocal(path2, raw) {
|
|
53992
54422
|
const previous = localRaw(path2);
|
|
53993
54423
|
if (previous === raw) return;
|
|
53994
|
-
(0,
|
|
54424
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path38.dirname)(path2), { recursive: true });
|
|
53995
54425
|
if (previous !== null) {
|
|
53996
54426
|
const ledger = parseReleaseLedger(previous);
|
|
53997
|
-
const archive = (0,
|
|
53998
|
-
if (!(0,
|
|
53999
|
-
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)}`);
|
|
54000
54430
|
}
|
|
54001
|
-
if (raw !== null) (0,
|
|
54431
|
+
if (raw !== null) (0, import_node_fs41.writeFileSync)(path2, raw, "utf8");
|
|
54002
54432
|
}
|
|
54003
54433
|
async function commandIdentity(file, args, run, repo) {
|
|
54004
54434
|
if (file === "git" && args[0] === "push") {
|
|
@@ -54108,7 +54538,7 @@ function withHubReleaseCustody(deps) {
|
|
|
54108
54538
|
if (!authority.ok || !authority.train || authority.authenticatedDelegation !== true) {
|
|
54109
54539
|
throw new ReleaseCustodyError("repository custody requires the server-verified owner-triggered train session");
|
|
54110
54540
|
}
|
|
54111
|
-
const controller = { ...runIdentity, token: (0,
|
|
54541
|
+
const controller = { ...runIdentity, token: (0, import_node_crypto18.randomUUID)() };
|
|
54112
54542
|
const raw = localRaw(path2);
|
|
54113
54543
|
const requiredContexts = () => discoverRequiredCheckContexts(
|
|
54114
54544
|
deps,
|
|
@@ -54188,8 +54618,8 @@ function withHubReleaseCustody(deps) {
|
|
|
54188
54618
|
}
|
|
54189
54619
|
|
|
54190
54620
|
// src/project-info-sync.ts
|
|
54191
|
-
var
|
|
54192
|
-
var
|
|
54621
|
+
var import_node_fs42 = require("node:fs");
|
|
54622
|
+
var import_node_path39 = require("node:path");
|
|
54193
54623
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
54194
54624
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
54195
54625
|
projectV2 { id }
|
|
@@ -54236,14 +54666,14 @@ function sharedName(entries, fallback) {
|
|
|
54236
54666
|
}
|
|
54237
54667
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
54238
54668
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
54239
|
-
const readmePath = (0,
|
|
54240
|
-
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`);
|
|
54241
54671
|
const entries = entriesFor(project2, projects);
|
|
54242
54672
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
54243
54673
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
54244
54674
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
54245
54675
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
54246
|
-
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)}.`;
|
|
54247
54677
|
const lines2 = [
|
|
54248
54678
|
`# ${projectName}`,
|
|
54249
54679
|
"",
|
|
@@ -54262,8 +54692,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
54262
54692
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
54263
54693
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
54264
54694
|
const orgDocs = [
|
|
54265
|
-
(0,
|
|
54266
|
-
(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)` : ""
|
|
54267
54697
|
].filter(Boolean);
|
|
54268
54698
|
if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
|
|
54269
54699
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
|
|
@@ -54285,8 +54715,8 @@ async function syncProjectInfo(plan, client, apply) {
|
|
|
54285
54715
|
}
|
|
54286
54716
|
|
|
54287
54717
|
// src/train-commands.ts
|
|
54288
|
-
var
|
|
54289
|
-
var
|
|
54718
|
+
var import_node_fs43 = require("node:fs");
|
|
54719
|
+
var import_node_path40 = require("node:path");
|
|
54290
54720
|
init_cli_shared();
|
|
54291
54721
|
init_clean_exit();
|
|
54292
54722
|
init_client_version();
|
|
@@ -54301,7 +54731,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
54301
54731
|
}
|
|
54302
54732
|
function readRepoVersion() {
|
|
54303
54733
|
try {
|
|
54304
|
-
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;
|
|
54305
54735
|
} catch {
|
|
54306
54736
|
return void 0;
|
|
54307
54737
|
}
|
|
@@ -54412,7 +54842,7 @@ function releaseProgressVerdict(status, subject = "Release") {
|
|
|
54412
54842
|
|
|
54413
54843
|
// src/suite-pin.ts
|
|
54414
54844
|
var import_promises6 = require("node:fs/promises");
|
|
54415
|
-
var
|
|
54845
|
+
var import_node_path41 = require("node:path");
|
|
54416
54846
|
var SuitePinError = class _SuitePinError extends Error {
|
|
54417
54847
|
diagnostic;
|
|
54418
54848
|
constructor(repo, tag, error) {
|
|
@@ -54480,8 +54910,8 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
54480
54910
|
if (input.carriedOnly) return { status: "unchanged" };
|
|
54481
54911
|
throw new Error("pins already match without a merged cut PR \u2014 provenance unverified");
|
|
54482
54912
|
}
|
|
54483
|
-
await (0, import_promises6.mkdir)((0,
|
|
54484
|
-
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-"));
|
|
54485
54915
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
54486
54916
|
await run("git", ["clone", "--quiet", "--branch", prs.length ? branch : "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
54487
54917
|
if (prs.length) {
|
|
@@ -54493,7 +54923,7 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
54493
54923
|
await run("git", ["switch", "-c", branch]);
|
|
54494
54924
|
}
|
|
54495
54925
|
for (const stage of ["rc", "main"]) {
|
|
54496
|
-
const path2 = (0,
|
|
54926
|
+
const path2 = (0, import_node_path41.join)(scratch, `${stage}-pins.json`);
|
|
54497
54927
|
await (0, import_promises6.writeFile)(path2, JSON.stringify(input.pins.map((p) => ({ module: p.module, url: p[stage] }))));
|
|
54498
54928
|
await run("node", ["scripts/pin-modules.mjs", "--stage", stage, "--pins", path2]);
|
|
54499
54929
|
}
|
|
@@ -54546,12 +54976,12 @@ async function openSuiteRestorePinPr(deps, input) {
|
|
|
54546
54976
|
}
|
|
54547
54977
|
});
|
|
54548
54978
|
if (!todo.length) return void 0;
|
|
54549
|
-
await (0, import_promises6.mkdir)((0,
|
|
54550
|
-
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-"));
|
|
54551
54981
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
54552
54982
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
54553
54983
|
await run("git", ["switch", "-c", branch]);
|
|
54554
|
-
const path2 = (0,
|
|
54984
|
+
const path2 = (0, import_node_path41.join)(scratch, "main-pins.json");
|
|
54555
54985
|
await (0, import_promises6.writeFile)(path2, JSON.stringify(todo.map((p) => ({ module: p.module, url: p.prior }))));
|
|
54556
54986
|
await run("node", ["scripts/pin-modules.mjs", "--stage", "main", "--pins", path2]);
|
|
54557
54987
|
await run("git", ["add", "seed/modules.json"]);
|
|
@@ -54625,8 +55055,8 @@ async function openSuitePinPr(deps, input) {
|
|
|
54625
55055
|
if (newer.length) return { status: "superseded", pr: newer[0].url, url: pin };
|
|
54626
55056
|
const open2 = onBranch.find((p) => p.state === "OPEN");
|
|
54627
55057
|
if (open2 && tagOf(open2.title ?? "") === tag) return { status: "pending", pr: open2.url, url: pin };
|
|
54628
|
-
await (0, import_promises6.mkdir)((0,
|
|
54629
|
-
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-"));
|
|
54630
55060
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
54631
55061
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
54632
55062
|
await run("git", ["switch", "-c", branch]);
|
|
@@ -54675,7 +55105,7 @@ function prTitleRefusal(title) {
|
|
|
54675
55105
|
|
|
54676
55106
|
// src/command-register-collaboration.ts
|
|
54677
55107
|
var import_node_child_process17 = require("node:child_process");
|
|
54678
|
-
var
|
|
55108
|
+
var import_node_fs51 = require("node:fs");
|
|
54679
55109
|
var import_promises8 = require("node:fs/promises");
|
|
54680
55110
|
init_clean_exit();
|
|
54681
55111
|
init_cli_shared();
|
|
@@ -54699,11 +55129,11 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
54699
55129
|
}
|
|
54700
55130
|
|
|
54701
55131
|
// src/command-register-collaboration.ts
|
|
54702
|
-
var
|
|
55132
|
+
var import_node_path48 = require("node:path");
|
|
54703
55133
|
init_house_map();
|
|
54704
55134
|
|
|
54705
55135
|
// src/issue-body.ts
|
|
54706
|
-
var
|
|
55136
|
+
var import_node_os23 = require("node:os");
|
|
54707
55137
|
init_error_codes();
|
|
54708
55138
|
var TextArgError = class extends Error {
|
|
54709
55139
|
constructor(message2, code, offendingFlag) {
|
|
@@ -54716,7 +55146,7 @@ var TextArgError = class extends Error {
|
|
|
54716
55146
|
offendingFlag;
|
|
54717
55147
|
};
|
|
54718
55148
|
function emptyStdinMessage(fileFlag) {
|
|
54719
|
-
if ((0,
|
|
55149
|
+
if ((0, import_node_os23.platform)() === "win32") {
|
|
54720
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)`;
|
|
54721
55151
|
}
|
|
54722
55152
|
return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
|
|
@@ -54816,8 +55246,8 @@ init_github_client();
|
|
|
54816
55246
|
init_github_quota();
|
|
54817
55247
|
|
|
54818
55248
|
// src/issue-commands.ts
|
|
54819
|
-
var
|
|
54820
|
-
var
|
|
55249
|
+
var import_node_fs44 = require("node:fs");
|
|
55250
|
+
var import_node_crypto19 = require("node:crypto");
|
|
54821
55251
|
init_cli_shared();
|
|
54822
55252
|
init_clean_exit();
|
|
54823
55253
|
init_error_codes();
|
|
@@ -55009,7 +55439,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
55009
55439
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
55010
55440
|
const patch = {};
|
|
55011
55441
|
let bodyChanged = false;
|
|
55012
|
-
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("") };
|
|
55013
55443
|
if (options.titleFile !== void 0) {
|
|
55014
55444
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
55015
55445
|
} else if (options.title !== void 0) {
|
|
@@ -55118,8 +55548,11 @@ async function closeIssue(client, options, deps = {}) {
|
|
|
55118
55548
|
}
|
|
55119
55549
|
const appWriter = await selectClaimWriteClient(client, deps.appClient);
|
|
55120
55550
|
if (reason === "completed" && options.evidence !== void 0) {
|
|
55121
|
-
|
|
55122
|
-
|
|
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
|
+
}
|
|
55123
55556
|
await appWriter.rest("POST", `repos/${repo}/issues/${parsed.number}/labels`, {
|
|
55124
55557
|
body: { labels: [CLOSED_MERGED_LOOP_STATE_LABEL] }
|
|
55125
55558
|
});
|
|
@@ -55291,7 +55724,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
55291
55724
|
const identity = `${spec.type}
|
|
55292
55725
|
${spec.title.trim()}
|
|
55293
55726
|
${spec.body ?? ""}`;
|
|
55294
|
-
const hash = (0,
|
|
55727
|
+
const hash = (0, import_node_crypto19.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
55295
55728
|
return `${batchKey}:${hash}`;
|
|
55296
55729
|
}
|
|
55297
55730
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -55673,7 +56106,7 @@ function extendCreateCommand(issue, batchAttach) {
|
|
|
55673
56106
|
if (opts.batch) {
|
|
55674
56107
|
let specs;
|
|
55675
56108
|
try {
|
|
55676
|
-
const raw = (0,
|
|
56109
|
+
const raw = (0, import_node_fs44.readFileSync)(opts.batch, "utf8");
|
|
55677
56110
|
specs = JSON.parse(raw);
|
|
55678
56111
|
if (!Array.isArray(specs)) {
|
|
55679
56112
|
const top = specs === null ? "null" : typeof specs === "object" ? `an object with keys: ${Object.keys(specs).join(", ") || "(none)"}` : `a ${typeof specs}`;
|
|
@@ -55750,8 +56183,8 @@ ${lines2}`, {
|
|
|
55750
56183
|
}
|
|
55751
56184
|
|
|
55752
56185
|
// src/post-merge-recon.ts
|
|
55753
|
-
var
|
|
55754
|
-
var
|
|
56186
|
+
var import_node_fs45 = require("node:fs");
|
|
56187
|
+
var import_node_path42 = require("node:path");
|
|
55755
56188
|
|
|
55756
56189
|
// src/cross-repo-filing-issue.ts
|
|
55757
56190
|
init_github_client();
|
|
@@ -55811,7 +56244,8 @@ function explicitSameOrgIssueUrls(text, prRepo, closingNumbers) {
|
|
|
55811
56244
|
}
|
|
55812
56245
|
return [...found.values()];
|
|
55813
56246
|
}
|
|
55814
|
-
async function reconcileCrossRepoFilingIssue(
|
|
56247
|
+
async function reconcileCrossRepoFilingIssue(rawClient, prRepo, prNumber) {
|
|
56248
|
+
const client = withAppOn401(rawClient, `cross-repo filing reconcile ${prRepo}#${prNumber}`);
|
|
55815
56249
|
try {
|
|
55816
56250
|
const pr = await client.rest("GET", `repos/${prRepo}/pulls/${prNumber}`);
|
|
55817
56251
|
if (!pr.merged_at) return { status: "not-applicable" };
|
|
@@ -55843,7 +56277,7 @@ ${pr.body ?? ""}`;
|
|
|
55843
56277
|
const issue = `${target.repo}#${target.number}`;
|
|
55844
56278
|
if (target.state === "CLOSED") return { status: "already-closed", issue, url: target.url };
|
|
55845
56279
|
const prUrl = pr.html_url ?? `https://github.com/${prRepo}/pull/${prNumber}`;
|
|
55846
|
-
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`, {
|
|
55847
56281
|
body: { body: `Resolved by merged PR ${prUrl}.
|
|
55848
56282
|
|
|
55849
56283
|
<!-- mmi-cross-repo-close:${prRepo}#${prNumber} -->` }
|
|
@@ -55920,16 +56354,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
55920
56354
|
}
|
|
55921
56355
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
55922
56356
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
55923
|
-
(0,
|
|
55924
|
-
(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)}
|
|
55925
56359
|
`, "utf8");
|
|
55926
56360
|
return path2;
|
|
55927
56361
|
}
|
|
55928
56362
|
function clearPostMergeReconRecovery(cwd, repo, pr) {
|
|
55929
56363
|
const path2 = postMergeReconStatePath(cwd, repo, pr);
|
|
55930
|
-
if (!(0,
|
|
56364
|
+
if (!(0, import_node_fs45.existsSync)(path2)) return;
|
|
55931
56365
|
try {
|
|
55932
|
-
(0,
|
|
56366
|
+
(0, import_node_fs45.unlinkSync)(path2);
|
|
55933
56367
|
} catch {
|
|
55934
56368
|
}
|
|
55935
56369
|
}
|
|
@@ -56405,9 +56839,9 @@ function registerPrLifecycleCommands(program3) {
|
|
|
56405
56839
|
|
|
56406
56840
|
// src/review-verdict.ts
|
|
56407
56841
|
var import_node_child_process14 = require("node:child_process");
|
|
56408
|
-
var
|
|
56409
|
-
var
|
|
56410
|
-
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");
|
|
56411
56845
|
init_cli_shared();
|
|
56412
56846
|
var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
|
|
56413
56847
|
var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
|
|
@@ -56572,15 +57006,15 @@ async function readPrIssueComments(number, repo) {
|
|
|
56572
57006
|
return comments;
|
|
56573
57007
|
}
|
|
56574
57008
|
async function postPrCommentFromFile(number, repo, body) {
|
|
56575
|
-
const dir = (0,
|
|
56576
|
-
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");
|
|
56577
57011
|
try {
|
|
56578
|
-
(0,
|
|
57012
|
+
(0, import_node_fs46.writeFileSync)(path2, body, "utf8");
|
|
56579
57013
|
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
56580
57014
|
return stdout.trim();
|
|
56581
57015
|
} finally {
|
|
56582
57016
|
try {
|
|
56583
|
-
(0,
|
|
57017
|
+
(0, import_node_fs46.rmSync)(dir, { recursive: true, force: true });
|
|
56584
57018
|
} catch {
|
|
56585
57019
|
}
|
|
56586
57020
|
}
|
|
@@ -56993,18 +57427,18 @@ function createPreparationDeps(input, io = {}) {
|
|
|
56993
57427
|
}
|
|
56994
57428
|
|
|
56995
57429
|
// src/worktree-residue-sweep.ts
|
|
56996
|
-
var
|
|
56997
|
-
var
|
|
57430
|
+
var import_node_fs50 = require("node:fs");
|
|
57431
|
+
var import_node_path47 = require("node:path");
|
|
56998
57432
|
|
|
56999
57433
|
// src/worktree-merge-cleanup.ts
|
|
57000
|
-
var
|
|
57001
|
-
var
|
|
57434
|
+
var import_node_fs49 = require("node:fs");
|
|
57435
|
+
var import_node_path46 = require("node:path");
|
|
57002
57436
|
init_cli_shared();
|
|
57003
57437
|
|
|
57004
57438
|
// src/stage-runner.ts
|
|
57005
57439
|
var import_node_child_process16 = require("node:child_process");
|
|
57006
|
-
var
|
|
57007
|
-
var
|
|
57440
|
+
var import_node_fs48 = require("node:fs");
|
|
57441
|
+
var import_node_path45 = require("node:path");
|
|
57008
57442
|
var import_node_net = require("node:net");
|
|
57009
57443
|
var import_node_util5 = require("node:util");
|
|
57010
57444
|
|
|
@@ -57014,8 +57448,8 @@ function normalizeEol2(s) {
|
|
|
57014
57448
|
}
|
|
57015
57449
|
|
|
57016
57450
|
// src/env-detection.ts
|
|
57017
|
-
var
|
|
57018
|
-
var
|
|
57451
|
+
var import_node_fs47 = require("node:fs");
|
|
57452
|
+
var import_node_path44 = require("node:path");
|
|
57019
57453
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
|
|
57020
57454
|
function isEnvBaseName(name) {
|
|
57021
57455
|
return name.toLowerCase() === ".env";
|
|
@@ -57025,7 +57459,7 @@ function findEnvFiles(root) {
|
|
|
57025
57459
|
const walk2 = (dir, rel) => {
|
|
57026
57460
|
let entries;
|
|
57027
57461
|
try {
|
|
57028
|
-
entries = (0,
|
|
57462
|
+
entries = (0, import_node_fs47.readdirSync)(dir, { withFileTypes: true });
|
|
57029
57463
|
} catch {
|
|
57030
57464
|
return;
|
|
57031
57465
|
}
|
|
@@ -57033,13 +57467,13 @@ function findEnvFiles(root) {
|
|
|
57033
57467
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
57034
57468
|
if (entry.isDirectory()) {
|
|
57035
57469
|
if (SKIPPED_DIRS.has(entry.name)) continue;
|
|
57036
|
-
walk2((0,
|
|
57470
|
+
walk2((0, import_node_path44.join)(dir, entry.name), relPath);
|
|
57037
57471
|
continue;
|
|
57038
57472
|
}
|
|
57039
57473
|
if (isEnvBaseName(entry.name)) {
|
|
57040
57474
|
let isDir = false;
|
|
57041
57475
|
try {
|
|
57042
|
-
isDir = (0,
|
|
57476
|
+
isDir = (0, import_node_fs47.lstatSync)((0, import_node_path44.join)(dir, entry.name)).isDirectory();
|
|
57043
57477
|
} catch {
|
|
57044
57478
|
}
|
|
57045
57479
|
if (!isDir) found.push(relPath);
|
|
@@ -57178,12 +57612,12 @@ var DOCKER_TIMEOUT_MS = 15e3;
|
|
|
57178
57612
|
function stageBash() {
|
|
57179
57613
|
if (process.platform !== "win32") return "bash";
|
|
57180
57614
|
const configured = process.env.SHELL;
|
|
57181
|
-
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;
|
|
57182
57616
|
const candidates = [
|
|
57183
|
-
(0,
|
|
57184
|
-
...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")] : []
|
|
57185
57619
|
];
|
|
57186
|
-
const bash = candidates.find((path2) => (0,
|
|
57620
|
+
const bash = candidates.find((path2) => (0, import_node_fs48.existsSync)(path2));
|
|
57187
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.");
|
|
57188
57622
|
return bash;
|
|
57189
57623
|
}
|
|
@@ -57321,7 +57755,7 @@ function appendForceRecreate(up) {
|
|
|
57321
57755
|
return `${up.trimEnd()} --force-recreate`;
|
|
57322
57756
|
}
|
|
57323
57757
|
function stageStatePath(cwd = process.cwd()) {
|
|
57324
|
-
return (0,
|
|
57758
|
+
return (0, import_node_path45.join)(cwd, "tmp", "stage", "state.json");
|
|
57325
57759
|
}
|
|
57326
57760
|
function normPath(path2) {
|
|
57327
57761
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -57538,8 +57972,8 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
57538
57972
|
}
|
|
57539
57973
|
function composeResolvesPort(cwd) {
|
|
57540
57974
|
if (process.env.PORT) return true;
|
|
57541
|
-
const envFile = (0,
|
|
57542
|
-
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");
|
|
57543
57977
|
}
|
|
57544
57978
|
function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
57545
57979
|
const composeProject = config.env?.COMPOSE_PROJECT_NAME ?? vaultEnvMerge?.COMPOSE_PROJECT_NAME ?? stageComposeProjectName(cwd);
|
|
@@ -57554,13 +57988,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
|
57554
57988
|
function stageComposeFiles(cwd) {
|
|
57555
57989
|
const files = ["docker-compose.yml"];
|
|
57556
57990
|
for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
|
|
57557
|
-
if ((0,
|
|
57991
|
+
if ((0, import_node_fs48.existsSync)((0, import_node_path45.join)(cwd, conventional))) files.push(conventional);
|
|
57558
57992
|
}
|
|
57559
57993
|
return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
|
|
57560
57994
|
}
|
|
57561
57995
|
async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
57562
57996
|
const keys = Object.keys(vaultEnvMerge ?? {});
|
|
57563
|
-
if (!keys.length || !(0,
|
|
57997
|
+
if (!keys.length || !(0, import_node_fs48.existsSync)((0, import_node_path45.join)(cwd, "docker-compose.yml"))) return {};
|
|
57564
57998
|
let configJson;
|
|
57565
57999
|
try {
|
|
57566
58000
|
const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
|
|
@@ -57576,21 +58010,21 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
|
57576
58010
|
}
|
|
57577
58011
|
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson), runtimeEnvSelects(configJson));
|
|
57578
58012
|
if (!body) return {};
|
|
57579
|
-
const overridePath = (0,
|
|
57580
|
-
(0,
|
|
57581
|
-
(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");
|
|
57582
58016
|
return stageComposeFileEnv(stageComposeFiles(cwd));
|
|
57583
58017
|
}
|
|
57584
58018
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
57585
58019
|
if (!config.ensureEnv) return;
|
|
57586
|
-
const target = (0,
|
|
57587
|
-
const example = (0,
|
|
57588
|
-
if (!(0,
|
|
57589
|
-
(0,
|
|
57590
|
-
} else if ((0,
|
|
57591
|
-
const stale = detectStaleEnvFile((0,
|
|
57592
|
-
exampleMtimeMs: (0,
|
|
57593
|
-
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
|
|
57594
58028
|
});
|
|
57595
58029
|
if (stale) {
|
|
57596
58030
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running mmi-cli stage`;
|
|
@@ -57598,8 +58032,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
57598
58032
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
57599
58033
|
}
|
|
57600
58034
|
}
|
|
57601
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
57602
|
-
(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");
|
|
57603
58037
|
}
|
|
57604
58038
|
}
|
|
57605
58039
|
async function gitText(cwd, args) {
|
|
@@ -57629,20 +58063,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
57629
58063
|
return void 0;
|
|
57630
58064
|
}
|
|
57631
58065
|
function readState(path2) {
|
|
57632
|
-
if (!(0,
|
|
58066
|
+
if (!(0, import_node_fs48.existsSync)(path2)) return null;
|
|
57633
58067
|
try {
|
|
57634
|
-
return JSON.parse((0,
|
|
58068
|
+
return JSON.parse((0, import_node_fs48.readFileSync)(path2, "utf8"));
|
|
57635
58069
|
} catch {
|
|
57636
58070
|
return null;
|
|
57637
58071
|
}
|
|
57638
58072
|
}
|
|
57639
58073
|
function mkdirFor(path2) {
|
|
57640
58074
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
57641
|
-
(0,
|
|
58075
|
+
(0, import_node_fs48.mkdirSync)(dir, { recursive: true });
|
|
57642
58076
|
}
|
|
57643
58077
|
function writeState(path2, state) {
|
|
57644
58078
|
mkdirFor(path2);
|
|
57645
|
-
(0,
|
|
58079
|
+
(0, import_node_fs48.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
57646
58080
|
}
|
|
57647
58081
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
57648
58082
|
const reservation = {
|
|
@@ -57671,7 +58105,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd, currentEn
|
|
|
57671
58105
|
);
|
|
57672
58106
|
}
|
|
57673
58107
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
57674
|
-
(0,
|
|
58108
|
+
(0, import_node_fs48.rmSync)(path2, { force: true });
|
|
57675
58109
|
}
|
|
57676
58110
|
}
|
|
57677
58111
|
async function killTree(pid) {
|
|
@@ -57918,8 +58352,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
57918
58352
|
});
|
|
57919
58353
|
}
|
|
57920
58354
|
} catch (e) {
|
|
57921
|
-
(0,
|
|
57922
|
-
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 });
|
|
57923
58357
|
throw e;
|
|
57924
58358
|
}
|
|
57925
58359
|
const started = await startStage(config, {
|
|
@@ -57938,9 +58372,9 @@ function normPath2(p) {
|
|
|
57938
58372
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
57939
58373
|
}
|
|
57940
58374
|
function unlinkNodeModulesJunction(wtPath) {
|
|
57941
|
-
const nm = (0,
|
|
58375
|
+
const nm = (0, import_node_path46.join)(wtPath, "node_modules");
|
|
57942
58376
|
try {
|
|
57943
|
-
if ((0,
|
|
58377
|
+
if ((0, import_node_fs49.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs49.rmdirSync)(nm);
|
|
57944
58378
|
return { ok: true };
|
|
57945
58379
|
} catch (e) {
|
|
57946
58380
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -57951,8 +58385,8 @@ function defaultSleep2(ms) {
|
|
|
57951
58385
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
57952
58386
|
}
|
|
57953
58387
|
async function removeResidueDirectory(wtPath, options = {}) {
|
|
57954
|
-
const { sleep: sleep2 = defaultSleep2, removeEmptyDir =
|
|
57955
|
-
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 }));
|
|
57956
58390
|
const junction = unlinkNodeModulesJunction(wtPath);
|
|
57957
58391
|
if (!junction.ok) return junction;
|
|
57958
58392
|
try {
|
|
@@ -58075,28 +58509,28 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
|
|
|
58075
58509
|
}
|
|
58076
58510
|
async function listNestedIgnoredNodeModules(wtPath, execGit) {
|
|
58077
58511
|
const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
|
|
58078
|
-
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)));
|
|
58079
58513
|
}
|
|
58080
58514
|
function safeRemoveTree(path2) {
|
|
58081
|
-
const stat4 = (0,
|
|
58515
|
+
const stat4 = (0, import_node_fs49.lstatSync)(path2);
|
|
58082
58516
|
if (stat4.isSymbolicLink()) {
|
|
58083
58517
|
try {
|
|
58084
|
-
(0,
|
|
58518
|
+
(0, import_node_fs49.rmdirSync)(path2);
|
|
58085
58519
|
} catch {
|
|
58086
|
-
(0,
|
|
58520
|
+
(0, import_node_fs49.unlinkSync)(path2);
|
|
58087
58521
|
}
|
|
58088
58522
|
return;
|
|
58089
58523
|
}
|
|
58090
58524
|
if (stat4.isDirectory()) {
|
|
58091
|
-
for (const entry of (0,
|
|
58092
|
-
(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);
|
|
58093
58527
|
return;
|
|
58094
58528
|
}
|
|
58095
|
-
(0,
|
|
58529
|
+
(0, import_node_fs49.unlinkSync)(path2);
|
|
58096
58530
|
}
|
|
58097
58531
|
async function removeWorktreeNodeModules(wtPath) {
|
|
58098
58532
|
try {
|
|
58099
|
-
safeRemoveTree((0,
|
|
58533
|
+
safeRemoveTree((0, import_node_path46.join)(wtPath, "node_modules"));
|
|
58100
58534
|
return { ok: true };
|
|
58101
58535
|
} catch (e) {
|
|
58102
58536
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -58108,7 +58542,7 @@ function errorMessage(e) {
|
|
|
58108
58542
|
}
|
|
58109
58543
|
function resolvedOrRaw(path2) {
|
|
58110
58544
|
try {
|
|
58111
|
-
return normPath2((0,
|
|
58545
|
+
return normPath2((0, import_node_fs49.realpathSync)(path2));
|
|
58112
58546
|
} catch {
|
|
58113
58547
|
return normPath2(path2);
|
|
58114
58548
|
}
|
|
@@ -58116,10 +58550,10 @@ function resolvedOrRaw(path2) {
|
|
|
58116
58550
|
function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
58117
58551
|
let realRoot;
|
|
58118
58552
|
try {
|
|
58119
|
-
if ((0,
|
|
58553
|
+
if ((0, import_node_fs49.lstatSync)(root).isSymbolicLink()) {
|
|
58120
58554
|
return { ok: false, error: `delete root ${normPath2(root)} is a reparse point resolving to ${resolvedOrRaw(root)}` };
|
|
58121
58555
|
}
|
|
58122
|
-
realRoot = normPath2((0,
|
|
58556
|
+
realRoot = normPath2((0, import_node_fs49.realpathSync)(root));
|
|
58123
58557
|
} catch (e) {
|
|
58124
58558
|
if (e.code === "ENOENT") return { ok: true, unlinked: [] };
|
|
58125
58559
|
return { ok: false, error: `cannot resolve delete root ${normPath2(root)}: ${errorMessage(e)}` };
|
|
@@ -58135,17 +58569,17 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
58135
58569
|
const dir = stack.pop();
|
|
58136
58570
|
let entries;
|
|
58137
58571
|
try {
|
|
58138
|
-
entries = (0,
|
|
58572
|
+
entries = (0, import_node_fs49.readdirSync)(dir, { withFileTypes: true });
|
|
58139
58573
|
} catch (e) {
|
|
58140
58574
|
if (e.code === "ENOENT") continue;
|
|
58141
58575
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
58142
58576
|
}
|
|
58143
58577
|
for (const entry of entries) {
|
|
58144
|
-
const child2 = (0,
|
|
58578
|
+
const child2 = (0, import_node_path46.join)(dir, entry.name);
|
|
58145
58579
|
if (entry.isSymbolicLink()) {
|
|
58146
58580
|
let target = "";
|
|
58147
58581
|
try {
|
|
58148
|
-
target = normPath2((0,
|
|
58582
|
+
target = normPath2((0, import_node_fs49.realpathSync)(child2));
|
|
58149
58583
|
} catch {
|
|
58150
58584
|
target = "";
|
|
58151
58585
|
}
|
|
@@ -58328,7 +58762,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
58328
58762
|
return report;
|
|
58329
58763
|
}
|
|
58330
58764
|
const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
58331
|
-
const pathExists = options.pathExists ??
|
|
58765
|
+
const pathExists = options.pathExists ?? import_node_fs49.existsSync;
|
|
58332
58766
|
let afterWorktrees = [];
|
|
58333
58767
|
try {
|
|
58334
58768
|
afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
|
|
@@ -58468,7 +58902,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
58468
58902
|
const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
|
|
58469
58903
|
if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
|
|
58470
58904
|
unlinkedReparsePoints.push(...preHelperGuard.unlinked);
|
|
58471
|
-
if (pathExists((0,
|
|
58905
|
+
if (pathExists((0, import_node_path46.join)(wtPath, "node_modules"))) {
|
|
58472
58906
|
const nmRemoved = await (options.removeRealNodeModules ?? removeWorktreeNodeModules)(wtPath);
|
|
58473
58907
|
if (!nmRemoved.ok) {
|
|
58474
58908
|
report.worktree = {
|
|
@@ -58604,18 +59038,18 @@ function renderPrMergeCleanupLines(cleanup) {
|
|
|
58604
59038
|
// src/worktree-residue-sweep.ts
|
|
58605
59039
|
var RESIDUE_LEDGER_FILE = "mmi-worktree-residue.json";
|
|
58606
59040
|
function canonicalWorktreeRoot(primary) {
|
|
58607
|
-
return (0,
|
|
59041
|
+
return (0, import_node_path47.join)((0, import_node_path47.dirname)(primary), "mmi-worktrees", (0, import_node_path47.basename)(primary));
|
|
58608
59042
|
}
|
|
58609
59043
|
function norm(p) {
|
|
58610
59044
|
const s = p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
58611
59045
|
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
58612
59046
|
}
|
|
58613
59047
|
function residueLedgerPath(gitCommonDir) {
|
|
58614
|
-
return (0,
|
|
59048
|
+
return (0, import_node_path47.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
|
|
58615
59049
|
}
|
|
58616
59050
|
function readPaths(file, deps) {
|
|
58617
59051
|
try {
|
|
58618
|
-
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0,
|
|
59052
|
+
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs50.readFileSync)(f, "utf8")))(file));
|
|
58619
59053
|
return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
|
|
58620
59054
|
} catch {
|
|
58621
59055
|
return [];
|
|
@@ -58624,8 +59058,8 @@ function readPaths(file, deps) {
|
|
|
58624
59058
|
function writePaths(file, paths, deps) {
|
|
58625
59059
|
try {
|
|
58626
59060
|
const write = deps.writeLedger ?? ((f, t) => {
|
|
58627
|
-
(0,
|
|
58628
|
-
(0,
|
|
59061
|
+
(0, import_node_fs50.mkdirSync)((0, import_node_path47.dirname)(f), { recursive: true });
|
|
59062
|
+
(0, import_node_fs50.writeFileSync)(f, t, "utf8");
|
|
58629
59063
|
});
|
|
58630
59064
|
write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
|
|
58631
59065
|
`);
|
|
@@ -58639,17 +59073,17 @@ function recordResidue(gitCommonDir, path2, deps = {}) {
|
|
|
58639
59073
|
writePaths(file, paths, deps);
|
|
58640
59074
|
}
|
|
58641
59075
|
async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
58642
|
-
const exists = deps.exists ??
|
|
58643
|
-
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));
|
|
58644
59078
|
const isDir = deps.isDir ?? ((p) => {
|
|
58645
59079
|
try {
|
|
58646
|
-
return (0,
|
|
59080
|
+
return (0, import_node_fs50.statSync)(p).isDirectory();
|
|
58647
59081
|
} catch {
|
|
58648
59082
|
return false;
|
|
58649
59083
|
}
|
|
58650
59084
|
});
|
|
58651
59085
|
const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
|
|
58652
|
-
const removeEmpty = deps.removeEmpty ?? ((p) => (0,
|
|
59086
|
+
const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs50.rmdirSync)(p));
|
|
58653
59087
|
const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
|
|
58654
59088
|
const live = new Set(registered.map(norm));
|
|
58655
59089
|
const primary = registered[0] ? norm(registered[0]) : void 0;
|
|
@@ -58684,7 +59118,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
|
58684
59118
|
} catch {
|
|
58685
59119
|
}
|
|
58686
59120
|
for (const name of children) {
|
|
58687
|
-
const child2 = (0,
|
|
59121
|
+
const child2 = (0, import_node_path47.join)(root, name);
|
|
58688
59122
|
const key = norm(child2);
|
|
58689
59123
|
if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
|
|
58690
59124
|
let entries;
|
|
@@ -58727,10 +59161,10 @@ function argvWantsJson2() {
|
|
|
58727
59161
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
58728
59162
|
}
|
|
58729
59163
|
function hubRoot() {
|
|
58730
|
-
const fromPkg = (0,
|
|
59164
|
+
const fromPkg = (0, import_node_path48.join)(__dirname, "..", "..");
|
|
58731
59165
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
58732
|
-
if ((0,
|
|
58733
|
-
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();
|
|
58734
59168
|
return null;
|
|
58735
59169
|
}
|
|
58736
59170
|
function ciAuditDeps() {
|
|
@@ -58742,12 +59176,12 @@ function ciAuditDeps() {
|
|
|
58742
59176
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
58743
59177
|
readSeedFile: (path2) => {
|
|
58744
59178
|
if (!root) return null;
|
|
58745
|
-
const fullPath = (0,
|
|
58746
|
-
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;
|
|
58747
59181
|
}
|
|
58748
59182
|
};
|
|
58749
59183
|
}
|
|
58750
|
-
async function
|
|
59184
|
+
async function ghJson2(args, timeout = 1e4) {
|
|
58751
59185
|
const { stdout } = await execFileP("gh", args, { timeout });
|
|
58752
59186
|
return JSON.parse(stdout);
|
|
58753
59187
|
}
|
|
@@ -58821,6 +59255,24 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
58821
59255
|
}
|
|
58822
59256
|
return { onBoard: true };
|
|
58823
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
|
+
}
|
|
58824
59276
|
if (isRateLimitText(detail)) {
|
|
58825
59277
|
if (targetRepo2) {
|
|
58826
59278
|
try {
|
|
@@ -59115,7 +59567,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59115
59567
|
async function readParentField(number, repo) {
|
|
59116
59568
|
let payload;
|
|
59117
59569
|
try {
|
|
59118
|
-
payload = await
|
|
59570
|
+
payload = await ghJson2(["api", `repos/${repo}/issues/${number}`]);
|
|
59119
59571
|
} catch (e) {
|
|
59120
59572
|
const err = e;
|
|
59121
59573
|
return { parentReadError: (err.stderr || err.message || String(e)).trim() };
|
|
@@ -59132,7 +59584,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59132
59584
|
}
|
|
59133
59585
|
async function readIssueTypeField(number, repo) {
|
|
59134
59586
|
try {
|
|
59135
|
-
return { issueType: resolveIssueType(await
|
|
59587
|
+
return { issueType: resolveIssueType(await ghJson2(["api", `repos/${repo}/issues/${number}`])) };
|
|
59136
59588
|
} catch (e) {
|
|
59137
59589
|
const err = e;
|
|
59138
59590
|
return { issueTypeReadError: (err.stderr || err.message || String(e)).trim() };
|
|
@@ -59178,7 +59630,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59178
59630
|
const decisions = parseDecisionMarkers(data2.comments);
|
|
59179
59631
|
return { data: { ...data2, decisions }, parent: await readParentField(targetNumber, targetRepo2) };
|
|
59180
59632
|
}
|
|
59181
|
-
const data = await
|
|
59633
|
+
const data = await ghJson2(["issue", "view", String(targetNumber), "--repo", targetRepo2, "--json", gh2.ghFields]);
|
|
59182
59634
|
return { data, parent: await readParentField(targetNumber, targetRepo2) };
|
|
59183
59635
|
};
|
|
59184
59636
|
const readClosedBy = async (targetRepo2, targetNumber) => ({
|
|
@@ -59213,7 +59665,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59213
59665
|
const repo = await resolveRepo(o.repo);
|
|
59214
59666
|
if (!repo) return fail("issue discover-related: could not resolve repo");
|
|
59215
59667
|
try {
|
|
59216
|
-
const issues = await
|
|
59668
|
+
const issues = await ghJson2([
|
|
59217
59669
|
"issue",
|
|
59218
59670
|
"list",
|
|
59219
59671
|
"--repo",
|
|
@@ -59228,7 +59680,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59228
59680
|
const candidates = findRelatedIssues({ number, title: o.title, body: o.body }, issues);
|
|
59229
59681
|
if (o.json) return console.log(JSON.stringify({ number, repo, candidates }, null, 2));
|
|
59230
59682
|
if (!candidates.length) return;
|
|
59231
|
-
const viewed = await
|
|
59683
|
+
const viewed = await ghJson2([
|
|
59232
59684
|
"issue",
|
|
59233
59685
|
"view",
|
|
59234
59686
|
String(number),
|
|
@@ -59329,7 +59781,7 @@ function registerCollaborationCommands(program3) {
|
|
|
59329
59781
|
const checked = o.off !== true;
|
|
59330
59782
|
let body;
|
|
59331
59783
|
try {
|
|
59332
|
-
const viewed = await
|
|
59784
|
+
const viewed = await ghJson2(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
|
|
59333
59785
|
body = viewed.body ?? "";
|
|
59334
59786
|
} catch (e) {
|
|
59335
59787
|
return fail(`issue check: could not read ${repo}#${parsed.number}: ${e.message}`);
|
|
@@ -59430,7 +59882,7 @@ ${list}`);
|
|
|
59430
59882
|
if (!o.force) {
|
|
59431
59883
|
let openLessons = [];
|
|
59432
59884
|
try {
|
|
59433
|
-
openLessons = await
|
|
59885
|
+
openLessons = await ghJson2([
|
|
59434
59886
|
"issue",
|
|
59435
59887
|
"list",
|
|
59436
59888
|
"--repo",
|
|
@@ -59567,7 +60019,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59567
60019
|
console.log(JSON.stringify(data2));
|
|
59568
60020
|
return;
|
|
59569
60021
|
}
|
|
59570
|
-
const data = await
|
|
60022
|
+
const data = await ghJson2(["pr", "view", String(n), "--repo", repo, "--json", effective]);
|
|
59571
60023
|
console.log(JSON.stringify(data));
|
|
59572
60024
|
} catch (e) {
|
|
59573
60025
|
const err = e;
|
|
@@ -59575,11 +60027,11 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59575
60027
|
}
|
|
59576
60028
|
});
|
|
59577
60029
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
59578
|
-
const wfDir = (0,
|
|
59579
|
-
if (!(0,
|
|
59580
|
-
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) => {
|
|
59581
60033
|
try {
|
|
59582
|
-
return workflowReportsPrChecks((0,
|
|
60034
|
+
return workflowReportsPrChecks((0, import_node_fs51.readFileSync)((0, import_node_path48.join)(wfDir, name), "utf8"));
|
|
59583
60035
|
} catch {
|
|
59584
60036
|
return true;
|
|
59585
60037
|
}
|
|
@@ -59784,7 +60236,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59784
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) => {
|
|
59785
60237
|
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
59786
60238
|
const verdict = o.verdict;
|
|
59787
|
-
const findings = o.findingsFile ? (0,
|
|
60239
|
+
const findings = o.findingsFile ? (0, import_node_fs51.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
59788
60240
|
let patch;
|
|
59789
60241
|
let head;
|
|
59790
60242
|
try {
|
|
@@ -59865,7 +60317,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59865
60317
|
await readClosingGuardInput(number, repoArgs, landRepoForGuard, "pr land"),
|
|
59866
60318
|
async (n) => {
|
|
59867
60319
|
if (!landRepoForGuard) return void 0;
|
|
59868
|
-
const viewed = await
|
|
60320
|
+
const viewed = await ghJson2(["issue", "view", String(n), "--repo", landRepoForGuard, "--json", "state"]);
|
|
59869
60321
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
59870
60322
|
}
|
|
59871
60323
|
);
|
|
@@ -60062,7 +60514,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60062
60514
|
const cleanupMerged = async (r) => {
|
|
60063
60515
|
if (opts.preserveWorktree) return "cleanup skipped (--preserve-worktree)";
|
|
60064
60516
|
const ran = await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(["pr", "merge", r.pr, "--repo", r.repo])], {
|
|
60065
|
-
cwd: (0,
|
|
60517
|
+
cwd: (0, import_node_fs51.existsSync)(cwd0) ? cwd0 : primaryRoot0,
|
|
60066
60518
|
timeout: GH_MUTATION_TIMEOUT_MS * 3
|
|
60067
60519
|
}).then((x) => ({ stdout: x.stdout, stderr: x.stderr }), (e) => ({ stdout: e.stdout ?? "", stderr: e.stderr ?? "" }));
|
|
60068
60520
|
const json = ran.stdout.split("\n").reverse().find((l) => l.startsWith("{"));
|
|
@@ -60123,7 +60575,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60123
60575
|
await readClosingGuardInput(number, repoArgs, repoForPostCleanup, "pr merge"),
|
|
60124
60576
|
async (n) => {
|
|
60125
60577
|
if (!repoForPostCleanup) return void 0;
|
|
60126
|
-
const viewed = await
|
|
60578
|
+
const viewed = await ghJson2(["issue", "view", String(n), "--repo", repoForPostCleanup, "--json", "state"]);
|
|
60127
60579
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
60128
60580
|
}
|
|
60129
60581
|
);
|
|
@@ -60133,7 +60585,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60133
60585
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
60134
60586
|
closingGuardInput,
|
|
60135
60587
|
method === "--squash",
|
|
60136
|
-
o.squashBodyFile ? (0,
|
|
60588
|
+
o.squashBodyFile ? (0, import_node_fs51.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
60137
60589
|
);
|
|
60138
60590
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
60139
60591
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -60165,7 +60617,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60165
60617
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
60166
60618
|
let foreignCheckout;
|
|
60167
60619
|
if (foreignCwd) {
|
|
60168
|
-
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]);
|
|
60169
60621
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
60170
60622
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
60171
60623
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -60284,7 +60736,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60284
60736
|
}
|
|
60285
60737
|
if (!repoForPostCleanup) throw e;
|
|
60286
60738
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
60287
|
-
const commitMessage = bodyFile ? (0,
|
|
60739
|
+
const commitMessage = bodyFile ? (0, import_node_fs51.readFileSync)(bodyFile, "utf8") : void 0;
|
|
60288
60740
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
60289
60741
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
60290
60742
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -60399,7 +60851,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60399
60851
|
let residueSweep;
|
|
60400
60852
|
if (!(foreignCwd && !foreignCheckout)) {
|
|
60401
60853
|
try {
|
|
60402
|
-
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());
|
|
60403
60855
|
residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
|
|
60404
60856
|
} catch {
|
|
60405
60857
|
}
|
|
@@ -60417,7 +60869,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
60417
60869
|
preserveWorktree: o.preserveWorktree,
|
|
60418
60870
|
gcAcknowledged: o.gc,
|
|
60419
60871
|
expectedHeadOid: headRefOid,
|
|
60420
|
-
pathExists: (p) => (0,
|
|
60872
|
+
pathExists: (p) => (0, import_node_fs51.existsSync)(p),
|
|
60421
60873
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
60422
60874
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
60423
60875
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -61680,7 +62132,7 @@ function rcRunCauseFromLog(log) {
|
|
|
61680
62132
|
function memberAnnounceSummaryFile(repo, summaryFile, memberSummaryFiles) {
|
|
61681
62133
|
return repo === SUITE_REPO ? summaryFile : memberSummaryFiles[repo];
|
|
61682
62134
|
}
|
|
61683
|
-
var suiteStateDir = (root) => (0,
|
|
62135
|
+
var suiteStateDir = (root) => (0, import_node_path50.join)(resolvePrimaryGitDir(root), "mmi-runtime", "suite");
|
|
61684
62136
|
async function runSuiteMemberRcand(repo, previousRc, rcWasAbsent) {
|
|
61685
62137
|
const deps = trainApplyDeps();
|
|
61686
62138
|
deps.suiteCut = suiteCutContext(repo, SUITE_REPO);
|
|
@@ -61727,16 +62179,16 @@ async function rcandInCheckout(repo, deps, previousRc, rcWasAbsent) {
|
|
|
61727
62179
|
}
|
|
61728
62180
|
}
|
|
61729
62181
|
function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr, memberSummaryFilesTr = {}) {
|
|
61730
|
-
const recordPath = (0,
|
|
61731
|
-
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");
|
|
61732
62184
|
const checkout = async (repo) => {
|
|
61733
62185
|
if (repo === SUITE_REPO) return root;
|
|
61734
|
-
const target = (0,
|
|
61735
|
-
if (!(0,
|
|
61736
|
-
(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 });
|
|
61737
62189
|
await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
|
|
61738
62190
|
}
|
|
61739
|
-
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`);
|
|
61740
62192
|
const origin = await exec("git", ["remote", "get-url", "origin"], target);
|
|
61741
62193
|
if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
|
|
61742
62194
|
throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
|
|
@@ -61800,7 +62252,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
61800
62252
|
if (repo === SUITE_REPO) return inTrain(repo, (deps) => rcandInCheckout(repo, deps, previousRc, rcWasAbsent));
|
|
61801
62253
|
const target = await checkout(repo);
|
|
61802
62254
|
return new Promise((resolveResult, reject2) => {
|
|
61803
|
-
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"] : []], {
|
|
61804
62256
|
cwd: target,
|
|
61805
62257
|
stdio: ["ignore", "inherit", "inherit", "ipc"],
|
|
61806
62258
|
execArgv: []
|
|
@@ -62043,9 +62495,9 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
62043
62495
|
const body = await gh(["release", "view", leadTag, "--repo", SUITE_REPO, "--json", "body", "--jq", ".body"]);
|
|
62044
62496
|
if (parseSuiteMapping(body)) return;
|
|
62045
62497
|
const dir = suiteStateDir(root);
|
|
62046
|
-
(0,
|
|
62047
|
-
const file = (0,
|
|
62048
|
-
(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}
|
|
62049
62501
|
|
|
62050
62502
|
${suiteMappingBlock(leadTag, mapping)}`);
|
|
62051
62503
|
await gh(["release", "edit", leadTag, "--repo", SUITE_REPO, "--notes-file", file]);
|
|
@@ -62086,7 +62538,7 @@ ${suiteMappingBlock(leadTag, mapping)}`);
|
|
|
62086
62538
|
|
|
62087
62539
|
// src/suite-commands.ts
|
|
62088
62540
|
init_cli_shared();
|
|
62089
|
-
var
|
|
62541
|
+
var import_node_fs53 = require("node:fs");
|
|
62090
62542
|
function parseMemberSummaryFiles(entries) {
|
|
62091
62543
|
const memberSummaryFiles = {};
|
|
62092
62544
|
for (const entry of entries) {
|
|
@@ -62180,7 +62632,7 @@ function registerSuiteCommands(program3) {
|
|
|
62180
62632
|
try {
|
|
62181
62633
|
const io = suiteTrainIo(root);
|
|
62182
62634
|
retired = await retireFailedLeadRcLedger({ ledgerPath: releaseLedgerPath(root), rcHead: () => io.head(SUITE_REPO, "rc").then((h) => h ?? void 0), markAbandoned: io.markAbandoned }, o.ownerGo === true);
|
|
62183
|
-
if (retired && !(0,
|
|
62635
|
+
if (retired && !(0, import_node_fs53.existsSync)(io.recordPath)) {
|
|
62184
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)`);
|
|
62185
62637
|
return;
|
|
62186
62638
|
}
|
|
@@ -62420,9 +62872,9 @@ function registerJevCommands(program3) {
|
|
|
62420
62872
|
|
|
62421
62873
|
// src/packages-adopt-export.ts
|
|
62422
62874
|
var import_node_child_process19 = require("node:child_process");
|
|
62423
|
-
var
|
|
62424
|
-
var
|
|
62425
|
-
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");
|
|
62426
62878
|
var import_node_util6 = require("node:util");
|
|
62427
62879
|
init_cli_shared();
|
|
62428
62880
|
|
|
@@ -63042,38 +63494,38 @@ function expectedFromRun(run, opts) {
|
|
|
63042
63494
|
}
|
|
63043
63495
|
async function adoptExport(opts, deps) {
|
|
63044
63496
|
if (!/^sha256:[0-9a-f]{64}$/.test(opts.digest)) throw new Error("--digest must be sha256:<64 hex>, from the export run summary");
|
|
63045
|
-
const packagePath = (0,
|
|
63046
|
-
const lockPath = (0,
|
|
63047
|
-
const packageText = (0,
|
|
63048
|
-
const lockText = (0,
|
|
63049
|
-
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-"));
|
|
63050
63502
|
try {
|
|
63051
63503
|
const run = JSON.parse((await deps.ghApi(`repos/${EXPORT_REPO}/actions/runs/${opts.run}`)).toString("utf8"));
|
|
63052
|
-
(0,
|
|
63053
|
-
(0,
|
|
63054
|
-
(0,
|
|
63055
|
-
const script = (0,
|
|
63056
|
-
(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);
|
|
63057
63509
|
await deps.python(script, ["verify", work, opts.ds, opts.screens]);
|
|
63058
|
-
const verified = (0,
|
|
63059
|
-
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"))]));
|
|
63060
63512
|
const plan = planAdoption(packageText, lockText, metadata);
|
|
63061
63513
|
for (const { name } of plan.result.adopted) {
|
|
63062
63514
|
const short3 = PACKAGES.find((p) => p.name === name).short;
|
|
63063
|
-
await deps.npm(["cache", "add", (0,
|
|
63515
|
+
await deps.npm(["cache", "add", (0, import_node_path51.join)(verified, `${short3}.tgz`)], opts.dir);
|
|
63064
63516
|
}
|
|
63065
|
-
(0,
|
|
63066
|
-
(0,
|
|
63517
|
+
(0, import_node_fs54.writeFileSync)(packagePath, plan.packageText);
|
|
63518
|
+
(0, import_node_fs54.writeFileSync)(lockPath, plan.lockText);
|
|
63067
63519
|
try {
|
|
63068
63520
|
await deps.npm(["ci", "--offline", "--ignore-scripts", "--no-audit", "--no-fund"], opts.dir);
|
|
63069
63521
|
} catch (error) {
|
|
63070
|
-
(0,
|
|
63071
|
-
(0,
|
|
63522
|
+
(0, import_node_fs54.writeFileSync)(packagePath, packageText);
|
|
63523
|
+
(0, import_node_fs54.writeFileSync)(lockPath, lockText);
|
|
63072
63524
|
throw new Error(`npm ci --offline failed, package.json and package-lock.json restored: ${error.message}`);
|
|
63073
63525
|
}
|
|
63074
63526
|
return plan.result;
|
|
63075
63527
|
} finally {
|
|
63076
|
-
(0,
|
|
63528
|
+
(0, import_node_fs54.rmSync)(work, { recursive: true, force: true });
|
|
63077
63529
|
}
|
|
63078
63530
|
}
|
|
63079
63531
|
var execFileBuffer = (0, import_node_util6.promisify)(import_node_child_process19.execFile);
|
|
@@ -63177,12 +63629,12 @@ function registerPackagesCommands(program3) {
|
|
|
63177
63629
|
init_cli_shared();
|
|
63178
63630
|
|
|
63179
63631
|
// src/wave-status.ts
|
|
63180
|
-
var
|
|
63632
|
+
var import_node_fs55 = require("node:fs");
|
|
63181
63633
|
function observeStage(worktreePath) {
|
|
63182
63634
|
const statePath = stageStatePath(worktreePath);
|
|
63183
63635
|
let raw;
|
|
63184
63636
|
try {
|
|
63185
|
-
raw = (0,
|
|
63637
|
+
raw = (0, import_node_fs55.readFileSync)(statePath, "utf8");
|
|
63186
63638
|
} catch (error) {
|
|
63187
63639
|
if (error.code === "ENOENT") return { state: "ok", value: { running: false } };
|
|
63188
63640
|
return { state: "unavailable", reason: "stage state could not be read" };
|
|
@@ -63249,9 +63701,9 @@ async function collectWaveStatus(deps) {
|
|
|
63249
63701
|
// src/discovery-commands.ts
|
|
63250
63702
|
init_github_client();
|
|
63251
63703
|
init_cli_shared();
|
|
63252
|
-
var
|
|
63253
|
-
var
|
|
63254
|
-
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");
|
|
63255
63707
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
63256
63708
|
var defaultStatusDeps = {
|
|
63257
63709
|
repo: resolveRepo,
|
|
@@ -63470,10 +63922,10 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
63470
63922
|
else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
|
|
63471
63923
|
else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
|
|
63472
63924
|
}
|
|
63473
|
-
const home = (0,
|
|
63925
|
+
const home = (0, import_node_os26.homedir)();
|
|
63474
63926
|
const plugin = onboardPluginGate({
|
|
63475
|
-
readKnown: () => readFileSyncSafe((0,
|
|
63476
|
-
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)
|
|
63477
63929
|
});
|
|
63478
63930
|
return {
|
|
63479
63931
|
guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
|
|
@@ -65659,7 +66111,7 @@ function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
|
|
|
65659
66111
|
}
|
|
65660
66112
|
|
|
65661
66113
|
// src/api-read.ts
|
|
65662
|
-
var
|
|
66114
|
+
var import_node_fs57 = require("node:fs");
|
|
65663
66115
|
|
|
65664
66116
|
// src/run-log-read.ts
|
|
65665
66117
|
init_cli_shared();
|
|
@@ -65856,7 +66308,7 @@ var BROKER_MAX_BUFFER = 64 * 1024 * 1024;
|
|
|
65856
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}}}}}";
|
|
65857
66309
|
function missingBrokerPrRepoMessage(subcommand, number) {
|
|
65858
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`;
|
|
65859
|
-
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}\`)`;
|
|
65860
66312
|
}
|
|
65861
66313
|
function buildPrQueueSpec(number, o) {
|
|
65862
66314
|
const repo = parseBrokerRepo(o.repo, "--repo");
|
|
@@ -66251,25 +66703,38 @@ function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
|
|
|
66251
66703
|
}
|
|
66252
66704
|
await dispatchGh(spec, "read run watch");
|
|
66253
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
|
+
};
|
|
66254
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)");
|
|
66255
|
-
pr.command("list").description("list viewer-free PRs (author/viewer filters stay personal)").option("--repo <owner/repo>", "target repo (
|
|
66256
|
-
|
|
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;
|
|
66257
66720
|
let spec;
|
|
66258
66721
|
try {
|
|
66259
66722
|
requireJqJson(o.jq, o.json);
|
|
66260
|
-
spec = buildPrListSpec(o);
|
|
66723
|
+
spec = buildPrListSpec({ ...o, repo });
|
|
66261
66724
|
} catch (e) {
|
|
66262
66725
|
return fail(e.message);
|
|
66263
66726
|
}
|
|
66264
66727
|
await dispatchGh(spec, "read pr list", o.jq);
|
|
66265
66728
|
});
|
|
66266
|
-
pr.command("diff").description("read a bounded PR diff on the App pool").option("--repo <owner/repo>", "target repo (
|
|
66267
|
-
|
|
66268
|
-
|
|
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");
|
|
66269
66733
|
});
|
|
66270
|
-
pr.command("queue").description("read one PR mergeQueueEntry (fixed GraphQL query, no permission widening)").option("--repo <owner/repo>", "target repo (
|
|
66271
|
-
|
|
66272
|
-
|
|
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 });
|
|
66273
66738
|
requireBrokerApp("read pr queue");
|
|
66274
66739
|
try {
|
|
66275
66740
|
process.exitCode = await runBrokerGh(async (args, opts) => {
|
|
@@ -66550,7 +67015,7 @@ function registerApiReadCommands(program3, overrides) {
|
|
|
66550
67015
|
);
|
|
66551
67016
|
if (o.jq !== void 0) body = await jqRun(o.jq, body, localJqEnv());
|
|
66552
67017
|
if (zip) {
|
|
66553
|
-
(0,
|
|
67018
|
+
(0, import_node_fs57.writeFileSync)(o.out, body, "latin1");
|
|
66554
67019
|
return;
|
|
66555
67020
|
}
|
|
66556
67021
|
} catch (e) {
|
|
@@ -67466,8 +67931,8 @@ function registerSessionReport(program3) {
|
|
|
67466
67931
|
}
|
|
67467
67932
|
|
|
67468
67933
|
// src/stage-commands.ts
|
|
67469
|
-
var
|
|
67470
|
-
var
|
|
67934
|
+
var import_node_fs58 = require("node:fs");
|
|
67935
|
+
var import_node_path53 = require("node:path");
|
|
67471
67936
|
init_cli_shared();
|
|
67472
67937
|
init_clean_exit();
|
|
67473
67938
|
|
|
@@ -67715,8 +68180,8 @@ function registerStageCommands(program3) {
|
|
|
67715
68180
|
const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
|
|
67716
68181
|
const resolution = decideStage({
|
|
67717
68182
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
67718
|
-
hasCompose: (0,
|
|
67719
|
-
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")),
|
|
67720
68185
|
repo
|
|
67721
68186
|
});
|
|
67722
68187
|
if (resolution.source === "derived" && resolution.derived) {
|
|
@@ -68008,20 +68473,20 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
68008
68473
|
}
|
|
68009
68474
|
|
|
68010
68475
|
// src/tenant-artifact.ts
|
|
68011
|
-
var
|
|
68012
|
-
var
|
|
68476
|
+
var import_node_crypto20 = require("node:crypto");
|
|
68477
|
+
var import_node_fs59 = require("node:fs");
|
|
68013
68478
|
var import_promises11 = require("node:fs/promises");
|
|
68014
|
-
var
|
|
68479
|
+
var import_node_path54 = require("node:path");
|
|
68015
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}$/;
|
|
68016
68481
|
var MAX_BYTES2 = 5 * 1024 * 1024 * 1024;
|
|
68017
68482
|
async function sha256File(path2) {
|
|
68018
|
-
const hash = (0,
|
|
68019
|
-
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);
|
|
68020
68485
|
return hash.digest("hex");
|
|
68021
68486
|
}
|
|
68022
68487
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
68023
68488
|
if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
|
|
68024
|
-
const path2 = (0,
|
|
68489
|
+
const path2 = (0, import_node_path54.resolve)(inputPath);
|
|
68025
68490
|
const info = await (0, import_promises11.stat)(path2);
|
|
68026
68491
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
68027
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`);
|
|
@@ -68039,7 +68504,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
68039
68504
|
return [key, value];
|
|
68040
68505
|
}));
|
|
68041
68506
|
headers["content-length"] = String(info.size);
|
|
68042
|
-
const stream = (0,
|
|
68507
|
+
const stream = (0, import_node_fs59.createReadStream)(path2);
|
|
68043
68508
|
let uploaded;
|
|
68044
68509
|
try {
|
|
68045
68510
|
uploaded = await fetch(body.uploadUrl, {
|
|
@@ -68393,7 +68858,7 @@ function renderVerifySecrets(body) {
|
|
|
68393
68858
|
}
|
|
68394
68859
|
|
|
68395
68860
|
// src/command-register-developer.ts
|
|
68396
|
-
var
|
|
68861
|
+
var import_node_fs64 = require("node:fs");
|
|
68397
68862
|
init_clean_exit();
|
|
68398
68863
|
init_cli_shared();
|
|
68399
68864
|
init_error_codes();
|
|
@@ -68426,12 +68891,12 @@ async function resolveWhoami(deps) {
|
|
|
68426
68891
|
}
|
|
68427
68892
|
|
|
68428
68893
|
// src/command-register-developer.ts
|
|
68429
|
-
var
|
|
68894
|
+
var import_node_path61 = require("node:path");
|
|
68430
68895
|
|
|
68431
68896
|
// src/doc-refs-command-source.ts
|
|
68432
68897
|
var import_node_child_process21 = require("node:child_process");
|
|
68433
|
-
var
|
|
68434
|
-
var
|
|
68898
|
+
var import_node_fs60 = require("node:fs");
|
|
68899
|
+
var import_node_path55 = require("node:path");
|
|
68435
68900
|
var BUNDLE_MANIFEST_TIMEOUT_MS = 12e4;
|
|
68436
68901
|
var BUNDLE_MANIFEST_MAX_BUFFER = 64 * 1024 * 1024;
|
|
68437
68902
|
var flagsOf = (options) => new Set((options ?? []).flatMap((option) => String(option?.flags ?? "").match(/--[\w-]+/g) ?? []));
|
|
@@ -68452,7 +68917,7 @@ function inventoryFromManifest(manifest, source) {
|
|
|
68452
68917
|
function readBundleManifest(bundle, exec3 = import_node_child_process21.execFileSync) {
|
|
68453
68918
|
try {
|
|
68454
68919
|
const text = exec3(process.execPath, [bundle, "commands", "--all", "--json"], {
|
|
68455
|
-
cwd: (0,
|
|
68920
|
+
cwd: (0, import_node_path55.dirname)(bundle),
|
|
68456
68921
|
encoding: "utf8",
|
|
68457
68922
|
windowsHide: true,
|
|
68458
68923
|
timeout: BUNDLE_MANIFEST_TIMEOUT_MS,
|
|
@@ -68468,19 +68933,19 @@ function readBundleManifest(bundle, exec3 = import_node_child_process21.execFile
|
|
|
68468
68933
|
function isRunningBundle(bundle, argv1 = process.argv[1], platform2 = process.platform) {
|
|
68469
68934
|
if (!argv1) return false;
|
|
68470
68935
|
const norm2 = (path2) => {
|
|
68471
|
-
const absolute = (0,
|
|
68936
|
+
const absolute = (0, import_node_path55.resolve)(path2);
|
|
68472
68937
|
return platform2 === "win32" ? absolute.toLowerCase() : absolute;
|
|
68473
68938
|
};
|
|
68474
68939
|
const running = norm2(argv1);
|
|
68475
|
-
if (!["main.cjs", "index.cjs"].includes((0,
|
|
68476
|
-
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));
|
|
68477
68942
|
}
|
|
68478
68943
|
function resolveCommandInventory(deps) {
|
|
68479
|
-
const exists = deps.exists ??
|
|
68944
|
+
const exists = deps.exists ?? import_node_fs60.existsSync;
|
|
68480
68945
|
const read = deps.read ?? ((bundle) => readBundleManifest(bundle));
|
|
68481
68946
|
const warnings = [];
|
|
68482
68947
|
if (deps.from) {
|
|
68483
|
-
const bundle = (0,
|
|
68948
|
+
const bundle = (0, import_node_path55.isAbsolute)(deps.from) ? deps.from : (0, import_node_path55.resolve)(deps.root, deps.from);
|
|
68484
68949
|
if (!exists(bundle)) {
|
|
68485
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)`);
|
|
68486
68951
|
}
|
|
@@ -68490,7 +68955,7 @@ function resolveCommandInventory(deps) {
|
|
|
68490
68955
|
}
|
|
68491
68956
|
return { inventory: inventory2, warnings };
|
|
68492
68957
|
}
|
|
68493
|
-
const treeBundle = (0,
|
|
68958
|
+
const treeBundle = (0, import_node_path55.join)(deps.root, "cli", "dist", "main.cjs");
|
|
68494
68959
|
if (!exists(treeBundle) || isRunningBundle(treeBundle, deps.argv1, deps.platform)) {
|
|
68495
68960
|
return { inventory: deps.inProcess(), warnings };
|
|
68496
68961
|
}
|
|
@@ -68607,28 +69072,28 @@ async function executeWaveLand(plan, deps) {
|
|
|
68607
69072
|
|
|
68608
69073
|
// src/dist-drift.ts
|
|
68609
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
|
|
68610
69081
|
var import_node_crypto21 = require("node:crypto");
|
|
68611
69082
|
var import_node_fs61 = require("node:fs");
|
|
68612
|
-
var import_node_os26 = require("node:os");
|
|
68613
69083
|
var import_node_path56 = require("node:path");
|
|
68614
|
-
|
|
68615
|
-
// ../scripts/distribution-digest.mjs
|
|
68616
|
-
var import_node_crypto20 = require("node:crypto");
|
|
68617
|
-
var import_node_fs60 = require("node:fs");
|
|
68618
|
-
var import_node_path55 = require("node:path");
|
|
68619
69084
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
68620
69085
|
function repoPath(root, declaredPath, label) {
|
|
68621
|
-
const absoluteRoot = (0,
|
|
68622
|
-
const target = (0,
|
|
68623
|
-
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}`)) {
|
|
68624
69089
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
68625
69090
|
}
|
|
68626
69091
|
return target;
|
|
68627
69092
|
}
|
|
68628
69093
|
function digestFiles(files) {
|
|
68629
|
-
const hash = (0,
|
|
69094
|
+
const hash = (0, import_node_crypto21.createHash)("sha256");
|
|
68630
69095
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
68631
|
-
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);
|
|
68632
69097
|
hash.update(file.relative, "utf8");
|
|
68633
69098
|
hash.update("\0");
|
|
68634
69099
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -68643,8 +69108,8 @@ function digestFiles(files) {
|
|
|
68643
69108
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
68644
69109
|
return digestFiles(packedFiles.map((path2) => {
|
|
68645
69110
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
68646
|
-
if (!(0,
|
|
68647
|
-
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) };
|
|
68648
69113
|
}));
|
|
68649
69114
|
}
|
|
68650
69115
|
|
|
@@ -68657,7 +69122,7 @@ var DIST_ARTIFACTS = [
|
|
|
68657
69122
|
];
|
|
68658
69123
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
68659
69124
|
var ABSENT = "absent";
|
|
68660
|
-
var sha2562 = (bytes) => `sha256:${(0,
|
|
69125
|
+
var sha2562 = (bytes) => `sha256:${(0, import_node_crypto22.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
68661
69126
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
68662
69127
|
const committed = committedBytes ? sha2562(committedBytes) : ABSENT;
|
|
68663
69128
|
const rebuiltExpected = rebuiltBytes ? sha2562(rebuiltBytes) : ABSENT;
|
|
@@ -68729,13 +69194,13 @@ function renderDistDriftReceipt(receipt) {
|
|
|
68729
69194
|
return lines2;
|
|
68730
69195
|
}
|
|
68731
69196
|
function readOrNull(path2) {
|
|
68732
|
-
return (0,
|
|
69197
|
+
return (0, import_node_fs62.existsSync)(path2) ? (0, import_node_fs62.readFileSync)(path2) : null;
|
|
68733
69198
|
}
|
|
68734
69199
|
function walkFiles(root) {
|
|
68735
69200
|
const files = [];
|
|
68736
69201
|
const walk2 = (directory) => {
|
|
68737
|
-
for (const entry of (0,
|
|
68738
|
-
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);
|
|
68739
69204
|
if (entry.isDirectory()) walk2(child2);
|
|
68740
69205
|
else files.push(child2);
|
|
68741
69206
|
}
|
|
@@ -68745,10 +69210,10 @@ function walkFiles(root) {
|
|
|
68745
69210
|
}
|
|
68746
69211
|
function bomPathFor(root) {
|
|
68747
69212
|
try {
|
|
68748
|
-
const registry2 = JSON.parse((0,
|
|
68749
|
-
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");
|
|
68750
69215
|
} catch {
|
|
68751
|
-
return (0,
|
|
69216
|
+
return (0, import_node_path57.join)(root, "distribution-bom.json");
|
|
68752
69217
|
}
|
|
68753
69218
|
}
|
|
68754
69219
|
function rebuildTo(packageRoot, outDir) {
|
|
@@ -68761,40 +69226,40 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
68761
69226
|
});
|
|
68762
69227
|
}
|
|
68763
69228
|
function runDistStatus(root) {
|
|
68764
|
-
const stage = (0,
|
|
69229
|
+
const stage = (0, import_node_fs62.mkdtempSync)((0, import_node_path57.join)((0, import_node_os27.tmpdir)(), "mmi-dist-drift-"));
|
|
68765
69230
|
let overlayCount = 0;
|
|
68766
69231
|
try {
|
|
68767
|
-
const cliOut = (0,
|
|
68768
|
-
const hubOut = (0,
|
|
68769
|
-
rebuildTo((0,
|
|
68770
|
-
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);
|
|
68771
69236
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
68772
69237
|
const rebuilt = (path2) => {
|
|
68773
69238
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
68774
|
-
return spec ? readOrNull((0,
|
|
69239
|
+
return spec ? readOrNull((0, import_node_path57.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
68775
69240
|
};
|
|
68776
|
-
const committed = (path2) => readOrNull((0,
|
|
68777
|
-
const tree = (path2) => readOrNull((0,
|
|
68778
|
-
const distRoot = (0,
|
|
68779
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
68780
|
-
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"));
|
|
68781
69246
|
const digest = (entries) => {
|
|
68782
|
-
const overlay = (0,
|
|
69247
|
+
const overlay = (0, import_node_path57.join)(stage, `overlay-${overlayCount++}`);
|
|
68783
69248
|
for (const entry of entries) {
|
|
68784
|
-
const target = (0,
|
|
68785
|
-
(0,
|
|
68786
|
-
(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);
|
|
68787
69252
|
}
|
|
68788
69253
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
68789
69254
|
};
|
|
68790
69255
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
68791
69256
|
} finally {
|
|
68792
|
-
(0,
|
|
69257
|
+
(0, import_node_fs62.rmSync)(stage, { recursive: true, force: true });
|
|
68793
69258
|
}
|
|
68794
69259
|
}
|
|
68795
69260
|
|
|
68796
69261
|
// src/doc-refs-live.ts
|
|
68797
|
-
var
|
|
69262
|
+
var import_node_path58 = require("node:path");
|
|
68798
69263
|
var concrete = (value) => !!value && !/[<>{}$]/.test(value);
|
|
68799
69264
|
var liveOption = (tokens2, flag) => {
|
|
68800
69265
|
const index = tokens2.indexOf(flag);
|
|
@@ -68867,7 +69332,7 @@ async function runLiveDocRefs(root, deps, live) {
|
|
|
68867
69332
|
const offline = runDocRefs(root, { ...deps, runbooksOnly: true });
|
|
68868
69333
|
const read = deps.readFile ?? readFileOrNull;
|
|
68869
69334
|
const docs = Object.fromEntries((deps.listDocs ?? defaultListDocs)(root).flatMap((doc) => {
|
|
68870
|
-
const body = read((0,
|
|
69335
|
+
const body = read((0, import_node_path58.join)(root, doc));
|
|
68871
69336
|
return body !== null && isRunbook(doc, body) && !deps.isIgnored?.([doc]).has(doc) ? [[doc, body]] : [];
|
|
68872
69337
|
}));
|
|
68873
69338
|
const remote = await checkRunbookLive(docs, live);
|
|
@@ -69078,7 +69543,7 @@ async function diagnoseHub(deps) {
|
|
|
69078
69543
|
|
|
69079
69544
|
// src/schedules-lift-command.ts
|
|
69080
69545
|
var import_promises12 = require("node:fs/promises");
|
|
69081
|
-
var
|
|
69546
|
+
var import_node_path59 = require("node:path");
|
|
69082
69547
|
init_clean_exit();
|
|
69083
69548
|
init_cli_shared();
|
|
69084
69549
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -69107,7 +69572,7 @@ async function readWorkflowFiles(dir) {
|
|
|
69107
69572
|
const files = [];
|
|
69108
69573
|
for (const name of names.sort()) {
|
|
69109
69574
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
69110
|
-
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") });
|
|
69111
69576
|
}
|
|
69112
69577
|
return files;
|
|
69113
69578
|
}
|
|
@@ -69190,8 +69655,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
69190
69655
|
|
|
69191
69656
|
// src/spawn-policy-core.ts
|
|
69192
69657
|
var import_node_child_process24 = require("node:child_process");
|
|
69193
|
-
var
|
|
69194
|
-
var
|
|
69658
|
+
var import_node_fs63 = require("node:fs");
|
|
69659
|
+
var import_node_path60 = require("node:path");
|
|
69195
69660
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
69196
69661
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
69197
69662
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -69277,7 +69742,7 @@ function runSpawnPolicy(root) {
|
|
|
69277
69742
|
for (const file of files) {
|
|
69278
69743
|
let raw;
|
|
69279
69744
|
try {
|
|
69280
|
-
raw = (0,
|
|
69745
|
+
raw = (0, import_node_fs63.readFileSync)((0, import_node_path60.join)(root, file), "utf8");
|
|
69281
69746
|
} catch {
|
|
69282
69747
|
continue;
|
|
69283
69748
|
}
|
|
@@ -69297,19 +69762,19 @@ function runSpawnPolicy(root) {
|
|
|
69297
69762
|
function registerDeveloperCommands(program3) {
|
|
69298
69763
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
69299
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) => {
|
|
69300
|
-
const path2 = (0,
|
|
69301
|
-
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;
|
|
69302
69767
|
const plan = planManagedGitignore(current);
|
|
69303
69768
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
69304
69769
|
if (opts.json) {
|
|
69305
|
-
if (opts.write && plan.changed) (0,
|
|
69770
|
+
if (opts.write && plan.changed) (0, import_node_fs64.writeFileSync)(path2, plan.content, "utf8");
|
|
69306
69771
|
console.log(JSON.stringify(plan, null, 2));
|
|
69307
69772
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
69308
69773
|
return;
|
|
69309
69774
|
}
|
|
69310
69775
|
if (opts.write) {
|
|
69311
69776
|
if (plan.changed) {
|
|
69312
|
-
(0,
|
|
69777
|
+
(0, import_node_fs64.writeFileSync)(path2, plan.content, "utf8");
|
|
69313
69778
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
69314
69779
|
} else {
|
|
69315
69780
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -69626,12 +70091,12 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
69626
70091
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
69627
70092
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
69628
70093
|
function envHealLockPath(home) {
|
|
69629
|
-
return (0,
|
|
70094
|
+
return (0, import_node_path62.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
69630
70095
|
}
|
|
69631
70096
|
async function withEnvHealLock(what, run) {
|
|
69632
70097
|
try {
|
|
69633
70098
|
return await withFileLock(
|
|
69634
|
-
envHealLockPath((0,
|
|
70099
|
+
envHealLockPath((0, import_node_os28.homedir)()),
|
|
69635
70100
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
69636
70101
|
run
|
|
69637
70102
|
);
|
|
@@ -69729,7 +70194,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69729
70194
|
const configRoot = surfaceConfigRoot(surface);
|
|
69730
70195
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
69731
70196
|
const plan = buildPluginCachePlan(
|
|
69732
|
-
(0,
|
|
70197
|
+
(0, import_node_os28.homedir)(),
|
|
69733
70198
|
running,
|
|
69734
70199
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
69735
70200
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -69757,14 +70222,14 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69757
70222
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
69758
70223
|
const installed = installedActivePluginVersion(surface);
|
|
69759
70224
|
const plan = buildPluginCachePlan(
|
|
69760
|
-
(0,
|
|
70225
|
+
(0, import_node_os28.homedir)(),
|
|
69761
70226
|
running,
|
|
69762
70227
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
69763
70228
|
{ configRoot, includeStaging: surface !== "codex", installedVersion: installed }
|
|
69764
70229
|
);
|
|
69765
70230
|
const result = applyPluginCachePlan(
|
|
69766
70231
|
plan,
|
|
69767
|
-
(p) => (0,
|
|
70232
|
+
(p) => (0, import_node_fs65.rmSync)(p, { recursive: true }),
|
|
69768
70233
|
stagingApplyFsGuard(configRoot)
|
|
69769
70234
|
);
|
|
69770
70235
|
return {
|
|
@@ -69799,7 +70264,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69799
70264
|
// has no generated routing index would
|
|
69800
70265
|
// get a permanent — demanding an artifact it never asked for.
|
|
69801
70266
|
docsIndexState: (root) => {
|
|
69802
|
-
if (!(0,
|
|
70267
|
+
if (!(0, import_node_fs65.existsSync)((0, import_node_path62.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
69803
70268
|
const real = createDocsIndexDeps(root);
|
|
69804
70269
|
let docs;
|
|
69805
70270
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -69808,7 +70273,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
69808
70273
|
},
|
|
69809
70274
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
69810
70275
|
healDocsIndex: (root) => {
|
|
69811
|
-
if (!(0,
|
|
70276
|
+
if (!(0, import_node_fs65.existsSync)((0, import_node_path62.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
69812
70277
|
const real = createDocsIndexDeps(root);
|
|
69813
70278
|
let docs;
|
|
69814
70279
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -70107,7 +70572,7 @@ function envelopeAwareWriteErr(str2) {
|
|
|
70107
70572
|
const invoked = resolveCommandFromArgv(program2, argv);
|
|
70108
70573
|
const invokedPath = invoked ? canonicalPathFor(commandPath3(invoked)) ?? commandPath3(invoked) : void 0;
|
|
70109
70574
|
const canonicalArgv = canonicalArgvFor(argv);
|
|
70110
|
-
const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, invokedPath, canonicalArgv) ?? issueCommentAppendOnlyRecovery(flag, invokedPath, canonicalArgv) : void 0;
|
|
70575
|
+
const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, invokedPath, canonicalArgv) ?? issueCommentAppendOnlyRecovery(flag, invokedPath, canonicalArgv) ?? trainPlanRedundantFlagRecovery(flag, invokedPath, canonicalArgv) : void 0;
|
|
70111
70576
|
if (claimStatus) {
|
|
70112
70577
|
if (argvWantsJson3()) {
|
|
70113
70578
|
process.stderr.write(
|
|
@@ -70178,7 +70643,7 @@ function appActorDeps() {
|
|
|
70178
70643
|
fetch: (url, init) => fetch(url, init)
|
|
70179
70644
|
};
|
|
70180
70645
|
}
|
|
70181
|
-
setPollTokenProvider(async () => activeAppToken() ?? pollTokenOrUndefined(appActorDeps()));
|
|
70646
|
+
setPollTokenProvider(async (o) => (o?.fresh ? void 0 : activeAppToken()) ?? pollTokenOrUndefined(appActorDeps()));
|
|
70182
70647
|
setGhReadTokenProvider(pollToken);
|
|
70183
70648
|
setGitHubReadTokenProvider(pollToken);
|
|
70184
70649
|
var filingToken;
|
|
@@ -70186,7 +70651,10 @@ setGhWriteTokenProvider(() => {
|
|
|
70186
70651
|
if (process.env.MMI_ACTOR?.trim() === "app") {
|
|
70187
70652
|
return mintFilingWriteToken(appActorDeps()).then((t) => t.token);
|
|
70188
70653
|
}
|
|
70189
|
-
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
|
+
});
|
|
70190
70658
|
});
|
|
70191
70659
|
setClaimWriteTokenProvider(() => mintFilingWriteToken(appActorDeps()).then((t) => t.token));
|
|
70192
70660
|
setStatusWriteTokenProvider(() => mintStatusWriteToken(appActorDeps()));
|
|
@@ -70593,7 +71061,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
70593
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`);
|
|
70594
71062
|
if (o.secretsFile) {
|
|
70595
71063
|
try {
|
|
70596
|
-
vars.push(`secrets=${(0,
|
|
71064
|
+
vars.push(`secrets=${(0, import_node_fs65.readFileSync)(o.secretsFile, "utf8")}`);
|
|
70597
71065
|
} catch (e) {
|
|
70598
71066
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
70599
71067
|
}
|
|
@@ -70985,16 +71453,16 @@ function ciAuditDeps2() {
|
|
|
70985
71453
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
70986
71454
|
readSeedFile: (path2) => {
|
|
70987
71455
|
if (!root) return null;
|
|
70988
|
-
const fullPath = (0,
|
|
70989
|
-
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;
|
|
70990
71458
|
}
|
|
70991
71459
|
};
|
|
70992
71460
|
}
|
|
70993
71461
|
function hubRoot2() {
|
|
70994
|
-
const fromPkg = (0,
|
|
71462
|
+
const fromPkg = (0, import_node_path62.join)(__dirname, "..", "..");
|
|
70995
71463
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
70996
|
-
if ((0,
|
|
70997
|
-
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();
|
|
70998
71466
|
return null;
|
|
70999
71467
|
}
|
|
71000
71468
|
registerQueryCommands(program2);
|
|
@@ -71135,10 +71603,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
71135
71603
|
targets = resolution.targets;
|
|
71136
71604
|
}
|
|
71137
71605
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
71138
|
-
const fileMatrix = (0,
|
|
71606
|
+
const fileMatrix = (0, import_node_fs65.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs65.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
71139
71607
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
71140
71608
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
71141
|
-
const sanctioned = (0,
|
|
71609
|
+
const sanctioned = (0, import_node_fs65.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs65.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
71142
71610
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
71143
71611
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
71144
71612
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -71169,16 +71637,16 @@ function directoryBytes(path2) {
|
|
|
71169
71637
|
let total = 0;
|
|
71170
71638
|
let entries;
|
|
71171
71639
|
try {
|
|
71172
|
-
entries = (0,
|
|
71640
|
+
entries = (0, import_node_fs65.readdirSync)(path2, { withFileTypes: true });
|
|
71173
71641
|
} catch {
|
|
71174
71642
|
return 0;
|
|
71175
71643
|
}
|
|
71176
71644
|
for (const entry of entries) {
|
|
71177
|
-
const child2 = (0,
|
|
71645
|
+
const child2 = (0, import_node_path62.join)(path2, entry.name);
|
|
71178
71646
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
71179
71647
|
else {
|
|
71180
71648
|
try {
|
|
71181
|
-
total += (0,
|
|
71649
|
+
total += (0, import_node_fs65.statSync)(child2).size;
|
|
71182
71650
|
} catch {
|
|
71183
71651
|
}
|
|
71184
71652
|
}
|
|
@@ -71186,25 +71654,25 @@ function directoryBytes(path2) {
|
|
|
71186
71654
|
return total;
|
|
71187
71655
|
}
|
|
71188
71656
|
function listDirEntries(dir) {
|
|
71189
|
-
return (0,
|
|
71657
|
+
return (0, import_node_fs65.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
71190
71658
|
}
|
|
71191
71659
|
function readInstalledPluginRefs(configRoot) {
|
|
71192
71660
|
const p = installedPluginsPathForConfig(configRoot);
|
|
71193
|
-
if (!(0,
|
|
71661
|
+
if (!(0, import_node_fs65.existsSync)(p)) return [];
|
|
71194
71662
|
try {
|
|
71195
|
-
return installedPluginPaths((0,
|
|
71663
|
+
return installedPluginPaths((0, import_node_fs65.readFileSync)(p, "utf8"));
|
|
71196
71664
|
} catch {
|
|
71197
71665
|
return null;
|
|
71198
71666
|
}
|
|
71199
71667
|
}
|
|
71200
71668
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
71201
71669
|
return {
|
|
71202
|
-
exists: (p) => (0,
|
|
71203
|
-
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),
|
|
71204
71672
|
dirBytes,
|
|
71205
|
-
listStagingDirs: (root) => (0,
|
|
71673
|
+
listStagingDirs: (root) => (0, import_node_fs65.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
71206
71674
|
try {
|
|
71207
|
-
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) };
|
|
71208
71676
|
} catch {
|
|
71209
71677
|
return { name: d.name, mtimeMs: Date.now() };
|
|
71210
71678
|
}
|
|
@@ -71218,10 +71686,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
71218
71686
|
return {
|
|
71219
71687
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
71220
71688
|
mtimeMs: (name) => {
|
|
71221
|
-
const p = (0,
|
|
71222
|
-
if (!(0,
|
|
71689
|
+
const p = (0, import_node_path62.join)(stagingRoot, name);
|
|
71690
|
+
if (!(0, import_node_fs65.existsSync)(p)) return null;
|
|
71223
71691
|
try {
|
|
71224
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
71692
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs65.statSync)(q).mtimeMs);
|
|
71225
71693
|
} catch {
|
|
71226
71694
|
return null;
|
|
71227
71695
|
}
|
|
@@ -71241,13 +71709,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
71241
71709
|
return;
|
|
71242
71710
|
}
|
|
71243
71711
|
const plan = buildPluginCachePlan(
|
|
71244
|
-
(0,
|
|
71712
|
+
(0, import_node_os28.homedir)(),
|
|
71245
71713
|
running,
|
|
71246
71714
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
71247
71715
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
71248
71716
|
);
|
|
71249
71717
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
71250
|
-
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;
|
|
71251
71719
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
71252
71720
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
71253
71721
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -71257,7 +71725,7 @@ program2.command("archive-prune").description("inventory worktree evidence archi
|
|
|
71257
71725
|
let primary = o.repoRoot;
|
|
71258
71726
|
if (!primary) {
|
|
71259
71727
|
const common = await gitOut(["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
71260
|
-
primary = common ? (0,
|
|
71728
|
+
primary = common ? (0, import_node_path62.dirname)(common) : "";
|
|
71261
71729
|
}
|
|
71262
71730
|
if (!primary) {
|
|
71263
71731
|
const detail = "archive-prune: not inside a git checkout (pass --repo-root <primary checkout>)";
|