@mutmutco/cli 4.5.31 → 4.5.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.cjs +137 -37
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -362,6 +362,16 @@ var init_error_codes = __esm({
362
362
  });
363
363
 
364
364
  // src/github-client.ts
365
+ var github_client_exports = {};
366
+ __export(github_client_exports, {
367
+ GitHubApiError: () => GitHubApiError,
368
+ classifyGhTokenExecFailure: () => classifyGhTokenExecFailure,
369
+ createGitHubClient: () => createGitHubClient,
370
+ defaultGitHubClient: () => defaultGitHubClient,
371
+ githubToken: () => githubToken,
372
+ githubTokenRead: () => githubTokenRead,
373
+ rateLimitResetNote: () => rateLimitResetNote
374
+ });
365
375
  function classifyGhTokenExecFailure(e) {
366
376
  const err = e;
367
377
  const stderr = typeof err?.stderr === "string" ? err.stderr.trim() : "";
@@ -403,7 +413,7 @@ function rateLimitFromResponse(res, detail) {
403
413
  const resetHeader = res.headers.get("x-ratelimit-reset");
404
414
  const reset = resetHeader && /^\d+$/.test(resetHeader) ? Number(resetHeader) : void 0;
405
415
  const rateLimited = (res.status === 403 || res.status === 429) && remaining === "0" || /rate limit|secondary rate|abuse detection/i.test(detail);
406
- return { rateLimited, ...rateLimited && reset !== void 0 ? { rateLimitReset: reset } : {} };
416
+ return { rateLimited, ...remaining === "0" && !/secondary|abuse/i.test(detail) && res.headers.get("x-ratelimit-resource") ? { rateLimitResource: res.headers.get("x-ratelimit-resource") } : {}, ...rateLimited && reset !== void 0 ? { rateLimitReset: reset } : {} };
407
417
  }
408
418
  function joinUrl(base, path2) {
409
419
  if (path2.startsWith("http://") || path2.startsWith("https://")) return path2;
@@ -456,7 +466,7 @@ async function errorFromResponse(res) {
456
466
  const budgetNote = limit.rateLimited ? ` \u2014 ${rateLimitResetNote(limit.rateLimitReset)}` : "";
457
467
  return new GitHubApiError(`HTTP ${res.status}${suffix}${budgetNote} (${res.url})`, {
458
468
  status: res.status,
459
- ...limit.rateLimited ? { rateLimited: true, rateLimitReset: limit.rateLimitReset } : {}
469
+ ...limit.rateLimited ? { rateLimited: true, rateLimitReset: limit.rateLimitReset, rateLimitResource: limit.rateLimitResource } : {}
460
470
  });
461
471
  }
462
472
  function createGitHubClient(options = {}) {
@@ -530,6 +540,7 @@ function createGitHubClient(options = {}) {
530
540
  status: 200,
531
541
  graphqlErrors: parsed.errors,
532
542
  rateLimited: true,
543
+ ...!/secondary|abuse/i.test(message2) && parsed.errors.some((e) => e.type === "RATE_LIMITED") && (!res.headers.get("x-ratelimit-resource") || res.headers.get("x-ratelimit-resource") === "graphql") ? { rateLimitResource: "graphql" } : {},
533
544
  rateLimitReset: reset
534
545
  });
535
546
  }
@@ -560,6 +571,7 @@ var init_github_client = __esm({
560
571
  rateLimited;
561
572
  /** Epoch seconds when the exhausted pool resets (`X-Ratelimit-Reset` of the refusing response). */
562
573
  rateLimitReset;
574
+ rateLimitResource;
563
575
  constructor(message2, opts = {}) {
564
576
  super(message2);
565
577
  this.name = "GitHubApiError";
@@ -568,6 +580,7 @@ var init_github_client = __esm({
568
580
  this.graphqlErrors = opts.graphqlErrors;
569
581
  this.rateLimited = opts.rateLimited;
570
582
  this.rateLimitReset = opts.rateLimitReset;
583
+ this.rateLimitResource = opts.rateLimitResource;
571
584
  }
572
585
  };
573
586
  DEFAULT_TIMEOUT_MS = 2e4;
@@ -5470,7 +5483,7 @@ function isSnapshotShape(body) {
5470
5483
  const b = body;
5471
5484
  const rate = b?.github?.rateLimit;
5472
5485
  const staleValid = b?.stale === void 0 || typeof b.stale === "object" && b.stale !== null && Number.isFinite(b.stale.ageMs) && b.stale.ageMs >= 0;
5473
- const githubValid = b?.github === void 0 || b.github.credential === "app_installation" && Number.isFinite(rate?.limit) && Number.isFinite(rate?.remaining) && Number.isFinite(rate?.cost) && typeof rate?.resetAt === "string";
5486
+ const githubValid = b?.github === void 0 || b.github.credential === "app_installation" && (rate === void 0 || Number.isFinite(rate?.limit) && Number.isFinite(rate?.remaining) && Number.isFinite(rate?.cost) && typeof rate?.resetAt === "string");
5474
5487
  return Boolean(
5475
5488
  b && typeof b === "object" && typeof b.project?.id === "string" && typeof b.project?.title === "string" && typeof b.viewer === "string" && b.viewer.length > 0 && Array.isArray(b.nodes) && Array.isArray(b.writableRepos) && Array.isArray(b.unreadableRepos) && Array.isArray(b.pullRequests) && Array.isArray(b.warnings) && typeof b.partial === "boolean" && githubValid && staleValid
5476
5489
  );
@@ -5511,7 +5524,11 @@ async function fetchHubBoardSnapshot(request, deps) {
5511
5524
  const body2 = await res.json().catch(() => null);
5512
5525
  if (body2?.error === "rate_limited") {
5513
5526
  const reset = typeof body2.resetAt === "string" ? `; resets ${body2.resetAt}` : "; reset time unavailable";
5514
- return { state: "unavailable", reason: `GitHub App quota exhausted${reset}` };
5527
+ return {
5528
+ state: "unavailable",
5529
+ reason: `GitHub App quota exhausted${reset}`,
5530
+ ...body2.github?.credential === "app_installation" && body2.github.resource === "graphql" && /^github-app-v1:[a-f0-9]{64}$/.test(body2.github.quotaScope ?? "") ? { quota: { status: "rate_limited", ...typeof body2.resetAt === "string" ? { resetAt: body2.resetAt } : {}, github: { credential: "app_installation", resource: "graphql", quotaScope: body2.github.quotaScope } } } : {}
5531
+ };
5515
5532
  }
5516
5533
  }
5517
5534
  if (!res.ok) return { state: "unavailable", reason: `board snapshot HTTP ${res.status}` };
@@ -12280,7 +12297,7 @@ function renderBoardItem(item) {
12280
12297
  }
12281
12298
  function renderBoardReport(report) {
12282
12299
  const lines2 = [`Board \xB7 ${report.project.title} \xB7 @${report.viewer}`, renderBoardSource()];
12283
- if (report.github) {
12300
+ if (report.github?.rateLimit) {
12284
12301
  const { limit, remaining, cost, resetAt } = report.github.rateLimit;
12285
12302
  lines2.push(`github: app installation \xB7 GraphQL ${remaining}/${limit} remaining \xB7 cost ${cost} \xB7 resets ${resetAt}`);
12286
12303
  }
@@ -12510,7 +12527,7 @@ async function collectBoardPreferringSnapshot(cfg, options, deps) {
12510
12527
  };
12511
12528
  }
12512
12529
  if (options.snapshotOnly && attempt?.state === "unavailable") {
12513
- throw new Error(`Hub board snapshot unavailable (${attempt.reason}) \u2014 --snapshot-only refuses the direct user-auth fallback`);
12530
+ throw Object.assign(new Error(`Hub board snapshot unavailable (${attempt.reason}) \u2014 --snapshot-only refuses the direct user-auth fallback`), { quota: attempt.quota });
12514
12531
  }
12515
12532
  const collected = await collectBoardItems(cfg, { repo: options.repo, allowPartial: options.allowPartial, activeOnly: true }, deps);
12516
12533
  if (attempt?.state === "unavailable") {
@@ -12535,7 +12552,7 @@ async function readBoard(options, deps = {}) {
12535
12552
  unknown: new Set(snapshot.unreadableRepos.map((entry) => entry.repo.toLowerCase()))
12536
12553
  };
12537
12554
  pullRequests = snapshot.pullRequests;
12538
- github = snapshot.github;
12555
+ github = snapshot.github && { ...snapshot.github, ...snapshot.quotaResetAt ? { quotaResetAt: snapshot.quotaResetAt } : {} };
12539
12556
  } else {
12540
12557
  const probed = await resolveWritableReposForClaimables(collected.items, client);
12541
12558
  collected.warnings.push(...probed.warnings);
@@ -16820,10 +16837,10 @@ var rollout_plan_default = {
16820
16837
  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)."
16821
16838
  },
16822
16839
  baseline: {
16823
- version: "4.5.31",
16824
- tag: "v4.5.31",
16825
- commit: "9e03ef1880b6",
16826
- npm: "@mutmutco/cli@4.5.31"
16840
+ version: "4.5.33",
16841
+ tag: "v4.5.33",
16842
+ commit: "43f256b77935",
16843
+ npm: "@mutmutco/cli@4.5.33"
16827
16844
  },
16828
16845
  exitCriterion: "fleet-n-of-n",
16829
16846
  hubOnlyShortcut: "forbidden",
@@ -16840,14 +16857,14 @@ var rollout_plan_default = {
16840
16857
  repo: "mutmutco/mmi-hub",
16841
16858
  role: "canary",
16842
16859
  schedule: "train",
16843
- v3Target: "v4.5.31"
16860
+ v3Target: "v4.5.33"
16844
16861
  }
16845
16862
  ],
16846
16863
  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.",
16847
16864
  rollback: {
16848
16865
  independent: true,
16849
- mechanism: "npm dist-tag latest -> 4.5.31 and redeploy the Hub Lambda from tag v4.5.31 (9e03ef1880b6); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16850
- v3Target: "v4.5.31 (@mutmutco/cli@4.5.31, tag commit 9e03ef1880b6 \u2014 last known-good release carrying the repo-index v4-only contract)"
16866
+ mechanism: "npm dist-tag latest -> 4.5.33 and redeploy the Hub Lambda from tag v4.5.33 (43f256b77935); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16867
+ v3Target: "v4.5.33 (@mutmutco/cli@4.5.33, tag commit 43f256b77935 \u2014 last known-good release carrying the repo-index v4-only contract)"
16851
16868
  }
16852
16869
  },
16853
16870
  {
@@ -17946,6 +17963,51 @@ function registerQueryCommands(program3) {
17946
17963
  }
17947
17964
  });
17948
17965
  }
17966
+ async function readPrQuotaContext(repo, number, quotaScope, client) {
17967
+ if (!quotaScope) throw new Error("PR quota context requires the verified App actor");
17968
+ const [owner, name] = repo.split("/");
17969
+ const github = { credential: "app_installation", quotaScope, resource: "graphql" };
17970
+ try {
17971
+ const issues = [];
17972
+ const checks = [];
17973
+ let issuesCursor;
17974
+ let checksCursor;
17975
+ let readIssues = true;
17976
+ let readChecks = true;
17977
+ for (let page = 0; page < 100; page++) {
17978
+ const data = await client.graphql(`query($owner:String!,$name:String!,$number:Int!,$issuesCursor:String,$checksCursor:String,$readIssues:Boolean!,$readChecks:Boolean!) {
17979
+ rateLimit { limit remaining resetAt }
17980
+ repository(owner:$owner,name:$name) { pullRequest(number:$number) {
17981
+ number closingIssuesReferences(first:100,after:$issuesCursor) @include(if:$readIssues) { nodes { number } pageInfo { hasNextPage endCursor } }
17982
+ commits(last:1) @include(if:$readChecks) { nodes { commit { statusCheckRollup { contexts(first:100,after:$checksCursor) {
17983
+ nodes { ... on CheckRun { status conclusion } ... on StatusContext { state } }
17984
+ pageInfo { hasNextPage endCursor }
17985
+ } } } } }
17986
+ } }
17987
+ }`, { owner, name, number, issuesCursor, checksCursor, readIssues, readChecks });
17988
+ const pr = data.repository?.pullRequest;
17989
+ if (!pr) throw new Error("PR quota context could not resolve the PR");
17990
+ const issuePage = pr.closingIssuesReferences;
17991
+ const checkPage = pr.commits?.nodes[0]?.commit.statusCheckRollup?.contexts;
17992
+ if (readIssues && !issuePage) throw new Error("PR closing references unreadable");
17993
+ if (readIssues) issues.push(...issuePage.nodes);
17994
+ if (readChecks) checks.push(...checkPage?.nodes ?? []);
17995
+ readIssues = readIssues && issuePage.pageInfo.hasNextPage;
17996
+ readChecks = readChecks && (checkPage?.pageInfo.hasNextPage ?? false);
17997
+ if (!readIssues && !readChecks) return { number: pr.number, closingIssuesReferences: issues, statusCheckRollup: checks, github: { ...github, rateLimit: data.rateLimit } };
17998
+ const nextIssues = issuePage?.pageInfo.endCursor;
17999
+ const nextChecks = checkPage?.pageInfo.endCursor;
18000
+ if (readIssues && (!nextIssues || nextIssues === issuesCursor) || readChecks && (!nextChecks || nextChecks === checksCursor)) throw new Error("PR quota context pagination cursor did not advance");
18001
+ issuesCursor = nextIssues;
18002
+ checksCursor = nextChecks;
18003
+ }
18004
+ throw new Error("PR quota context pagination exceeded 100 pages");
18005
+ } catch (error) {
18006
+ const { GitHubApiError: GitHubApiError2 } = await Promise.resolve().then(() => (init_github_client(), github_client_exports));
18007
+ if (!(error instanceof GitHubApiError2) || !error.rateLimited || error.rateLimitResource !== "graphql") throw error;
18008
+ return { status: "rate_limited", github, ...error.rateLimitReset === void 0 ? {} : { resetAt: new Date(error.rateLimitReset * 1e3).toISOString() } };
18009
+ }
18010
+ }
17949
18011
 
17950
18012
  // src/fleet-track-inventory.ts
17951
18013
  var NO_DEPLOY_SURFACE_MODELS = /* @__PURE__ */ new Set(["none", "content"]);
@@ -19292,8 +19354,21 @@ async function pushAlignmentBranch(deps, target, tag) {
19292
19354
  if (!commit) throw new Error(`git commit-tree wrote no commit for the ${target} alignment of ${tag} (#6288)`);
19293
19355
  await deps.run("git", ["push", "origin", "--delete", branch]).catch(() => void 0);
19294
19356
  await runGitPush(deps, ["push", "origin", `${commit}:refs/heads/${branch}`]);
19357
+ await sweepLandedAlignmentBranches(deps, target, branch);
19295
19358
  return branch;
19296
19359
  }
19360
+ async function sweepLandedAlignmentBranches(deps, target, keep) {
19361
+ const prefix = `refs/heads/release-align/${target}/`;
19362
+ const listed = await deps.run("git", ["ls-remote", "--heads", "origin", `${prefix}*`]).catch(() => "");
19363
+ for (const line of clean2(listed).split("\n")) {
19364
+ const [sha, ref] = line.split(" ");
19365
+ if (!sha || !ref?.startsWith(prefix)) continue;
19366
+ const name = ref.slice("refs/heads/".length);
19367
+ if (name === keep) continue;
19368
+ const landed = await deps.run("git", ["merge-base", "--is-ancestor", sha, `origin/${target}`]).then(() => true, () => false);
19369
+ if (landed) await deps.run("git", ["push", "origin", "--delete", name]).catch(() => void 0);
19370
+ }
19371
+ }
19297
19372
  async function findAlignmentPr(deps, ctx, target, head) {
19298
19373
  const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", target, "--head", head, "--state", "all", "--json", "number,url,state"]));
19299
19374
  if (!out) return void 0;
@@ -28647,8 +28722,14 @@ function renderPluginCachePlan(plan, applied) {
28647
28722
  return lines2.join("\n");
28648
28723
  }
28649
28724
 
28650
- // src/app-actor.ts
28725
+ // ../shared/github-quota-scope.ts
28651
28726
  var import_node_crypto6 = require("node:crypto");
28727
+ function appQuotaScope(appId, installationId, origin = "https://api.github.com", resource = "graphql") {
28728
+ return `github-app-v1:${(0, import_node_crypto6.createHash)("sha256").update(JSON.stringify([new URL(origin).origin, appId.trim(), installationId.trim(), resource])).digest("hex")}`;
28729
+ }
28730
+
28731
+ // src/app-actor.ts
28732
+ var import_node_crypto7 = require("node:crypto");
28652
28733
  var APP_ACTOR_ENV = "MMI_ACTOR";
28653
28734
  var APP_VAULT_REPO = "mutmutco/MMI-Hub";
28654
28735
  var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
@@ -28699,10 +28780,14 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
28699
28780
  exp: now + APP_JWT_TTL_S,
28700
28781
  iss: appId
28701
28782
  }));
28702
- const signer = (0, import_node_crypto6.createSign)("RSA-SHA256");
28783
+ const signer = (0, import_node_crypto7.createSign)("RSA-SHA256");
28703
28784
  signer.update(`${header}.${payload}`);
28704
28785
  return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
28705
28786
  }
28787
+ var activeQuotaScope;
28788
+ function activeAppQuotaScope() {
28789
+ return activeQuotaScope;
28790
+ }
28706
28791
  async function mintInstallationToken(deps) {
28707
28792
  const [appId, installationId, privateKey] = await Promise.all(APP_VAULT_KEYS.map((k) => deps.fetchSecret(k)));
28708
28793
  if (!appId || !installationId || !privateKey) {
@@ -28748,7 +28833,7 @@ async function mintInstallationToken(deps) {
28748
28833
  if (!body.token || !body.expires_at) {
28749
28834
  throw new AppActorError("app actor: mint response carried no token \u2014 automation fails closed");
28750
28835
  }
28751
- return { token: body.token, expiresAt: body.expires_at };
28836
+ return { token: body.token, expiresAt: body.expires_at, quotaScope: appQuotaScope(appId, installationId, process.env.GITHUB_API_URL) };
28752
28837
  }
28753
28838
  async function pollTokenOrUndefined(deps) {
28754
28839
  try {
@@ -28760,6 +28845,7 @@ async function pollTokenOrUndefined(deps) {
28760
28845
  var notedIneligibleCommand = false;
28761
28846
  async function activateAppActor(commandPath3, env, mint, note = (line) => process.stderr.write(`${line}
28762
28847
  `)) {
28848
+ activeQuotaScope = void 0;
28763
28849
  const requested = (env[APP_ACTOR_ENV] ?? "").trim();
28764
28850
  if (!requested) return "personal";
28765
28851
  if (requested !== "app") {
@@ -28776,6 +28862,7 @@ async function activateAppActor(commandPath3, env, mint, note = (line) => proces
28776
28862
  delete env.GITHUB_TOKEN;
28777
28863
  const minted = await mint();
28778
28864
  env.GH_TOKEN = minted.token;
28865
+ activeQuotaScope = minted.quotaScope;
28779
28866
  return "app";
28780
28867
  }
28781
28868
 
@@ -28991,6 +29078,7 @@ function registerBoardCommands(program3) {
28991
29078
  if (!o.out) console.log(output);
28992
29079
  else console.log(`Wrote board read to ${o.out} (UTF-8, ${writeUtf8Receipt(o.out, output)} bytes)`);
28993
29080
  } catch (e) {
29081
+ if (o.json && e && typeof e === "object" && "quota" in e && e.quota) console.log(JSON.stringify(e.quota));
28994
29082
  return failGraceful(`board read failed: ${withDiscoverMissDetail(e.message)}`);
28995
29083
  }
28996
29084
  }
@@ -31221,7 +31309,7 @@ function renderDeployPortDoctor(report) {
31221
31309
  }
31222
31310
 
31223
31311
  // src/bootstrap-drift.ts
31224
- var import_node_crypto7 = require("node:crypto");
31312
+ var import_node_crypto8 = require("node:crypto");
31225
31313
  function byteComparableSeeds(manifest, cls) {
31226
31314
  return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
31227
31315
  }
@@ -31241,7 +31329,7 @@ function compareSeedBytes(hubContent, repoContent) {
31241
31329
  return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
31242
31330
  }
31243
31331
  function seedContentHash(content) {
31244
- return (0, import_node_crypto7.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
31332
+ return (0, import_node_crypto8.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
31245
31333
  }
31246
31334
  function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
31247
31335
  const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
@@ -31403,7 +31491,7 @@ function renderPropagationReport(plan) {
31403
31491
  }
31404
31492
 
31405
31493
  // src/bootstrap-propagation-identity.ts
31406
- var import_node_crypto8 = require("node:crypto");
31494
+ var import_node_crypto9 = require("node:crypto");
31407
31495
  var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
31408
31496
  var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
31409
31497
  function safeBranchPart(value, maxLength, fallback) {
@@ -31416,7 +31504,7 @@ function repoSlug2(repo) {
31416
31504
  function propagationBranch(repo, target) {
31417
31505
  const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
31418
31506
  const targetPart = safeBranchPart(target, 48, "target");
31419
- const hash = (0, import_node_crypto8.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
31507
+ const hash = (0, import_node_crypto9.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
31420
31508
  return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
31421
31509
  }
31422
31510
  function legacyPropagationBranch(repo) {
@@ -32572,7 +32660,7 @@ function renderBootstrapVerifyReport(report) {
32572
32660
  }
32573
32661
 
32574
32662
  // src/bootstrap-seed-delivery.ts
32575
- var import_node_crypto9 = require("node:crypto");
32663
+ var import_node_crypto10 = require("node:crypto");
32576
32664
  init_github_client();
32577
32665
  var SHA = /^[a-f0-9]{40}$/;
32578
32666
  async function readSeedFile(repo, target, ref, client) {
@@ -32605,7 +32693,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
32605
32693
  }
32606
32694
  const baseSha = base?.object?.sha;
32607
32695
  if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
32608
- const identity = (0, import_node_crypto9.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
32696
+ const identity = (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
32609
32697
  return { ...planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`), baseSha, baseBranch };
32610
32698
  }
32611
32699
  async function verifyTargetedSeedBranch(repo, plan, target, client, allowMissing = false) {
@@ -34173,12 +34261,13 @@ async function stageStatus(member, stage, deps) {
34173
34261
  }
34174
34262
  }
34175
34263
  }
34264
+ const lag = commit && head !== "unknown" ? commit.toLowerCase() !== head.toLowerCase() : null;
34176
34265
  return {
34177
34266
  stage,
34178
34267
  head,
34179
34268
  served,
34180
34269
  source,
34181
- lag: commit && head !== "unknown" ? commit.toLowerCase() !== head.toLowerCase() : null,
34270
+ lag: source === "registry-pin" && lag === false ? null : lag,
34182
34271
  ...deployStatus ? { deployStatus } : {}
34183
34272
  };
34184
34273
  }
@@ -34208,7 +34297,7 @@ function formatSuiteStatus(report) {
34208
34297
  // src/suite-train.ts
34209
34298
  var import_node_fs29 = require("node:fs");
34210
34299
  var import_node_path28 = require("node:path");
34211
- var import_node_crypto10 = require("node:crypto");
34300
+ var import_node_crypto11 = require("node:crypto");
34212
34301
  var SCHEMA = "mmi-suite-cut/v1";
34213
34302
  var SHA2 = /^[a-f0-9]{40}$/i;
34214
34303
  var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
@@ -34243,7 +34332,7 @@ function readSuiteCut(path2) {
34243
34332
  }
34244
34333
  function writeSuiteCut(path2, record) {
34245
34334
  (0, import_node_fs29.mkdirSync)((0, import_node_path28.dirname)(path2), { recursive: true });
34246
- const temp = `${path2}.${(0, import_node_crypto10.randomBytes)(8).toString("hex")}.tmp`;
34335
+ const temp = `${path2}.${(0, import_node_crypto11.randomBytes)(8).toString("hex")}.tmp`;
34247
34336
  try {
34248
34337
  (0, import_node_fs29.writeFileSync)(temp, JSON.stringify(record, null, 2), { flag: "wx" });
34249
34338
  (0, import_node_fs29.renameSync)(temp, path2);
@@ -37866,7 +37955,7 @@ init_github_client();
37866
37955
 
37867
37956
  // src/issue-commands.ts
37868
37957
  var import_node_fs34 = require("node:fs");
37869
- var import_node_crypto11 = require("node:crypto");
37958
+ var import_node_crypto12 = require("node:crypto");
37870
37959
  init_cli_shared();
37871
37960
  init_clean_exit();
37872
37961
  init_error_codes();
@@ -38339,7 +38428,7 @@ function rowIdempotencyKey(batchKey, spec) {
38339
38428
  const identity = `${spec.type}
38340
38429
  ${spec.title.trim()}
38341
38430
  ${spec.body ?? ""}`;
38342
- const hash = (0, import_node_crypto11.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
38431
+ const hash = (0, import_node_crypto12.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
38343
38432
  return `${batchKey}:${hash}`;
38344
38433
  }
38345
38434
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -42316,7 +42405,7 @@ ${list}`);
42316
42405
  'Keep closing keywords on a dedicated line ("Closes #N") \u2014 GitHub parses close/fix/resolve + #N even mid-sentence, so pr create rewrites such prose to "part of #N" (#6039).',
42317
42406
  'When acceptance is only provable AFTER deploy, link with "Part of #N", never "Closes" \u2014 a closing reference closes the issue and advances its board card to Done on merge, before the acceptance evidence exists. Close it on that evidence instead (#6407).'
42318
42407
  ]);
42319
- pr.command("view <ref>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) \u2014 the mmi-cli read path (#2347). --comments folds in every comment; --context also adds linkedIssues (the issues it closes/references, #2894)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').option("--comments", "include every comment (body + comments in one call) \u2014 read the whole PR before landing it (#2894)").option("--context", "full working context in one call: implies --comments and also adds linkedIssues (the issues the PR closes/references) (#2894)").action(async (ref, o) => {
42408
+ pr.command("view <ref>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) \u2014 the mmi-cli read path (#2347). --comments folds in every comment; --context also adds linkedIssues (the issues it closes/references, #2894)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').option("--comments", "include every comment (body + comments in one call) \u2014 read the whole PR before landing it (#2894)").option("--context", "full working context in one call: implies --comments and also adds linkedIssues (the issues the PR closes/references) (#2894)").option("--quota-context", "fixed App-only PR check read with verified quota scope and reset for harnesses").action(async (ref, o) => {
42320
42409
  let parsed;
42321
42410
  try {
42322
42411
  parsed = parseIssueRef(ref, o.repo);
@@ -42329,6 +42418,13 @@ ${list}`);
42329
42418
  const defaultPrFields = "number,title,state,url,isDraft,mergeable,mergedAt,mergeCommit,headRefName,baseRefName,author,labels";
42330
42419
  const effective = resolvePrViewFields(o.json, defaultPrFields);
42331
42420
  try {
42421
+ if (o.quotaContext) {
42422
+ if (o.comments || o.context || o.json) return fail("pr view: --quota-context cannot be combined with --comments, --context or --json");
42423
+ const data2 = await readPrQuotaContext(repo, n, activeAppQuotaScope(), defaultGitHubClient());
42424
+ console.log(JSON.stringify(data2));
42425
+ if (data2.status === "rate_limited") process.exitCode = 1;
42426
+ return;
42427
+ }
42332
42428
  if (o.comments || o.context) {
42333
42429
  const data2 = await runPrViewContext(queryDeps(), { number: n, repo, comments: Boolean(o.comments), context: Boolean(o.context), jsonFields: effective });
42334
42430
  console.log(JSON.stringify(data2));
@@ -43244,6 +43340,10 @@ var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
43244
43340
  var NPM_TRAIN_TIMEOUT_MS = 6e4;
43245
43341
  var NPM_FOLD_TIMEOUT_MS = NODE_PREPARE_TIMEOUT_MS;
43246
43342
  var JERV_GATEWAY_DEPLOY_TIMEOUT_MS = 20 * 6e4;
43343
+ var GIT_COMMIT_TIMEOUT_MS = 6e4;
43344
+ function trainGitArgs(args) {
43345
+ return ["-c", "gc.auto=0", "-c", "maintenance.auto=false", ...args];
43346
+ }
43247
43347
  async function announceSummaryRefusal(path2, isHub = false) {
43248
43348
  let lines2;
43249
43349
  try {
@@ -43283,10 +43383,10 @@ function trainApplyDeps() {
43283
43383
  // #7027: every mutating train verb holds this checkout's train writer lock for its whole run.
43284
43384
  trainWriter: (verb, cwd) => acquireTrainWriterLock(cwd, `mmi-cli devops ${verb}`),
43285
43385
  run: async (file, args) => {
43286
- const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" && (args[0] === "version" || args[0] === "ci" || args[0] === "install") ? NPM_FOLD_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file === "bash" && /jerv-gateway-release-deploy\.sh$/.test(args[0] ?? "") ? JERV_GATEWAY_DEPLOY_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
43386
+ const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" && (args[0] === "version" || args[0] === "ci" || args[0] === "install") ? NPM_FOLD_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file === "bash" && /jerv-gateway-release-deploy\.sh$/.test(args[0] ?? "") ? JERV_GATEWAY_DEPLOY_TIMEOUT_MS : file === "git" && args[0] === "commit" ? GIT_COMMIT_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
43287
43387
  const watchToken = file === "gh" && args[0] === "run" && args[1] === "watch" ? await pollToken() : void 0;
43288
43388
  try {
43289
- return isWin3 && file === "npm" ? (await execFileP("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP(file, args, { timeout, ...watchToken ? { env: { ...process.env, GH_TOKEN: watchToken } } : {} })).stdout;
43389
+ return isWin3 && file === "npm" ? (await execFileP("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP(file, file === "git" ? trainGitArgs(args) : args, { timeout, ...watchToken ? { env: { ...process.env, GH_TOKEN: watchToken } } : {} })).stdout;
43290
43390
  } catch (e) {
43291
43391
  throw surfaceTrainSubprocessFailure(e, file, args);
43292
43392
  }
@@ -48343,14 +48443,14 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
48343
48443
  }
48344
48444
 
48345
48445
  // src/tenant-artifact.ts
48346
- var import_node_crypto12 = require("node:crypto");
48446
+ var import_node_crypto13 = require("node:crypto");
48347
48447
  var import_node_fs48 = require("node:fs");
48348
48448
  var import_promises10 = require("node:fs/promises");
48349
48449
  var import_node_path45 = require("node:path");
48350
48450
  var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
48351
48451
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
48352
48452
  async function sha256File(path2) {
48353
- const hash = (0, import_node_crypto12.createHash)("sha256");
48453
+ const hash = (0, import_node_crypto13.createHash)("sha256");
48354
48454
  for await (const chunk of (0, import_node_fs48.createReadStream)(path2)) hash.update(chunk);
48355
48455
  return hash.digest("hex");
48356
48456
  }
@@ -48580,13 +48680,13 @@ async function executeWaveLand(plan, deps) {
48580
48680
 
48581
48681
  // src/dist-drift.ts
48582
48682
  var import_node_child_process17 = require("node:child_process");
48583
- var import_node_crypto14 = require("node:crypto");
48683
+ var import_node_crypto15 = require("node:crypto");
48584
48684
  var import_node_fs50 = require("node:fs");
48585
48685
  var import_node_os21 = require("node:os");
48586
48686
  var import_node_path47 = require("node:path");
48587
48687
 
48588
48688
  // ../scripts/distribution-digest.mjs
48589
- var import_node_crypto13 = require("node:crypto");
48689
+ var import_node_crypto14 = require("node:crypto");
48590
48690
  var import_node_fs49 = require("node:fs");
48591
48691
  var import_node_path46 = require("node:path");
48592
48692
  var slash = (value) => value.replaceAll("\\", "/");
@@ -48599,7 +48699,7 @@ function repoPath(root, declaredPath, label) {
48599
48699
  return target;
48600
48700
  }
48601
48701
  function digestFiles(files) {
48602
- const hash = (0, import_node_crypto13.createHash)("sha256");
48702
+ const hash = (0, import_node_crypto14.createHash)("sha256");
48603
48703
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
48604
48704
  const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs49.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs49.readFileSync)(file.absolute);
48605
48705
  hash.update(file.relative, "utf8");
@@ -48629,7 +48729,7 @@ var DIST_ARTIFACTS = [
48629
48729
  ];
48630
48730
  var BOM_DIST_TREE_ID = "mmi-cli-dist";
48631
48731
  var ABSENT = "absent";
48632
- var sha256 = (bytes) => `sha256:${(0, import_node_crypto14.createHash)("sha256").update(bytes).digest("hex")}`;
48732
+ var sha256 = (bytes) => `sha256:${(0, import_node_crypto15.createHash)("sha256").update(bytes).digest("hex")}`;
48633
48733
  function artifactDrift(path2, committedBytes, rebuiltBytes) {
48634
48734
  const committed = committedBytes ? sha256(committedBytes) : ABSENT;
48635
48735
  const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.31",
3
+ "version": "4.5.33",
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",