@mutmutco/cli 3.67.0 → 3.69.0

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 (2) hide show
  1. package/dist/main.cjs +138 -64
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -5003,6 +5003,7 @@ function buildPrMergeResultPayload(input) {
5003
5003
  function buildPrMergeArgs(input) {
5004
5004
  const args = ["pr", "merge", input.number, ...input.repoArgs, input.method];
5005
5005
  if (input.deleteBranch !== false) args.push("--delete-branch");
5006
+ if (input.bodyFile) args.push("--body-file", input.bodyFile);
5006
5007
  if (input.auto) args.push("--auto");
5007
5008
  return args;
5008
5009
  }
@@ -7235,7 +7236,7 @@ function checkGateBudget(files, opts = {}) {
7235
7236
  }
7236
7237
 
7237
7238
  // src/project-model.ts
7238
- var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "non-deployable", "cli-tool", "worker"];
7239
+ var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "mobile-app", "non-deployable", "cli-tool", "worker"];
7239
7240
  var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "registry-publish", "content", "none"];
7240
7241
  var RELEASE_TRACKS = ["full", "direct", "trunk"];
7241
7242
  var PROJECT_TYPE_SET = new Set(PROJECT_TYPES);
@@ -7272,7 +7273,7 @@ function resolveDeployModel(meta, repo) {
7272
7273
  const projectType = resolveProjectType(meta, repo);
7273
7274
  if (projectType === "content" || meta?.class === "content") return "content";
7274
7275
  if (projectType === "hub-service" || repoIsHub(repo)) return "hub-serverless";
7275
- if (projectType === "desktop-app" || projectType === "desktop-game" || projectType === "non-deployable") return "none";
7276
+ if (projectType === "desktop-app" || projectType === "desktop-game" || projectType === "mobile-app" || projectType === "non-deployable") return "none";
7276
7277
  if (projectType === "cli-tool") return "registry-publish";
7277
7278
  return "tenant-container";
7278
7279
  }
@@ -8970,7 +8971,7 @@ async function executeWaveLand(plan, deps, opts = { preserveWorktree: true }) {
8970
8971
  }
8971
8972
 
8972
8973
  // src/index.ts
8973
- var import_node_os10 = require("node:os");
8974
+ var import_node_os11 = require("node:os");
8974
8975
 
8975
8976
  // src/board.ts
8976
8977
  var import_node_child_process7 = require("node:child_process");
@@ -12517,7 +12518,6 @@ for (const [helpGroup, names] of PRIMARY_GROUPS) {
12517
12518
  HELP_GROUP_ORDER.set("Operations", HELP_GROUP_ORDER.size);
12518
12519
  for (const name of OPERATIONAL_TOP_LEVEL) TOP_LEVEL_ORDER.set(name, topLevelPosition++);
12519
12520
  var PATH_OVERRIDES = {
12520
- "board slice-refresh": { category: "internal", discovery: "hidden", help_group: "Operations" },
12521
12521
  "secrets find": { category: "admin", discovery: "all-only", help_group: "Operations" },
12522
12522
  "secrets catalog": { category: "admin", discovery: "all-only", help_group: "Operations" },
12523
12523
  "secrets doctor": { category: "admin", discovery: "all-only", help_group: "Operations" },
@@ -16549,6 +16549,43 @@ function evaluate(changed, policy, present = () => false) {
16549
16549
  function git(args, cwd) {
16550
16550
  return (0, import_node_child_process10.execFileSync)("git", args, { cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
16551
16551
  }
16552
+ var COAUTHOR_KEY = "Co-authored-by";
16553
+ var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
16554
+ var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
16555
+ function parseTrailers(message, cwd) {
16556
+ try {
16557
+ return (0, import_node_child_process10.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
16558
+ cwd,
16559
+ input: message,
16560
+ encoding: "utf8",
16561
+ maxBuffer: 4 * 1024 * 1024
16562
+ }).split("\n").map((l) => l.trim()).filter(Boolean);
16563
+ } catch {
16564
+ return [];
16565
+ }
16566
+ }
16567
+ function squashBodyWithOverride(commits, cwd) {
16568
+ const overrides = [];
16569
+ const coauthors = [];
16570
+ const bodies = [];
16571
+ for (const commit of commits) {
16572
+ const message = `${commit.headline}
16573
+
16574
+ ${commit.body}`.trim();
16575
+ for (const trailer of parseTrailers(message, cwd)) {
16576
+ if (trailer.toLowerCase().startsWith(`${TRAILER_KEY.toLowerCase()}:`)) {
16577
+ if (!overrides.includes(trailer)) overrides.push(trailer);
16578
+ } else if (trailer.toLowerCase().startsWith(`${COAUTHOR_KEY.toLowerCase()}:`)) {
16579
+ if (!coauthors.some((c) => c.toLowerCase() === trailer.toLowerCase())) coauthors.push(trailer);
16580
+ }
16581
+ }
16582
+ const source = commits.length > 1 ? message : commit.body;
16583
+ const kept = source.split("\n").filter((line) => !LIFTED_KEYS.test(line.trim()) && !GH_MESSAGE_SEPARATOR.test(line.trim())).join("\n").trim();
16584
+ if (kept) bodies.push(kept);
16585
+ }
16586
+ if (!overrides.length) return null;
16587
+ return [...bodies, [...overrides, ...coauthors].join("\n")].join("\n\n");
16588
+ }
16552
16589
  function resolveBase(cwd, explicit) {
16553
16590
  const candidates = [explicit, process.env.TEST_POLICY_BASE, "origin/development", "origin/main"].filter(Boolean);
16554
16591
  for (const ref of candidates) {
@@ -16608,7 +16645,9 @@ function invisibleTrailer(sha, line) {
16608
16645
  AUDITED, and a reason the auditor cannot see is not a reason this gate accepts (#3628).
16609
16646
  Git reads trailers from the LAST paragraph only, and every line of that paragraph must be a
16610
16647
  trailer or an indented continuation \u2014 one bare line such as \`Closes #123\` disqualifies the whole
16611
- block. Indent the continuation lines, and leave nothing but trailers in that paragraph.`
16648
+ block. Indent the continuation lines, and leave nothing but trailers in that paragraph.
16649
+ This is fixable forward: push a LATER commit on this branch carrying a well-formed trailer.
16650
+ The nearest waiver wins, and it supersedes this one \u2014 no force-push, no re-filed branch.`
16612
16651
  };
16613
16652
  }
16614
16653
  function unknownScope(sha, unknown) {
@@ -16629,13 +16668,13 @@ function readOverride(base, cwd) {
16629
16668
  for (const record of git(["log", `${base}..HEAD`, `--format=${format}`], cwd).split(REC)) {
16630
16669
  const [sha, trailer, body] = record.replace(/^\s+/, "").split(FLD);
16631
16670
  if (!sha) continue;
16671
+ if (override) break;
16632
16672
  const value = (trailer ?? "").trim();
16633
16673
  if (!value) {
16634
16674
  const shaped = OVERRIDE_RE.exec(body ?? "");
16635
16675
  if (shaped) refusals.push(invisibleTrailer(sha, shaped[0].trim()));
16636
16676
  continue;
16637
16677
  }
16638
- if (override) continue;
16639
16678
  const { kinds, reason, unknown } = parseScope(value);
16640
16679
  if (unknown.length > 0) refusals.push(unknownScope(sha, unknown));
16641
16680
  else override = { sha, reason, kinds };
@@ -21349,6 +21388,7 @@ function registerBoardCommands(program3) {
21349
21388
  var import_node_fs27 = require("node:fs");
21350
21389
  var import_promises6 = require("node:fs/promises");
21351
21390
  var import_node_path26 = require("node:path");
21391
+ var import_node_os8 = require("node:os");
21352
21392
  var import_node_child_process12 = require("node:child_process");
21353
21393
 
21354
21394
  // src/board-advance.ts
@@ -22257,8 +22297,36 @@ ${err?.stdout ?? ""}`.split("\n").map((line) => line.trim()).find((line) => line
22257
22297
  var defaultGhMergeAutoIo = {
22258
22298
  gh: async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout
22259
22299
  };
22300
+ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
22301
+ try {
22302
+ const raw = await gh(["pr", "view", prNumber, ...repoArgs, "--json", "commits"], GC_GH_TIMEOUT_MS2);
22303
+ const commits = JSON.parse(raw).commits ?? [];
22304
+ const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
22305
+ if (!body) return void 0;
22306
+ const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path26.join)((0, import_node_os8.tmpdir)(), "mmi-squash-body-"));
22307
+ const path2 = (0, import_node_path26.join)(dir, "body.txt");
22308
+ (0, import_node_fs27.writeFileSync)(path2, `${body}
22309
+ `, "utf8");
22310
+ return { path: path2, cleanup: () => {
22311
+ try {
22312
+ (0, import_node_fs27.rmSync)(dir, { recursive: true, force: true });
22313
+ } catch {
22314
+ }
22315
+ } };
22316
+ } catch {
22317
+ return void 0;
22318
+ }
22319
+ }
22260
22320
  async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAutoIo) {
22261
22321
  const args = repo ? ["--repo", repo] : [];
22322
+ const overrideBody = await composeOverrideBodyFile(prNumber, args, io.gh);
22323
+ try {
22324
+ return await mergeAutoEnqueueWithBody(prNumber, args, method, io, overrideBody?.path);
22325
+ } finally {
22326
+ overrideBody?.cleanup();
22327
+ }
22328
+ }
22329
+ async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile) {
22262
22330
  const headRef = (await io.gh(["pr", "view", prNumber, ...args, "--json", "headRefName", "--jq", ".headRefName"], GC_GH_TIMEOUT_MS2).catch(() => "")).trim();
22263
22331
  const deleteBranch = !isProtectedBranch(headRef);
22264
22332
  const readMergeState = () => readGhPrStateWithRetry(
@@ -22277,7 +22345,7 @@ async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAut
22277
22345
  return s.ok && s.state === "MERGED";
22278
22346
  },
22279
22347
  reEnqueue: async () => {
22280
- await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
22348
+ await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
22281
22349
  }
22282
22350
  });
22283
22351
  if (confirmation === "merged") return { mergeStatus: "merged" };
@@ -22285,7 +22353,7 @@ async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAut
22285
22353
  return { mergeStatus: "failed", error: "auto-merge did not stick \u2014 GitHub reported no autoMergeRequest after enqueue; retry once the PR has a pending check" };
22286
22354
  };
22287
22355
  try {
22288
- await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
22356
+ await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
22289
22357
  } catch (e) {
22290
22358
  const message = String(e.message || "");
22291
22359
  if (/already been merged/i.test(message)) return { mergeStatus: "merged" };
@@ -22293,7 +22361,7 @@ async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAut
22293
22361
  if (note) return { mergeStatus: "failed", error: note };
22294
22362
  if (mergeAutoRejectedPrAlreadyClean(message)) {
22295
22363
  try {
22296
- await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: false, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
22364
+ await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: false, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
22297
22365
  } catch (e2) {
22298
22366
  const m2 = String(e2.message || "");
22299
22367
  if (/already been merged/i.test(m2)) return { mergeStatus: "merged" };
@@ -22304,7 +22372,7 @@ async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAut
22304
22372
  return { mergeStatus: "failed", error: `could not confirm PR state after clean-status fallback: ${afterDirect.error}` };
22305
22373
  }
22306
22374
  try {
22307
- await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
22375
+ await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
22308
22376
  } catch (e3) {
22309
22377
  const m3 = String(e3.message || "");
22310
22378
  if (/already been merged/i.test(m3)) return { mergeStatus: "merged" };
@@ -23828,7 +23896,7 @@ function registerDeployCommands(program3) {
23828
23896
 
23829
23897
  // src/discovery-commands.ts
23830
23898
  var import_node_fs31 = require("node:fs");
23831
- var import_node_os8 = require("node:os");
23899
+ var import_node_os9 = require("node:os");
23832
23900
  var import_node_path29 = require("node:path");
23833
23901
  var GC_GH_TIMEOUT_MS3 = 2e4;
23834
23902
  async function collectStatus() {
@@ -24004,7 +24072,7 @@ async function collectOnboardStatus() {
24004
24072
  else if (top) nextCommand = `mmi-cli board claim ${top.number} # ${top.title}`;
24005
24073
  else nextCommand = "mmi-cli board read \u2014 no claimable items found";
24006
24074
  }
24007
- const home = (0, import_node_os8.homedir)();
24075
+ const home = (0, import_node_os9.homedir)();
24008
24076
  const plugin = onboardPluginGate({
24009
24077
  readKnown: () => readFileSyncSafe((0, import_node_path29.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs31.readFileSync),
24010
24078
  readSettings: () => readFileSyncSafe((0, import_node_path29.join)(home, ".claude", "settings.json"), import_node_fs31.readFileSync)
@@ -25387,17 +25455,17 @@ function parseOriginRepo(remoteUrl) {
25387
25455
  }
25388
25456
  function ghHostsConfigPath(env, platform2) {
25389
25457
  const sep2 = platform2 === "win32" ? "\\" : "/";
25390
- const join26 = (...parts) => parts.join(sep2);
25458
+ const join27 = (...parts) => parts.join(sep2);
25391
25459
  const explicit = env.GH_CONFIG_DIR?.trim();
25392
- if (explicit) return join26(explicit, "hosts.yml");
25460
+ if (explicit) return join27(explicit, "hosts.yml");
25393
25461
  if (platform2 === "win32") {
25394
25462
  const appData = (env.AppData ?? env.APPDATA)?.trim();
25395
- return appData ? join26(appData, "GitHub CLI", "hosts.yml") : void 0;
25463
+ return appData ? join27(appData, "GitHub CLI", "hosts.yml") : void 0;
25396
25464
  }
25397
25465
  const xdg = env.XDG_CONFIG_HOME?.trim();
25398
- if (xdg) return join26(xdg, "gh", "hosts.yml");
25466
+ if (xdg) return join27(xdg, "gh", "hosts.yml");
25399
25467
  const home = env.HOME?.trim();
25400
- return home ? join26(home, ".config", "gh", "hosts.yml") : void 0;
25468
+ return home ? join27(home, ".config", "gh", "hosts.yml") : void 0;
25401
25469
  }
25402
25470
  function parseGhHostsAccounts(yaml, host = "github.com") {
25403
25471
  let hostIndent = null;
@@ -25448,7 +25516,7 @@ function ghAccountCaveat(announcedLogin, accounts) {
25448
25516
 
25449
25517
  // src/doctor-io.ts
25450
25518
  var import_node_fs32 = require("node:fs");
25451
- var import_node_os9 = require("node:os");
25519
+ var import_node_os10 = require("node:os");
25452
25520
  var import_node_path30 = require("node:path");
25453
25521
  var import_node_child_process13 = require("node:child_process");
25454
25522
  var import_node_util8 = require("node:util");
@@ -25457,7 +25525,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
25457
25525
  function installedClaudePluginVersion() {
25458
25526
  try {
25459
25527
  const file = JSON.parse(
25460
- (0, import_node_fs32.readFileSync)((0, import_node_path30.join)((0, import_node_os9.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
25528
+ (0, import_node_fs32.readFileSync)((0, import_node_path30.join)((0, import_node_os10.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
25461
25529
  );
25462
25530
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
25463
25531
  if (versions.length === 0) return void 0;
@@ -25566,7 +25634,7 @@ function envHealLockPath(home) {
25566
25634
  async function withEnvHealLock(what, run) {
25567
25635
  try {
25568
25636
  return await withFileLock(
25569
- envHealLockPath((0, import_node_os10.homedir)()),
25637
+ envHealLockPath((0, import_node_os11.homedir)()),
25570
25638
  { staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
25571
25639
  run
25572
25640
  );
@@ -25661,9 +25729,9 @@ function mmiDoctorDeps(opts = {}) {
25661
25729
  },
25662
25730
  pluginCache: () => {
25663
25731
  const plan = buildPluginCachePlan(
25664
- (0, import_node_os10.homedir)(),
25732
+ (0, import_node_os11.homedir)(),
25665
25733
  runningPluginVersion(process.env, resolveClientVersion()),
25666
- pluginCacheFsDeps((0, import_node_os10.homedir)(), () => 0)
25734
+ pluginCacheFsDeps((0, import_node_os11.homedir)(), () => 0)
25667
25735
  );
25668
25736
  return {
25669
25737
  stale: plan.prune,
@@ -25712,7 +25780,7 @@ function mmiDoctorDeps(opts = {}) {
25712
25780
  // which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
25713
25781
  marketplaceRows: () => {
25714
25782
  try {
25715
- const home = (0, import_node_os10.homedir)();
25783
+ const home = (0, import_node_os11.homedir)();
25716
25784
  return marketplaceRows(
25717
25785
  MMI_MARKETPLACE_NAME,
25718
25786
  readFileSyncSafe((0, import_node_path31.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs33.readFileSync),
@@ -27526,44 +27594,50 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
27526
27594
  let remoteDeleteAttempted = false;
27527
27595
  let upgradedToAuto = false;
27528
27596
  let remoteNotAttemptedReason = headIsProtected ? "protected-branch" : void 0;
27529
- await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: o.auto, deleteBranch: !headIsProtected }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch(async (e) => {
27530
- const message = String(e.message || "");
27531
- if (/already been merged/i.test(message)) {
27532
- remoteNotAttemptedReason = "pr-already-merged";
27533
- return;
27534
- }
27535
- const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
27536
- if (note) throw new Error(`gh pr merge ${number}: ${note}`);
27537
- if (o.auto && mergeAutoRejectedPrAlreadyClean(message)) {
27538
- await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: !headIsProtected }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
27539
- const m2 = String(e2.message || "");
27540
- if (/already been merged/i.test(m2)) {
27541
- remoteNotAttemptedReason = "pr-already-merged";
27542
- return;
27543
- }
27544
- const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
27545
- if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
27546
- if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
27547
- });
27548
- return;
27549
- }
27550
- if (!o.auto && basePolicyBlocksImmediateMerge(message)) {
27551
- console.warn(`pr merge: the base-branch policy blocks an immediate merge \u2014 upgrading to --auto (merges once required checks pass).`);
27552
- upgradedToAuto = true;
27553
- await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: true, deleteBranch: !headIsProtected }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
27554
- const m2 = String(e2.message || "");
27555
- if (/already been merged/i.test(m2)) {
27556
- remoteNotAttemptedReason = "pr-already-merged";
27557
- return;
27558
- }
27559
- const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
27560
- if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
27561
- if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
27562
- });
27563
- return;
27564
- }
27565
- if (!ghPrMergeLocalBranchDeleteWarning(message)) throw e;
27566
- });
27597
+ const overrideBody = await composeOverrideBodyFile(number, repoArgs, async (a, t) => (await execFileP2("gh", a, { timeout: t })).stdout);
27598
+ const bodyFile = overrideBody?.path;
27599
+ try {
27600
+ await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: o.auto, deleteBranch: !headIsProtected, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch(async (e) => {
27601
+ const message = String(e.message || "");
27602
+ if (/already been merged/i.test(message)) {
27603
+ remoteNotAttemptedReason = "pr-already-merged";
27604
+ return;
27605
+ }
27606
+ const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
27607
+ if (note) throw new Error(`gh pr merge ${number}: ${note}`);
27608
+ if (o.auto && mergeAutoRejectedPrAlreadyClean(message)) {
27609
+ await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: !headIsProtected, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
27610
+ const m2 = String(e2.message || "");
27611
+ if (/already been merged/i.test(m2)) {
27612
+ remoteNotAttemptedReason = "pr-already-merged";
27613
+ return;
27614
+ }
27615
+ const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
27616
+ if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
27617
+ if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
27618
+ });
27619
+ return;
27620
+ }
27621
+ if (!o.auto && basePolicyBlocksImmediateMerge(message)) {
27622
+ console.warn(`pr merge: the base-branch policy blocks an immediate merge \u2014 upgrading to --auto (merges once required checks pass).`);
27623
+ upgradedToAuto = true;
27624
+ await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: true, deleteBranch: !headIsProtected, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
27625
+ const m2 = String(e2.message || "");
27626
+ if (/already been merged/i.test(m2)) {
27627
+ remoteNotAttemptedReason = "pr-already-merged";
27628
+ return;
27629
+ }
27630
+ const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
27631
+ if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
27632
+ if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
27633
+ });
27634
+ return;
27635
+ }
27636
+ if (!ghPrMergeLocalBranchDeleteWarning(message)) throw e;
27637
+ });
27638
+ } finally {
27639
+ overrideBody?.cleanup();
27640
+ }
27567
27641
  const stateRead = await readGhPrStateWithRetry(
27568
27642
  () => execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "state", "--jq", ".state"], { timeout: GC_GH_TIMEOUT_MS2 }).then((r) => r.stdout)
27569
27643
  );
@@ -28051,13 +28125,13 @@ function stagingApplyFsGuard(home) {
28051
28125
  }
28052
28126
  program2.command("plugin-prune").description(`prune stale cached MMI plugin versions (keeps running + newest, ${PLUGIN_CACHE_KEEP} total) and orphaned temp_git_* staging dirs; dry-run unless --apply (#2903, #2990)`).option("--apply", "actually delete the stale version dirs + orphaned staging dirs (default: report only)").option("--json", "machine-readable output").action((o) => {
28053
28127
  const plan = buildPluginCachePlan(
28054
- (0, import_node_os10.homedir)(),
28128
+ (0, import_node_os11.homedir)(),
28055
28129
  runningPluginVersion(process.env, resolveClientVersion()),
28056
- pluginCacheFsDeps((0, import_node_os10.homedir)(), directoryBytes),
28130
+ pluginCacheFsDeps((0, import_node_os11.homedir)(), directoryBytes),
28057
28131
  { withBytes: true }
28058
28132
  );
28059
28133
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
28060
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs33.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os10.homedir)())) : void 0;
28134
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs33.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os11.homedir)())) : void 0;
28061
28135
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
28062
28136
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
28063
28137
  else console.log(renderPluginCachePlan(plan, result));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.67.0",
3
+ "version": "3.69.0",
4
4
  "description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",