@mutmutco/codex-plugin 4.5.94 → 4.5.96
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/.codex-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +48 -24
- package/cli/package.json +1 -1
- package/package.json +1 -1
package/cli/dist/main.cjs
CHANGED
|
@@ -7460,11 +7460,11 @@ function workflowEntry(repo, workflowPath, yamlText) {
|
|
|
7460
7460
|
};
|
|
7461
7461
|
}
|
|
7462
7462
|
var DECLARED_ENTRIES = [
|
|
7463
|
-
{ name: "mmi-backup (mm-central, mm-work
|
|
7463
|
+
{ name: "mmi-backup (mm-central, mm-work)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh; every Docker volume is tarred and every running Postgres container is pg_dumpall'd beside it (#7147; source scripts/nightly-backup.sh); owner-approved mmi-oguz rebuild as CI-only mmi-runner2 with full erasure and no backups (#7859); zuber (46.225.58.31) is gone: deleted 2026-09-18 by the owner-approved ZuberShade decommission (MMC-ZuberShade#1838, snapshot kept); Hetzner has since reassigned that IP to another machine (#7351)" },
|
|
7464
7464
|
// #7148: a backup nobody restored is a hope. Monthly, restore the latest artifact of every
|
|
7465
7465
|
// archived stage (including a stage whose containers are down) into scratch Postgres and compare
|
|
7466
7466
|
// row counts, and read-verify every storage archive against its listing; per-stage PASS/FAIL.
|
|
7467
|
-
{ name: "mmi-backup-restore-check (mm-central, mm-work
|
|
7467
|
+
{ name: "mmi-backup-restore-check (mm-central, mm-work)", cadence: "0 3 1 * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup-restore-check \u2192 /opt/mmi-control/nightly-backup-restore-check.sh; monthly restore proof for every archived stage (#7148; source scripts/nightly-backup-restore-check.sh); owner-approved mmi-oguz rebuild as CI-only mmi-runner2 with full erasure and no backups (#7859); zuber (46.225.58.31) is gone: deleted 2026-09-18 by the owner-approved ZuberShade decommission (MMC-ZuberShade#1838, snapshot kept); Hetzner has since reassigned that IP to another machine (#7351)" },
|
|
7468
7468
|
// #7662 (MM-Work#1520): Vercel Cron's 15-minute central mail call, moved to the box with MM-Work. The app
|
|
7469
7469
|
// calls its own endpoint with its own CRON_SECRET via docker exec, so the secret stays in the container.
|
|
7470
7470
|
{ name: "mm-work-mail-tick (mm-work)", cadence: "*/15 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mm-work-mail-tick \u2192 /opt/mmi-control/mm-work-mail-tick.sh; GET /api/internal/mail per stage listed in MM_WORK_MAIL_STAGES (source scripts/mm-work-mail-tick.sh; log /var/log/mm-work-mail-tick.log)" },
|
|
@@ -7472,10 +7472,11 @@ var DECLARED_ENTRIES = [
|
|
|
7472
7472
|
// setup-node before it can prune, so at 100% it dies at checkout and never prunes — the cleanup is
|
|
7473
7473
|
// unavailable exactly when it is needed. This one is root cron on the box: no checkout, no node, and
|
|
7474
7474
|
// a no-op below 90% disk, which is why 15 minutes is affordable.
|
|
7475
|
-
{ name: "mmi-runner-hygiene-oob (mmi-runner)", cadence: "*/15 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-hygiene \u2192 /opt/mmi-control/runner-hygiene-oob.sh; routine docker/cache prunes every
|
|
7475
|
+
{ name: "mmi-runner-hygiene-oob (mmi-runner, mmi-runner2)", cadence: "*/15 * * * * + 5-59/15,10-59/15 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-hygiene \u2192 /opt/mmi-control/runner-hygiene-oob.sh; routine docker/cache prunes every 15 min, relief-only pressure checks between, _work relief at >=90% disk or <8 GiB free; second-host rollout #7859 (verify each box via mmi-cli devops runtime box get <box> --ssh)" },
|
|
7476
7476
|
// #4118: Listening + systemd-active ≠ GitHub online after acquirejob 503. restart.conf cannot see
|
|
7477
7477
|
// it; an Actions workflow on mmi-live cannot heal it when half the fleet is offline. Root cron.
|
|
7478
7478
|
{ name: "mmi-runner-online-reconcile (mmi-runner)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; API-offline + _diag acquirejob, max 2 restarts/tick (verify: mmi-cli devops runtime box get mmi-runner --ssh)" },
|
|
7479
|
+
{ name: "mmi-runner-online-reconcile (mmi-runner2)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; scripts/runner2-online-reconcile.cron selects lanes 13-16, disables credential files, _diag acquirejob only, max 2 restarts/tick; rollout #7859 (verify: mmi-cli devops runtime box get mmi-runner2 --ssh)" },
|
|
7479
7480
|
// #5292: a lane that holds a dispatched JOB but produces zero output past the budget honestly
|
|
7480
7481
|
// reports "busy", so the online reconcile never fires and GitHub will not re-dispatch while the
|
|
7481
7482
|
// run holds the assignment. This tick cancels + re-triggers those runs (max 1/tick, debounced).
|
|
@@ -7483,7 +7484,7 @@ var DECLARED_ENTRIES = [
|
|
|
7483
7484
|
// #3852: the runner /tmp sweep — daily inode hygiene (03:41 UTC), deliberately the one clutter path the
|
|
7484
7485
|
// out-of-band disk-relief script must never touch. Verified installed 2026-08-18: the cron.d file and
|
|
7485
7486
|
// flock line match scripts/runner-tmp-sweep.cron exactly.
|
|
7486
|
-
{ name: "mmi-runner-tmp-sweep (mmi-runner)", cadence: "41 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-tmp-sweep \u2192 /opt/mmi-control/runner-tmp-sweep.sh; hourly inode hygiene, 6h age (#6274), flock -n (verify
|
|
7487
|
+
{ name: "mmi-runner-tmp-sweep (mmi-runner, mmi-runner2)", cadence: "41 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-tmp-sweep \u2192 /opt/mmi-control/runner-tmp-sweep.sh; hourly inode hygiene, 6h age (#6274), flock -n; second-host rollout #7859 (verify each box via mmi-cli devops runtime box get <box> --ssh)" }
|
|
7487
7488
|
// RETIRED 2026-08-18 (MMI-Hub#5234): `zuber-bake` (every 30 min) and `zuber-bake-full` (daily 00:30 UTC)
|
|
7488
7489
|
// were declared as zuber systemd timers. The ZuberShade full park (#1774, 2026-08-06) stopped them: on
|
|
7489
7490
|
// the box (46.225.58.31) zuber-bake.timer / zuber-bake-full.timer answer not-found and
|
|
@@ -17255,10 +17256,10 @@ var rollout_plan_default = {
|
|
|
17255
17256
|
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)."
|
|
17256
17257
|
},
|
|
17257
17258
|
baseline: {
|
|
17258
|
-
version: "4.5.
|
|
17259
|
-
tag: "v4.5.
|
|
17260
|
-
commit: "
|
|
17261
|
-
npm: "@mutmutco/cli@4.5.
|
|
17259
|
+
version: "4.5.96",
|
|
17260
|
+
tag: "v4.5.96",
|
|
17261
|
+
commit: "191fc3cf3396",
|
|
17262
|
+
npm: "@mutmutco/cli@4.5.96"
|
|
17262
17263
|
},
|
|
17263
17264
|
exitCriterion: "fleet-n-of-n",
|
|
17264
17265
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17275,14 +17276,14 @@ var rollout_plan_default = {
|
|
|
17275
17276
|
repo: "mutmutco/mmi-hub",
|
|
17276
17277
|
role: "canary",
|
|
17277
17278
|
schedule: "train",
|
|
17278
|
-
v3Target: "v4.5.
|
|
17279
|
+
v3Target: "v4.5.96"
|
|
17279
17280
|
}
|
|
17280
17281
|
],
|
|
17281
17282
|
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.",
|
|
17282
17283
|
rollback: {
|
|
17283
17284
|
independent: true,
|
|
17284
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17285
|
-
v3Target: "v4.5.
|
|
17285
|
+
mechanism: "npm dist-tag latest -> 4.5.96 and redeploy the Hub Lambda from tag v4.5.96 (191fc3cf3396); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17286
|
+
v3Target: "v4.5.96 (@mutmutco/cli@4.5.96, tag commit 191fc3cf3396 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17286
17287
|
}
|
|
17287
17288
|
},
|
|
17288
17289
|
{
|
|
@@ -36127,6 +36128,21 @@ async function suiteMarkAbandoned(deps, ownerGo = false) {
|
|
|
36127
36128
|
for (const t of tags) await deps.markAbandoned(t.repo, t.tag, t.sha);
|
|
36128
36129
|
return tags.map((t) => `${t.repo}@${t.tag}`);
|
|
36129
36130
|
}
|
|
36131
|
+
async function retireFailedLeadRcLedger(deps, ownerGo) {
|
|
36132
|
+
if (!(0, import_node_fs29.existsSync)(deps.ledgerPath)) return void 0;
|
|
36133
|
+
return withLedgerLock(deps.ledgerPath, async (fence) => {
|
|
36134
|
+
const ledger = parseReleaseLedger((0, import_node_fs29.readFileSync)(deps.ledgerPath, "utf8"));
|
|
36135
|
+
const states = Object.values(ledger.phases).map((p) => p.state);
|
|
36136
|
+
if (ledger.repo !== SUITE_REPO || ledger.lane !== "rcand" || ledger.phases.promotion.state === "complete" || !states.includes("failed")) return void 0;
|
|
36137
|
+
if (!ownerGo) throw new Error(`the lead's failed rcand ledger for ${ledger.tag} blocks every train; retire it with suite abandon --owner-go`);
|
|
36138
|
+
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`);
|
|
36139
|
+
const archive = (0, import_node_path29.join)((0, import_node_path29.dirname)(deps.ledgerPath), `phases.archive-${ledger.tag}.json`);
|
|
36140
|
+
if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
|
|
36141
|
+
await deps.markAbandoned(SUITE_REPO, ledger.tag, ledger.tagSha);
|
|
36142
|
+
await fence(() => (0, import_node_fs29.renameSync)(deps.ledgerPath, archive));
|
|
36143
|
+
return `${ledger.tag} (ledger archived to ${archive})`;
|
|
36144
|
+
});
|
|
36145
|
+
}
|
|
36130
36146
|
function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Date()) {
|
|
36131
36147
|
const record = abandonableCut(recordPath, ownerGo);
|
|
36132
36148
|
const archive = `${recordPath}.abandoned-${record.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
|
|
@@ -37000,8 +37016,8 @@ function bothSidesText(portFailures, portLaneShas) {
|
|
|
37000
37016
|
if (rows.length === 0) return "";
|
|
37001
37017
|
return `BEFORE that --ack: ${rows.map((r) => `${r.label} also conflicts on ${r.paths.join(", ")}`).join("; ")}, which exist on both branches \u2014 the port resolves them by hand for main, so land the SAME resolution on development first (the --ack covers the whole commit, and the next release would otherwise overwrite or clash with the main-only content). `;
|
|
37002
37018
|
}
|
|
37003
|
-
function formatHotfixBatchPreflightRefusal(failures, fromSpecs, portLaneShas = /* @__PURE__ */ new Map()) {
|
|
37004
|
-
const lines2 = failures.map((f) => ` - ${f.label}: ${f.blocking.join(", ")}`);
|
|
37019
|
+
function formatHotfixBatchPreflightRefusal(failures, fromSpecs, portLaneShas = /* @__PURE__ */ new Map(), context = { sources: failures, ackedShas: [] }) {
|
|
37020
|
+
const lines2 = failures.map((f) => ` - ${f.label}: ${f.blocking.join(", ")}` + (f.afterAbortedPicks?.length ? ` (observed after aborted pick(s): ${f.afterAbortedPicks.join(", ")}; may depend on those missing changes)` : ""));
|
|
37005
37021
|
const portFailures = failures.filter((f) => portLaneShas.has(f.sha));
|
|
37006
37022
|
const synthFailures = failures.filter((f) => !portLaneShas.has(f.sha));
|
|
37007
37023
|
const head = `hotfix start batch preflight refused: ${failures.length} pick(s) would hard-stop on origin/main:
|
|
@@ -37009,15 +37025,15 @@ ${lines2.join("\n")}
|
|
|
37009
37025
|
`;
|
|
37010
37026
|
const batchFrom = fromSpecs.join(",");
|
|
37011
37027
|
const guide = "https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#hotfix-pick-refused";
|
|
37012
|
-
|
|
37013
|
-
|
|
37014
|
-
|
|
37015
|
-
|
|
37016
|
-
|
|
37017
|
-
|
|
37018
|
-
|
|
37019
|
-
${
|
|
37020
|
-
|
|
37028
|
+
const replaceNow = failures.filter((f) => !f.afterAbortedPicks?.length);
|
|
37029
|
+
const replacements = new Map(replaceNow.map((f, i) => [f.sha, `<replacementSHA${i + 1}>`]));
|
|
37030
|
+
const picks = context.sources.map((s) => replacements.get(s.sha) ?? s.sha).join(",");
|
|
37031
|
+
const exclusions = [.../* @__PURE__ */ new Set([...context.ackedShas, ...replaceNow.map((f) => f.sha)])].join(",");
|
|
37032
|
+
const mapping = replaceNow.map((f) => `${replacements.get(f.sha)} replaces ${f.label} (${f.sha})`).join("; ");
|
|
37033
|
+
const dependencyNote = failures.some((f) => f.afterAbortedPicks?.length) ? "Later failures were observed after earlier picks were aborted. Keep those original SHAs in the retry. Re-preflight the complete repaired batch before treating a later path absent on static origin/main as an independent port requirement. If a later pick still fails, replacing it requires separate approval for its exact original SHA. " : "";
|
|
37034
|
+
const portText = portFailures.length > 0 ? `Pick(s) ${portFailures.map((f) => f.label).join(", ")} have path(s) ABSENT on origin/main but present on origin/development. If they still fail after prerequisite repairs, use the main-based PORT-COMMIT lane: cut a branch FROM origin/main, add the missing paths and fix, push it and keep it pushed until the release \u2014 never merge it into development. Record the port SHA in the hotfix PR body. The NEXT rc\u2192main release must pass \`--ack <the hotfix's main commit>\` (hotfix-coverage). ` + bothSidesText(portFailures, portLaneShas) : "";
|
|
37035
|
+
const repairText = synthFailures.length > 0 ? `Pick(s) ${synthFailures.map((f) => f.label).join(", ")} need a development repair PR: cut FROM origin/development and land the conflict correction through normal CI. Its picked commit must carry the required main-clean delta; merely citing the original fixes carries no content and grants no coverage exemption. If that delta cannot be produced on development, stop and ask the owner for a scoped port plan. ` : "";
|
|
37036
|
+
return `${head}${portFailures.length > 0 && synthFailures.length > 0 ? "Mixed batch. " : ""}${dependencyNote}${portText}${repairText}Original ordered --from: ${batchFrom}. Replacement mapping: ${mapping}. Keep every passing pick in its original position and retain existing approved exclusions. Additional owner approval required for replacing/excluding original development commit(s): ${replaceNow.map((f) => f.sha).join(",")}. No cite-only marker is implemented; mmi-hotfix-carries is release verification only. Only after that explicit approval, replace the placeholders and rerun \`mmi-cli devops hotfix start --from ${picks} --ack ${exclusions}\`. Re-preflight the whole batch. Never hand-resolve onto main or merge a main-parented branch into development; see ${guide}`;
|
|
37021
37037
|
}
|
|
37022
37038
|
var CHERRY_PICKED_FROM = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
37023
37039
|
async function scanHotfixPickGaps(deps, sources, tolerated, rcRef) {
|
|
@@ -37107,7 +37123,8 @@ async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
|
|
|
37107
37123
|
failures.push({
|
|
37108
37124
|
label: source.label,
|
|
37109
37125
|
sha: source.sha,
|
|
37110
|
-
blocking: blocking.length > 0 ? blocking : [message2.split("\n")[0] ?? message2]
|
|
37126
|
+
blocking: blocking.length > 0 ? blocking : [message2.split("\n")[0] ?? message2],
|
|
37127
|
+
...failures.length > 0 ? { afterAbortedPicks: failures.map((f) => f.label) } : {}
|
|
37111
37128
|
});
|
|
37112
37129
|
}
|
|
37113
37130
|
}
|
|
@@ -37322,7 +37339,7 @@ async function runHotfixStartLocked(deps, options, doctor) {
|
|
|
37322
37339
|
const preflight2 = await preflightHotfixBatchPicks(deps, sources, pickTolerated);
|
|
37323
37340
|
if (preflight2.failures.length > 0) {
|
|
37324
37341
|
const portLaneShas = await classifyHotfixPortLanePicks(deps, preflight2.failures);
|
|
37325
|
-
throw new Error(formatHotfixBatchPreflightRefusal(preflight2.failures, specs, portLaneShas));
|
|
37342
|
+
throw new Error(formatHotfixBatchPreflightRefusal(preflight2.failures, specs, portLaneShas, { sources, ackedShas }));
|
|
37326
37343
|
}
|
|
37327
37344
|
if (preflight2.skipped.length > 0) {
|
|
37328
37345
|
const skippedShas = new Set(preflight2.skipped.map((s) => s.sha));
|
|
@@ -47480,8 +47497,14 @@ function registerSuiteCommands(program3) {
|
|
|
47480
47497
|
let result;
|
|
47481
47498
|
let restored;
|
|
47482
47499
|
let marked;
|
|
47500
|
+
let retired;
|
|
47483
47501
|
try {
|
|
47484
47502
|
const io = suiteTrainIo(root);
|
|
47503
|
+
retired = await retireFailedLeadRcLedger({ ledgerPath: releaseLedgerPath(root), rcHead: () => io.head(SUITE_REPO, "rc").then((h) => h ?? void 0), markAbandoned: io.markAbandoned }, o.ownerGo === true);
|
|
47504
|
+
if (retired && !(0, import_node_fs44.existsSync)(io.recordPath)) {
|
|
47505
|
+
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)`);
|
|
47506
|
+
return;
|
|
47507
|
+
}
|
|
47485
47508
|
restored = o.ownerGo === true ? await suiteRestoreMainPins(io) : void 0;
|
|
47486
47509
|
marked = await suiteMarkAbandoned(io, o.ownerGo === true);
|
|
47487
47510
|
result = suiteAbandon(io.recordPath, o.ownerGo === true);
|
|
@@ -47492,6 +47515,7 @@ function registerSuiteCommands(program3) {
|
|
|
47492
47515
|
`suite abandon: archived the cut record to ${result.archive}`,
|
|
47493
47516
|
...result.orphanTags.length ? [" member rc tags left in place (immutable; the next cut reuses one still unreleased on its rc head, #7450):", ...result.orphanTags.map((t) => ` - ${t}`)] : [" no member rc tag was minted"],
|
|
47494
47517
|
...restored ? [` main pins the cut planned for unreleased tags are restored by ${restored.pr} (${restored.status}, #7758)`] : [],
|
|
47518
|
+
...retired ? [` retired the lead's failed, never-promoted rc candidate ${retired} (#7854)`] : [],
|
|
47495
47519
|
...marked.length ? [` marked abandoned on origin, so the hotfix guard stops counting them as active (#7801): ${marked.join(", ")}`] : [],
|
|
47496
47520
|
" the next suite rcand --owner-go re-snapshots every member"
|
|
47497
47521
|
].join("\n"));
|
package/cli/package.json
CHANGED