@mutmutco/cli 4.6.6 → 4.6.8

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
@@ -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",
@@ -15315,7 +15365,7 @@ async function mintFilingWriteToken(deps) {
15315
15365
  try {
15316
15366
  return await mintFromMember(
15317
15367
  deps,
15318
- { metadata: "read", contents: "write", issues: "write", pull_requests: "write" },
15368
+ { metadata: "read", contents: "write", issues: "write", pull_requests: "write", workflows: "write" },
15319
15369
  APP_VAULT_KEYS
15320
15370
  );
15321
15371
  } catch (e) {
@@ -17811,7 +17861,7 @@ var DECLARED_ENTRIES = [
17811
17861
  // #4118: Listening + systemd-active ≠ GitHub online after acquirejob 503. restart.conf cannot see
17812
17862
  // it; an Actions workflow on mmi-live cannot heal it when half the fleet is offline. Root cron.
17813
17863
  { 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)" },
17864
+ { 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
17865
  // #5292: a lane that holds a dispatched JOB but produces zero output past the budget honestly
17816
17866
  // reports "busy", so the online reconcile never fires and GitHub will not re-dispatch while the
17817
17867
  // run holds the assignment. This tick cancels + re-triggers those runs (max 1/tick, debounced).
@@ -27456,10 +27506,10 @@ var rollout_plan_default = {
27456
27506
  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
27507
  },
27458
27508
  baseline: {
27459
- version: "4.6.6",
27460
- tag: "v4.6.6",
27461
- commit: "8431efc33b8c",
27462
- npm: "@mutmutco/cli@4.6.6"
27509
+ version: "4.6.8",
27510
+ tag: "v4.6.8",
27511
+ commit: "0dd075a0d2c1",
27512
+ npm: "@mutmutco/cli@4.6.8"
27463
27513
  },
27464
27514
  exitCriterion: "fleet-n-of-n",
27465
27515
  hubOnlyShortcut: "forbidden",
@@ -27476,14 +27526,14 @@ var rollout_plan_default = {
27476
27526
  repo: "mutmutco/mmi-hub",
27477
27527
  role: "canary",
27478
27528
  schedule: "train",
27479
- v3Target: "v4.6.6"
27529
+ v3Target: "v4.6.8"
27480
27530
  }
27481
27531
  ],
27482
27532
  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
27533
  rollback: {
27484
27534
  independent: true,
27485
- mechanism: "npm dist-tag latest -> 4.6.6 and redeploy the Hub Lambda from tag v4.6.6 (8431efc33b8c); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
27486
- v3Target: "v4.6.6 (@mutmutco/cli@4.6.6, tag commit 8431efc33b8c \u2014 last known-good release carrying the repo-index v4-only contract)"
27535
+ mechanism: "npm dist-tag latest -> 4.6.8 and redeploy the Hub Lambda from tag v4.6.8 (0dd075a0d2c1); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
27536
+ v3Target: "v4.6.8 (@mutmutco/cli@4.6.8, tag commit 0dd075a0d2c1 \u2014 last known-good release carrying the repo-index v4-only contract)"
27487
27537
  }
27488
27538
  },
27489
27539
  {
@@ -41374,6 +41424,20 @@ async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
41374
41424
  state: "queued",
41375
41425
  ...read.mergeQueueEntry ? { position: read.mergeQueueEntry.position, entryState: read.mergeQueueEntry.state } : {}
41376
41426
  };
41427
+ const groupSha = read.mergeQueueEntry?.headCommit?.oid;
41428
+ if (!options.enqueueOnly && groupSha && deps.findQueueStartupFailure) {
41429
+ const startup = await deps.findQueueStartupFailure(base.repo, groupSha).catch(() => void 0);
41430
+ if (startup) {
41431
+ const detail = startup.annotation ? `: ${startup.annotation}` : startup.url ? ` (${startup.url})` : "";
41432
+ const result2 = {
41433
+ ...base,
41434
+ queueRunStartupFailure: startup,
41435
+ error: `queue-run-startup-failure: ${startup.workflow} run ${startup.runId} on ${startup.headSha}${detail}`
41436
+ };
41437
+ deps.queueProgress?.(result2);
41438
+ return result2;
41439
+ }
41440
+ }
41377
41441
  deps.queueProgress?.(base);
41378
41442
  if (options.enqueueOnly) return { ...base, status: "queued" };
41379
41443
  } else if (read.state === "OPEN" && !read.autoMergeRequest) {
@@ -41678,6 +41742,17 @@ function readOverrideTrailer(trailer, body, cwd) {
41678
41742
  }).split(/\r?\n/).filter((line) => line.trim().toLowerCase().startsWith(prefix)).map((line) => line.trim().slice(prefix.length).trim()).join("\n").trim();
41679
41743
  return { value, messageTransform: footer[1] ? "strip-squash-separator-coauthors" : "strip-terminal-coauthors" };
41680
41744
  }
41745
+ function readMessageOverride(message2, cwd) {
41746
+ const prefix = `${TRAILER_KEY.toLowerCase()}:`;
41747
+ const direct = (0, import_node_child_process10.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
41748
+ windowsHide: true,
41749
+ cwd,
41750
+ input: message2,
41751
+ encoding: "utf8",
41752
+ maxBuffer: 32 * 1024 * 1024
41753
+ }).split(/\r?\n/).filter((line) => line.trim().toLowerCase().startsWith(prefix)).map((line) => line.trim().slice(prefix.length).trim()).join("\n").trim();
41754
+ return readOverrideTrailer(direct, message2, cwd).value;
41755
+ }
41681
41756
  function readOverride(base, cwd, head = "HEAD") {
41682
41757
  const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
41683
41758
  const refusals = [];
@@ -41706,6 +41781,9 @@ var HOTFIX_DIFF_BASE = "origin/main";
41706
41781
  var TEST_RE = TEST_FILE_RE;
41707
41782
  var PY_TEST_RE = PY_TEST_FILE_RE;
41708
41783
  var GO_TEST_RE = GO_TEST_FILE_RE;
41784
+ function readMessageOverride2(message2, cwd) {
41785
+ return readMessageOverride(message2, cwd);
41786
+ }
41709
41787
  function translate(glob) {
41710
41788
  let out = "";
41711
41789
  for (let i = 0; i < glob.length; i++) {
@@ -42531,33 +42609,38 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
42531
42609
  });
42532
42610
  const rejected = (reason) => admissionRejected(reason, repo, pr);
42533
42611
  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 });
42612
+ const maxReads = options.enqueueOnly ? 3 : 1;
42613
+ const sleep3 = options.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
42614
+ for (let readAttempt = 0; readAttempt < maxReads; readAttempt++) {
42615
+ if (readAttempt) await sleep3(readAttempt * 1e3);
42616
+ try {
42617
+ const observed = await readPrMergeQueue(pr, repo, read);
42618
+ if (observed.state === "MERGED" && observed.headRefOid === head) return { mergeStatus: "merged" };
42619
+ if (observed.isInMergeQueue && observed.headRefOid === head) {
42620
+ const entry = observed.mergeQueueEntry;
42621
+ if (recordEntry && receipt?.head === head && entry?.headCommit && entry.enqueuedAt) {
42622
+ journal.save("enqueued", receipt?.uuid, { headCommitOid: entry.headCommit.oid, enqueuedAt: entry.enqueuedAt });
42623
+ }
42624
+ return { mergeStatus: "auto-merge-enqueued" };
42541
42625
  }
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");
42626
+ const saved = recordEntry ? admissionJournal(repo, pr, options.receiptDirectory).receipt : void 0;
42627
+ const boundMs = saved?.queueEntry ? Date.parse(saved.queueEntry.enqueuedAt) : resumingSaved && saved?.state === "enqueued" && saved.queueWitnessed === void 0 ? saved.observedAt : void 0;
42628
+ if (saved && boundMs !== void 0 && Number.isFinite(boundMs) && observed.state === "OPEN" && !observed.isInMergeQueue && observed.headRefOid === saved.head) {
42629
+ const events = await readArrayPages(
42630
+ `repos/${repo}/issues/${pr}/timeline?per_page=100`,
42631
+ options.timelineRead ?? read,
42632
+ "queue ejection timeline unreadable"
42633
+ );
42634
+ const times = (name) => events.filter((event) => event.event === name).map((event) => Date.parse(event.created_at));
42635
+ const adds = times("added_to_merge_queue").filter((at) => at >= boundMs);
42636
+ const removals = adds.length === 1 ? times("removed_from_merge_queue").filter((at) => at > adds[0]) : [];
42637
+ if (removals.length === 1) {
42638
+ journal.save("ejected");
42639
+ return rejected("the previous admission was ejected by GitHub; rerun to submit a new intent");
42640
+ }
42558
42641
  }
42642
+ } catch {
42559
42643
  }
42560
- } catch {
42561
42644
  }
42562
42645
  return unknown(reason);
42563
42646
  };
@@ -42738,6 +42821,19 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
42738
42821
  error: "GitHub accepted queue admission but has not confirmed queue membership. Rerun pr land to resume the saved operation read-only."
42739
42822
  };
42740
42823
  }
42824
+ function githubQueueSquashMessage(title, pr, commits) {
42825
+ const list = commits.map((commit) => `* ${commit.headline}
42826
+
42827
+ ${commit.body}`.trim()).join("\n\n");
42828
+ return `${title} (#${pr})
42829
+
42830
+ ${list}
42831
+
42832
+ ---------
42833
+
42834
+ Co-authored-by: GitHub <noreply@github.com>
42835
+ `;
42836
+ }
42741
42837
  async function enqueuePrMergeQueue(pr, repo, options = {}) {
42742
42838
  if (!/^\d+$/.test(pr)) throw new Error("merge queue enqueue requires a PR number");
42743
42839
  pr = pr.replace(/^0+(?=\d)/, "");
@@ -42853,6 +42949,19 @@ ${pull.body}`;
42853
42949
  ])];
42854
42950
  return preparation("queue-message", targets, `GitHub's unchanged ${policy.body} message is unsafe for ${targets.map((number) => `${repo}#${number}`).join(", ")}. ${verdict.message}`);
42855
42951
  }
42952
+ 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}
42953
+ ${message2.body}`)) && !readMessageOverride2(githubQueueSquashMessage(pull.title, pr, messages), process.cwd())) {
42954
+ return {
42955
+ mergeStatus: "failed",
42956
+ preparation: {
42957
+ status: "preparation-needed",
42958
+ reason: "queue-message-override",
42959
+ issueTargets: [],
42960
+ 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."
42961
+ },
42962
+ 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"
42963
+ };
42964
+ }
42856
42965
  const requiredBody = options.bodyText ?? composedBody;
42857
42966
  const prBodyPreservesOverrides = requiredBody != null && method === "SQUASH" && policy.body === "PR_BODY" && options.bodyText == null && prBodyPreservesCommitOverrides(messages, headline, pull.body, process.cwd());
42858
42967
  if (requiredBody && !prBodyPreservesOverrides && (method !== "SQUASH" || requiredBody.trim() !== body.trim())) {
@@ -42904,6 +43013,54 @@ async function findPrMergeGroupRun(pr, repo, base, gh2 = readApi) {
42904
43013
  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
43014
  return run ? { id: run.id, url: run.html_url, conclusion: run.conclusion } : void 0;
42906
43015
  }
43016
+ async function findPrMergeQueueStartupFailure(repo, groupHeadSha, gh2 = readApi) {
43017
+ if (!/^[a-f0-9]{40}$/i.test(groupHeadSha)) return void 0;
43018
+ try {
43019
+ const body = JSON.parse(await gh2([`repos/${repo}/actions/runs?head_sha=${groupHeadSha}&per_page=100`]));
43020
+ const runs2 = (body.workflow_runs ?? []).filter((r) => r.head_sha?.toLowerCase() === groupHeadSha.toLowerCase() && r.status === "completed");
43021
+ for (const run of runs2) {
43022
+ let isStartupFailure = run.conclusion === "startup_failure";
43023
+ if (!isStartupFailure && run.conclusion === "failure") {
43024
+ const jobsBody = JSON.parse(await gh2([`repos/${repo}/actions/runs/${run.id}/jobs?per_page=1`]));
43025
+ if (jobsBody.total_count === 0 && Array.isArray(jobsBody.jobs) && jobsBody.jobs.length === 0) {
43026
+ isStartupFailure = true;
43027
+ }
43028
+ }
43029
+ if (!isStartupFailure) continue;
43030
+ const workflow = (run.path?.replace(/^\.github\/workflows\//, "") || run.name || "workflow").trim();
43031
+ let annotation;
43032
+ if (run.check_suite_id) {
43033
+ try {
43034
+ const checkRuns = JSON.parse(await gh2([`repos/${repo}/check-suites/${run.check_suite_id}/check-runs?per_page=20`]));
43035
+ for (const cr of checkRuns.check_runs ?? []) {
43036
+ const summary = [cr.output?.title, cr.output?.summary].filter(Boolean).join(" \u2014 ").trim();
43037
+ if (summary) {
43038
+ annotation = summary;
43039
+ break;
43040
+ }
43041
+ const anns = JSON.parse(await gh2([`repos/${repo}/check-runs/${cr.id}/annotations?per_page=10`]));
43042
+ const first = Array.isArray(anns) ? anns.find((a) => a?.message || a?.title) : void 0;
43043
+ if (first) {
43044
+ annotation = (first.message || first.title || "").trim();
43045
+ break;
43046
+ }
43047
+ }
43048
+ } catch {
43049
+ }
43050
+ }
43051
+ return {
43052
+ workflow,
43053
+ runId: run.id,
43054
+ headSha: groupHeadSha,
43055
+ ...run.html_url ? { url: run.html_url } : {},
43056
+ ...annotation ? { annotation } : {}
43057
+ };
43058
+ }
43059
+ } catch {
43060
+ return void 0;
43061
+ }
43062
+ return void 0;
43063
+ }
42907
43064
  function workflowNeeds(text) {
42908
43065
  const doc = (0, import_yaml3.parse)(text);
42909
43066
  const out = /* @__PURE__ */ new Map();
@@ -42999,6 +43156,9 @@ async function readBaseRedVerdict(result, gh2 = appOnlyReadApi) {
42999
43156
  return mine && base && failuresAllOnBase(mine, base) ? { baseRunId: String(baseRun.id) } : void 0;
43000
43157
  }
43001
43158
  function prMergeQueueLine(result) {
43159
+ if (result.queueRunStartupFailure) {
43160
+ return `pr land: PR #${result.pr} ${result.error ?? `queue-run-startup-failure: ${result.queueRunStartupFailure.workflow} run ${result.queueRunStartupFailure.runId} on ${result.queueRunStartupFailure.headSha}`}`;
43161
+ }
43002
43162
  const queue = result.mergeQueue;
43003
43163
  if (!queue) return void 0;
43004
43164
  switch (queue.state) {
@@ -59501,6 +59661,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
59501
59661
  readMergeQueue: readPrMergeQueue,
59502
59662
  observeQueued: (prNumber, repo, snapshot) => observeQueuedAdmission(repo, prNumber, snapshot),
59503
59663
  findMergeGroupRun: findPrMergeGroupRun,
59664
+ findQueueStartupFailure: findPrMergeQueueStartupFailure,
59504
59665
  queueProgress: (receipt) => {
59505
59666
  const line = prMergeQueueLine(receipt);
59506
59667
  if (line) (o.json ? console.warn : printLine)(line);
@@ -65676,15 +65837,18 @@ function formatBrokerRunListRecovery(spec, jq) {
65676
65837
  }
65677
65838
  function brokerGhFailureHint(failureText, args, jq) {
65678
65839
  const target = args[0] === "pr" && args[1] === "view" ? args[2] : void 0;
65840
+ const repoIdx = args.indexOf("--repo");
65841
+ const repo = repoIdx >= 0 && args[repoIdx + 1] && !args[repoIdx + 1].startsWith("-") ? args[repoIdx + 1] : void 0;
65679
65842
  const unknownField = /Unknown JSON field: "([^"]+)"/.exec(failureText)?.[1] ?? /Unknown JSON field: ([A-Za-z][A-Za-z0-9]*)/.exec(failureText)?.[1];
65680
65843
  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`;
65844
+ 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
65845
  }
65683
65846
  if (unknownField) {
65684
65847
  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
65848
  }
65686
65849
  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`;
65850
+ const slug = repo ?? "<owner/repo>";
65851
+ 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
65852
  }
65689
65853
  if (args[0] === "run" && args[1] === "list") {
65690
65854
  return formatBrokerRunListRecovery({ program: "gh", args: [...args], timeoutMs: SINGLE_SHOT_TIMEOUT_MS }, jq);
@@ -66062,11 +66226,26 @@ function branchRulesReadHint(path2) {
66062
66226
  const slug = `${match[1]}/${match[2]}`;
66063
66227
  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
66228
  }
66229
+ function commitsReadHint(path2) {
66230
+ const clean4 = path2.split("?")[0];
66231
+ if (branchRulesReadHint(clean4) || checkRunsReadHint(clean4)) return "";
66232
+ const match = new RegExp(`^repos/(${NAME})/(${NAME})/(?:commits|branches)(?:[/?#]|$)`).exec(clean4);
66233
+ if (!match) return "";
66234
+ const slug = `${match[1]}/${match[2]}`;
66235
+ 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\`.`;
66236
+ }
66237
+ function checkRunsReadHint(path2) {
66238
+ const clean4 = path2.split("?")[0];
66239
+ const match = new RegExp(`^repos/(${NAME})/(${NAME})/(?:check-suites(?:/|$)|check-runs(?:/|$)|commits/[^/?#]+/(?:check-runs|check-suites)(?:/|$))`).exec(clean4);
66240
+ if (!match) return "";
66241
+ const slug = `${match[1]}/${match[2]}`;
66242
+ 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).`;
66243
+ }
66065
66244
  function parseApiGetPath(rawPath) {
66066
66245
  const path2 = rawPath.trim();
66067
66246
  const refuse = () => {
66068
66247
  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}`;
66248
+ const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${commitsReadHint(path2)}${checkRunsReadHint(path2)}${hint}`;
66070
66249
  return new Error(`api get: refusing '${rawPath}' \u2014${routeHints}${API_GET_RECOVERY} Only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.`);
66071
66250
  };
66072
66251
  if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
@@ -69695,13 +69874,15 @@ function envelopeAwareWriteErr(str2) {
69695
69874
  const flag = match[1];
69696
69875
  const argv = process.argv.slice(2);
69697
69876
  const invoked = resolveCommandFromArgv(program2, argv);
69698
- const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, canonicalPathFor(commandPath3(invoked)) ?? commandPath3(invoked), canonicalArgvFor(argv)) : void 0;
69877
+ const invokedPath = invoked ? canonicalPathFor(commandPath3(invoked)) ?? commandPath3(invoked) : void 0;
69878
+ const canonicalArgv = canonicalArgvFor(argv);
69879
+ const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, invokedPath, canonicalArgv) ?? issueCommentAppendOnlyRecovery(flag, invokedPath, canonicalArgv) : void 0;
69699
69880
  if (claimStatus) {
69700
69881
  if (argvWantsJson3()) {
69701
69882
  process.stderr.write(
69702
69883
  formatErrorEnvelope(claimStatus.message, {
69703
69884
  code: ERROR_CODES.ERR_UNKNOWN_FLAG,
69704
- offending_flag: "--status",
69885
+ offending_flag: flag.split("=")[0],
69705
69886
  corrected_command: claimStatus.corrected
69706
69887
  }) + "\n"
69707
69888
  );
@@ -455,11 +455,13 @@ var init_cli_shared = __esm({
455
455
  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";
456
456
  recordGhUsage("gh", pool, args.filter((a) => !a.startsWith("-")).slice(0, 2).join(" "));
457
457
  }
458
- if (!appEnv) return tag(run(), options.env?.MMI_GH_POOL === "app" ? "app_installation" : "user");
458
+ const ambientEnv = options.env ?? process.env;
459
+ const ambient = ambientEnv.MMI_GH_POOL === "app" || /^ghs_/.test(ambientEnv.GH_TOKEN || ambientEnv.GITHUB_TOKEN || "") ? "app_installation" : "user";
460
+ if (!appEnv) return tag(run(), ambient);
459
461
  try {
460
462
  return await tag(run(appEnv), "app_installation");
461
463
  } catch (e) {
462
- if (shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(), "user");
464
+ if (shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(), ambient);
463
465
  throw e;
464
466
  }
465
467
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.6.6",
3
+ "version": "4.6.8",
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",