@mutmutco/cli 4.5.77 → 4.5.78

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 +129 -31
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -6986,6 +6986,24 @@ function findClaimedButUnregisteredClosings(text, closing, alreadyClosed = []) {
6986
6986
  }
6987
6987
  return [...claimed].filter((n) => !registered.has(n) && !closed.has(n));
6988
6988
  }
6989
+ function findLineStartClosings(text, repo) {
6990
+ const found = /* @__PURE__ */ new Set();
6991
+ let inFence = false;
6992
+ for (const line of text.split(/\r?\n/)) {
6993
+ if (FENCE_LINE_RE.test(line)) {
6994
+ inFence = !inFence;
6995
+ continue;
6996
+ }
6997
+ if (inFence || !LINE_START_CLOSING_RE.test(line)) continue;
6998
+ const m = findClosingMentions(line)[0];
6999
+ if (!m || m.negated || !Number.isInteger(m.issue) || m.issue <= 0) continue;
7000
+ const link = /\[#\d+\]\(([^)\s]+)\)/.exec(line);
7001
+ if (link && !(link[1] ?? "").toLowerCase().includes(`github.com/${repo.toLowerCase()}/issues/${m.issue}`)) continue;
7002
+ found.add(m.issue);
7003
+ }
7004
+ const ambiguous = new Set(findCrossRepoAmbiguousClosings(text, [...found], repo));
7005
+ return [...found].filter((n) => !ambiguous.has(n));
7006
+ }
6989
7007
  function claimedClosingWarning(unregistered, context = "pr merge") {
6990
7008
  const named = unregistered.map((n) => `#${n}`).join(", ");
6991
7009
  const first = `#${unregistered[0] ?? "N"}`;
@@ -17231,10 +17249,10 @@ var rollout_plan_default = {
17231
17249
  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)."
17232
17250
  },
17233
17251
  baseline: {
17234
- version: "4.5.77",
17235
- tag: "v4.5.77",
17236
- commit: "760ee9725625",
17237
- npm: "@mutmutco/cli@4.5.77"
17252
+ version: "4.5.78",
17253
+ tag: "v4.5.78",
17254
+ commit: "55782d939329",
17255
+ npm: "@mutmutco/cli@4.5.78"
17238
17256
  },
17239
17257
  exitCriterion: "fleet-n-of-n",
17240
17258
  hubOnlyShortcut: "forbidden",
@@ -17251,14 +17269,14 @@ var rollout_plan_default = {
17251
17269
  repo: "mutmutco/mmi-hub",
17252
17270
  role: "canary",
17253
17271
  schedule: "train",
17254
- v3Target: "v4.5.77"
17272
+ v3Target: "v4.5.78"
17255
17273
  }
17256
17274
  ],
17257
17275
  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.",
17258
17276
  rollback: {
17259
17277
  independent: true,
17260
- mechanism: "npm dist-tag latest -> 4.5.77 and redeploy the Hub Lambda from tag v4.5.77 (760ee9725625); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17261
- v3Target: "v4.5.77 (@mutmutco/cli@4.5.77, tag commit 760ee9725625 \u2014 last known-good release carrying the repo-index v4-only contract)"
17278
+ mechanism: "npm dist-tag latest -> 4.5.78 and redeploy the Hub Lambda from tag v4.5.78 (55782d939329); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17279
+ v3Target: "v4.5.78 (@mutmutco/cli@4.5.78, tag commit 55782d939329 \u2014 last known-good release carrying the repo-index v4-only contract)"
17262
17280
  }
17263
17281
  },
17264
17282
  {
@@ -20072,10 +20090,10 @@ async function listRemoteReleaseTags(deps) {
20072
20090
  }
20073
20091
  return [...tags].sort(cmpReleaseTag);
20074
20092
  }
20075
- async function assertComputedReleaseTagStillNext(deps, tag) {
20093
+ async function assertComputedReleaseTagStillNext(deps, tag, preOneZero = false) {
20076
20094
  if (!/^v\d+\.\d+\.\d+$/.test(tag)) return;
20077
20095
  const remote = await listRemoteReleaseTags(deps);
20078
- const newer = remote.filter((t) => cmpReleaseTag(t, tag) > 0);
20096
+ const newer = remote.filter((t) => cmpReleaseTag(t, tag) > 0 && !(preOneZero && !t.startsWith("v0.")));
20079
20097
  if (newer.length === 0) return;
20080
20098
  const latest = newer[newer.length - 1];
20081
20099
  throw new Error(
@@ -21697,6 +21715,17 @@ Resume it: ${resumeCommand}
21697
21715
  );
21698
21716
  }
21699
21717
 
21718
+ // src/pre-one-zero.ts
21719
+ function preOneZeroInForce(meta) {
21720
+ return !!meta?.suite && meta.preOneZero !== false;
21721
+ }
21722
+ function assertPreOneZero(repo, meta, tag) {
21723
+ if (preOneZeroInForce(meta) && /^v?[1-9]\d*\./.test(tag)) {
21724
+ throw new Error(`${repo} is a ${meta.suite} suite member held on 0.x.y; ${tag} would be major >= 1. Only the owner lifts this: set registry META preOneZero to false. If the tag came from a stale scripts/next-version.mjs, propagate the current seed first (#7699).`);
21725
+ }
21726
+ return tag;
21727
+ }
21728
+
21700
21729
  // src/train-doctor.ts
21701
21730
  var import_node_child_process9 = require("node:child_process");
21702
21731
  var import_node_fs23 = require("node:fs");
@@ -24833,7 +24862,7 @@ async function runTrainDoctor(input) {
24833
24862
  let newestRemoteReleaseTag;
24834
24863
  try {
24835
24864
  remoteTags = tagNames(await runGitRemoteRead(train, ["ls-remote", "--tags", "origin", "refs/tags/v*"]));
24836
- const releaseTags = await listRemoteReleaseTags(train);
24865
+ const releaseTags = (await listRemoteReleaseTags(train)).filter((t) => !preOneZeroInForce(meta) || t.startsWith("v0."));
24837
24866
  const latest = releaseTags[releaseTags.length - 1];
24838
24867
  newestRemoteReleaseTag = latest;
24839
24868
  if (latest) {
@@ -26055,7 +26084,7 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
26055
26084
  let tagLedgerStarted = false;
26056
26085
  const recoveryInput = { repo: ctx.repo, tag, stage: "main", deployModel, meta, branchHints };
26057
26086
  try {
26058
- await assertComputedReleaseTagStillNext(deps, tag);
26087
+ await assertComputedReleaseTagStillNext(deps, tag, preOneZeroInForce(meta));
26059
26088
  if (deployModel === "hub-serverless") {
26060
26089
  await deps.run("node", ["scripts/release-distribution.mjs", "assert-release-lineage", tag.replace(/^v/, ""), "--release-commit", releaseSha]);
26061
26090
  }
@@ -26479,7 +26508,7 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
26479
26508
  async function runReleaseResumePipeline(deps, options, prepared) {
26480
26509
  const watch = options.watch ?? false;
26481
26510
  const { ctx, meta, branchHints, directTrack, deployModel } = prepared;
26482
- const tags = clean2(await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"])).split("\n").map((t) => t.trim()).filter((t) => /^v\d+\.\d+\.\d+$/.test(t));
26511
+ const tags = clean2(await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"])).split("\n").map((t) => t.trim()).filter((t) => (preOneZeroInForce(meta) ? /^v0\.\d+\.\d+$/ : /^v\d+\.\d+\.\d+$/).test(t));
26483
26512
  const tag = tags[0];
26484
26513
  if (!tag) throw new Error("release --resume: no final v* release tag found \u2014 there is no release to resume");
26485
26514
  const tagSha = await probeRemoteTag(deps, tag);
@@ -26839,7 +26868,7 @@ async function runReleaseAbortLocked(deps, options) {
26839
26868
  throw new Error("release --abort must run from the primary checkout left on main by the failed train; nothing was written");
26840
26869
  }
26841
26870
  await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
26842
- const tags = clean2(await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"])).split("\n").map((tag2) => tag2.trim()).filter((tag2) => /^v\d+\.\d+\.\d+$/.test(tag2));
26871
+ const tags = clean2(await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"])).split("\n").map((tag2) => tag2.trim()).filter((tag2) => (preOneZeroInForce(meta) ? /^v0\.\d+\.\d+$/ : /^v\d+\.\d+\.\d+$/).test(tag2));
26843
26872
  const tag = tags[0];
26844
26873
  if (!tag) throw new Error("release --abort found no final v* tag; nothing was written");
26845
26874
  const tagSha = await probeRemoteTag(deps, tag);
@@ -27024,7 +27053,7 @@ async function runTrainApplyPipeline(mode, input) {
27024
27053
  "nothing to promote: origin/development is not ahead of origin/rc"
27025
27054
  );
27026
27055
  const deployModel2 = await preflight(deps, ctx, "rc", meta);
27027
- const releaseBase = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release base");
27056
+ const releaseBase = requireValue(clean2(await nextVersion(deps, ctx, meta, "cycle")), "release base");
27028
27057
  const { tolerated: tolerated2, predicted: predicted2 } = await preflightMergeToMain(
27029
27058
  deps,
27030
27059
  deployModel2,
@@ -27049,7 +27078,7 @@ async function runTrainApplyPipeline(mode, input) {
27049
27078
  else await mergeWithToleratedResolution(deps, "development", "development -> rc", "theirs", tolerated2);
27050
27079
  rcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
27051
27080
  resume = await resolveRcResumeTag(deps, releaseBase, rcSha, rcTagProbe);
27052
- tag2 = resume.tag ?? requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
27081
+ tag2 = resume.tag ?? requireValue(clean2(await nextVersion(deps, ctx, meta, "rc")), "rc tag");
27053
27082
  requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "rc");
27054
27083
  assertTagAddressableRequiredContexts(deps, requiredChecks, ctx.repo);
27055
27084
  const anchors2 = { repo: ctx.repo, tag: tag2, tagSha: rcSha };
@@ -27129,7 +27158,7 @@ ${spent.note}`), rcandRecovery(tag2));
27129
27158
  "nothing to release: origin/development is not ahead of origin/main"
27130
27159
  );
27131
27160
  const deployModel2 = await preflight(deps, ctx, "main", meta);
27132
- const tag2 = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release tag");
27161
+ const tag2 = requireValue(clean2(await nextVersion(deps, ctx, meta, "cycle")), "release tag");
27133
27162
  const rcShaAtRelease = !directTrack && hasRcBranch ? clean2(await deps.run("git", ["rev-parse", "origin/rc"])) : "";
27134
27163
  const { foldPaths: foldPaths2, tolerated: tolerated2, predicted: predicted2 } = await preflightMergeToMain(
27135
27164
  deps,
@@ -27247,7 +27276,7 @@ ${spent.note}`), rcandRecovery(tag2));
27247
27276
  );
27248
27277
  }
27249
27278
  const releasedRcSha = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
27250
- const tag = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "release"])), "release tag");
27279
+ const tag = requireValue(clean2(await nextVersion(deps, ctx, meta, "release")), "release tag");
27251
27280
  const tagProbe = { remoteSha: await probeRemoteTag(deps, tag) };
27252
27281
  const preFold = {};
27253
27282
  const { versionFold, releaseSha } = await runFoldStage(deps, "rc", preFold, "mmi-cli devops release --apply", async () => {
@@ -27306,19 +27335,23 @@ ${spent.note}`), rcandRecovery(tag2));
27306
27335
  ...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {}
27307
27336
  };
27308
27337
  }
27309
- async function latestLocalReleaseTag(deps, mergedInto) {
27338
+ async function nextVersion(deps, ctx, meta, mode) {
27339
+ const held = preOneZeroInForce(meta);
27340
+ return assertPreOneZero(ctx.repo, meta, clean2(await deps.run("node", ["scripts/next-version.mjs", mode, ...held ? ["--pre-one-zero"] : []])));
27341
+ }
27342
+ async function latestLocalReleaseTag(deps, mergedInto, preOneZero = false) {
27310
27343
  const tags = clean2(await deps.run("git", ["tag", "--list", "v*", ...mergedInto ? ["--merged", mergedInto] : []])).split("\n");
27311
27344
  let latest = { major: 0, minor: 0, patch: 0 };
27312
27345
  for (const tag of tags) {
27313
27346
  const m = /^v(\d+)\.(\d+)\.(\d+)$/.exec(tag.trim());
27314
- if (!m) continue;
27347
+ if (!m || preOneZero && m[1] !== "0") continue;
27315
27348
  const v = { major: +m[1], minor: +m[2], patch: +m[3] };
27316
27349
  const delta = v.major - latest.major || v.minor - latest.minor || v.patch - latest.patch;
27317
27350
  if (delta > 0) latest = v;
27318
27351
  }
27319
27352
  return latest;
27320
27353
  }
27321
- async function resolveTrainBumpIntent(deps, env = process.env, latestRelease) {
27354
+ async function resolveTrainBumpIntent(deps, env = process.env, latestRelease, preOneZero = false) {
27322
27355
  const explicitIntent = (env.MMI_BUMP_INTENT ?? "").trim();
27323
27356
  if (explicitIntent) {
27324
27357
  if (!TRAIN_BUMP_INTENTS.includes(explicitIntent)) {
@@ -27331,7 +27364,8 @@ async function resolveTrainBumpIntent(deps, env = process.env, latestRelease) {
27331
27364
  const m = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(explicitVersion);
27332
27365
  if (!m) throw new Error(`MMI_RELEASE_VERSION must be X.Y.Z (got ${explicitVersion})`);
27333
27366
  const explicit = { major: +m[1], minor: +m[2], patch: +m[3] };
27334
- const latest = latestRelease ?? await latestLocalReleaseTag(deps);
27367
+ if (preOneZero && explicit.major >= 1) throw new Error(`MMI_RELEASE_VERSION ${explicitVersion} is major >= 1 but this suite member is held on 0.x.y; only the owner lifts it (registry META preOneZero=false) (#7699)`);
27368
+ const latest = latestRelease ?? await latestLocalReleaseTag(deps, void 0, preOneZero);
27335
27369
  const delta = explicit.major - latest.major || explicit.minor - latest.minor || explicit.patch - latest.patch;
27336
27370
  if (delta <= 0) {
27337
27371
  throw new Error(
@@ -27342,8 +27376,8 @@ async function resolveTrainBumpIntent(deps, env = process.env, latestRelease) {
27342
27376
  }
27343
27377
  return "patch";
27344
27378
  }
27345
- async function resolveHotfixVersion(deps, env = process.env) {
27346
- const latest = await latestLocalReleaseTag(deps, "origin/main");
27379
+ async function resolveHotfixVersion(deps, env = process.env, preOneZero = false) {
27380
+ const latest = await latestLocalReleaseTag(deps, "origin/main", preOneZero);
27347
27381
  if (latest.major === 0 && latest.minor === 0 && latest.patch === 0) {
27348
27382
  throw new Error("no vX.Y.Z release tag found on origin/main \u2014 cannot derive the hotfix PATCH version");
27349
27383
  }
@@ -27355,14 +27389,14 @@ async function resolveHotfixVersion(deps, env = process.env) {
27355
27389
  declared.push({
27356
27390
  name: "MMI_BUMP_INTENT",
27357
27391
  value: explicitIntent,
27358
- intent: await resolveTrainBumpIntent(deps, { MMI_BUMP_INTENT: explicitIntent }, latest)
27392
+ intent: await resolveTrainBumpIntent(deps, { MMI_BUMP_INTENT: explicitIntent }, latest, preOneZero)
27359
27393
  });
27360
27394
  }
27361
27395
  if (explicitVersion) {
27362
27396
  declared.push({
27363
27397
  name: "MMI_RELEASE_VERSION",
27364
27398
  value: explicitVersion,
27365
- intent: await resolveTrainBumpIntent(deps, { MMI_RELEASE_VERSION: explicitVersion }, latest)
27399
+ intent: await resolveTrainBumpIntent(deps, { MMI_RELEASE_VERSION: explicitVersion }, latest, preOneZero)
27366
27400
  });
27367
27401
  }
27368
27402
  const nonPatch = declared.find((d) => d.intent !== "patch");
@@ -27386,8 +27420,8 @@ async function runTrainApplyLocked(command, deps, options = {}) {
27386
27420
  const ctx = await buildTrainApplyContext(deps);
27387
27421
  await requireCleanTree(deps);
27388
27422
  await runGitRemoteRead(deps, ["fetch", "origin"]);
27389
- const bumpIntent = await resolveTrainBumpIntent(deps);
27390
27423
  const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
27424
+ const bumpIntent = await resolveTrainBumpIntent(deps, process.env, void 0, preOneZeroInForce(meta));
27391
27425
  const branchHints = await loadReleaseTrackBranchHints(deps);
27392
27426
  const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
27393
27427
  const pipelineInput = { deps, ctx, command, meta, branchHints, watch, options, bumpIntent };
@@ -36610,7 +36644,7 @@ async function runHotfixStartLocked(deps, options, doctor) {
36610
36644
  await deps.run("git", ["fetch", "origin", "--tags"]);
36611
36645
  const { deployModel, meta } = await hotfixPreflight(deps, ctx, "start");
36612
36646
  const bumpNotes = [];
36613
- let resolved = await resolveHotfixVersion(deps);
36647
+ let resolved = await resolveHotfixVersion(deps, process.env, preOneZeroInForce(meta));
36614
36648
  for (; ; ) {
36615
36649
  const priorPr = await findHotfixPr(deps, ctx, resolved.tag);
36616
36650
  if ((priorPr?.state ?? "").toUpperCase() !== "MERGED") break;
@@ -37793,7 +37827,6 @@ async function advanceClosedIssuesToDone(deps) {
37793
37827
  error: `expected an array of closing issue references, got ${closing === null ? "null" : typeof closing}`
37794
37828
  };
37795
37829
  }
37796
- if (closing.length === 0) return { status: "none", entries: [] };
37797
37830
  const refs = [];
37798
37831
  for (const [i, candidate] of closing.entries()) {
37799
37832
  const repo = repoOf2(candidate);
@@ -37807,6 +37840,29 @@ async function advanceClosedIssuesToDone(deps) {
37807
37840
  refs.push({ ref: candidate, repo });
37808
37841
  }
37809
37842
  const entries = [];
37843
+ const fallbackNumbers = /* @__PURE__ */ new Set();
37844
+ let fallbackError;
37845
+ if (deps.fallback) {
37846
+ try {
37847
+ const registered = new Set(refs.filter((r) => r.repo.toLowerCase() === deps.repo.toLowerCase()).map((r) => r.ref.number));
37848
+ for (const n of [...new Set(await deps.fallback.fetchClaimed())].filter((n2) => !registered.has(n2))) {
37849
+ try {
37850
+ const closed = await deps.fallback.closeIssue(n);
37851
+ if ("error" in closed) entries.push({ issue: `#${n}`, moved: false, status: "failed", error: `GitHub did not register the closing reference and the CLI could not close it (${closed.error})` });
37852
+ else if (closed.state === "closed") {
37853
+ fallbackNumbers.add(n);
37854
+ const [owner, name] = deps.repo.split("/");
37855
+ refs.push({ ref: { number: n, repository: { name: name ?? "", owner: { login: owner ?? "" } } }, repo: deps.repo });
37856
+ }
37857
+ } catch (e) {
37858
+ entries.push({ issue: `#${n}`, moved: false, status: "failed", error: `GitHub did not register the closing reference and the CLI could not close it (${e.message})` });
37859
+ }
37860
+ }
37861
+ } catch (e) {
37862
+ fallbackError = e.message;
37863
+ }
37864
+ }
37865
+ if (refs.length === 0 && entries.length === 0) return { status: "none", entries: [], ...fallbackError ? { fallbackError } : {} };
37810
37866
  for (const { ref, repo } of refs) {
37811
37867
  if (repo.toLowerCase() !== deps.repo.toLowerCase()) {
37812
37868
  entries.push({
@@ -37847,14 +37903,15 @@ async function advanceClosedIssuesToDone(deps) {
37847
37903
  entries.push({ issue, moved: false, status: "failed", error: e.message });
37848
37904
  }
37849
37905
  }
37850
- return { status: entries.every((e) => e.moved) ? "ok" : "partial", entries };
37906
+ for (const e of entries) if (fallbackNumbers.has(Number(e.issue.slice(1)))) e.closedByFallback = true;
37907
+ return { status: entries.every((e) => e.moved) && !fallbackError ? "ok" : "partial", entries, ...fallbackError ? { fallbackError } : {} };
37851
37908
  }
37852
37909
  function boardAdvanceFailures(result) {
37853
37910
  return (result?.entries ?? []).filter((e) => !e.moved);
37854
37911
  }
37855
37912
  function boardAdvanceExitCode(result) {
37856
37913
  if (!result) return void 0;
37857
- if (result.status === "fetch-failed") return 1;
37914
+ if (result.status === "fetch-failed" || result.fallbackError) return 1;
37858
37915
  return boardAdvanceFailures(result).length > 0 ? 1 : void 0;
37859
37916
  }
37860
37917
  function boardAdvanceFailureMessage(result) {
@@ -37863,6 +37920,7 @@ function boardAdvanceFailureMessage(result) {
37863
37920
  return `pr merge: could not read the PR's closing issues (${result.error ?? "unknown error"}) \u2014 the PR MERGED, but no board item was advanced. Check the board by hand.`;
37864
37921
  }
37865
37922
  const failures = boardAdvanceFailures(result);
37923
+ if (result.fallbackError) return `pr merge: could not read the PR body to close unregistered closing lines (${result.fallbackError}) \u2014 the PR MERGED; check by hand whether its "Closes #N" issues are still open.`;
37866
37924
  if (!failures.length) return void 0;
37867
37925
  const named = failures.map((f) => f.error ? `${f.issue} (${f.error})` : f.issue).join(", ");
37868
37926
  return `pr merge: board advance incomplete for ${named} \u2014 the PR MERGED, but ${failures.length === 1 ? "this issue" : "these issues"} did not reach Done on the board.`;
@@ -38955,6 +39013,41 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
38955
39013
  }
38956
39014
  return advanceClosedIssuesToDone({
38957
39015
  repo,
39016
+ // #7691: GitHub sometimes fails to register a body `Closes #N`, so the merge closes nothing. Close the
39017
+ // still-OPEN issues the body claims on a deliberate line-start closing line (same repo only). GitHub closes
39018
+ // only for PRs into the default branch, so a PR into rc/main (a train or hotfix PR) never falls back.
39019
+ fallback: {
39020
+ fetchClaimed: async () => {
39021
+ const { stdout } = await execFileP("gh", ["pr", "view", prNumber, ...repoArgs, "--json", "body,baseRefName"], { timeout: GC_GH_TIMEOUT_MS });
39022
+ const { body, baseRefName } = JSON.parse(stdout || "null") ?? {};
39023
+ if (typeof body !== "string" || typeof baseRefName !== "string") throw new Error("gh returned no PR body or base branch");
39024
+ const repoView = await execFileP("gh", ["repo", "view", repo, "--json", "defaultBranchRef"], { timeout: GC_GH_TIMEOUT_MS });
39025
+ const defaultBranch = JSON.parse(repoView.stdout || "null")?.defaultBranchRef?.name;
39026
+ if (typeof defaultBranch !== "string" || !defaultBranch) throw new Error("gh returned no default branch");
39027
+ if (baseRefName !== defaultBranch) return [];
39028
+ return findLineStartClosings(body, repo);
39029
+ },
39030
+ closeIssue: async (n) => {
39031
+ try {
39032
+ const view = await execFileP("gh", ["issue", "view", String(n), "--repo", repo, "--json", "state"], { timeout: GC_GH_TIMEOUT_MS });
39033
+ if (JSON.parse(view.stdout || "null")?.state !== "OPEN") return { state: "already-closed" };
39034
+ await execFileP("gh", [
39035
+ "issue",
39036
+ "close",
39037
+ String(n),
39038
+ "--repo",
39039
+ repo,
39040
+ "--reason",
39041
+ "completed",
39042
+ "--comment",
39043
+ `Closed by the CLI after PR #${prNumber} merged: its body says it closes this issue, but GitHub did not register the closing reference (#7691).`
39044
+ ], { timeout: GH_MUTATION_TIMEOUT_MS });
39045
+ return { state: "closed" };
39046
+ } catch (e) {
39047
+ return { error: e.message };
39048
+ }
39049
+ }
39050
+ },
38958
39051
  fetchClosingIssues: async () => {
38959
39052
  const { stdout } = await execFileP("gh", ["pr", "view", prNumber, ...repoArgs, "--json", "closingIssuesReferences"], { timeout: GC_GH_TIMEOUT_MS });
38960
39053
  const parsed = JSON.parse(stdout || "null");
@@ -44224,6 +44317,11 @@ ${list}`);
44224
44317
  `
44225
44318
  );
44226
44319
  }
44320
+ const safeTitle = rewriteUnregisteredClosingKeywords(title, findClosingMentions(body).map((m) => m.issue));
44321
+ if (safeTitle !== title) {
44322
+ title = safeTitle;
44323
+ process.stderr.write('pr create: WARNING \u2014 rewrote close-keyword text in the title to "part of #N": the squash commit subject would close it (#7688). Put closing keywords on a "Closes #N" body line.\n');
44324
+ }
44227
44325
  const docsCheck = await checkDocsIndexAtHead({ repo: o.repo, head: o.head }, createPrCreateDocsIndexDeps());
44228
44326
  if (docsCheck && !docsCheck.ok) {
44229
44327
  return fail(`pr create: ${docsCheck.detail} \u2014 ${docsCheck.fix}`);
@@ -45171,7 +45269,7 @@ async function requireFreshTrainCli(commandName) {
45171
45269
  process.exit(converge.reexecCode);
45172
45270
  }
45173
45271
  var GH_TRAIN_TIMEOUT_MS = 3e4;
45174
- var GH_RUN_WATCH_TIMEOUT_MS = 20 * 6e4;
45272
+ var GH_RUN_WATCH_TIMEOUT_MS = 90 * 6e4;
45175
45273
  var NODE_PREPARE_TIMEOUT_MS = 10 * 6e4;
45176
45274
  var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
45177
45275
  var NPM_TRAIN_TIMEOUT_MS = 6e4;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.77",
3
+ "version": "4.5.78",
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",