@mutmutco/cli 4.6.4 → 4.6.5

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
@@ -1355,6 +1355,7 @@ __export(cli_shared_exports, {
1355
1355
  fail: () => fail,
1356
1356
  failGraceful: () => failGraceful,
1357
1357
  failGracefulEnvelope: () => failGracefulEnvelope,
1358
+ ghFilingAppEnv: () => ghFilingAppEnv,
1358
1359
  gitOut: () => gitOut,
1359
1360
  githubLogin: () => githubLogin,
1360
1361
  hubHeaders: () => hubHeaders,
@@ -1385,10 +1386,10 @@ function setGhReadTokenProvider(provider) {
1385
1386
  function setGhWriteTokenProvider(provider) {
1386
1387
  ghWriteTokenProvider = provider;
1387
1388
  }
1388
- async function ghFilingAppEnv(file, args) {
1389
- if (!ghWriteTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || process.env.MMI_ACTOR?.trim() === "personal") return void 0;
1389
+ async function ghFilingAppEnv(file, args, requireApp = false) {
1390
+ if (!ghWriteTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || !requireApp && process.env.MMI_ACTOR?.trim() === "personal") return void 0;
1390
1391
  if (!isAppFilingWrite(args)) return void 0;
1391
- const explicitApp = process.env.MMI_ACTOR?.trim() === "app";
1392
+ const explicitApp = requireApp || process.env.MMI_ACTOR?.trim() === "app";
1392
1393
  let token;
1393
1394
  try {
1394
1395
  token = await ghWriteTokenProvider();
@@ -1616,7 +1617,7 @@ var init_cli_shared = __esm({
1616
1617
  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";
1617
1618
  recordGhUsage("gh", pool, args.filter((a) => !a.startsWith("-")).slice(0, 2).join(" "));
1618
1619
  }
1619
- if (!appEnv) return tag(run(), "user");
1620
+ if (!appEnv) return tag(run(), options.env?.MMI_GH_POOL === "app" ? "app_installation" : "user");
1620
1621
  try {
1621
1622
  return await tag(run(appEnv), "app_installation");
1622
1623
  } catch (e) {
@@ -23741,10 +23742,14 @@ async function claimOneBoardItem(ctx, selector, options) {
23741
23742
  return;
23742
23743
  }
23743
23744
  if (!options.force) {
23745
+ if (options.bulk) {
23746
+ const { holder: holder2, why } = laneContestParts(contest);
23747
+ throw new Error(isUnknownHold(contest) ? `${item.ref} cannot be verified as free: ${holder2} (${why}); the liveness reads failed so the claim is refused fail-closed \u2014 ${CONTESTED_CLAIM_RECOVERY}` : `${item.ref} is already claimed by ${holder2} with live evidence of active work (${why}); ${CONTESTED_CLAIM_RECOVERY}`);
23748
+ }
23744
23749
  const candidateRefs = [...report.primary.claimable, ...report.secondary.claimable].filter((candidate) => candidate.contentType === "Issue" && candidate.state === "OPEN" && !candidate.claim?.currentlyClaimed && !ctx.selectedRefs.has(candidate.ref.toLowerCase())).map((candidate) => candidate.ref);
23745
23750
  const alternatives = candidateRefs.length ? `Board snapshot candidates (not guaranteed free): ${candidateRefs.join(", ")}` : "Board snapshot candidates: none in this read";
23746
23751
  const refusal = `${laneContestMessage(item.ref, contest, "claim")}. ${alternatives}; refresh mmi-cli oracle board read before claiming.`;
23747
- throw new Error(options.bulk ? refusal : withGatedWriteChainHint(refusal));
23752
+ throw new Error(withGatedWriteChainHint(refusal));
23748
23753
  }
23749
23754
  previousHolder = displaced();
23750
23755
  };
@@ -23799,6 +23804,13 @@ async function claimBoardIssue(options, deps = {}) {
23799
23804
  const result = await claimOneBoardItem(ctx, selector, options);
23800
23805
  return ctx.report.warnings.length ? { ...result, warnings: [...ctx.report.warnings] } : result;
23801
23806
  }
23807
+ function formatBulkClaimSummary(results) {
23808
+ const skipped = results.filter((r) => !r.claimed);
23809
+ if (skipped.length === 0) return void 0;
23810
+ const landed = results.filter((r) => r.claimed).map((r) => r.ref);
23811
+ const notClaimed = skipped.map((r) => `${r.ref}${/already claimed by /i.test(r.reason) ? " (held by another lane)" : ""}`);
23812
+ return `claimed ${landed.length} of ${results.length}: ${landed.length ? `${landed.join(", ")} landed` : "none landed"}; not claimed: ${notClaimed.join(", ")}`;
23813
+ }
23802
23814
  async function claimBoardIssues(options, deps = {}) {
23803
23815
  const cfg = resolveBoardConfig(options.config);
23804
23816
  const { collected, snapshot } = await collectBoardPreferringSnapshot(cfg, options, deps);
@@ -27388,10 +27400,10 @@ var rollout_plan_default = {
27388
27400
  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)."
27389
27401
  },
27390
27402
  baseline: {
27391
- version: "4.6.4",
27392
- tag: "v4.6.4",
27393
- commit: "db079140cdfb",
27394
- npm: "@mutmutco/cli@4.6.4"
27403
+ version: "4.6.5",
27404
+ tag: "v4.6.5",
27405
+ commit: "cd6f8a959ed5",
27406
+ npm: "@mutmutco/cli@4.6.5"
27395
27407
  },
27396
27408
  exitCriterion: "fleet-n-of-n",
27397
27409
  hubOnlyShortcut: "forbidden",
@@ -27408,14 +27420,14 @@ var rollout_plan_default = {
27408
27420
  repo: "mutmutco/mmi-hub",
27409
27421
  role: "canary",
27410
27422
  schedule: "train",
27411
- v3Target: "v4.6.4"
27423
+ v3Target: "v4.6.5"
27412
27424
  }
27413
27425
  ],
27414
27426
  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.",
27415
27427
  rollback: {
27416
27428
  independent: true,
27417
- mechanism: "npm dist-tag latest -> 4.6.4 and redeploy the Hub Lambda from tag v4.6.4 (db079140cdfb); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
27418
- v3Target: "v4.6.4 (@mutmutco/cli@4.6.4, tag commit db079140cdfb \u2014 last known-good release carrying the repo-index v4-only contract)"
27429
+ mechanism: "npm dist-tag latest -> 4.6.5 and redeploy the Hub Lambda from tag v4.6.5 (cd6f8a959ed5); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
27430
+ v3Target: "v4.6.5 (@mutmutco/cli@4.6.5, tag commit cd6f8a959ed5 \u2014 last known-good release carrying the repo-index v4-only contract)"
27419
27431
  }
27420
27432
  },
27421
27433
  {
@@ -41941,6 +41953,13 @@ var writeApi = async (args) => {
41941
41953
  const { stdout, credential } = await execFileP("gh", ["api", ...args], { timeout: GH_MUTATION_TIMEOUT_MS, maxBuffer: 16 * 1024 * 1024 });
41942
41954
  return { stdout, credential };
41943
41955
  };
41956
+ var autoMergeApi = async (pr, repo, head) => {
41957
+ const args = ["pr", "merge", pr, "-R", repo, "--squash", "--auto", "--match-head-commit", head];
41958
+ const env = await ghFilingAppEnv("gh", args, true);
41959
+ if (!env) throw new Error("app actor: filing write token unavailable for merge-queue auto-merge fallback");
41960
+ await execFileP("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS, maxBuffer: 16 * 1024 * 1024, env });
41961
+ return { credential: "app_installation" };
41962
+ };
41944
41963
  var ASYNC_HEADERS = ["-H", "Accept: application/vnd.github+json", "-H", "X-GitHub-Api-Version: 2026-03-10"];
41945
41964
  var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
41946
41965
  function admissionJournal(repo, pr, root) {
@@ -42017,6 +42036,21 @@ function admissionResponse(output) {
42017
42036
  }
42018
42037
  return { code: Number(response[1]), body: result };
42019
42038
  }
42039
+ function admissionRejected(reason) {
42040
+ if (/enqueuer is not authorized to merge|not authorized to merge/i.test(reason)) {
42041
+ return {
42042
+ mergeStatus: "failed",
42043
+ retryable: false,
42044
+ admissionCause: "enqueuer-not-authorized",
42045
+ error: `queue admission rejected (missing merge permission): ${reason}. The landing identity reported in \`credential\` may not merge into this base through the merge queue. Granting it (the GitHub App's merge permission, or a ruleset bypass) is a repository or organization setting only an admin can change; do not rerun pr land until that grant exists, and never fall back to a personal token.`
42046
+ };
42047
+ }
42048
+ return {
42049
+ mergeStatus: "failed",
42050
+ retryable: false,
42051
+ error: `queue admission rejected: ${reason}. Resolve the reported cause, then explicitly rerun pr land; it will repeat preflight before a new request.`
42052
+ };
42053
+ }
42020
42054
  async function submitQueueAdmission(pr, repo, head, read, write, options, resume = false) {
42021
42055
  const unknown = (reason) => ({
42022
42056
  mergeStatus: "failed",
@@ -42024,11 +42058,7 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
42024
42058
  outcomeUnknown: true,
42025
42059
  error: `queue submission outcome unknown: ${reason}. Preserve the receipt and rerun pr land to reconcile read-only; no new request was sent after this uncertainty.`
42026
42060
  });
42027
- const rejected = (reason) => ({
42028
- mergeStatus: "failed",
42029
- retryable: false,
42030
- error: `queue admission rejected: ${reason}. Resolve the reported cause, then explicitly rerun pr land; it will repeat preflight before a new request.`
42031
- });
42061
+ const rejected = admissionRejected;
42032
42062
  const reconcile = async (reason, recordEntry = false) => {
42033
42063
  try {
42034
42064
  const observed = await readPrMergeQueue(pr, repo, read);
@@ -42138,13 +42168,45 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
42138
42168
  }
42139
42169
  return reconcile(`GitHub reported ${body.status}, but matching-head membership or merged state is unconfirmed`, body.status === "enqueued");
42140
42170
  }
42171
+ const tryAutoMergeFallback = async (reason, uuid) => {
42172
+ const autoMerge = options.autoMerge ?? (write === writeApi ? autoMergeApi : void 0);
42173
+ if (!/enqueuer is not authorized to merge/i.test(reason) || !autoMerge) return void 0;
42174
+ try {
42175
+ const fallback = await autoMerge(pr, repo, head);
42176
+ const fallbackCredential = fallback?.credential ?? credential ?? receipt?.credential;
42177
+ const observed = await readPrMergeQueue(pr, repo, read);
42178
+ if (observed.state === "MERGED" && observed.headRefOid === head) {
42179
+ try {
42180
+ journal.save("merged", uuid, void 0, fallbackCredential);
42181
+ } catch {
42182
+ return reconcile("the completed auto-merge fallback could not be checkpointed");
42183
+ }
42184
+ return { mergeStatus: "merged" };
42185
+ }
42186
+ if (observed.isInMergeQueue && observed.headRefOid === head) {
42187
+ const entry = observed.mergeQueueEntry;
42188
+ const queueEntry = entry?.headCommit && entry.enqueuedAt ? { headCommitOid: entry.headCommit.oid, enqueuedAt: entry.enqueuedAt } : void 0;
42189
+ try {
42190
+ journal.save("enqueued", uuid, queueEntry, fallbackCredential);
42191
+ } catch {
42192
+ return reconcile("the enqueued auto-merge fallback could not be checkpointed");
42193
+ }
42194
+ return { mergeStatus: "auto-merge-enqueued" };
42195
+ }
42196
+ } catch {
42197
+ }
42198
+ return void 0;
42199
+ };
42141
42200
  if (code === 400 || code === 403 || code === 404 || code === 422) {
42201
+ const reason = body.details?.message ?? body.message ?? `HTTP ${code}`;
42202
+ const fallbackResult = await tryAutoMergeFallback(reason);
42203
+ if (fallbackResult) return fallbackResult;
42142
42204
  try {
42143
42205
  journal.save("failed", void 0, void 0, credential);
42144
42206
  } catch {
42145
42207
  return unknown("the explicit rejection could not be checkpointed");
42146
42208
  }
42147
- return rejected(body.details?.message ?? body.message ?? `HTTP ${code}`);
42209
+ return rejected(reason);
42148
42210
  }
42149
42211
  const details = body.details;
42150
42212
  if (code !== 202 && code !== 409 || body.status !== "pending" || !UUID.test(details?.uuid ?? "")) return reconcile("the send returned no recognized admission result");
@@ -42175,6 +42237,36 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
42175
42237
  continue;
42176
42238
  }
42177
42239
  if (!["failed", "enqueued", "merged"].includes(status ?? "")) return reconcile("the operation returned an unrecognized state");
42240
+ if (status === "failed") {
42241
+ const reason = details?.message ?? "GitHub reported a failed operation";
42242
+ const autoMerge = options.autoMerge ?? (write === writeApi ? autoMergeApi : void 0);
42243
+ if (/enqueuer is not authorized to merge/i.test(reason) && autoMerge && receipt.head === head) {
42244
+ try {
42245
+ const fallback = await autoMerge(pr, repo, head);
42246
+ const fallbackCredential = fallback?.credential ?? receipt?.credential;
42247
+ const observed = await readPrMergeQueue(pr, repo, read);
42248
+ if (observed.state === "MERGED" && observed.headRefOid === head) {
42249
+ try {
42250
+ journal.save("merged", receipt.uuid, void 0, fallbackCredential);
42251
+ } catch {
42252
+ return reconcile("the completed auto-merge fallback could not be checkpointed");
42253
+ }
42254
+ return { mergeStatus: "merged" };
42255
+ }
42256
+ if (observed.isInMergeQueue && observed.headRefOid === head) {
42257
+ const entry = observed.mergeQueueEntry;
42258
+ const queueEntry = entry?.headCommit && entry.enqueuedAt ? { headCommitOid: entry.headCommit.oid, enqueuedAt: entry.enqueuedAt } : void 0;
42259
+ try {
42260
+ journal.save("enqueued", receipt.uuid, queueEntry, fallbackCredential);
42261
+ } catch {
42262
+ return reconcile("the enqueued auto-merge fallback could not be checkpointed");
42263
+ }
42264
+ return { mergeStatus: "auto-merge-enqueued" };
42265
+ }
42266
+ } catch {
42267
+ }
42268
+ }
42269
+ }
42178
42270
  try {
42179
42271
  journal.save(status, receipt.uuid);
42180
42272
  } catch {
@@ -42513,22 +42605,24 @@ function registerCancelOrphans(ci2, writeToken) {
42513
42605
  });
42514
42606
  }
42515
42607
  var RUN_CANCEL_STATUS = "cancel-requested";
42516
- async function cancelWorkflowRun(repo, id, deps) {
42608
+ var RUN_FORCE_CANCEL_STATUS = "force-cancel-requested";
42609
+ async function cancelWorkflowRun(repo, id, deps, force = false) {
42517
42610
  const raw = String(id).trim();
42518
42611
  if (!/^[1-9]\d*$/.test(raw)) throw new Error(`run cancel: <id> must be a positive workflow run id (received '${String(id)}')`);
42519
42612
  const runId = Number(raw);
42520
42613
  if (!Number.isSafeInteger(runId)) throw new Error(`run cancel: <id> ${raw} is beyond the safe integer range; refusing to address a different run`);
42521
42614
  if (!/^[\w.-]+\/[\w.-]+$/.test(repo)) throw new Error(`run cancel: --repo must be owner/repo (received '${repo}')`);
42522
42615
  try {
42523
- await deps.cancel(repo, runId);
42616
+ if (force && deps.forceCancel) await deps.forceCancel(repo, runId);
42617
+ else await deps.cancel(repo, runId);
42524
42618
  } catch (error) {
42525
42619
  const detail = String(error.stderr ?? error);
42526
42620
  throw new Error(/\b403\b/.test(detail) ? "run cancel: GitHub App denied (HTTP 403); actions:write permission is required; no personal-token fallback" : `run cancel: ${detail}`);
42527
42621
  }
42528
- return { ok: true, repo, runId, status: RUN_CANCEL_STATUS };
42622
+ return { ok: true, repo, runId, ...force ? { force: true } : {}, status: force ? RUN_FORCE_CANCEL_STATUS : RUN_CANCEL_STATUS };
42529
42623
  }
42530
42624
  function registerRunCancel(run, writeToken) {
42531
- run.command("cancel <id>").description("cancel one queued or in-progress workflow run on the org App (actions:write); requests the cancel and prints a receipt").requiredOption("--repo <owner/repo>", "repository that owns the run").option("--json", "machine-readable receipt").action(async (id, options) => {
42625
+ run.command("cancel <id>").description("cancel one queued or in-progress workflow run on the org App (actions:write); requests the cancel and prints a receipt").requiredOption("--repo <owner/repo>", "repository that owns the run").option("--force", "force-cancel the run so always() jobs stop too (#9512)").option("--json", "machine-readable receipt").action(async (id, options) => {
42532
42626
  let receipt;
42533
42627
  try {
42534
42628
  receipt = await cancelWorkflowRun(options.repo, id, {
@@ -42538,13 +42632,20 @@ function registerRunCancel(run, writeToken) {
42538
42632
  timeout: 2e4,
42539
42633
  env: { ...process.env, GH_TOKEN: token }
42540
42634
  });
42635
+ },
42636
+ forceCancel: async (repo, runId) => {
42637
+ const token = await writeToken();
42638
+ await execFileP("gh", ["api", `repos/${repo}/actions/runs/${runId}/force-cancel`, "--method", "POST"], {
42639
+ timeout: 2e4,
42640
+ env: { ...process.env, GH_TOKEN: token }
42641
+ });
42541
42642
  }
42542
- });
42643
+ }, options.force === true);
42543
42644
  } catch (error) {
42544
42645
  return fail(error.message);
42545
42646
  }
42546
42647
  if (options.json) console.log(JSON.stringify(receipt));
42547
- else console.log(`run cancel: ${receipt.repo} run ${receipt.runId} \u2014 cancel requested (status: ${receipt.status})`);
42648
+ else console.log(`run cancel: ${receipt.repo} run ${receipt.runId} \u2014 ${receipt.force ? "force-cancel" : "cancel"} requested (status: ${receipt.status})`);
42548
42649
  });
42549
42650
  }
42550
42651
 
@@ -42963,6 +43064,8 @@ function registerBoardCommands(program3) {
42963
43064
  console.log(result.claimed ? claimVerdict(result.ref, result) : `Skipped ${result.ref}: ${result.reason}`);
42964
43065
  if (result.claimed) for (const line of formatDecisionLines(result.ref, ruled[i].decisions ?? [])) console.log(line);
42965
43066
  });
43067
+ const summary = formatBulkClaimSummary(bulk.results);
43068
+ if (summary) console.log(summary);
42966
43069
  printClaimWarnings([...bulk.warnings ?? [], ...rulingWarnings]);
42967
43070
  }
42968
43071
  if (bulk.failed > 0) process.exitCode = 1;
@@ -65735,11 +65838,17 @@ function tagsReadHint(path2) {
65735
65838
  const slug = `${match[1]}/${match[2]}`;
65736
65839
  return ` No App-token REST read exists for git tags or refs \u2014 read git tags directly with \`git ls-remote --tags origin 'refs/tags/*'\` (no token needed; \`git ls-remote origin refs/tags/<tag>\` for one tag), or read tag-backed GitHub Releases with \`MMI_ACTOR=app mmi-cli devops api get "repos/${slug}/releases?per_page=20"\`.`;
65737
65840
  }
65841
+ function branchRulesReadHint(path2) {
65842
+ const match = new RegExp(`^repos/(${NAME})/(${NAME})/(?:rules/branches/[^/?#]+|branches/[^/?#]+/protection(?:/|$)|rulesets(?:/|$))`).exec(path2.split("?")[0]);
65843
+ if (!match) return "";
65844
+ const slug = `${match[1]}/${match[2]}`;
65845
+ 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\`.`;
65846
+ }
65738
65847
  function parseApiGetPath(rawPath) {
65739
65848
  const path2 = rawPath.trim();
65740
65849
  const refuse = () => {
65741
65850
  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." : "";
65742
- const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${hint}`;
65851
+ const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${hint}`;
65743
65852
  return new Error(`api get: refusing '${rawPath}' \u2014${routeHints}${API_GET_RECOVERY} Only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.`);
65744
65853
  };
65745
65854
  if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
@@ -69457,7 +69566,7 @@ registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand
69457
69566
  appActorCommand = commandPath4;
69458
69567
  setAuditCommand(commandPath4);
69459
69568
  if (commandPath4 !== "whoami diagnose-hub") emitCliCallTelemetry(commandPath4);
69460
- if (commandPath4 === "ci cancel-orphans") return;
69569
+ if (commandPath4 === "ci cancel-orphans" || commandPath4 === "run cancel") return;
69461
69570
  try {
69462
69571
  await activateAppActor(commandPath4, process.env, () => APP_WRITE_COMMANDS.has(commandPath4) ? mintCommandWriteToken(appActorDeps(), commandPath4) : mintInstallationToken(appActorDeps()));
69463
69572
  } catch (e) {
@@ -70294,7 +70403,7 @@ registerCiChecksWait(ci);
70294
70403
  registerCancelOrphans(ci, async () => (await mintCommandWriteToken(appActorDeps(), "ci cancel-orphans")).token);
70295
70404
  registerRunCancel(
70296
70405
  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"),
70297
- async () => (await mintCommandWriteToken(appActorDeps(), "run cancel")).token
70406
+ async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run cancel")).token
70298
70407
  );
70299
70408
  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) => {
70300
70409
  if (o.apply && o.parkRuleset) {
@@ -359,10 +359,10 @@ function shouldRetryIntegrationRefusalOnAmbient(err, env = process.env) {
359
359
  if (env.MMI_ACTOR?.trim() === "app") return false;
360
360
  return /Resource not accessible by integration/i.test(String(err?.stderr ?? ""));
361
361
  }
362
- async function ghFilingAppEnv(file, args) {
363
- if (!ghWriteTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || process.env.MMI_ACTOR?.trim() === "personal") return void 0;
362
+ async function ghFilingAppEnv(file, args, requireApp = false) {
363
+ if (!ghWriteTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || !requireApp && process.env.MMI_ACTOR?.trim() === "personal") return void 0;
364
364
  if (!isAppFilingWrite(args)) return void 0;
365
- const explicitApp = process.env.MMI_ACTOR?.trim() === "app";
365
+ const explicitApp = requireApp || process.env.MMI_ACTOR?.trim() === "app";
366
366
  let token;
367
367
  try {
368
368
  token = await ghWriteTokenProvider();
@@ -455,7 +455,7 @@ 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(), "user");
458
+ if (!appEnv) return tag(run(), options.env?.MMI_GH_POOL === "app" ? "app_installation" : "user");
459
459
  try {
460
460
  return await tag(run(appEnv), "app_installation");
461
461
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.6.4",
3
+ "version": "4.6.5",
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",