@mutmutco/codex-plugin 4.6.7 → 4.6.9

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mmi",
3
- "version": "4.6.7",
3
+ "version": "4.6.9",
4
4
  "mmiCompat": "4.x",
5
5
  "description": "MMI workflow skills and org gates delivery.",
6
6
  "author": {
package/cli/dist/main.cjs CHANGED
@@ -1236,7 +1236,7 @@ var init_house_map = __esm({
1236
1236
  api: "devops",
1237
1237
  // allowlisted GitHub REST GET reads for automation (pull files, compare, contents)
1238
1238
  run: "devops",
1239
- // #9479: one named workflow run — currently the bounded `run cancel <id>` write
1239
+ // #9479: one named workflow run — the bounded `run cancel <id>` and `run rerun <id>` writes
1240
1240
  ci: "devops",
1241
1241
  // the CI/gate audit
1242
1242
  jev: "devops",
@@ -1636,11 +1636,13 @@ var init_cli_shared = __esm({
1636
1636
  const pool = appEnv ? "app" : !callerEnv ? ambientPool() : !callerEnv.GH_TOKEN ? "personal" : callerEnv.MMI_GH_POOL === "app" ? "app" : callerEnv.GH_TOKEN === process.env.GH_TOKEN ? ambientPool() : "caller";
1637
1637
  recordGhUsage("gh", pool, args.filter((a) => !a.startsWith("-")).slice(0, 2).join(" "));
1638
1638
  }
1639
- if (!appEnv) return tag(run(), options.env?.MMI_GH_POOL === "app" ? "app_installation" : "user");
1639
+ const ambientEnv = options.env ?? process.env;
1640
+ const ambient = ambientEnv.MMI_GH_POOL === "app" || /^ghs_/.test(ambientEnv.GH_TOKEN || ambientEnv.GITHUB_TOKEN || "") ? "app_installation" : "user";
1641
+ if (!appEnv) return tag(run(), ambient);
1640
1642
  try {
1641
1643
  return await tag(run(appEnv), "app_installation");
1642
1644
  } catch (e) {
1643
- if (shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(), "user");
1645
+ if (shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(), ambient);
1644
1646
  throw e;
1645
1647
  }
1646
1648
  };
@@ -13871,6 +13873,45 @@ function boardClaimStatusFlagRecovery(flag, canonicalPath, argv) {
13871
13873
  corrected: `mmi-cli ${kept.join(" ")}`
13872
13874
  };
13873
13875
  }
13876
+ function issueCommentAppendOnlyRecovery(flag, canonicalPath, argv) {
13877
+ if (canonicalPath !== "oracle issue comment") return void 0;
13878
+ const hasEditVerb = argv.some((t, idx) => idx >= 3 && !t.startsWith("-") && /^(?:edit|update|patch|modify)$/i.test(t));
13879
+ if (flag !== "--id" && !flag.startsWith("--id=") && !hasEditVerb) return void 0;
13880
+ const valueFlags = /* @__PURE__ */ new Set(["--repo", "--body", "--body-file"]);
13881
+ const kept = [];
13882
+ let sawTarget = false;
13883
+ for (let i = 0; i < argv.length; i++) {
13884
+ const tok = argv[i];
13885
+ if (i < 3) {
13886
+ kept.push(tok);
13887
+ continue;
13888
+ }
13889
+ if (tok.startsWith("--id=")) continue;
13890
+ if (tok === "--id") {
13891
+ if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) i++;
13892
+ continue;
13893
+ }
13894
+ if (tok.startsWith("-")) {
13895
+ kept.push(tok);
13896
+ const name = tok.includes("=") ? tok.slice(0, tok.indexOf("=")) : tok;
13897
+ if (!tok.includes("=") && valueFlags.has(name) && i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
13898
+ kept.push(argv[++i]);
13899
+ }
13900
+ continue;
13901
+ }
13902
+ if (/^(?:edit|update|patch|modify)$/i.test(tok)) continue;
13903
+ sawTarget = true;
13904
+ kept.push(tok);
13905
+ }
13906
+ if (!sawTarget) {
13907
+ kept.splice(3, 0, "<ref>");
13908
+ }
13909
+ const corrected = `mmi-cli ${kept.join(" ")}`;
13910
+ return {
13911
+ message: `unknown option '${flag}' \u2014 \`oracle issue comment\` is append-only (no comment edit/update or \`--id\` route); post a new comment with \`${corrected}\` or read existing comments with \`mmi-cli oracle issue view <ref> --json comments\``,
13912
+ corrected
13913
+ };
13914
+ }
13874
13915
  var BOARD_CLAIM_OWN_FLAGS = ["--repo", "--for", "--force", "--check", "--allow-partial", "--json"];
13875
13916
  function splitInlineSelectors(argv, flags) {
13876
13917
  const out = [];
@@ -15016,6 +15057,15 @@ var GUIDES = /* @__PURE__ */ new Map([
15016
15057
  did_you_mean: "oracle issue edit"
15017
15058
  }
15018
15059
  ],
15060
+ // #9574: `oracle issue comment` is append-only; callers guessing `oracle issue comment edit` get the append-only route.
15061
+ [
15062
+ "oracle issue comment:edit",
15063
+ {
15064
+ hint: "no `oracle issue comment edit` \u2014 issue comments are append-only; post a new comment with `mmi-cli oracle issue comment <ref> --body-file <path>` or read existing comments with `mmi-cli oracle issue view <ref> --json comments`",
15065
+ expected: ["oracle issue comment", "oracle issue view"],
15066
+ did_you_mean: "oracle issue comment"
15067
+ }
15068
+ ],
15019
15069
  // Worktree creation belongs to Git or the selected host, not an MMI command.
15020
15070
  [
15021
15071
  "worktree:create",
@@ -15297,6 +15347,8 @@ var APP_WRITE_COMMANDS = /* @__PURE__ */ new Map([
15297
15347
  ["ci cancel-orphans", { metadata: "read", actions: "write" }],
15298
15348
  // #9479: one named run cancelled under the same actions:write profile as the orphan sweep.
15299
15349
  ["run cancel", { metadata: "read", actions: "write" }],
15350
+ // #9587: one named run rerun (all or failed jobs) — same actions:write profile, nothing broader.
15351
+ ["run rerun", { metadata: "read", actions: "write" }],
15300
15352
  ["issue comment", { metadata: "read", issues: "write", pull_requests: "write" }],
15301
15353
  // #8401: the detached related-issue lookup (issue list/view reads + one marker comment) — no identity contract.
15302
15354
  ["issue discover-related", { metadata: "read", issues: "write" }]
@@ -17811,7 +17863,7 @@ var DECLARED_ENTRIES = [
17811
17863
  // #4118: Listening + systemd-active ≠ GitHub online after acquirejob 503. restart.conf cannot see
17812
17864
  // it; an Actions workflow on mmi-live cannot heal it when half the fleet is offline. Root cron.
17813
17865
  { name: "mmi-runner-online-reconcile (mmi-runner)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; API-offline + _diag acquirejob, max 2 restarts/tick (verify: mmi-cli devops runtime box get mmi-runner --ssh)" },
17814
- { name: "mmi-runner-online-reconcile (mmi-runner2)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; scripts/runner2-online-reconcile.cron selects lanes 15-16, disables credential files, _diag acquirejob only, max 2 restarts/tick; rollout #7859 (verify: mmi-cli devops runtime box get mmi-runner2 --ssh)" },
17866
+ { name: "mmi-runner-online-reconcile (mmi-runner2)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; scripts/runner2-online-reconcile.cron selects lanes 13-16, disables credential files, _diag acquirejob only, max 2 restarts/tick; rollout #7859 (verify: mmi-cli devops runtime box get mmi-runner2 --ssh)" },
17815
17867
  // #5292: a lane that holds a dispatched JOB but produces zero output past the budget honestly
17816
17868
  // reports "busy", so the online reconcile never fires and GitHub will not re-dispatch while the
17817
17869
  // run holds the assignment. This tick cancels + re-triggers those runs (max 1/tick, debounced).
@@ -24989,10 +25041,15 @@ function checkRunbookCoverage(docs, base, git3) {
24989
25041
  const heading = headings[i];
24990
25042
  const end = headings.slice(i + 1).find((h) => h.level <= heading.level)?.start ?? lines2.length + 1;
24991
25043
  const markerEnd = headings[i + 1]?.start ?? end;
24992
- const globs = lines2.slice(heading.start, markerEnd - 1).flatMap((text) => /<!--\s*runbook-covers:\s*(.*?)\s*-->/.exec(text)?.[1].split(",").map((s) => s.trim()) ?? []);
25044
+ const markers = lines2.slice(heading.start, markerEnd - 1).flatMap((text, j) => {
25045
+ const found = /<!--\s*runbook-covers:\s*(.*?)\s*-->/.exec(text)?.[1].split(",").map((s) => s.trim());
25046
+ return found ? [{ line: heading.start + 1 + j, globs: found }] : [];
25047
+ });
25048
+ const globs = markers.flatMap((m) => m.globs);
24993
25049
  const affected = changed.filter((path2) => globs.some((glob) => matches(glob, path2)) && !changesOnlyUses(path2));
24994
25050
  if (affected.length && !ranges.some((r) => r.count > 0 && r.start < end && r.start + r.count > heading.start)) {
24995
- findings.push({ kind: "runbook-section-stale", doc, line: heading.start, detail: `${heading.title} (lines ${heading.start}\u2013${end - 1}): changed ${affected.join(", ")}; update this section's text (lines ${heading.start}\u2013${end - 1}) or runbook-covers list, or add a \`Runbook-Unaffected: <reason>\` trailer to a commit message in this PR`, section: { heading: heading.title, level: heading.level, startLine: heading.start, endLine: end - 1, covers: globs }, sources: affected });
25051
+ const markerLine = markers.find((m) => m.globs.some((glob) => affected.some((path2) => matches(glob, path2)))).line;
25052
+ findings.push({ kind: "runbook-section-stale", doc, line: markerLine, detail: `${heading.title} (lines ${heading.start}\u2013${end - 1}, covers marker at line ${markerLine}): changed ${affected.join(", ")}; update this section's text (lines ${heading.start}\u2013${end - 1}) or runbook-covers list, or add a \`Runbook-Unaffected: <reason>\` trailer to a commit message in this PR`, section: { heading: heading.title, level: heading.level, startLine: heading.start, endLine: end - 1, covers: globs }, sources: affected });
24996
25053
  }
24997
25054
  }
24998
25055
  }
@@ -27456,10 +27513,10 @@ var rollout_plan_default = {
27456
27513
  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)."
27457
27514
  },
27458
27515
  baseline: {
27459
- version: "4.6.7",
27460
- tag: "v4.6.7",
27461
- commit: "c78249dd0d9d",
27462
- npm: "@mutmutco/cli@4.6.7"
27516
+ version: "4.6.9",
27517
+ tag: "v4.6.9",
27518
+ commit: "7cbf84926132",
27519
+ npm: "@mutmutco/cli@4.6.9"
27463
27520
  },
27464
27521
  exitCriterion: "fleet-n-of-n",
27465
27522
  hubOnlyShortcut: "forbidden",
@@ -27476,14 +27533,14 @@ var rollout_plan_default = {
27476
27533
  repo: "mutmutco/mmi-hub",
27477
27534
  role: "canary",
27478
27535
  schedule: "train",
27479
- v3Target: "v4.6.7"
27536
+ v3Target: "v4.6.9"
27480
27537
  }
27481
27538
  ],
27482
27539
  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.",
27483
27540
  rollback: {
27484
27541
  independent: true,
27485
- mechanism: "npm dist-tag latest -> 4.6.7 and redeploy the Hub Lambda from tag v4.6.7 (c78249dd0d9d); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
27486
- v3Target: "v4.6.7 (@mutmutco/cli@4.6.7, tag commit c78249dd0d9d \u2014 last known-good release carrying the repo-index v4-only contract)"
27542
+ mechanism: "npm dist-tag latest -> 4.6.9 and redeploy the Hub Lambda from tag v4.6.9 (7cbf84926132); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
27543
+ v3Target: "v4.6.9 (@mutmutco/cli@4.6.9, tag commit 7cbf84926132 \u2014 last known-good release carrying the repo-index v4-only contract)"
27487
27544
  }
27488
27545
  },
27489
27546
  {
@@ -32056,8 +32113,11 @@ function classifyRunnerCost(selector) {
32056
32113
  }
32057
32114
  var STANDARD_HOSTED_ALLOWLIST = [
32058
32115
  ["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"],
32059
- ["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"]
32116
+ ["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"],
32117
+ // #9571 (MM-Work#8894 chooser): owned mmi-heavy first, included ubuntu-latest only on a deep backlog.
32118
+ ["mutmutco/MMI-Hub", ".github/workflows/gate.yml", "cli-part", "hosted-spill"]
32060
32119
  ];
32120
+ var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*'([^']+)'\s*\|\|\s*)?fromJSON\(needs\.[\w-]+\.outputs\.heavy_runs_on\s*\|\|\s*('[^']+')\)\s*\}\}$/;
32061
32121
  function standardHostedLabelFor(repo, path2, jobId) {
32062
32122
  if (repo === "mutmutco/Jerv-Hub") return ["ubuntu-latest", "windows-latest", "macos-14"];
32063
32123
  return STANDARD_HOSTED_ALLOWLIST.find(([r, p, j]) => r === repo && p === path2 && j === jobId)?.[3];
@@ -32072,6 +32132,8 @@ function classifyJobRunnerCost(job, allowance2) {
32072
32132
  if (approved(raw) || allowance2 === "windows-11-arm" && raw === allowance2) return "standard-hosted";
32073
32133
  const fallback = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'([^']+)'\s*\|\|\s*(fromJSON\('[^']+'\))\s*\}\}$/.exec(raw);
32074
32134
  if (fallback && approved(fallback[1]) && classifyRunnerCost("${{ " + fallback[2] + " }}") === "self-hosted") return "standard-hosted";
32135
+ const spill = allowance2 === "hosted-spill" ? HOSTED_SPILL_RE.exec(raw) : null;
32136
+ if (spill) return (!spill[1] || spill[1] === "ubuntu-latest") && classifyRunnerCost(`\${{ fromJSON(${spill[2]}) }}`) === "self-hosted" ? "standard-hosted" : "unknown";
32075
32137
  const direct = classifyRunnerCost(raw);
32076
32138
  const matrix = /^\$\{\{\s*matrix\.([\w-]+)(?:\.([\w-]+))?\s*\}\}$/.exec(raw);
32077
32139
  if (!matrix || direct === "hosted") return direct;
@@ -38593,7 +38655,7 @@ function runsOnLabelSets(job) {
38593
38655
  if (plainRecord(selector)) selector = selector.labels;
38594
38656
  if (typeof selector !== "string" || !selector.includes("${{")) return [toSet(selector)].filter((s) => Boolean(s));
38595
38657
  const sets = [];
38596
- for (const m of selector.matchAll(/fromJSON\('([^']+)'\)/g)) {
38658
+ for (const m of selector.matchAll(/fromJSON\((?:needs\.[\w-]+\.outputs\.[\w-]+\s*\|\|\s*)?'([^']+)'\)/g)) {
38597
38659
  try {
38598
38660
  const parsed = toSet(JSON.parse(m[1]));
38599
38661
  if (parsed) sets.push(parsed);
@@ -41374,6 +41436,20 @@ async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
41374
41436
  state: "queued",
41375
41437
  ...read.mergeQueueEntry ? { position: read.mergeQueueEntry.position, entryState: read.mergeQueueEntry.state } : {}
41376
41438
  };
41439
+ const groupSha = read.mergeQueueEntry?.headCommit?.oid;
41440
+ if (!options.enqueueOnly && groupSha && deps.findQueueStartupFailure) {
41441
+ const startup = await deps.findQueueStartupFailure(base.repo, groupSha).catch(() => void 0);
41442
+ if (startup) {
41443
+ const detail = startup.annotation ? `: ${startup.annotation}` : startup.url ? ` (${startup.url})` : "";
41444
+ const result2 = {
41445
+ ...base,
41446
+ queueRunStartupFailure: startup,
41447
+ error: `queue-run-startup-failure: ${startup.workflow} run ${startup.runId} on ${startup.headSha}${detail}`
41448
+ };
41449
+ deps.queueProgress?.(result2);
41450
+ return result2;
41451
+ }
41452
+ }
41377
41453
  deps.queueProgress?.(base);
41378
41454
  if (options.enqueueOnly) return { ...base, status: "queued" };
41379
41455
  } else if (read.state === "OPEN" && !read.autoMergeRequest) {
@@ -41678,6 +41754,17 @@ function readOverrideTrailer(trailer, body, cwd) {
41678
41754
  }).split(/\r?\n/).filter((line) => line.trim().toLowerCase().startsWith(prefix)).map((line) => line.trim().slice(prefix.length).trim()).join("\n").trim();
41679
41755
  return { value, messageTransform: footer[1] ? "strip-squash-separator-coauthors" : "strip-terminal-coauthors" };
41680
41756
  }
41757
+ function readMessageOverride(message2, cwd) {
41758
+ const prefix = `${TRAILER_KEY.toLowerCase()}:`;
41759
+ const direct = (0, import_node_child_process10.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
41760
+ windowsHide: true,
41761
+ cwd,
41762
+ input: message2,
41763
+ encoding: "utf8",
41764
+ maxBuffer: 32 * 1024 * 1024
41765
+ }).split(/\r?\n/).filter((line) => line.trim().toLowerCase().startsWith(prefix)).map((line) => line.trim().slice(prefix.length).trim()).join("\n").trim();
41766
+ return readOverrideTrailer(direct, message2, cwd).value;
41767
+ }
41681
41768
  function readOverride(base, cwd, head = "HEAD") {
41682
41769
  const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
41683
41770
  const refusals = [];
@@ -41706,6 +41793,9 @@ var HOTFIX_DIFF_BASE = "origin/main";
41706
41793
  var TEST_RE = TEST_FILE_RE;
41707
41794
  var PY_TEST_RE = PY_TEST_FILE_RE;
41708
41795
  var GO_TEST_RE = GO_TEST_FILE_RE;
41796
+ function readMessageOverride2(message2, cwd) {
41797
+ return readMessageOverride(message2, cwd);
41798
+ }
41709
41799
  function translate(glob) {
41710
41800
  let out = "";
41711
41801
  for (let i = 0; i < glob.length; i++) {
@@ -42531,33 +42621,38 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
42531
42621
  });
42532
42622
  const rejected = (reason) => admissionRejected(reason, repo, pr);
42533
42623
  const reconcile = async (reason, recordEntry = false, resumingSaved = false) => {
42534
- try {
42535
- const observed = await readPrMergeQueue(pr, repo, read);
42536
- if (observed.state === "MERGED" && observed.headRefOid === head) return { mergeStatus: "merged" };
42537
- if (observed.isInMergeQueue && observed.headRefOid === head) {
42538
- const entry = observed.mergeQueueEntry;
42539
- if (recordEntry && receipt?.head === head && entry?.headCommit && entry.enqueuedAt) {
42540
- journal.save("enqueued", receipt?.uuid, { headCommitOid: entry.headCommit.oid, enqueuedAt: entry.enqueuedAt });
42624
+ const maxReads = options.enqueueOnly ? 3 : 1;
42625
+ const sleep3 = options.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
42626
+ for (let readAttempt = 0; readAttempt < maxReads; readAttempt++) {
42627
+ if (readAttempt) await sleep3(readAttempt * 1e3);
42628
+ try {
42629
+ const observed = await readPrMergeQueue(pr, repo, read);
42630
+ if (observed.state === "MERGED" && observed.headRefOid === head) return { mergeStatus: "merged" };
42631
+ if (observed.isInMergeQueue && observed.headRefOid === head) {
42632
+ const entry = observed.mergeQueueEntry;
42633
+ if (recordEntry && receipt?.head === head && entry?.headCommit && entry.enqueuedAt) {
42634
+ journal.save("enqueued", receipt?.uuid, { headCommitOid: entry.headCommit.oid, enqueuedAt: entry.enqueuedAt });
42635
+ }
42636
+ return { mergeStatus: "auto-merge-enqueued" };
42541
42637
  }
42542
- return { mergeStatus: "auto-merge-enqueued" };
42543
- }
42544
- const saved = recordEntry ? admissionJournal(repo, pr, options.receiptDirectory).receipt : void 0;
42545
- const boundMs = saved?.queueEntry ? Date.parse(saved.queueEntry.enqueuedAt) : resumingSaved && saved?.state === "enqueued" && saved.queueWitnessed === void 0 ? saved.observedAt : void 0;
42546
- if (saved && boundMs !== void 0 && Number.isFinite(boundMs) && observed.state === "OPEN" && !observed.isInMergeQueue && observed.headRefOid === saved.head) {
42547
- const events = await readArrayPages(
42548
- `repos/${repo}/issues/${pr}/timeline?per_page=100`,
42549
- options.timelineRead ?? read,
42550
- "queue ejection timeline unreadable"
42551
- );
42552
- const times = (name) => events.filter((event) => event.event === name).map((event) => Date.parse(event.created_at));
42553
- const adds = times("added_to_merge_queue").filter((at) => at >= boundMs);
42554
- const removals = adds.length === 1 ? times("removed_from_merge_queue").filter((at) => at > adds[0]) : [];
42555
- if (removals.length === 1) {
42556
- journal.save("ejected");
42557
- return rejected("the previous admission was ejected by GitHub; rerun to submit a new intent");
42638
+ const saved = recordEntry ? admissionJournal(repo, pr, options.receiptDirectory).receipt : void 0;
42639
+ const boundMs = saved?.queueEntry ? Date.parse(saved.queueEntry.enqueuedAt) : resumingSaved && saved?.state === "enqueued" && saved.queueWitnessed === void 0 ? saved.observedAt : void 0;
42640
+ if (saved && boundMs !== void 0 && Number.isFinite(boundMs) && observed.state === "OPEN" && !observed.isInMergeQueue && observed.headRefOid === saved.head) {
42641
+ const events = await readArrayPages(
42642
+ `repos/${repo}/issues/${pr}/timeline?per_page=100`,
42643
+ options.timelineRead ?? read,
42644
+ "queue ejection timeline unreadable"
42645
+ );
42646
+ const times = (name) => events.filter((event) => event.event === name).map((event) => Date.parse(event.created_at));
42647
+ const adds = times("added_to_merge_queue").filter((at) => at >= boundMs);
42648
+ const removals = adds.length === 1 ? times("removed_from_merge_queue").filter((at) => at > adds[0]) : [];
42649
+ if (removals.length === 1) {
42650
+ journal.save("ejected");
42651
+ return rejected("the previous admission was ejected by GitHub; rerun to submit a new intent");
42652
+ }
42558
42653
  }
42654
+ } catch {
42559
42655
  }
42560
- } catch {
42561
42656
  }
42562
42657
  return unknown(reason);
42563
42658
  };
@@ -42738,6 +42833,19 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
42738
42833
  error: "GitHub accepted queue admission but has not confirmed queue membership. Rerun pr land to resume the saved operation read-only."
42739
42834
  };
42740
42835
  }
42836
+ function githubQueueSquashMessage(title, pr, commits) {
42837
+ const list = commits.map((commit) => `* ${commit.headline}
42838
+
42839
+ ${commit.body}`.trim()).join("\n\n");
42840
+ return `${title} (#${pr})
42841
+
42842
+ ${list}
42843
+
42844
+ ---------
42845
+
42846
+ Co-authored-by: GitHub <noreply@github.com>
42847
+ `;
42848
+ }
42741
42849
  async function enqueuePrMergeQueue(pr, repo, options = {}) {
42742
42850
  if (!/^\d+$/.test(pr)) throw new Error("merge queue enqueue requires a PR number");
42743
42851
  pr = pr.replace(/^0+(?=\d)/, "");
@@ -42853,6 +42961,19 @@ ${pull.body}`;
42853
42961
  ])];
42854
42962
  return preparation("queue-message", targets, `GitHub's unchanged ${policy.body} message is unsafe for ${targets.map((number) => `${repo}#${number}`).join(", ")}. ${verdict.message}`);
42855
42963
  }
42964
+ if (method === "SQUASH" && policy.body === "COMMIT_MESSAGES" && messages.length > 1 && options.bodyText == null && messages.some((message2) => /^\s*Test-Policy-Override\s*:/im.test(`${message2.headline}
42965
+ ${message2.body}`)) && !readMessageOverride2(githubQueueSquashMessage(pull.title, pr, messages), process.cwd())) {
42966
+ return {
42967
+ mergeStatus: "failed",
42968
+ preparation: {
42969
+ status: "preparation-needed",
42970
+ reason: "queue-message-override",
42971
+ issueTargets: [],
42972
+ nextAction: "PR author: this repository squashes with COMMIT_MESSAGES, and GitHub composes a multi-commit squash as a list of every commit message followed by its own co-author block, so this Test-Policy-Override trailer would not be in the final trailer block the queue check reads. Put the change in ONE commit whose message ends with a trailer block carrying the waiver (no force-push: push a new branch whose single commit has the same tree, parented on the PR base; open it as a new PR and close this one), or make the waiver the final trailer block of the LAST commit, then rerun pr land. Keep repository settings unchanged."
42973
+ },
42974
+ error: "queue preparation needed: the multi-commit COMMIT_MESSAGES squash would bury the Test-Policy-Override waiver mid-message, so the queue check would fail malformed-override-trailer"
42975
+ };
42976
+ }
42856
42977
  const requiredBody = options.bodyText ?? composedBody;
42857
42978
  const prBodyPreservesOverrides = requiredBody != null && method === "SQUASH" && policy.body === "PR_BODY" && options.bodyText == null && prBodyPreservesCommitOverrides(messages, headline, pull.body, process.cwd());
42858
42979
  if (requiredBody && !prBodyPreservesOverrides && (method !== "SQUASH" || requiredBody.trim() !== body.trim())) {
@@ -42904,6 +43025,54 @@ async function findPrMergeGroupRun(pr, repo, base, gh2 = readApi) {
42904
43025
  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];
42905
43026
  return run ? { id: run.id, url: run.html_url, conclusion: run.conclusion } : void 0;
42906
43027
  }
43028
+ async function findPrMergeQueueStartupFailure(repo, groupHeadSha, gh2 = readApi) {
43029
+ if (!/^[a-f0-9]{40}$/i.test(groupHeadSha)) return void 0;
43030
+ try {
43031
+ const body = JSON.parse(await gh2([`repos/${repo}/actions/runs?head_sha=${groupHeadSha}&per_page=100`]));
43032
+ const runs2 = (body.workflow_runs ?? []).filter((r) => r.head_sha?.toLowerCase() === groupHeadSha.toLowerCase() && r.status === "completed");
43033
+ for (const run of runs2) {
43034
+ let isStartupFailure = run.conclusion === "startup_failure";
43035
+ if (!isStartupFailure && run.conclusion === "failure") {
43036
+ const jobsBody = JSON.parse(await gh2([`repos/${repo}/actions/runs/${run.id}/jobs?per_page=1`]));
43037
+ if (jobsBody.total_count === 0 && Array.isArray(jobsBody.jobs) && jobsBody.jobs.length === 0) {
43038
+ isStartupFailure = true;
43039
+ }
43040
+ }
43041
+ if (!isStartupFailure) continue;
43042
+ const workflow = (run.path?.replace(/^\.github\/workflows\//, "") || run.name || "workflow").trim();
43043
+ let annotation;
43044
+ if (run.check_suite_id) {
43045
+ try {
43046
+ const checkRuns = JSON.parse(await gh2([`repos/${repo}/check-suites/${run.check_suite_id}/check-runs?per_page=20`]));
43047
+ for (const cr of checkRuns.check_runs ?? []) {
43048
+ const summary = [cr.output?.title, cr.output?.summary].filter(Boolean).join(" \u2014 ").trim();
43049
+ if (summary) {
43050
+ annotation = summary;
43051
+ break;
43052
+ }
43053
+ const anns = JSON.parse(await gh2([`repos/${repo}/check-runs/${cr.id}/annotations?per_page=10`]));
43054
+ const first = Array.isArray(anns) ? anns.find((a) => a?.message || a?.title) : void 0;
43055
+ if (first) {
43056
+ annotation = (first.message || first.title || "").trim();
43057
+ break;
43058
+ }
43059
+ }
43060
+ } catch {
43061
+ }
43062
+ }
43063
+ return {
43064
+ workflow,
43065
+ runId: run.id,
43066
+ headSha: groupHeadSha,
43067
+ ...run.html_url ? { url: run.html_url } : {},
43068
+ ...annotation ? { annotation } : {}
43069
+ };
43070
+ }
43071
+ } catch {
43072
+ return void 0;
43073
+ }
43074
+ return void 0;
43075
+ }
42907
43076
  function workflowNeeds(text) {
42908
43077
  const doc = (0, import_yaml3.parse)(text);
42909
43078
  const out = /* @__PURE__ */ new Map();
@@ -42999,6 +43168,9 @@ async function readBaseRedVerdict(result, gh2 = appOnlyReadApi) {
42999
43168
  return mine && base && failuresAllOnBase(mine, base) ? { baseRunId: String(baseRun.id) } : void 0;
43000
43169
  }
43001
43170
  function prMergeQueueLine(result) {
43171
+ if (result.queueRunStartupFailure) {
43172
+ return `pr land: PR #${result.pr} ${result.error ?? `queue-run-startup-failure: ${result.queueRunStartupFailure.workflow} run ${result.queueRunStartupFailure.runId} on ${result.queueRunStartupFailure.headSha}`}`;
43173
+ }
43002
43174
  const queue = result.mergeQueue;
43003
43175
  if (!queue) return void 0;
43004
43176
  switch (queue.state) {
@@ -43206,6 +43378,57 @@ function registerRunCancel(run, writeToken) {
43206
43378
  else console.log(`run cancel: ${receipt.repo} run ${receipt.runId} \u2014 ${receipt.force ? "force-cancel" : "cancel"} requested (status: ${receipt.status}${receipt.runStatus ? `, runStatus: ${receipt.runStatus}` : ""})`);
43207
43379
  });
43208
43380
  }
43381
+ var RUN_RERUN_STATUS = "rerun-requested";
43382
+ async function rerunWorkflowRun(repo, id, deps, failed2 = false) {
43383
+ const raw = String(id).trim();
43384
+ if (!/^[1-9]\d*$/.test(raw)) throw new Error(`run rerun: <id> must be a positive workflow run id (received '${String(id)}')`);
43385
+ const runId = Number(raw);
43386
+ if (!Number.isSafeInteger(runId)) throw new Error(`run rerun: <id> ${raw} is beyond the safe integer range; refusing to address a different run`);
43387
+ if (!/^[\w.-]+\/[\w.-]+$/.test(repo)) throw new Error(`run rerun: --repo must be owner/repo (received '${repo}')`);
43388
+ const projects = await deps.projects();
43389
+ if (!projects) throw new Error("run rerun: the Hub project registry is unreadable; refusing to rerun on a repo this command cannot prove registered");
43390
+ const key = registryRepoKey(repo);
43391
+ if (!projects.some((p) => (p.repos ?? []).some((r) => typeof r === "string" && registryRepoKey(qualifyRegistryRepo(r)) === key))) {
43392
+ throw new Error(`run rerun: ${repo} is not a registered org repo; App writes are limited to repos in the Hub registry`);
43393
+ }
43394
+ const mode = failed2 ? "failed" : "all";
43395
+ let httpStatus;
43396
+ try {
43397
+ httpStatus = await deps.post(`repos/${repo}/actions/runs/${runId}/${failed2 ? "rerun-failed-jobs" : "rerun"}`);
43398
+ } catch (error) {
43399
+ const e = error;
43400
+ const detail = String(e.stderr ?? error);
43401
+ const code = /\bHTTP(?:\/[\d.]+)? (\d{3})\b/.exec(`${detail} ${e.stdout ?? ""}`)?.[1];
43402
+ if (code === "403") throw new Error(`run rerun: GitHub App denied (HTTP 403) on ${repo}; actions:write permission is required; no personal-token fallback`);
43403
+ if (code === "404") throw new Error(`run rerun: run ${runId} not found on ${repo} (HTTP 404), or the App cannot see that repo`);
43404
+ if (code === "409" || code === "422") throw new Error(`run rerun: run ${runId} is not rerunnable (HTTP ${code}); it may still be in progress, or be past GitHub's rerun window`);
43405
+ throw new Error(`run rerun: ${detail}`);
43406
+ }
43407
+ return { ok: true, repo, runId, mode, httpStatus, credential: "app_installation", status: RUN_RERUN_STATUS };
43408
+ }
43409
+ function appRunPost(writeToken, exec3 = execFileP) {
43410
+ let token;
43411
+ return async (path2) => {
43412
+ token ??= await writeToken();
43413
+ const { stdout } = await exec3("gh", ["api", "--include", "--method", "POST", path2], {
43414
+ timeout: 2e4,
43415
+ env: { ...process.env, GH_TOKEN: token, MMI_GH_POOL: "app" }
43416
+ });
43417
+ return Number(/^HTTP\/[\d.]+ (\d{3})/.exec(stdout)?.[1] ?? 0);
43418
+ };
43419
+ }
43420
+ function registerRunRerun(run, writeToken, projects) {
43421
+ run.command("rerun <id>").description("rerun one workflow run on the org App (actions:write), registered repos only; --failed reruns only the failed jobs; prints a receipt").requiredOption("--repo <owner/repo>", "registered repository that owns the run").option("--failed", "rerun only the failed jobs (POST .../rerun-failed-jobs)").option("--json", "machine-readable receipt").action(async (id, options) => {
43422
+ let receipt;
43423
+ try {
43424
+ receipt = await rerunWorkflowRun(options.repo, id, { projects, post: appRunPost(writeToken) }, options.failed === true);
43425
+ } catch (error) {
43426
+ return fail(error.message);
43427
+ }
43428
+ if (options.json) console.log(JSON.stringify(receipt));
43429
+ else console.log(`run rerun: ${receipt.repo} run ${receipt.runId} \u2014 ${receipt.mode === "failed" ? "failed jobs" : "all jobs"} rerun requested (HTTP ${receipt.httpStatus}, credential: ${receipt.credential})`);
43430
+ });
43431
+ }
43209
43432
 
43210
43433
  // src/board-commands.ts
43211
43434
  init_cli_shared();
@@ -59501,6 +59724,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
59501
59724
  readMergeQueue: readPrMergeQueue,
59502
59725
  observeQueued: (prNumber, repo, snapshot) => observeQueuedAdmission(repo, prNumber, snapshot),
59503
59726
  findMergeGroupRun: findPrMergeGroupRun,
59727
+ findQueueStartupFailure: findPrMergeQueueStartupFailure,
59504
59728
  queueProgress: (receipt) => {
59505
59729
  const line = prMergeQueueLine(receipt);
59506
59730
  if (line) (o.json ? console.warn : printLine)(line);
@@ -65676,15 +65900,18 @@ function formatBrokerRunListRecovery(spec, jq) {
65676
65900
  }
65677
65901
  function brokerGhFailureHint(failureText, args, jq) {
65678
65902
  const target = args[0] === "pr" && args[1] === "view" ? args[2] : void 0;
65903
+ const repoIdx = args.indexOf("--repo");
65904
+ const repo = repoIdx >= 0 && args[repoIdx + 1] && !args[repoIdx + 1].startsWith("-") ? args[repoIdx + 1] : void 0;
65679
65905
  const unknownField = /Unknown JSON field: "([^"]+)"/.exec(failureText)?.[1] ?? /Unknown JSON field: ([A-Za-z][A-Za-z0-9]*)/.exec(failureText)?.[1];
65680
65906
  if (unknownField === "mergeQueueEntry" || unknownField === "isInMergeQueue") {
65681
- return `'${unknownField}' is not a gh pr view --json field \u2014 read the merge queue with \`mmi-cli devops read pr queue ${target ?? "<number>"} --repo <owner/repo> --json\` and drop it from --json; repeating this read cannot succeed`;
65907
+ return `'${unknownField}' is not a gh pr view --json field \u2014 read the merge queue with \`mmi-cli devops read pr queue ${target ?? "<number>"} --repo ${repo ?? "<owner/repo>"} --json\` and drop it from --json; repeating this read cannot succeed`;
65682
65908
  }
65683
65909
  if (unknownField) {
65684
65910
  return `gh rejected --json field '${unknownField}' \u2014 drop it or use the typed read whose fields cover it (the "Available fields" list gh printed is its own vocabulary)`;
65685
65911
  }
65686
65912
  if (/Could not resolve to a PullRequest/i.test(failureText)) {
65687
- return `GitHub could not resolve the PR${target ? ` #${target}` : ""} \u2014 verify the number and that --repo names the repo that owns it before retrying; an identical re-run fails the same way`;
65913
+ const slug = repo ?? "<owner/repo>";
65914
+ return `GitHub could not resolve the PR${target ? ` #${target}` : ""}${repo ? ` in ${repo}` : ""} \u2014 verify the number and that --repo names the repo that owns it before retrying, or list PRs with \`mmi-cli devops read pr list --repo ${slug} --state all --json number,title,headRefName\`; an identical re-run fails the same way`;
65688
65915
  }
65689
65916
  if (args[0] === "run" && args[1] === "list") {
65690
65917
  return formatBrokerRunListRecovery({ program: "gh", args: [...args], timeoutMs: SINGLE_SHOT_TIMEOUT_MS }, jq);
@@ -66062,11 +66289,26 @@ function branchRulesReadHint(path2) {
66062
66289
  const slug = `${match[1]}/${match[2]}`;
66063
66290
  return ` No raw rules, rulesets or branch-protection read is on the App allowlist, so retrying path or query variants cannot work \u2014 inspect a PR's required status contexts with \`MMI_ACTOR=app mmi-cli devops pr checks-required <pr> --repo ${slug} --json\`, or verify repository ruleset and branch-protection health with \`mmi-cli devops bootstrap verify --repo ${slug} --json\` and \`mmi-cli devops ci audit --repo ${slug} --json\`.`;
66064
66291
  }
66292
+ function commitsReadHint(path2) {
66293
+ const clean4 = path2.split("?")[0];
66294
+ if (branchRulesReadHint(clean4) || checkRunsReadHint(clean4)) return "";
66295
+ const match = new RegExp(`^repos/(${NAME})/(${NAME})/(?:commits|branches)(?:[/?#]|$)`).exec(clean4);
66296
+ if (!match) return "";
66297
+ const slug = `${match[1]}/${match[2]}`;
66298
+ return ` No raw commits or branches endpoint is on the App allowlist \u2014 to compare branch tips, ahead/behind counts, and commit SHAs/dates (e.g. whether main is current with development), use the allowlisted compare read \`MMI_ACTOR=app mmi-cli devops api get "repos/${slug}/compare/main...development"\` (returns \`status\`, \`ahead_by\`, \`behind_by\`, \`base_commit\`, \`merge_base_commit\`, and \`commits\`), or inspect remote branch SHAs directly with \`git ls-remote --heads https://github.com/${slug}.git main development\`.`;
66299
+ }
66300
+ function checkRunsReadHint(path2) {
66301
+ const clean4 = path2.split("?")[0];
66302
+ const match = new RegExp(`^repos/(${NAME})/(${NAME})/(?:check-suites(?:/|$)|check-runs(?:/|$)|commits/[^/?#]+/(?:check-runs|check-suites)(?:/|$))`).exec(clean4);
66303
+ if (!match) return "";
66304
+ const slug = `${match[1]}/${match[2]}`;
66305
+ return ` Check-suite and check-run routes are deliberately outside the App allowlist \u2014 read per-check / per-job conclusions with the allowlisted \`MMI_ACTOR=app mmi-cli devops api get "repos/${slug}/actions/runs/<id>/jobs?per_page=100"\` or \`mmi-cli devops read run view <id> --repo ${slug} --json databaseId,workflowName,event,status,conclusion,jobs\`; check-run annotations and output text have no App-token read (ask a human token holder).`;
66306
+ }
66065
66307
  function parseApiGetPath(rawPath) {
66066
66308
  const path2 = rawPath.trim();
66067
66309
  const refuse = () => {
66068
66310
  const hint = /^[A-Za-z]:[/\\]/.test(path2) ? " Git Bash/MSYS may have rewritten a leading-slash argument; retry with MSYS_NO_PATHCONV=1 and the slash-free repos/... path. The endpoint must still be allowlisted." : "";
66069
- const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${hint}`;
66311
+ const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${commitsReadHint(path2)}${checkRunsReadHint(path2)}${hint}`;
66070
66312
  return new Error(`api get: refusing '${rawPath}' \u2014${routeHints}${API_GET_RECOVERY} Only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.`);
66071
66313
  };
66072
66314
  if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
@@ -69695,13 +69937,15 @@ function envelopeAwareWriteErr(str2) {
69695
69937
  const flag = match[1];
69696
69938
  const argv = process.argv.slice(2);
69697
69939
  const invoked = resolveCommandFromArgv(program2, argv);
69698
- const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, canonicalPathFor(commandPath3(invoked)) ?? commandPath3(invoked), canonicalArgvFor(argv)) : void 0;
69940
+ const invokedPath = invoked ? canonicalPathFor(commandPath3(invoked)) ?? commandPath3(invoked) : void 0;
69941
+ const canonicalArgv = canonicalArgvFor(argv);
69942
+ const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, invokedPath, canonicalArgv) ?? issueCommentAppendOnlyRecovery(flag, invokedPath, canonicalArgv) : void 0;
69699
69943
  if (claimStatus) {
69700
69944
  if (argvWantsJson3()) {
69701
69945
  process.stderr.write(
69702
69946
  formatErrorEnvelope(claimStatus.message, {
69703
69947
  code: ERROR_CODES.ERR_UNKNOWN_FLAG,
69704
- offending_flag: "--status",
69948
+ offending_flag: flag.split("=")[0],
69705
69949
  corrected_command: claimStatus.corrected
69706
69950
  }) + "\n"
69707
69951
  );
@@ -69784,7 +70028,7 @@ registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand
69784
70028
  appActorCommand = commandPath4;
69785
70029
  setAuditCommand(commandPath4);
69786
70030
  if (commandPath4 !== "whoami diagnose-hub") emitCliCallTelemetry(commandPath4);
69787
- if (commandPath4 === "ci cancel-orphans" || commandPath4 === "run cancel") return;
70031
+ if (commandPath4 === "ci cancel-orphans" || commandPath4 === "run cancel" || commandPath4 === "run rerun") return;
69788
70032
  try {
69789
70033
  await activateAppActor(commandPath4, process.env, () => APP_WRITE_COMMANDS.has(commandPath4) ? mintCommandWriteToken(appActorDeps(), commandPath4) : mintInstallationToken(appActorDeps()));
69790
70034
  } catch (e) {
@@ -70619,9 +70863,12 @@ ci.command("audit").description("read-only fleet scan: gate workflow, ruleset co
70619
70863
  });
70620
70864
  registerCiChecksWait(ci);
70621
70865
  registerCancelOrphans(ci, async () => (await mintCommandWriteToken(appActorDeps(), "ci cancel-orphans")).token);
70622
- registerRunCancel(
70623
- program2.command("run").description("bounded workflow-run writes \u2014 `run cancel <id>` is the only one, and it rides the org App actions:write profile"),
70624
- async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run cancel")).token
70866
+ var runRoot = program2.command("run").description("bounded workflow-run writes \u2014 `run cancel <id>` and `run rerun <id> [--failed]`, both on the org App actions:write profile");
70867
+ registerRunCancel(runRoot, async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run cancel")).token);
70868
+ registerRunRerun(
70869
+ runRoot,
70870
+ async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run rerun")).token,
70871
+ async () => fetchProjectsList(registryClientDeps(await loadConfig()))
70625
70872
  );
70626
70873
  ci.command("reconcile").description("audit + optionally apply merge settings and product ruleset activation (master-admin)").option("--json", "machine-readable output").option("--repo <owner/repo>", "reconcile one repo instead of the full registry").option("--apply", "PATCH merge settings + activate product ruleset when missing (master role required); activation is skipped while the repo's gate has never passed (#3694)").option("--park-ruleset", "stop the product ruleset enforcing without deleting it \u2014 keeps its required contexts (master role required)").action(async (o) => {
70627
70874
  if (o.apply && o.parkRuleset) {
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.6.7",
3
+ "version": "4.6.9",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/codex-plugin",
3
- "version": "4.6.7",
3
+ "version": "4.6.9",
4
4
  "description": "MMI workflow skills and org gates delivery.",
5
5
  "author": {
6
6
  "name": "MMI Future",