@mutmutco/cli 3.105.6 → 3.105.7

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 +85 -26
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -29109,6 +29109,21 @@ function derivePollState(buckets) {
29109
29109
  if (anyFailure) return anyPending ? "failing" : "failure";
29110
29110
  return anyPending ? "pending" : "success";
29111
29111
  }
29112
+ function partitionByRequired(entries, requiredContexts) {
29113
+ if (!requiredContexts || requiredContexts.size === 0) {
29114
+ return { relevant: entries.map((e) => e.bucket), ignoredFailures: [] };
29115
+ }
29116
+ const relevant = [];
29117
+ const ignoredFailures = [];
29118
+ for (const entry of entries) {
29119
+ if (requiredContexts.has(entry.name)) {
29120
+ relevant.push(entry.bucket);
29121
+ } else if (entry.bucket === "fail") {
29122
+ ignoredFailures.push(entry.name);
29123
+ }
29124
+ }
29125
+ return { relevant, ignoredFailures };
29126
+ }
29112
29127
  function parseNdjsonLines(stdout) {
29113
29128
  return stdout.split(/\r?\n/).filter((line) => line.trim()).map((line) => JSON.parse(line));
29114
29129
  }
@@ -29147,17 +29162,22 @@ async function fetchHeadCheckRuns(headSha, repo, gh) {
29147
29162
  const runsOut = await gh(["--paginate", `repos/${repo}/commits/${headSha}/check-runs?per_page=100`, "--jq", ".check_runs[] | {id, name, status, conclusion, app_id: .app.id}"]);
29148
29163
  return dedupeLatestCheckRuns(parseNdjsonLines(runsOut));
29149
29164
  }
29150
- async function fetchHeadCheckBuckets(headSha, repo, gh) {
29165
+ async function fetchHeadCheckEntries(headSha, repo, gh) {
29151
29166
  const [runs, statusesOut] = await Promise.all([
29152
29167
  fetchHeadCheckRuns(headSha, repo, gh),
29153
29168
  gh(["--paginate", `repos/${repo}/commits/${headSha}/status?per_page=100`, "--jq", ".statuses[] | {context, state}"])
29154
29169
  ]);
29155
29170
  const statuses = parseNdjsonLines(statusesOut);
29156
- return [...runs.map(classifyCheckRun), ...statuses.map((s) => classifyCommitStatus(s.state))];
29171
+ return [
29172
+ ...runs.map((run) => ({ name: run.name ?? `check-run ${run.id ?? "unknown"}`, bucket: classifyCheckRun(run) })),
29173
+ ...statuses.map((s) => ({ name: s.context ?? "unknown-status", bucket: classifyCommitStatus(s.state) }))
29174
+ ];
29157
29175
  }
29158
- async function pollRestPrChecks(prNumber, repo, gh = defaultGhApi) {
29176
+ async function pollRestPrChecks(prNumber, repo, gh = defaultGhApi, requiredContexts) {
29159
29177
  const snapshot = await fetchRestPrSnapshot(prNumber, repo, gh);
29160
- return derivePollState(await fetchHeadCheckBuckets(snapshot.headSha, repo, gh));
29178
+ const entries = await fetchHeadCheckEntries(snapshot.headSha, repo, gh);
29179
+ const { relevant } = partitionByRequired(entries, requiredContexts);
29180
+ return derivePollState(relevant);
29161
29181
  }
29162
29182
  async function pollRestPrMergeable(prNumber, repo, gh = defaultGhApi) {
29163
29183
  try {
@@ -29258,6 +29278,37 @@ async function diagnoseFailedRestChecks(prNumber, repo, gh = defaultGhApi) {
29258
29278
  return null;
29259
29279
  }
29260
29280
  }
29281
+ function isNotFoundError2(e) {
29282
+ const msg = `${e?.message ?? e} ${String(e?.stderr ?? "")}`;
29283
+ return /HTTP 404|Not Found|\(404\)/i.test(msg);
29284
+ }
29285
+ async function fetchRequiredCheckContexts(repo, branch, gh = defaultGhApi) {
29286
+ const contexts = /* @__PURE__ */ new Set();
29287
+ try {
29288
+ const raw = await gh([`repos/${repo}/branches/${encodeURIComponent(branch)}/protection/required_status_checks`]);
29289
+ const parsed = JSON.parse(raw);
29290
+ if (Array.isArray(parsed.contexts)) {
29291
+ for (const c of parsed.contexts) if (typeof c === "string") contexts.add(c);
29292
+ }
29293
+ } catch (e) {
29294
+ if (!isNotFoundError2(e)) return null;
29295
+ }
29296
+ try {
29297
+ const raw = await gh([`repos/${repo}/rules/branches/${encodeURIComponent(branch)}`]);
29298
+ const parsed = JSON.parse(raw);
29299
+ if (Array.isArray(parsed)) {
29300
+ for (const rule of parsed) {
29301
+ if (rule.type !== "required_status_checks") continue;
29302
+ for (const check of rule.parameters?.required_status_checks ?? []) {
29303
+ if (check.context) contexts.add(check.context);
29304
+ }
29305
+ }
29306
+ }
29307
+ } catch (e) {
29308
+ if (!isNotFoundError2(e)) return null;
29309
+ }
29310
+ return contexts;
29311
+ }
29261
29312
  async function fetchRestCorePool(gh = defaultGhApi) {
29262
29313
  try {
29263
29314
  const parsed = JSON.parse(await gh(["rate_limit"]));
@@ -36786,9 +36837,10 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
36786
36837
  const snapshot = await fetchRestPrSnapshot(number, repo).catch(() => null);
36787
36838
  const baseBranch = snapshot?.baseRef ?? "development";
36788
36839
  const ciHeadRef = snapshot && !snapshot.headIsFork && snapshot.headRef ? snapshot.headRef : void 0;
36840
+ const requiredContexts = await fetchRequiredCheckContexts(repo, baseBranch).catch(() => null);
36789
36841
  const result = await waitForPrChecks({
36790
36842
  resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef),
36791
- pollChecks: () => pollRestPrChecks(number, repo),
36843
+ pollChecks: () => pollRestPrChecks(number, repo, void 0, requiredContexts),
36792
36844
  pollMergeable: () => pollRestPrMergeable(number, repo),
36793
36845
  pollRateLimit: () => fetchRestCorePool(),
36794
36846
  // #3388: on a confirmed red, read the failing runs' annotations so a wall-clock budget kill stops
@@ -36854,31 +36906,37 @@ pr.command("land <number>").description("agent merge path (#1440): train probe ?
36854
36906
  },
36855
36907
  fetchTrainAuthority: async (repo) => fetchTrainAuthority(repo, registryClientDeps(await loadConfig())),
36856
36908
  resolveCiPolicy: (repo) => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
36857
- waitForChecks: (prNumber, repo) => waitForPrChecks({
36858
- resolvePolicy: () => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
36859
- // #3024: REST-only wait loop ? runPrLand's resolveRepo already guarantees `repo` is set.
36860
- pollChecks: () => pollRestPrChecks(prNumber, repo),
36861
- // #2970: `pr land` only ever lands PRs based on development (resolveRepo above already rejects any
36862
- // other base), so the fast-fail message's base branch is always 'development' here.
36863
- pollMergeable: () => pollRestPrMergeable(prNumber, repo),
36864
- pollRateLimit: () => fetchRestCorePool(),
36865
- // #3388: `pr land` is the batch path ? the one most likely to self-DOS the shared runner and
36866
- // then read its own wall-clock kill as a broken diff.
36867
- diagnoseFailure: () => diagnoseFailedRestChecks(prNumber, repo),
36868
- baseBranch: "development",
36869
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
36870
- log: (message) => console.warn(message),
36871
- // `pr land` inherits the same (raised) checks budget, so it needs the same liveness ? otherwise the
36872
- // 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
36873
- progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr land: waiting on checks ? ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
36874
- }),
36909
+ waitForChecks: async (prNumber, repo) => {
36910
+ const requiredContexts = await fetchRequiredCheckContexts(repo, "development").catch(() => null);
36911
+ return waitForPrChecks({
36912
+ resolvePolicy: () => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
36913
+ // #3024: REST-only wait loop ? runPrLand's resolveRepo already guarantees `repo` is set.
36914
+ pollChecks: () => pollRestPrChecks(prNumber, repo, void 0, requiredContexts),
36915
+ // #2970: `pr land` only ever lands PRs based on development (resolveRepo above already rejects any
36916
+ // other base), so the fast-fail message's base branch is always 'development' here.
36917
+ pollMergeable: () => pollRestPrMergeable(prNumber, repo),
36918
+ pollRateLimit: () => fetchRestCorePool(),
36919
+ // #3388: `pr land` is the batch path ? the one most likely to self-DOS the shared runner and
36920
+ // then read its own wall-clock kill as a broken diff.
36921
+ diagnoseFailure: () => diagnoseFailedRestChecks(prNumber, repo),
36922
+ baseBranch: "development",
36923
+ sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
36924
+ log: (message) => console.warn(message),
36925
+ // `pr land` inherits the same (raised) checks budget, so it needs the same liveness ? otherwise the
36926
+ // 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
36927
+ progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr land: waiting on checks ? ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
36928
+ });
36929
+ },
36875
36930
  // #2425: re-probe used ONLY to decide whether a failed `gh pr merge --auto` is worth retrying ? a
36876
36931
  // fresh read of state/mergeable/checks, independent of the merge call's own (possibly stale) error.
36932
+ // #4396: same required-status scoping as the wait loop above ? a non-required red must not read
36933
+ // as "not ready to retry" any more than it should have blocked the wait itself.
36877
36934
  probeMergeReady: async (prNumber, repo) => {
36878
- const [snapshot, checks] = await Promise.all([
36935
+ const [snapshot, requiredContexts] = await Promise.all([
36879
36936
  fetchRestPrSnapshot(prNumber, repo).catch(() => null),
36880
- pollRestPrChecks(prNumber, repo).catch(() => "error")
36937
+ fetchRequiredCheckContexts(repo, "development").catch(() => null)
36881
36938
  ]);
36939
+ const checks = await pollRestPrChecks(prNumber, repo, void 0, requiredContexts).catch(() => "error");
36882
36940
  return {
36883
36941
  open: snapshot?.state === "open",
36884
36942
  mergeable: snapshot?.mergeable === "MERGEABLE",
@@ -36961,9 +37019,10 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
36961
37019
  if (o.wait) {
36962
37020
  const repo = await requireRepo(o.repo);
36963
37021
  const baseBranch = await fetchRestPrSnapshot(number, repo).then((s) => s.baseRef).catch(() => "development");
37022
+ const requiredContexts = await fetchRequiredCheckContexts(repo, baseBranch).catch(() => null);
36964
37023
  const wait = await waitForPrChecks({
36965
37024
  resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef),
36966
- pollChecks: () => pollRestPrChecks(number, repo),
37025
+ pollChecks: () => pollRestPrChecks(number, repo, void 0, requiredContexts),
36967
37026
  pollMergeable: () => pollRestPrMergeable(number, repo),
36968
37027
  pollRateLimit: () => fetchRestCorePool(),
36969
37028
  diagnoseFailure: () => diagnoseFailedRestChecks(number, repo),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.105.6",
3
+ "version": "3.105.7",
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",