@mutmutco/cli 4.3.60 → 4.3.62

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.
Files changed (3) hide show
  1. package/README.md +4 -0
  2. package/dist/main.cjs +174 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -85,4 +85,8 @@ node cli/dist/index.cjs --version
85
85
  node cli/dist/index.cjs doctor --json
86
86
  ```
87
87
 
88
+ When reproducing or verifying CLI behavior from a checkout, always run the checkout's built
89
+ `node cli/dist/index.cjs` — the globally installed `mmi-cli` can lag or diverge from this source
90
+ (#6629), and a reproduction against the global install misreports the fix you are validating.
91
+
88
92
  The Claude Code plugin also ships its own `bin/mmi-cli` launcher, so plugin-enabled Bash sessions can use the bundled command even without a global npm install. Codex and other non-Claude surfaces use the standalone npm package or this repo-local fallback.
package/dist/main.cjs CHANGED
@@ -12070,12 +12070,15 @@ async function checkLaneContest(client, item, actor = describeSessionIdentity(),
12070
12070
  resume
12071
12071
  };
12072
12072
  }
12073
- function laneContestMessage(ref, contest, verb) {
12073
+ function laneContestParts(contest) {
12074
12074
  const holder = contest.marker ? `lane ${describeClaimMarker(contest.marker)}` : contest.unverifiable.includes("comments") ? "an unreadable lane (its claim-marker comments could not be read, so no holder can be named)" : "another lane";
12075
12075
  const seen = contest.live.join("; ");
12076
12076
  const unreadable = contest.unverifiable.length ? `liveness could not be fully verified (failed to read: ${contest.unverifiable.join(", ")})` : "";
12077
- const why = [seen, unreadable].filter(Boolean).join("; ");
12078
- return verb === "claim" ? `${ref} is already claimed by ${holder} with live evidence of active work (${why}); re-run with --force to take it anyway` : `refusing to unclaim ${ref}: ${holder} shows live evidence of active work (${why}); re-run with --force to release it anyway`;
12077
+ return { holder, why: [seen, unreadable].filter(Boolean).join("; ") };
12078
+ }
12079
+ function laneContestMessage(ref, contest, verb) {
12080
+ const { holder, why } = laneContestParts(contest);
12081
+ return verb === "claim" ? `${ref} is already claimed by ${holder} with live evidence of active work (${why}); re-run with --force to take it anyway` : `refusing to unclaim ${ref}: ${holder} shows live evidence of active work (${why}); re-run with --force --holder-dead once you have evidence the holder lane is dead, or wait for it to go stale (#6625)`;
12079
12082
  }
12080
12083
  function ghError(e) {
12081
12084
  const err = e;
@@ -12633,9 +12636,19 @@ async function unclaimBoardIssue(options, deps = {}) {
12633
12636
  throw boardNotFoundError(`${selector.repo}#${selector.number}`, { owner: cfg.projectOwner, number: cfg.projectNumber });
12634
12637
  }
12635
12638
  const toStatus = options.toStatus ?? "Todo";
12636
- if (!options.force && item.contentType === "Issue") {
12639
+ if (item.contentType === "Issue") {
12637
12640
  const contest = await checkLaneContest(client, item);
12638
- if (contest.contested) throw new Error(withGatedWriteChainHint(laneContestMessage(item.ref, contest, "unclaim")));
12641
+ if (contest.contested) {
12642
+ if (!options.force) {
12643
+ throw new Error(withGatedWriteChainHint(laneContestMessage(item.ref, contest, "unclaim")));
12644
+ }
12645
+ if (!options.holderDead) {
12646
+ const { holder, why } = laneContestParts(contest);
12647
+ throw new Error(withGatedWriteChainHint(
12648
+ `refusing to unclaim ${item.ref} with --force: ${holder} still shows live evidence of active work (${why}); --force no longer overrides a live lane alone \u2014 re-run with --holder-dead once you have evidence the holder's session is dead, or wait for its lane to go stale (#6625)`
12649
+ ));
12650
+ }
12651
+ }
12639
12652
  }
12640
12653
  try {
12641
12654
  await client.rest("DELETE", `repos/${item.repository}/issues/${item.number}/assignees`, {
@@ -15788,10 +15801,10 @@ var rollout_plan_default = {
15788
15801
  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)."
15789
15802
  },
15790
15803
  baseline: {
15791
- version: "4.3.60",
15792
- tag: "v4.3.60",
15793
- commit: "7602c0b24dc9",
15794
- npm: "@mutmutco/cli@4.3.60"
15804
+ version: "4.3.62",
15805
+ tag: "v4.3.62",
15806
+ commit: "d664b30d09d7",
15807
+ npm: "@mutmutco/cli@4.3.62"
15795
15808
  },
15796
15809
  exitCriterion: "fleet-n-of-n",
15797
15810
  hubOnlyShortcut: "forbidden",
@@ -15808,14 +15821,14 @@ var rollout_plan_default = {
15808
15821
  repo: "mutmutco/mmi-hub",
15809
15822
  role: "canary",
15810
15823
  schedule: "train",
15811
- v3Target: "v4.3.60"
15824
+ v3Target: "v4.3.62"
15812
15825
  }
15813
15826
  ],
15814
15827
  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.",
15815
15828
  rollback: {
15816
15829
  independent: true,
15817
- mechanism: "npm dist-tag latest -> 4.3.60 and redeploy the Hub Lambda from tag v4.3.60 (7602c0b24dc9); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15818
- v3Target: "v4.3.60 (@mutmutco/cli@4.3.60, tag commit 7602c0b24dc9 \u2014 last known-good release carrying the repo-index v4-only contract)"
15830
+ mechanism: "npm dist-tag latest -> 4.3.62 and redeploy the Hub Lambda from tag v4.3.62 (d664b30d09d7); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15831
+ v3Target: "v4.3.62 (@mutmutco/cli@4.3.62, tag commit d664b30d09d7 \u2014 last known-good release carrying the repo-index v4-only contract)"
15819
15832
  }
15820
15833
  },
15821
15834
  {
@@ -21451,6 +21464,69 @@ async function supersedeContentCausedDeployLeg(deps, cwd) {
21451
21464
  };
21452
21465
  });
21453
21466
  }
21467
+ function supersedeablePublishLegShape(rec) {
21468
+ return rec.state === "failed" || rec.state === "pending" && rec.runId == null;
21469
+ }
21470
+ async function supersedeContentCausedPublishLeg(deps, cwd) {
21471
+ const path2 = releaseLedgerPath(cwd);
21472
+ return withFileLock(`${path2}.lock`, { label: "release ledger" }, async () => {
21473
+ let prior;
21474
+ try {
21475
+ prior = parseReleaseLedger((0, import_node_fs21.readFileSync)(path2, "utf8"));
21476
+ } catch (e) {
21477
+ throw new ReleaseLedgerError(
21478
+ "unreadable",
21479
+ `release --supersede-publish: the active release ledger at ${path2} could not be read (${e instanceof Error ? e.message : String(e)}) \u2014 resolve it by hand before any train; nothing was written (#6638)`
21480
+ );
21481
+ }
21482
+ if (prior.phases.publish.superseded === true) {
21483
+ throw new Error(`release --supersede-publish: the ${prior.tag} publish leg is already superseded \u2014 nothing to do; the next train runs normally`);
21484
+ }
21485
+ if (!supersedeablePublishLegShape(prior.phases.publish)) {
21486
+ throw new Error(
21487
+ `release --supersede-publish: the ${prior.tag} publish leg is ${prior.phases.publish.state}` + (prior.phases.publish.runId != null ? ` with run ${prior.phases.publish.runId} \u2014 a run-backed leg is resolved by watching/reverifying its run, never superseded` : "") + "; the supersession closes only a FAILED publish leg, or a PENDING one no run correlates (the uncorrelatable historical shape); nothing was written (#6638)"
21488
+ );
21489
+ }
21490
+ const phases = { ...prior.phases };
21491
+ if (phases.alignment.state === "pending") {
21492
+ const closed = await alignmentLegClosedLive(deps, prior);
21493
+ if (closed) phases.alignment = closed;
21494
+ }
21495
+ const unresolved = ["promotion", "ordinaryCi", "githubRelease", "deploy", "alignment"].filter((phase) => phases[phase].state === "pending" || phases[phase].state === "failed");
21496
+ if (unresolved.length > 0) {
21497
+ throw new Error(
21498
+ `release --supersede-publish: ${prior.tag} has unresolved legs besides publish (${unresolved.map((p) => `${p}=${phases[p].state}`).join(", ")}) \u2014 the supersession closes only the publish leg of an otherwise-complete release; resolve those first, nothing was written (#6638)`
21499
+ );
21500
+ }
21501
+ let liveSha = "";
21502
+ try {
21503
+ liveSha = (await deps.run("git", ["ls-remote", "origin", `refs/tags/${prior.tag}`])).trim().split(/\s+/)[0] ?? "";
21504
+ } catch (e) {
21505
+ throw new Error(`release --supersede-publish: could not prove ${prior.tag} live on origin (${e instanceof Error ? e.message : String(e)}) \u2014 refusing to close the leg on an unproven state; retry when origin answers (#6638)`);
21506
+ }
21507
+ if (liveSha.toLowerCase() !== prior.tagSha.toLowerCase()) {
21508
+ throw new Error(
21509
+ `release --supersede-publish: origin reports ${prior.tag} at ${liveSha.slice(0, 12) || "no tag"} but the ledger anchors ${prior.tagSha.slice(0, 12)} \u2014 an absent or divergent release is NOT supersedeable (release --abort --apply is the unpublished candidate's own exit); nothing was written (#6638)`
21510
+ );
21511
+ }
21512
+ const note = `superseded: content-caused publish failure on the immutable promoted ${prior.tag} \u2014 the cause fix rides the next patch train (master-approved closure, #6638)`;
21513
+ atomicWriteJson(path2, {
21514
+ ...prior,
21515
+ phases: {
21516
+ ...phases,
21517
+ publish: { state: "skipped", sha: prior.tagSha, superseded: true, note, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
21518
+ },
21519
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
21520
+ });
21521
+ return {
21522
+ command: "release-supersede-publish",
21523
+ repo: prior.repo,
21524
+ tag: prior.tag,
21525
+ tagSha: prior.tagSha,
21526
+ note: `closed the ${prior.tag} publish leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm, then run the next patch train (hotfix or release) to carry the cause fix`
21527
+ };
21528
+ });
21529
+ }
21454
21530
  async function reverifyPriorRunLegs(deps, path2, targetTag) {
21455
21531
  let prior;
21456
21532
  try {
@@ -23243,7 +23319,9 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
23243
23319
  if (persisted.phases.deploy.superseded !== true) {
23244
23320
  await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(phaseInputs.deploy), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
23245
23321
  }
23246
- await recordPhase(ledger, deps, anchors, "publish", phaseEntry(phaseInputs.publish), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
23322
+ if (persisted.phases.publish.superseded !== true) {
23323
+ await recordPhase(ledger, deps, anchors, "publish", phaseEntry(phaseInputs.publish), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
23324
+ }
23247
23325
  await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified live: the GitHub Release exists at the immutable tag SHA" }), { strict: true, landed: "the promotion (verified live)" });
23248
23326
  await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified live: origin/main contains the immutable tag SHA" }), { strict: true, landed: "nothing beyond the already-public release" });
23249
23327
  }
@@ -23524,6 +23602,17 @@ async function runReleaseSupersedeDeploy(deps, options = {}) {
23524
23602
  throw new Error(message2.includes("nothing was written") || message2.includes("nothing to do") ? message2 : `${message2}; nothing was written`);
23525
23603
  }
23526
23604
  }
23605
+ async function runReleaseSupersedePublish(deps, options = {}) {
23606
+ if (!options.approved) {
23607
+ throw new Error("release --supersede-publish requires --apply after explicit master approval; nothing was written");
23608
+ }
23609
+ try {
23610
+ return await supersedeContentCausedPublishLeg(deps, process.cwd());
23611
+ } catch (e) {
23612
+ const message2 = e instanceof Error ? e.message : String(e);
23613
+ throw new Error(message2.includes("nothing was written") || message2.includes("nothing to do") ? message2 : `${message2}; nothing was written`);
23614
+ }
23615
+ }
23527
23616
  async function runReleasePublishRetry(deps, runId, options = {}) {
23528
23617
  if (!options.approved) {
23529
23618
  throw new Error("release --retry-publish requires --apply after explicit approval; nothing was written");
@@ -26318,7 +26407,7 @@ function registerBoardCommands(program3) {
26318
26407
  return failGraceful(`board set-priority failed: ${e.message}`);
26319
26408
  }
26320
26409
  });
26321
- board.command("unclaim <issue>").description("clear the assignee and reset the board Status (defaults to Todo) \u2014 the inverse of claim").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--to-status <status>", `set Status after unclaim (default Todo; one of ${BOARD_STATUSES.join(", ")})`).option("--force", "release the claim even when another session shows live evidence of active work (#3727)").option("--allow-partial", "return success JSON if the assignee clear or status move fails").action(async (issueRef, o) => {
26410
+ board.command("unclaim <issue>").description("clear the assignee and reset the board Status (defaults to Todo) \u2014 the inverse of claim").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--to-status <status>", `set Status after unclaim (default Todo; one of ${BOARD_STATUSES.join(", ")})`).option("--force", "release a claim whose lane has gone stale or shows no live evidence (#3727); a lane that still shows live evidence also needs --holder-dead (#6625)").option("--holder-dead", "acknowledge the holding lane is known dead \u2014 required with --force when the holder still shows live evidence (#6625)").option("--allow-partial", "return success JSON if the assignee clear or status move fails").action(async (issueRef, o) => {
26322
26411
  const toStatus = o.toStatus ? resolveBoardStatus(o.toStatus) : void 0;
26323
26412
  try {
26324
26413
  const result = await unclaimBoardIssue({
@@ -26327,6 +26416,7 @@ function registerBoardCommands(program3) {
26327
26416
  repo: o.repo,
26328
26417
  toStatus,
26329
26418
  force: o.force,
26419
+ holderDead: o.holderDead,
26330
26420
  allowPartial: o.allowPartial
26331
26421
  });
26332
26422
  if (o.json) return console.log(JSON.stringify(result));
@@ -33190,19 +33280,23 @@ function findCommandInManifest(manifest, commandPath3) {
33190
33280
  return visit(manifest.tree);
33191
33281
  }
33192
33282
  function registerExplainCommand(program3) {
33193
- program3.command("explain").description("print a command's purpose, flags, and examples from the live schema \u2014 one grounding call replaces trial-and-error").argument("[command...]", 'the command path to explain (e.g. "issue create")').addOption(new Option("--loop <name>", "print the canonical command sequence: agent | start-work | ship-pr | hotfix").choices(Object.keys(LOOP_PLAYBOOKS))).option("--json", "machine-readable focused command, route, or loop detail").option("--recursive", "include full schemas for immediate children (default: compact child index)").option("--full", "alias for --recursive").action((commandArgs, opts) => {
33283
+ program3.command("explain").description("print a command's purpose, flags, and examples from the live schema \u2014 one grounding call replaces trial-and-error").argument("[command...]", 'the command path to explain (e.g. "issue create")').addOption(new Option("--loop <name>", "print the canonical command sequence: agent | start-work | ship-pr | hotfix").choices(Object.keys(LOOP_PLAYBOOKS))).option("--json", "machine-readable focused command, route, or loop detail").option("--out <path>", "write the output to a UTF-8 file instead of stdout (#5802 byte contract)").option("--recursive", "include full schemas for immediate children (default: compact child index)").option("--full", "alias for --recursive").action((commandArgs, opts) => {
33284
+ const emit = (output) => {
33285
+ if (!opts.out) console.log(output);
33286
+ else console.log(`Wrote explain output to ${opts.out} (UTF-8, ${writeUtf8Receipt(opts.out, output)} bytes)`);
33287
+ };
33194
33288
  if (opts.loop) {
33195
33289
  if (!LOOP_PLAYBOOKS[opts.loop]) {
33196
33290
  const valid = Object.keys(LOOP_PLAYBOOKS).join(", ");
33197
33291
  fail(`explain --loop: unknown loop "${opts.loop}"; expected one of: ${valid}`, { code: ERROR_CODES.ERR_BAD_ENUM, expected: Object.keys(LOOP_PLAYBOOKS) });
33198
33292
  return;
33199
33293
  }
33200
- console.log(opts.json ? JSON.stringify({ schema_version: 1, scope: "loop", name: opts.loop, playbook: LOOP_PLAYBOOKS[opts.loop] }, null, 2) : formatExplainLoop(LOOP_PLAYBOOKS[opts.loop]));
33294
+ emit(opts.json ? JSON.stringify({ schema_version: 1, scope: "loop", name: opts.loop, playbook: LOOP_PLAYBOOKS[opts.loop] }, null, 2) : formatExplainLoop(LOOP_PLAYBOOKS[opts.loop]));
33201
33295
  return;
33202
33296
  }
33203
33297
  const manifest = buildCommandManifest(program3);
33204
33298
  if (!commandArgs.length) {
33205
- console.log(opts.json ? JSON.stringify(compactRouteManifest(manifest), null, 2) : formatManifestHuman(manifest));
33299
+ emit(opts.json ? JSON.stringify(compactRouteManifest(manifest), null, 2) : formatManifestHuman(manifest));
33206
33300
  return;
33207
33301
  }
33208
33302
  const commandPath3 = commandArgs.join(" ");
@@ -33225,7 +33319,7 @@ function registerExplainCommand(program3) {
33225
33319
  return;
33226
33320
  }
33227
33321
  const recursive = Boolean(opts.recursive || opts.full);
33228
- console.log(opts.json ? JSON.stringify(explainCommandManifest(manifest, command, { recursive }), null, 2) : command.subcommands.length ? formatExplainGroup(command, manifest.name) : formatExplainCommand(command, manifest.name));
33322
+ emit(opts.json ? JSON.stringify(explainCommandManifest(manifest, command, { recursive }), null, 2) : command.subcommands.length ? formatExplainGroup(command, manifest.name) : formatExplainCommand(command, manifest.name));
33229
33323
  });
33230
33324
  }
33231
33325
 
@@ -40325,6 +40419,19 @@ function registerCollaborationCommands(program3) {
40325
40419
  delete rest.number;
40326
40420
  return rest;
40327
40421
  }
40422
+ async function resolveTransferredRef(repo, number) {
40423
+ try {
40424
+ const { stdout } = await execFileP("gh", ["api", `repos/${repo}/issues/${number}`], { timeout: GIT_TIMEOUT_MS });
40425
+ const issue2 = JSON.parse(stdout);
40426
+ const match = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)$/.exec(issue2.html_url ?? "");
40427
+ if (!match || typeof issue2.number !== "number") return void 0;
40428
+ const [currentRepo, currentNumber] = [match[1], Number(match[2])];
40429
+ if (currentRepo.toLowerCase() === repo.toLowerCase() && currentNumber === number) return void 0;
40430
+ return { repo: currentRepo, number: currentNumber, url: issue2.html_url };
40431
+ } catch {
40432
+ return void 0;
40433
+ }
40434
+ }
40328
40435
  issue.command("view <ref>").aliases(["show", "get", "read"]).description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894). `show`, `get` and `read` alias view (#5483/#5706/#6078)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). `parent` is synthetic: selectable here, composed by the wrapper via a REST parent read, never sent to gh (#5970). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (ref, o) => {
40329
40436
  let parsed;
40330
40437
  try {
@@ -40338,19 +40445,33 @@ function registerCollaborationCommands(program3) {
40338
40445
  const fields = normalizeIssueViewJsonFields(o.json);
40339
40446
  const gh = resolveIssueViewGhFields(fields);
40340
40447
  const emit = (data, parent) => console.log(JSON.stringify({ ...gh.numberIsTransport ? withoutTransportNumber(data) : data, ...parent }));
40341
- try {
40448
+ const readIssue = async (targetRepo2, targetNumber) => {
40342
40449
  if (o.comments || o.context) {
40343
40450
  const boardProjectId = o.context ? await resolveBoardProjectId(o.repo) : void 0;
40344
- const data2 = await runIssueViewContext(queryDeps(), { number: n, repo, comments: Boolean(o.comments), context: Boolean(o.context), jsonFields: gh.ghFields, boardProjectId });
40345
- emit(data2, await readParentField(n, repo));
40346
- return;
40451
+ const data2 = await runIssueViewContext(queryDeps(), { number: targetNumber, repo: targetRepo2, comments: Boolean(o.comments), context: Boolean(o.context), jsonFields: gh.ghFields, boardProjectId });
40452
+ return { data: data2, parent: await readParentField(targetNumber, targetRepo2) };
40347
40453
  }
40348
- const data = await ghJson(["issue", "view", String(n), "--repo", repo, "--json", gh.ghFields]);
40349
- emit(data, await readParentField(n, repo));
40454
+ const data = await ghJson(["issue", "view", String(targetNumber), "--repo", targetRepo2, "--json", gh.ghFields]);
40455
+ return { data, parent: await readParentField(targetNumber, targetRepo2) };
40456
+ };
40457
+ try {
40458
+ const { data, parent } = await readIssue(repo, n);
40459
+ emit(data, parent);
40350
40460
  } catch (e) {
40351
40461
  const err = e;
40352
40462
  const raw = (err.stderr || err.message || String(e)).trim();
40353
40463
  const argvNote = o.json ? ` (received --json argv: ${JSON.stringify(o.json)}, normalized to "${fields}")` : "";
40464
+ const moved = /Could not resolve to an issue/i.test(raw) ? await resolveTransferredRef(repo, n) : void 0;
40465
+ if (moved) {
40466
+ try {
40467
+ const { data, parent } = await readIssue(moved.repo, moved.number);
40468
+ emit({ ...data, transferred: { from: `${repo}#${n}`, to: `${moved.repo}#${moved.number}`, url: moved.url } }, parent);
40469
+ return;
40470
+ } catch (reRead) {
40471
+ const reErr = reRead;
40472
+ return fail(`issue view: ${repo}#${n} was transferred to ${moved.repo}#${moved.number} (${moved.url}), but re-reading it there failed: ${(reErr.stderr || reErr.message || String(reRead)).trim()}`);
40473
+ }
40474
+ }
40354
40475
  return fail(`issue view: ${raw}${argvNote}`);
40355
40476
  }
40356
40477
  });
@@ -44202,6 +44323,9 @@ function renderReleaseAbort(r) {
44202
44323
  function renderReleaseSupersedeDeploy(r) {
44203
44324
  return `mmi-cli devops release --supersede-deploy --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
44204
44325
  }
44326
+ function renderReleaseSupersedePublish(r) {
44327
+ return `mmi-cli devops release --supersede-publish --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
44328
+ }
44205
44329
  function renderReleasePublishRetry(r) {
44206
44330
  return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
44207
44331
  }
@@ -44427,6 +44551,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44427
44551
  { flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" },
44428
44552
  { flags: "--abort", description: "with --apply, delete only a proven unpublished failed release candidate on any registered deploy model and restore local main for a same-version recut (#3944/#5650/#5657)" },
44429
44553
  { flags: "--supersede-deploy", description: "with --apply, master-approved closure of a content-caused failed deploy leg on a PUBLISHED release whose immutable tag can never deploy green \u2014 the next patch train then carries the fix (#6553)" },
44554
+ { flags: "--supersede-publish", description: "with --apply, master-approved closure of a content-caused failed publish leg on a fully PROMOTED release whose immutable tag can never publish green \u2014 the next patch train then carries the fix (#6638)" },
44430
44555
  { flags: "--retry-publish <run-id>", description: "with --apply, retry one proven failed or cancelled Hub publish release run (#3949/#5797)" }
44431
44556
  ];
44432
44557
  for (const f of RELEASE_ONLY_FLAGS) {
@@ -44457,6 +44582,9 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44457
44582
  if (o.supersedeDeploy && commandName !== "release") {
44458
44583
  return fail(`${commandName}: --supersede-deploy applies only to release \u2014 it closes a content-caused failed deploy leg on a published release's ledger. Run: mmi-cli devops release --supersede-deploy --apply`);
44459
44584
  }
44585
+ if (o.supersedePublish && commandName !== "release") {
44586
+ return fail(`${commandName}: --supersede-publish applies only to release \u2014 it closes a content-caused failed publish leg on a promoted release's ledger. Run: mmi-cli devops release --supersede-publish --apply`);
44587
+ }
44460
44588
  if (o.retryPublish && commandName !== "release") {
44461
44589
  return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli devops release --retry-publish <run-id> --apply --watch`);
44462
44590
  }
@@ -44504,6 +44632,9 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44504
44632
  return failGraceful(`release --abort: ${e.message}`);
44505
44633
  }
44506
44634
  }
44635
+ if (o.supersedeDeploy && o.supersedePublish) {
44636
+ return fail("release: --supersede-deploy and --supersede-publish are mutually exclusive \u2014 close the one leg that failed content-caused; the other leg has its own resolution paths");
44637
+ }
44507
44638
  if (o.supersedeDeploy) {
44508
44639
  if (o.resume) return fail("release: --supersede-deploy and --resume are mutually exclusive \u2014 the closure settles the ledger so a NEW train can run, resume re-derives the very leg it closes");
44509
44640
  if (o.abort) return fail("release: --supersede-deploy and --abort are mutually exclusive \u2014 the closure is for a PUBLISHED release, abort only ever deletes a proven unpublished candidate");
@@ -44523,6 +44654,25 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44523
44654
  return failGraceful(`release --supersede-deploy: ${e.message}`);
44524
44655
  }
44525
44656
  }
44657
+ if (o.supersedePublish) {
44658
+ if (o.resume) return fail("release: --supersede-publish and --resume are mutually exclusive \u2014 the closure settles the ledger so a NEW train can run, resume re-reads the very failed run it closes");
44659
+ if (o.abort) return fail("release: --supersede-publish and --abort are mutually exclusive \u2014 the closure is for a PROMOTED release, abort only ever deletes a proven unpublished candidate");
44660
+ if (o.retryPublish) return fail("release: --supersede-publish cannot be combined with --retry-publish \u2014 a content-caused failure on the immutable tag cannot be retried green");
44661
+ if (!o.apply) return fail("release: --supersede-publish requires --apply after explicit master approval; nothing was written");
44662
+ if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
44663
+ return fail("release: --supersede-publish accepts only --apply, --repo, --json and --out; it closes a ledger leg and dispatches nothing");
44664
+ }
44665
+ if (o.repo) {
44666
+ const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), "mmi-cli devops release --supersede-publish --apply");
44667
+ if (!guard.ok) return fail(`release: ${guard.message}`);
44668
+ }
44669
+ try {
44670
+ const result = await runReleaseSupersedePublish(trainApplyDeps(), { approved: true });
44671
+ return emitTrainResult("release --supersede-publish", o.json ? JSON.stringify(result, null, 2) : renderReleaseSupersedePublish(result), o.out);
44672
+ } catch (e) {
44673
+ return failGraceful(`release --supersede-publish: ${e.message}`);
44674
+ }
44675
+ }
44526
44676
  if (o.resume) {
44527
44677
  if (o.apply) return fail(`${commandName}: --resume and --apply are mutually exclusive \u2014 --apply cuts the NEXT version, --resume finishes the immutable tag already on origin`);
44528
44678
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.60",
3
+ "version": "4.3.62",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",