@mutmutco/cli 4.5.134 → 4.5.136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.cjs CHANGED
@@ -17509,6 +17509,7 @@ function assertGitBashOnWindowsForJervHubRelease(repo, platform2 = process.platf
17509
17509
 
17510
17510
  // src/train-apply-correlation.ts
17511
17511
  var import_node_fs19 = require("node:fs");
17512
+ init_github_quota();
17512
17513
  var import_node_path17 = require("node:path");
17513
17514
 
17514
17515
  // src/fleet-lockstep-enforce.ts
@@ -18274,10 +18275,10 @@ var rollout_plan_default = {
18274
18275
  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)."
18275
18276
  },
18276
18277
  baseline: {
18277
- version: "4.5.134",
18278
- tag: "v4.5.134",
18279
- commit: "f976ea4f8325",
18280
- npm: "@mutmutco/cli@4.5.134"
18278
+ version: "4.5.136",
18279
+ tag: "v4.5.136",
18280
+ commit: "642526387569",
18281
+ npm: "@mutmutco/cli@4.5.136"
18281
18282
  },
18282
18283
  exitCriterion: "fleet-n-of-n",
18283
18284
  hubOnlyShortcut: "forbidden",
@@ -18294,14 +18295,14 @@ var rollout_plan_default = {
18294
18295
  repo: "mutmutco/mmi-hub",
18295
18296
  role: "canary",
18296
18297
  schedule: "train",
18297
- v3Target: "v4.5.134"
18298
+ v3Target: "v4.5.136"
18298
18299
  }
18299
18300
  ],
18300
18301
  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.",
18301
18302
  rollback: {
18302
18303
  independent: true,
18303
- mechanism: "npm dist-tag latest -> 4.5.134 and redeploy the Hub Lambda from tag v4.5.134 (f976ea4f8325); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18304
- v3Target: "v4.5.134 (@mutmutco/cli@4.5.134, tag commit f976ea4f8325 \u2014 last known-good release carrying the repo-index v4-only contract)"
18304
+ mechanism: "npm dist-tag latest -> 4.5.136 and redeploy the Hub Lambda from tag v4.5.136 (642526387569); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18305
+ v3Target: "v4.5.136 (@mutmutco/cli@4.5.136, tag commit 642526387569 \u2014 last known-good release carrying the repo-index v4-only contract)"
18305
18306
  }
18306
18307
  },
18307
18308
  {
@@ -20419,11 +20420,22 @@ async function loadReleaseTrackBranchHints(deps) {
20419
20420
  ]);
20420
20421
  return { hasDevelopmentBranch, hasMainBranch, hasRcBranch };
20421
20422
  }
20423
+ async function readTrainLogin(deps, waitMs = 18e4, attempts = 5) {
20424
+ for (let i = 1; ; i++) {
20425
+ try {
20426
+ return requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
20427
+ } catch (e) {
20428
+ if (i >= attempts || !isRateLimitText(e instanceof Error ? e.message : String(e))) throw e;
20429
+ deps.warn?.(`GitHub login read rate-limited; retrying in ${waitMs / 6e4} min (${i}/${attempts - 1})`);
20430
+ await resolveSleep(deps)(waitMs);
20431
+ }
20432
+ }
20433
+ }
20422
20434
  async function buildTrainApplyContext(deps) {
20423
20435
  const repo = requireValue(clean2(await deps.run("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])), "repo");
20424
20436
  const [owner, name] = repo.split("/");
20425
20437
  if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
20426
- const login = requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
20438
+ const login = await readTrainLogin(deps);
20427
20439
  const verdict = await deps.trainAuthority(repo);
20428
20440
  if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
20429
20441
  if (!verdict.train) {
@@ -22443,7 +22455,7 @@ async function runTenantRedeploy(deps, options) {
22443
22455
  const repo = options.repo;
22444
22456
  const [owner, name] = repo.split("/");
22445
22457
  if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
22446
- const login = requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
22458
+ const login = await readTrainLogin(deps);
22447
22459
  const verdict = await deps.trainAuthority(repo);
22448
22460
  if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
22449
22461
  if (!verdict.train) throw new Error(`${commandAuthorityLabel(owner)}: @${login} is ${verdict.role} \u2014 no train authority on ${repo}`);
@@ -26417,36 +26429,75 @@ async function collectReleaseCredits(deps, repo, body) {
26417
26429
  return [];
26418
26430
  }
26419
26431
  }
26420
- function buildPlainWordsSection(lines2, big, heading = PLAIN_WORDS_HEADING, credits = []) {
26421
- const [title, ...notes] = lines2;
26422
- const body = [heading, title ?? "", ...notes.slice(0, 5).map((n) => `- ${n}`)];
26432
+ var UPDATE_SECTIONS = {
26433
+ en: { fixed: "Fixed", new: "New" },
26434
+ tr: { fixed: "D\xFCzeldi", new: "Yeni" }
26435
+ };
26436
+ var UPDATE_LINE_MAX = 120;
26437
+ var UPDATE_SECTION_MAX = 8;
26438
+ var UPDATE_LINE = /^[^:\s][^:]{0,39}: \S.*\.$/u;
26439
+ var UPDATE_FORBIDDEN = /#\d|\bv?\d+\.\d+\.\d+|^(?:feat|fix|chore|refactor|docs|test|ci|build|perf|style)\b/;
26440
+ function parseUpdateNotes(content, lang = "en") {
26441
+ const words = UPDATE_SECTIONS[lang];
26442
+ const lines2 = content.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
26443
+ const notes = { fixed: [], new: [] };
26444
+ let section;
26445
+ const seen = /* @__PURE__ */ new Set();
26446
+ for (const line of lines2) {
26447
+ const key = line === words.fixed ? "fixed" : line === words.new ? "new" : void 0;
26448
+ if (key) {
26449
+ if (seen.has(key) || key === "fixed" && seen.has("new")) throw new Error(`${lang} summary: "${words.fixed}" then "${words.new}", each at most once \u2014 see ${SUMMARY_GUIDE}`);
26450
+ seen.add(key);
26451
+ section = key;
26452
+ continue;
26453
+ }
26454
+ if (!section) throw new Error(`${lang} summary: start with "${words.fixed}" or "${words.new}" on its own line \u2014 see ${SUMMARY_GUIDE}`);
26455
+ if (line.length > UPDATE_LINE_MAX || !UPDATE_LINE.test(line) || UPDATE_FORBIDDEN.test(line)) {
26456
+ throw new Error(`${lang} summary line must read "<Area>: <sentence>." in at most ${UPDATE_LINE_MAX} characters, with no issue numbers, versions or commit prefixes: "${line}" \u2014 see ${SUMMARY_GUIDE}`);
26457
+ }
26458
+ notes[section].push(line);
26459
+ }
26460
+ for (const key of seen) {
26461
+ const n = notes[key].length;
26462
+ if (n === 0 || n > UPDATE_SECTION_MAX) throw new Error(`${lang} summary: each section needs 1\u2013${UPDATE_SECTION_MAX} lines (found ${n}) \u2014 see ${SUMMARY_GUIDE}`);
26463
+ }
26464
+ if (!seen.size) throw new Error(`${lang} summary needs a "${words.fixed}" or "${words.new}" section \u2014 see ${SUMMARY_GUIDE}`);
26465
+ return notes;
26466
+ }
26467
+ function buildPlainWordsSection(notes, big, lang = "en", credits = []) {
26468
+ const words = UPDATE_SECTIONS[lang];
26469
+ const body = [lang === "en" ? PLAIN_WORDS_HEADING : PLAIN_WORDS_HEADING_TR];
26470
+ for (const key of ["fixed", "new"]) {
26471
+ if (notes[key].length) body.push(`### ${words[key]}`, ...notes[key].map((n) => `- ${n}`));
26472
+ }
26423
26473
  if (big) body.push(PLAIN_WORDS_MARKER);
26424
26474
  const ids = [...new Set(credits.filter((id) => REPORT_ID.test(id)))].sort().slice(0, 20);
26425
26475
  if (ids.length) body.push(`<!-- mmi:credits ${ids.join(",")} -->`);
26426
26476
  return body.join("\n");
26427
26477
  }
26428
26478
  var SUMMARY_GUIDE = "https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing";
26429
- function validatePlainWordsSummary(content) {
26430
- const lines2 = content.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
26431
- if (lines2[0]?.startsWith("No changes to this module")) throw new Error(`requires a real MM-Work title, not the no-change placeholder \u2014 see ${SUMMARY_GUIDE}`);
26432
- if (lines2.length < 3 || lines2.length > 6) throw new Error(`requires 3\u20136 curated non-empty lines (title + 2\u20135 notes; found ${lines2.length}) \u2014 see ${SUMMARY_GUIDE}`);
26433
- }
26434
- function validateSuiteReleaseSummary(path2) {
26435
- if (!path2) throw new Error(`requires --announce-summary-file <path> before promoting any member \u2014 see ${SUMMARY_GUIDE}`);
26436
- let content;
26437
- try {
26438
- content = (0, import_node_fs25.readFileSync)(path2, "utf8");
26439
- } catch (e) {
26440
- throw new Error(`could not read summary file ${path2}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
26479
+ var SUMMARY_FLAG = { en: "--announce-summary-file", tr: "--announce-summary-file-tr" };
26480
+ function validateSuiteReleaseSummary(path2, pathTr) {
26481
+ const files = [["en", path2], ["tr", pathTr]];
26482
+ for (const [lang, file] of files) {
26483
+ if (!file) throw new Error(`requires ${SUMMARY_FLAG[lang]} <path> before promoting any member \u2014 see ${SUMMARY_GUIDE}`);
26484
+ }
26485
+ for (const [lang, file] of files) {
26486
+ let content;
26487
+ try {
26488
+ content = (0, import_node_fs25.readFileSync)(file, "utf8");
26489
+ } catch (e) {
26490
+ throw new Error(`could not read summary file ${file}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
26491
+ }
26492
+ parseUpdateNotes(content, lang);
26441
26493
  }
26442
- validatePlainWordsSummary(content);
26443
26494
  }
26444
- async function validateReleaseSummaries(releases, files, releaseExists) {
26495
+ async function validateReleaseSummaries(releases, files, filesTr, releaseExists) {
26445
26496
  const refusals = [];
26446
26497
  for (const { repo, tag } of releases) {
26447
26498
  if (await releaseExists(repo, tag)) continue;
26448
26499
  try {
26449
- validateSuiteReleaseSummary(files[repo]);
26500
+ validateSuiteReleaseSummary(files[repo], filesTr[repo]);
26450
26501
  } catch (e) {
26451
26502
  refusals.push(`${repo}: ${e.message}`);
26452
26503
  }
@@ -26454,15 +26505,20 @@ async function validateReleaseSummaries(releases, files, releaseExists) {
26454
26505
  if (refusals.length) throw new Error(`suite release summaries refused; no member was released:
26455
26506
  ${refusals.join("\n")}`);
26456
26507
  }
26457
- async function requireSuiteReleaseSummary(deps, path2) {
26458
- if (!path2) throw new Error(`a new mm-work suite Release requires --announce-summary-file <path> with a title + 2\u20135 notes \u2014 see ${SUMMARY_GUIDE}`);
26459
- let content;
26460
- try {
26461
- content = await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(path2);
26462
- } catch (e) {
26463
- throw new Error(`could not read --announce-summary-file ${path2}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
26508
+ async function requireSuiteReleaseSummary(deps, path2, pathTr) {
26509
+ const files = [["en", path2], ["tr", pathTr]];
26510
+ for (const [lang, file] of files) {
26511
+ if (!file) throw new Error(`a new mm-work suite Release requires ${SUMMARY_FLAG[lang]} <path> in the update-notes template \u2014 see ${SUMMARY_GUIDE}`);
26512
+ }
26513
+ for (const [lang, file] of files) {
26514
+ let content;
26515
+ try {
26516
+ content = await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(file);
26517
+ } catch (e) {
26518
+ throw new Error(`could not read ${SUMMARY_FLAG[lang]} ${file}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
26519
+ }
26520
+ parseUpdateNotes(content, lang);
26464
26521
  }
26465
- validatePlainWordsSummary(content);
26466
26522
  }
26467
26523
  function upsertPlainWordsSection(body, section) {
26468
26524
  let rest = body;
@@ -26486,19 +26542,15 @@ function upsertPlainWordsSection(body, section) {
26486
26542
 
26487
26543
  ${trimmedRest}` : section;
26488
26544
  }
26489
- function plainWordsSummaryLines(content) {
26490
- return content.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).slice(0, 6);
26491
- }
26492
26545
  async function writePlainWordsIfSuiteMember(deps, args) {
26493
26546
  if (args.meta?.suite !== "mm-work") return;
26494
26547
  try {
26495
- if (!args.summaryLines?.length) return;
26496
- const lines2 = args.summaryLines;
26548
+ if (!args.summary) return;
26497
26549
  const body = clean2(await deps.run("gh", ["release", "view", args.tag, "--repo", args.repo, "--json", "body", "-q", ".body"]));
26498
26550
  const credits = await collectReleaseCredits(deps, args.repo, body);
26499
26551
  const section = [
26500
- buildPlainWordsSection(lines2, args.big === true, PLAIN_WORDS_HEADING, credits),
26501
- ...args.summaryLinesTr?.length ? [buildPlainWordsSection(args.summaryLinesTr, false, PLAIN_WORDS_HEADING_TR)] : []
26552
+ buildPlainWordsSection(args.summary, args.big === true, "en", credits),
26553
+ ...args.summaryTr ? [buildPlainWordsSection(args.summaryTr, false, "tr")] : []
26502
26554
  ].join("\n\n");
26503
26555
  await deps.run("gh", ["release", "edit", args.tag, "--repo", args.repo, "--notes", upsertPlainWordsSection(body, section)]);
26504
26556
  } catch (e) {
@@ -26507,9 +26559,10 @@ async function writePlainWordsIfSuiteMember(deps, args) {
26507
26559
  }
26508
26560
  async function writePlainWordsSection(deps, options, repo, meta, tag) {
26509
26561
  try {
26510
- const summaryLines = options.announceSummaryFile ? plainWordsSummaryLines(await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(options.announceSummaryFile)) : void 0;
26511
- const summaryLinesTr = options.announceSummaryFileTr ? plainWordsSummaryLines(await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(options.announceSummaryFileTr)) : void 0;
26512
- await writePlainWordsIfSuiteMember(deps, { repo, tag, meta, summaryLines, summaryLinesTr, big: options.big, suiteTag: options.suiteTag });
26562
+ const read = deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8"));
26563
+ const summary = options.announceSummaryFile ? parseUpdateNotes(await read(options.announceSummaryFile), "en") : void 0;
26564
+ const summaryTr = options.announceSummaryFileTr ? parseUpdateNotes(await read(options.announceSummaryFileTr), "tr") : void 0;
26565
+ await writePlainWordsIfSuiteMember(deps, { repo, tag, meta, summary, summaryTr, big: options.big, suiteTag: options.suiteTag });
26513
26566
  } catch {
26514
26567
  }
26515
26568
  }
@@ -27844,7 +27897,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
27844
27897
  const preMainPartial = await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo);
27845
27898
  const promotedPartial = !preMainPartial && await isPromotedUnreleasedTag(deps, tag, tagSha, ctx.repo);
27846
27899
  const partialRelease = preMainPartial || promotedPartial;
27847
- if (partialRelease && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
27900
+ if (partialRelease && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile, options.announceSummaryFileTr);
27848
27901
  if (!partialRelease) {
27849
27902
  try {
27850
27903
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
@@ -28720,7 +28773,7 @@ async function runTrainApplyLocked(command, deps, options = {}) {
28720
28773
  const branchHints = await loadReleaseTrackBranchHints(deps);
28721
28774
  const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
28722
28775
  assertSuiteAutoVersion(ctx.repo, meta, process.env, command === "release" && !directTrack);
28723
- if (command === "release" && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
28776
+ if (command === "release" && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile, options.announceSummaryFileTr);
28724
28777
  const pipelineInput = { deps, ctx, command, meta, branchHints, watch, options, bumpIntent };
28725
28778
  if (command === "rcand") {
28726
28779
  if (directTrack) {
@@ -39055,7 +39108,7 @@ async function runHotfixReleaseLocked(deps, versionInput, options, doctor) {
39055
39108
  await assertConsumerBomLineage(deps, mergedSha, tag);
39056
39109
  }
39057
39110
  const releaseExists = await hotfixReleaseExists(deps, ctx, tag);
39058
- if (!releaseExists && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
39111
+ if (!releaseExists && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile, options.announceSummaryFileTr);
39059
39112
  if (!releaseExists && isHubControlRepo(ctx.repo)) {
39060
39113
  if (!options.announceSummaryFile) {
39061
39114
  throw new Error("a new MMI-Hub hotfix Release requires --announce-summary-file <path> with 3-6 short LLM-curated lines; create the summary file, then rerun the same hotfix release command \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing");
@@ -39804,6 +39857,7 @@ function boardAdvanceFailureMessage(result) {
39804
39857
  }
39805
39858
 
39806
39859
  // src/pr-land.ts
39860
+ var PR_LAND_QUEUE_TIMEOUT_MS = 45 * 6e4;
39807
39861
  function prHeadBehindBase(input) {
39808
39862
  if ((input.mergeStateStatus ?? "").toUpperCase() === "BEHIND") return true;
39809
39863
  return /out[- ]of[- ]date|not up to date|up to date with the base|update (?:the|your) branch/i.test(input.message ?? "");
@@ -39927,6 +39981,17 @@ async function runPrLand(prNumber, options, deps) {
39927
39981
  }
39928
39982
  const ciPolicy = await deps.resolveCiPolicy(repo);
39929
39983
  base.ciPolicy = ciPolicy;
39984
+ const hasQueue = await deps.hasMergeQueue?.(repo, resolved.base) ?? false;
39985
+ let queueSnapshot;
39986
+ if (hasQueue) {
39987
+ base.mergeQueue = { base: resolved.base, state: "queued" };
39988
+ if (!deps.readMergeQueue) throw new Error("merge queue reader unavailable");
39989
+ queueSnapshot = await deps.readMergeQueue(prNumber, repo);
39990
+ if (queueSnapshot.isInMergeQueue || queueSnapshot.state === "MERGED") {
39991
+ base.mergeStatus = "auto-merge-enqueued";
39992
+ return pollPrMergeQueue(prNumber, options, deps, base, queueSnapshot);
39993
+ }
39994
+ }
39930
39995
  const checksWaitError = (checksWait) => {
39931
39996
  if (checksWait.status === "conflicting") {
39932
39997
  return `checks-wait conflicting: ${checksWait.reason ?? "PR is conflicting with the base branch"}`;
@@ -39962,6 +40027,14 @@ async function runPrLand(prNumber, options, deps) {
39962
40027
  if (merge.mergeStatus === "failed") {
39963
40028
  return { ...base, error: merge.error ?? "merge failed" };
39964
40029
  }
40030
+ if (hasQueue) {
40031
+ if (merge.mergeStatus === "merged") {
40032
+ return queueMerged(base, deps);
40033
+ }
40034
+ const deadline = (deps.now ?? (() => Date.now()))() + PR_LAND_QUEUE_TIMEOUT_MS;
40035
+ await (deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms))))(PR_LAND_POLL_MS);
40036
+ return pollPrMergeQueue(prNumber, options, deps, base, void 0, deadline);
40037
+ }
39965
40038
  if (merge.mergeStatus === "merged") {
39966
40039
  return {
39967
40040
  ...base,
@@ -39984,6 +40057,53 @@ async function runPrLand(prNumber, options, deps) {
39984
40057
  error: "auto-merge enqueued but PR did not reach MERGED within timeout \u2014 poll manually with gh pr view"
39985
40058
  };
39986
40059
  }
40060
+ function queueMerged(base, deps) {
40061
+ const result = {
40062
+ ...base,
40063
+ status: "merged-via-queue",
40064
+ mergeStatus: "merged",
40065
+ mergeQueue: { ...base.mergeQueue, state: "merged" },
40066
+ branchFinality: prLandedBranchFinality(base.pr, base.base)
40067
+ };
40068
+ deps.queueProgress?.(result);
40069
+ return result;
40070
+ }
40071
+ async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
40072
+ const now = deps.now ?? (() => Date.now());
40073
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40074
+ const deadline = deadlineMs ?? now() + PR_LAND_QUEUE_TIMEOUT_MS;
40075
+ while (now() < deadline) {
40076
+ const read = snapshot ?? await deps.readMergeQueue(pr, base.repo);
40077
+ snapshot = void 0;
40078
+ if (read.state === "MERGED") return queueMerged(base, deps);
40079
+ if (read.isInMergeQueue) {
40080
+ base.mergeQueue = {
40081
+ base: base.base,
40082
+ state: "queued",
40083
+ ...read.mergeQueueEntry ? { position: read.mergeQueueEntry.position, entryState: read.mergeQueueEntry.state } : {}
40084
+ };
40085
+ deps.queueProgress?.(base);
40086
+ if (options.enqueueOnly) return { ...base, status: "queued" };
40087
+ } else if (read.state === "OPEN" && !read.autoMergeRequest) {
40088
+ const run = await deps.findMergeGroupRun(pr, base.repo, base.base);
40089
+ const result2 = {
40090
+ ...base,
40091
+ mergeQueue: { ...base.mergeQueue, state: "ejected", ...run ? { ejectedRun: run } : {} },
40092
+ error: `merge queue ejected PR #${pr} \u2014 ${run ? `merge_group run ${run.id} ${run.conclusion} ${run.url}` : "merge_group run not found"}`
40093
+ };
40094
+ deps.queueProgress?.(result2);
40095
+ return result2;
40096
+ }
40097
+ await sleep2(PR_LAND_POLL_MS);
40098
+ }
40099
+ const result = {
40100
+ ...base,
40101
+ mergeQueue: { ...base.mergeQueue, state: "timeout" },
40102
+ error: "merge queue did not reach MERGED within 45 minutes \u2014 rerun pr land to resume polling"
40103
+ };
40104
+ deps.queueProgress?.(result);
40105
+ return result;
40106
+ }
39987
40107
 
39988
40108
  // src/test-policy-core.ts
39989
40109
  var import_node_child_process13 = require("node:child_process");
@@ -41044,18 +41164,19 @@ async function ghMergeAutoEnqueue(prNumber, repo, method, extra) {
41044
41164
  bodyText: method === "--squash" ? extra?.bodyText : void 0
41045
41165
  });
41046
41166
  try {
41047
- return await mergeAutoEnqueueWithBody(prNumber, args, method, io, overrideBody?.path);
41167
+ return await mergeAutoEnqueueWithBody(prNumber, args, method, io, overrideBody?.path, extra?.mergeQueue);
41048
41168
  } finally {
41049
41169
  overrideBody?.cleanup();
41050
41170
  }
41051
41171
  }
41052
- async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile) {
41172
+ async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile, mergeQueue = false) {
41053
41173
  const headRef = (await io.gh(["pr", "view", prNumber, ...args, "--json", "headRefName", "--jq", ".headRefName"], GC_GH_TIMEOUT_MS).catch(() => "")).trim();
41054
41174
  const deleteBranch = !isProtectedBranch(headRef);
41055
41175
  const readMergeState = () => readGhPrStateWithRetry(
41056
41176
  () => io.gh(["pr", "view", prNumber, ...args, "--json", "state", "--jq", ".state"], GC_GH_TIMEOUT_MS)
41057
41177
  );
41058
41178
  const confirmEnqueueOutcome = async () => {
41179
+ if (mergeQueue) return { mergeStatus: "auto-merge-enqueued" };
41059
41180
  const stateRead = await readMergeState();
41060
41181
  if (!stateRead.ok) {
41061
41182
  return { mergeStatus: "failed", error: `could not read PR state after merge: ${stateRead.error}` };
@@ -41831,6 +41952,57 @@ function applyChecklistCheck(body, query, checked) {
41831
41952
  return { ok: true, edit: setChecklistMarker(body, sel.item, checked), item: sel.item };
41832
41953
  }
41833
41954
 
41955
+ // src/pr-merge-queue.ts
41956
+ init_cli_shared();
41957
+ var readApi = async (args) => {
41958
+ const env = await ghReadEnv();
41959
+ return (await execFileP("gh", ["api", ...args], {
41960
+ timeout: 2e4,
41961
+ ...env ? { env } : {}
41962
+ })).stdout;
41963
+ };
41964
+ async function queueGraphql(repo, fields, gh2) {
41965
+ const [owner, name] = repo.split("/");
41966
+ const response = JSON.parse(await gh2(["graphql", "-f", `query=query { repository(owner:${JSON.stringify(owner)},name:${JSON.stringify(name)}) { ${fields} } }`]));
41967
+ if (response.errors?.length) throw new Error(response.errors.map((e) => e.message).join("; "));
41968
+ if (!response.data?.repository) throw new Error("merge queue repository read failed");
41969
+ return response.data.repository;
41970
+ }
41971
+ async function hasPrMergeQueue(repo, base, gh2 = readApi) {
41972
+ const result = await queueGraphql(repo, `mergeQueue(branch:${JSON.stringify(base)}) { id }`, gh2);
41973
+ return result.mergeQueue !== null;
41974
+ }
41975
+ async function readPrMergeQueue(pr, repo, gh2 = readApi) {
41976
+ if (!/^\d+$/.test(pr)) throw new Error("merge queue read requires a PR number");
41977
+ const result = await queueGraphql(
41978
+ repo,
41979
+ `pullRequest(number:${pr}) { state isInMergeQueue mergeQueueEntry { state position } autoMergeRequest { enabledAt } }`,
41980
+ gh2
41981
+ );
41982
+ if (!result.pullRequest) throw new Error(`merge queue PR #${pr} not found`);
41983
+ return result.pullRequest;
41984
+ }
41985
+ async function findPrMergeGroupRun(pr, repo, base, gh2 = readApi) {
41986
+ const pages = JSON.parse(await gh2([`repos/${repo}/actions/runs?event=merge_group&per_page=100`, "--paginate", "--slurp"]));
41987
+ const prefix = `gh-readonly-queue/${base}/pr-${pr}-`;
41988
+ const run = pages.flatMap((page) => page.workflow_runs).filter((run2) => run2.head_branch?.startsWith(prefix)).sort((a, b) => b.created_at.localeCompare(a.created_at) || b.id - a.id)[0];
41989
+ return run ? { id: run.id, url: run.html_url, conclusion: run.conclusion } : void 0;
41990
+ }
41991
+ function prMergeQueueLine(result) {
41992
+ const queue = result.mergeQueue;
41993
+ if (!queue) return void 0;
41994
+ switch (queue.state) {
41995
+ case "queued":
41996
+ return `pr land: PR #${result.pr} queued on ${queue.base}${queue.position === void 0 ? "" : ` (position ${queue.position})`}`;
41997
+ case "merged":
41998
+ return `pr land: PR #${result.pr} merged via merge queue`;
41999
+ case "ejected":
42000
+ return `pr land: PR #${result.pr} ejected from merge queue \u2014 ${queue.ejectedRun ? `merge_group run ${queue.ejectedRun.id} ${queue.ejectedRun.conclusion} ${queue.ejectedRun.url}` : "merge_group run not found"}`;
42001
+ case "timeout":
42002
+ return `pr land: PR #${result.pr} merge queue timeout on ${queue.base}`;
42003
+ }
42004
+ }
42005
+
41834
42006
  // src/command-register-collaboration.ts
41835
42007
  init_github_client();
41836
42008
  init_github_quota();
@@ -46778,7 +46950,7 @@ ${list}`);
46778
46950
  }
46779
46951
  class PrHeadBehindBaseError extends Error {
46780
46952
  }
46781
- pr.command("land <number>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--force", "acknowledge and land past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal").option("--without-review <reason>", "compatibility option; shared merge helpers do not require a review verdict").action(async (number, o) => {
46953
+ pr.command("land <number>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--enqueue-only", "return once queue membership is confirmed; without a merge queue, land normally").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--force", "acknowledge and land past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal").option("--without-review <reason>", "compatibility option; shared merge helpers do not require a review verdict").action(async (number, o) => {
46782
46954
  if (/^(?:[^/]+\/[^/]+)?#\d+$/.test(number.trim())) {
46783
46955
  try {
46784
46956
  const parsed = parseIssueRef(number, o.repo);
@@ -46813,7 +46985,14 @@ ${list}`);
46813
46985
  return;
46814
46986
  }
46815
46987
  if (landClosingGuardVerdict.message) console.warn(landClosingGuardVerdict.message);
46816
- const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false }, {
46988
+ let mergeQueueEnabled = false;
46989
+ const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false, enqueueOnly: o.enqueueOnly }, {
46990
+ hasMergeQueue: async (repo, base) => mergeQueueEnabled = await hasPrMergeQueue(repo, base),
46991
+ readMergeQueue: readPrMergeQueue,
46992
+ findMergeGroupRun: findPrMergeGroupRun,
46993
+ queueProgress: (receipt) => {
46994
+ if (!o.json) printLine(prMergeQueueLine(receipt));
46995
+ },
46817
46996
  resolveRepo: async (prNumber, repoOpt) => {
46818
46997
  const knownRepo = repoOpt ?? await resolveRepo(o.repo).catch(() => void 0) ?? o.repo;
46819
46998
  let repoFromGh = "";
@@ -46903,7 +47082,8 @@ ${list}`);
46903
47082
  },
46904
47083
  mergeAuto: (prNumber, repo) => ghMergeAutoEnqueue(prNumber, repo, "--squash", {
46905
47084
  allowedClosing: landClosingGuardInput?.closing,
46906
- bodyText: landSquashBody
47085
+ bodyText: landSquashBody,
47086
+ mergeQueue: mergeQueueEnabled
46907
47087
  }),
46908
47088
  updateBranch: (prNumber, repo) => prLandUpdateBranch(prNumber, repo, Boolean(o.repo)),
46909
47089
  pollMerged: async (prNumber, repo, deadlineMs) => {
@@ -46927,7 +47107,7 @@ ${list}`);
46927
47107
  return false;
46928
47108
  }
46929
47109
  });
46930
- if (result.status !== "failed") {
47110
+ if (result.status !== "failed" && result.status !== "queued") {
46931
47111
  const boardAdvance = await advanceClosedIssuesToDone2(number, result.repo);
46932
47112
  const crossRepoFilingIssue = result.repo ? await reconcileCrossRepoFilingIssue(defaultGitHubClient(), result.repo, Number(number)) : { status: "failed", error: "could not resolve the PR repo for cross-repo filing-issue reconciliation" };
46933
47113
  const recovery = result.repo ? buildPostMergeReconRecovery({
@@ -47470,16 +47650,18 @@ async function announceSummaryLineCountRefusal(path2) {
47470
47650
  return `release: could not read --announce-summary-file ${path2}: ${e.message} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`;
47471
47651
  }
47472
47652
  const lines2 = summaryFileLines(content);
47653
+ const first = content.split(/\r?\n/).map((s) => s.trim()).find(Boolean);
47654
+ if (first && Object.values(UPDATE_SECTIONS).some((w) => first === w.fixed || first === w.new)) return null;
47473
47655
  return lines2.length < 3 || lines2.length > 6 ? `release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${lines2.length}) \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing` : null;
47474
47656
  }
47475
- async function announceSummaryRefusal(path2, isHub = false) {
47657
+ async function announceSummaryRefusal(path2, isHub = false, isWorkSuite = false) {
47476
47658
  let lines2;
47477
47659
  try {
47478
47660
  lines2 = summaryFileLines(await (0, import_promises9.readFile)(path2, "utf8"));
47479
47661
  } catch (e) {
47480
47662
  return `release: could not read --announce-summary-file ${path2}: ${e.message} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`;
47481
47663
  }
47482
- if (lines2.length < 3 || lines2.length > 6) {
47664
+ if (!isWorkSuite && (lines2.length < 3 || lines2.length > 6)) {
47483
47665
  return `release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${lines2.length}) \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`;
47484
47666
  }
47485
47667
  if (isHub) {
@@ -47577,7 +47759,13 @@ function trainApplyDeps() {
47577
47759
  // Slack release announcement (#883/#6521/#6523): the org alerts channel for the Hub, the project's own
47578
47760
  // registry releaseChannel META for a product repo — best-effort inside announceRelease itself.
47579
47761
  announce: (args) => announceRelease({
47580
- run: async (file, cmdArgs) => (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
47762
+ // #8236: the announcement reads an already-PUBLISHED Release, so its `release view` rides the App
47763
+ // read pool like the train's other reads (#8030), never the owner's personal REST quota. Scoped
47764
+ // here, not in isAppRoutableGhReadArgs: other train paths may view a draft the App cannot see.
47765
+ run: async (file, cmdArgs) => {
47766
+ const env = file === "gh" && (cmdArgs[0] === "release" && cmdArgs[1] === "view" || isAppRoutableGhReadArgs(cmdArgs)) ? await ghReadEnv() : void 0;
47767
+ return (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS, ...env ? { env } : {} })).stdout;
47768
+ },
47581
47769
  postAnnouncement: async (payload) => {
47582
47770
  const res = await releaseAnnouncement(payload, registryClientDeps(await loadConfig()));
47583
47771
  const body = res.body;
@@ -47951,6 +48139,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
47951
48139
  const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit project-admin or master approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded repo-authorized train after explicit approval (Hub: master only)").option("--suite-solo", "release one suite module alone, or cut the suite lead alone (requires --owner-go; a member rcand is always refused)").option("--owner-go", "explicit owner approval for a suite-solo backend release").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)").option("--out <path>", "write the terminal result (--apply, --resume, --abort, --retry-publish) to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)");
47952
48140
  const RELEASE_ONLY_FLAGS = [
47953
48141
  { flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line release Slack summary; required for a new MMI-Hub --apply (#883/#3901) and for a product repo whose registry META sets `releaseChannel` (#6523), in its `releaseLanguage`; nothing is posted to a project channel without it" },
48142
+ { flags: "--announce-summary-file-tr <path>", description: "Turkish update-notes summary; required next to --announce-summary-file for a new mm-work suite Release (#8230)" },
47954
48143
  { flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
47955
48144
  { 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)" },
47956
48145
  { 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)" },
@@ -48144,7 +48333,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
48144
48333
  const raw = await withReleaseProgress(
48145
48334
  trainApplyDeps(),
48146
48335
  "Checking the existing release",
48147
- (deps) => runReleaseResume(deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, suiteSolo: o.suiteSolo, big: o.big })
48336
+ (deps) => runReleaseResume(deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, announceSummaryFileTr: o.announceSummaryFileTr, suiteSolo: o.suiteSolo, big: o.big })
48148
48337
  );
48149
48338
  const result = raw.dispatch?.workflowRuns ? { ...raw, dispatch: { ...raw.dispatch, workflowRuns: raw.dispatch.workflowRuns.map(workflowRunWithEvidence) } } : raw;
48150
48339
  const projectInfoSync = await withReleaseProgress(
@@ -48200,7 +48389,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
48200
48389
  }
48201
48390
  if (isWorkSuite) {
48202
48391
  try {
48203
- await requireSuiteReleaseSummary({}, o.announceSummaryFile);
48392
+ await requireSuiteReleaseSummary({}, o.announceSummaryFile, o.announceSummaryFileTr);
48204
48393
  } catch (e) {
48205
48394
  return fail(`release: ${e.message}`);
48206
48395
  }
@@ -48211,14 +48400,14 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
48211
48400
  isHub ? "release: a new MMI-Hub release requires --announce-summary-file <path> with 3-6 short LLM-curated lines; create the summary file, then rerun the same release command \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing" : `release: ${slug} announces to its project release channel, so a new release requires --announce-summary-file <path> with 3-6 short plain-language lines in the project's releaseLanguage (${releaseLanguage2}); create the summary file, then rerun the same release command \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`
48212
48401
  );
48213
48402
  }
48214
- const refusal = await announceSummaryRefusal(o.announceSummaryFile, isHub);
48403
+ const refusal = await announceSummaryRefusal(o.announceSummaryFile, isHub, isWorkSuite);
48215
48404
  if (refusal) return fail(refusal);
48216
48405
  }
48217
48406
  }
48218
48407
  if (o.apply) {
48219
48408
  try {
48220
48409
  const ack = (o.ack ?? "").split(",").map((s) => s.trim()).filter(Boolean);
48221
- const apply = (deps) => runTrainApply(commandName, deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, ack, dev: o.dev, suiteSolo: o.suiteSolo, big: o.big });
48410
+ const apply = (deps) => runTrainApply(commandName, deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, announceSummaryFileTr: o.announceSummaryFileTr, ack, dev: o.dev, suiteSolo: o.suiteSolo, big: o.big });
48222
48411
  const raw = await withReleaseProgress(trainApplyDeps(), commandName === "release" ? "Preparing release" : "Preparing release candidate", apply);
48223
48412
  const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
48224
48413
  let projectInfoSync;
@@ -48403,16 +48592,16 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
48403
48592
  if (o.suiteSolo !== o.ownerGo) return fail("hotfix start: solo suite hotfix requires BOTH --suite-solo --owner-go");
48404
48593
  return runHotfixSub("start", () => runHotfixStart(trainApplyDeps(), { from: o.from, suiteSolo: o.suiteSolo, ack: o.ack ? o.ack.split(",").map((s) => s.trim()).filter(Boolean) : void 0 }), o, renderHotfixStart);
48405
48594
  });
48406
- hotfixCmd.command("release <version>").description("after the hotfix PR is merged + checks green: tag, GitHub Release, watch deploy/publish, verify distribution (idempotent)").option("--json", "machine-readable output").option("--announce-summary-file <path>", "agent-curated 3-6 line release Slack summary; required for a NEW MMI-Hub hotfix Release (#883/#3901/#6068), optional for a product repo announcing to its project release channel (#6517)").option("--carries <pr#|sha[,pr#|sha...]>", "declared fix target(s) this hotfix must carry; each must be proven present before tagging (#3056)").option("--ack <shas>", "accepted for flag symmetry with hotfix start; hotfix release proves every carry by ancestry, so it is ignored here, never an error (#7338)").option("--retry-deploy", "re-run only the failed deploy and/or fold-port alignment legs of a published hotfix of this same tag (#6985)").option("--suite-solo", "release one suite module alone (requires --owner-go)").option("--owner-go", "explicit owner approval for a solo backend hotfix").option("--out <path>", "write the result to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)").action(async (version, o) => {
48595
+ hotfixCmd.command("release <version>").description("after the hotfix PR is merged + checks green: tag, GitHub Release, watch deploy/publish, verify distribution (idempotent)").option("--json", "machine-readable output").option("--announce-summary-file <path>", "agent-curated 3-6 line release Slack summary; required for a NEW MMI-Hub hotfix Release (#883/#3901/#6068), optional for a product repo announcing to its project release channel (#6517)").option("--announce-summary-file-tr <path>", "Turkish update-notes summary; required next to --announce-summary-file for a new mm-work suite hotfix Release (#8230)").option("--carries <pr#|sha[,pr#|sha...]>", "declared fix target(s) this hotfix must carry; each must be proven present before tagging (#3056)").option("--ack <shas>", "accepted for flag symmetry with hotfix start; hotfix release proves every carry by ancestry, so it is ignored here, never an error (#7338)").option("--retry-deploy", "re-run only the failed deploy and/or fold-port alignment legs of a published hotfix of this same tag (#6985)").option("--suite-solo", "release one suite module alone (requires --owner-go)").option("--owner-go", "explicit owner approval for a solo backend hotfix").option("--out <path>", "write the result to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)").action(async (version, o) => {
48407
48596
  if (o.suiteSolo !== o.ownerGo) return fail("hotfix release: solo suite release requires BOTH --suite-solo --owner-go");
48408
48597
  if (o.retryDeploy) {
48409
- if (o.announceSummaryFile || o.carries) {
48598
+ if (o.announceSummaryFile || o.announceSummaryFileTr || o.carries) {
48410
48599
  return fail("hotfix release: --retry-deploy accepts only --json and --out; it re-runs failed deploy/alignment legs of the published tag and does not recut");
48411
48600
  }
48412
48601
  return runHotfixSub("retry-deploy", () => runHotfixRetryDeploy(trainApplyDeps(), version), o, renderHotfixRetryDeploy);
48413
48602
  }
48414
48603
  return runHotfixSub("release", async () => {
48415
- const result = await runHotfixRelease(trainApplyDeps(), version, { announceSummaryFile: o.announceSummaryFile, carries: o.carries ? [o.carries] : [], suiteSolo: o.suiteSolo, ...o.ack ? { ack: o.ack.split(",").map((s) => s.trim()).filter(Boolean) } : {} });
48604
+ const result = await runHotfixRelease(trainApplyDeps(), version, { announceSummaryFile: o.announceSummaryFile, announceSummaryFileTr: o.announceSummaryFileTr, carries: o.carries ? [o.carries] : [], suiteSolo: o.suiteSolo, ...o.ack ? { ack: o.ack.split(",").map((s) => s.trim()).filter(Boolean) } : {} });
48416
48605
  const suitePin = o.suiteSolo ? await pinCompletedMember(result) : void 0;
48417
48606
  const projectInfoSync = await runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo);
48418
48607
  return { ...result, projectInfoSync, ...o.suiteSolo ? { suiteSolo: true, suitePin } : {} };
@@ -48745,10 +48934,11 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
48745
48934
  };
48746
48935
  return {
48747
48936
  recordPath,
48748
- validateHotfixSummary: () => requireSuiteReleaseSummary({}, summaryFile),
48937
+ validateHotfixSummary: () => requireSuiteReleaseSummary({}, summaryFile, summaryFileTr),
48749
48938
  validateReleaseSummaries: (releases) => validateReleaseSummaries(
48750
48939
  releases,
48751
48940
  { ...memberSummaryFiles, [SUITE_REPO]: summaryFile },
48941
+ { ...memberSummaryFilesTr, [SUITE_REPO]: summaryFileTr },
48752
48942
  async (repo, tag) => {
48753
48943
  try {
48754
48944
  await gh(["release", "view", tag, "--repo", repo, "--json", "tagName"]);
@@ -48839,7 +49029,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
48839
49029
  },
48840
49030
  // #7738: a member hotfixes as suite-solo; the lead hotfixes under its suite-lead capability (inTrain sets it).
48841
49031
  hotfixStart: (repo, o) => inTrain(repo, async (deps) => {
48842
- await requireSuiteReleaseSummary(deps, summaryFile);
49032
+ await requireSuiteReleaseSummary(deps, summaryFile, summaryFileTr);
48843
49033
  const r = await runHotfixStart(deps, { from: o.from, ack: o.ack, suiteSolo: repo !== SUITE_REPO });
48844
49034
  return { tag: r.tag };
48845
49035
  }, repo !== SUITE_REPO),
@@ -49023,7 +49213,7 @@ function registerSuiteCommands(program3) {
49023
49213
  return fail(`suite abandon: ${e.message}`);
49024
49214
  }
49025
49215
  });
49026
- suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train; also this cut's plain-words summary for MM-Work itself").option("--member-summary-file <owner/repo=path>", "repeatable: required title + 2\u20135 notes for each member being released; carried members need none", (val, prev = []) => [...prev, val], []).option("--announce-summary-file-tr <path>", "Turkish plain-words summary for MM-Work itself").option("--member-summary-file-tr <owner/repo=path>", "repeatable: a member's own Turkish plain-words summary", (val, prev = []) => [...prev, val], []).option("--revamp <owner/repo>", `repeatable: marks that repo's release an owner-approved revamp in the plain-words section \u2014 the only "big" signal; versions carry no meaning (#8224)`, (val, prev = []) => [...prev, val], []).option("--big", "alias for --revamp mutmutco/MM-Work (#7510)").option("--json", "structured receipt").action(async (o) => {
49216
+ suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train; also this cut's plain-words summary for MM-Work itself").option("--member-summary-file <owner/repo=path>", "repeatable: required update-notes summary (Fixed/New) for each member being released; carried members need none (#8230)", (val, prev = []) => [...prev, val], []).option("--announce-summary-file-tr <path>", "required Turkish update-notes summary (D\xFCzeldi/Yeni) for MM-Work itself (#8230)").option("--member-summary-file-tr <owner/repo=path>", "repeatable: required Turkish update-notes summary for each member being released (#8230)", (val, prev = []) => [...prev, val], []).option("--revamp <owner/repo>", `repeatable: marks that repo's release an owner-approved revamp in the plain-words section \u2014 the only "big" signal; versions carry no meaning (#8224)`, (val, prev = []) => [...prev, val], []).option("--big", "alias for --revamp mutmutco/MM-Work (#7510)").option("--json", "structured receipt").action(async (o) => {
49027
49217
  try {
49028
49218
  if (!o.ownerGo) throw new Error("suite release requires --owner-go; nothing was written");
49029
49219
  const memberSummaryFiles = parseMemberSummaryFiles(o.memberSummaryFile ?? []);
@@ -49046,7 +49236,7 @@ function registerSuiteCommands(program3) {
49046
49236
  return fail(`suite release: ${e.message}`);
49047
49237
  }
49048
49238
  });
49049
- suite.command("hotfix <member>").description("member hotfix, main pin PR, MM-Work patch release, and the recorded suite version -> member versions mapping (#7738)").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR(s) or SHA(s) the member hotfix carries").option("--ack <shas>", "dev shas verified as not needed on main (see hotfix start --ack)").option("--announce-summary-file <path>", "required title + 2\u20135 notes for the member and lead hotfix Releases").option("--announce-summary-file-tr <path>", "optional Turkish plain-words notes").option("--owner-go", "explicit owner approval: ships the member and MM-Work to production (not needed for --dry-run)").option("--dry-run", "read-only: print the ordered plan; writes nothing").option("--json", "structured receipt").action(async (member, o) => {
49239
+ suite.command("hotfix <member>").description("member hotfix, main pin PR, MM-Work patch release, and the recorded suite version -> member versions mapping (#7738)").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR(s) or SHA(s) the member hotfix carries").option("--ack <shas>", "dev shas verified as not needed on main (see hotfix start --ack)").option("--announce-summary-file <path>", "required update-notes summary (Fixed/New) for the member and lead hotfix Releases (#8230)").option("--announce-summary-file-tr <path>", "required Turkish update-notes summary (D\xFCzeldi/Yeni) (#8230)").option("--owner-go", "explicit owner approval: ships the member and MM-Work to production (not needed for --dry-run)").option("--dry-run", "read-only: print the ordered plan; writes nothing").option("--json", "structured receipt").action(async (member, o) => {
49050
49240
  try {
49051
49241
  if (!o.ownerGo && !o.dryRun) throw new Error("suite hotfix requires --owner-go (it ships to production); nothing was written. --dry-run prints the plan");
49052
49242
  const root = await leadRoot();
@@ -50874,7 +51064,8 @@ var surfaces_default = {
50874
51064
  ],
50875
51065
  outputs: [
50876
51066
  "cli/dist/index.cjs",
50877
- "cli/dist/main.cjs"
51067
+ "cli/dist/main.cjs",
51068
+ "cli/dist/runbook-sweep.cjs"
50878
51069
  ]
50879
51070
  }
50880
51071
  ],
@@ -923,6 +923,9 @@ function clean(out) {
923
923
  }
924
924
  var HUB_REPO = "mutmutco/MMI-Hub";
925
925
 
926
+ // src/train-apply-correlation.ts
927
+ init_github_quota();
928
+
926
929
  // src/fleet-lockstep-enforce.ts
927
930
  init_github_client();
928
931
  init_clean_exit();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.134",
3
+ "version": "4.5.136",
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",