@mutmutco/cli 4.3.11 → 4.3.13

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 +93 -16
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -11867,7 +11867,7 @@ async function checkLaneContest(client, item, actor = describeSessionIdentity(),
11867
11867
  };
11868
11868
  }
11869
11869
  function laneContestMessage(ref, contest, verb) {
11870
- const holder = contest.marker ? `lane ${describeClaimMarker(contest.marker)}` : "another lane";
11870
+ const holder = contest.marker ? `lane ${describeClaimMarker(contest.marker)}` : contest.unverifiable.includes("comments") ? "an unreadable lane (its claim-marker comments could not be read, so no holder can be named)" : "another lane";
11871
11871
  const seen = contest.live.join("; ");
11872
11872
  const unreadable = contest.unverifiable.length ? `liveness could not be fully verified (failed to read: ${contest.unverifiable.join(", ")})` : "";
11873
11873
  const why = [seen, unreadable].filter(Boolean).join("; ");
@@ -12060,6 +12060,9 @@ function withPowerShellChainHint(message2, platform2 = process.platform) {
12060
12060
  return `${message2}
12061
12061
  note: in PowerShell a ';' chain runs every statement regardless of the previous exit code, so the chain ran past this refusal \u2014 rerun the refused write solo or chain gated writes with '&&'`;
12062
12062
  }
12063
+ function isArchivedItemRefusal(message2) {
12064
+ return /The item is archived and cannot be updated/i.test(message2);
12065
+ }
12063
12066
  async function moveBoardItem(options, deps = {}) {
12064
12067
  if (!BOARD_STATUSES.includes(options.status)) {
12065
12068
  throw new Error(`unknown status '${options.status}'; expected one of ${BOARD_STATUSES.join(", ")}`);
@@ -12077,6 +12080,17 @@ async function moveBoardItem(options, deps = {}) {
12077
12080
  try {
12078
12081
  await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, optionId);
12079
12082
  } catch (e) {
12083
+ if (options.status === "Done" && isArchivedItemRefusal(ghError(e))) {
12084
+ return {
12085
+ item,
12086
+ viewer: lookup.viewer,
12087
+ repo: currentRepo,
12088
+ status: item.status,
12089
+ partial: false,
12090
+ archived: true,
12091
+ warning: `${item.ref} is archived (already terminal \u2014 its close archived the card); no ${options.status} move was performed`
12092
+ };
12093
+ }
12080
12094
  const warning = `partial move: ${item.ref} status was not changed to ${options.status} (${ghError(e)})`;
12081
12095
  if (!options.allowPartial) throw new Error(warning);
12082
12096
  return { item, viewer: lookup.viewer, repo: currentRepo, status: item.status, partial: true, warning };
@@ -15462,10 +15476,10 @@ var rollout_plan_default = {
15462
15476
  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)."
15463
15477
  },
15464
15478
  baseline: {
15465
- version: "4.3.11",
15466
- tag: "v4.3.11",
15467
- commit: "c898259b41c3",
15468
- npm: "@mutmutco/cli@4.3.11"
15479
+ version: "4.3.13",
15480
+ tag: "v4.3.13",
15481
+ commit: "8442cc0ac255",
15482
+ npm: "@mutmutco/cli@4.3.13"
15469
15483
  },
15470
15484
  exitCriterion: "fleet-n-of-n",
15471
15485
  hubOnlyShortcut: "forbidden",
@@ -15482,14 +15496,14 @@ var rollout_plan_default = {
15482
15496
  repo: "mutmutco/mmi-hub",
15483
15497
  role: "canary",
15484
15498
  schedule: "train",
15485
- v3Target: "v4.3.11"
15499
+ v3Target: "v4.3.13"
15486
15500
  }
15487
15501
  ],
15488
15502
  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.",
15489
15503
  rollback: {
15490
15504
  independent: true,
15491
- mechanism: "npm dist-tag latest -> 4.3.11 and redeploy the Hub Lambda from tag v4.3.11 (c898259b41c3); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15492
- v3Target: "v4.3.11 (@mutmutco/cli@4.3.11, tag commit c898259b41c3 \u2014 last known-good release carrying the repo-index v4-only contract)"
15505
+ mechanism: "npm dist-tag latest -> 4.3.13 and redeploy the Hub Lambda from tag v4.3.13 (8442cc0ac255); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15506
+ v3Target: "v4.3.13 (@mutmutco/cli@4.3.13, tag commit 8442cc0ac255 \u2014 last known-good release carrying the repo-index v4-only contract)"
15493
15507
  }
15494
15508
  },
15495
15509
  {
@@ -24159,6 +24173,10 @@ async function finalizeCiReconcile(repo, deps, result, before, pendingReason) {
24159
24173
  result.errors.push(`post-apply audit failed \u2014 remaining: ${remaining.join(", ")}`);
24160
24174
  return result;
24161
24175
  }
24176
+ const unowned = after.checks.filter((c) => !c.ok && !remaining.includes(c.label)).map((c) => c.label);
24177
+ if (unowned.length > 0) {
24178
+ result.skipped.push(`audit still failing on rows reconcile does not own (${unowned.join("; ")}) \u2014 follow each row's remediation; re-running reconcile cannot fix them`);
24179
+ }
24162
24180
  const repaired = reconcileOwnedFailures(before).length > 0;
24163
24181
  result.postApply = { state: repaired ? "repaired" : "clean", audit: after, remaining: [] };
24164
24182
  (repaired ? result.applied : result.skipped).push(
@@ -25532,7 +25550,7 @@ function registerBoardCommands(program3) {
25532
25550
  const result = await moveBoardItem({ config: await loadConfigForBoardSelector2(issueRefs[0], o.repo), selector: issueRefs[0], status: canonicalStatus, repo: o.repo, allowPartial: o.allowPartial });
25533
25551
  invalidateStatuslineBoardCache();
25534
25552
  if (o.json) return console.log(JSON.stringify(result));
25535
- console.log(result.partial ? `Partially moved ${result.item.ref}: ${result.warning}` : `Moved ${result.item.ref} -> ${result.status}`);
25553
+ console.log(result.archived ? `Already terminal ${result.item.ref}: ${result.warning}` : result.partial ? `Partially moved ${result.item.ref}: ${result.warning}` : `Moved ${result.item.ref} -> ${result.status}`);
25536
25554
  } catch (e) {
25537
25555
  return failGraceful(`board move failed: ${e.message}`);
25538
25556
  }
@@ -33494,9 +33512,35 @@ async function fetchBranchTipSha(repo, ref, gh = defaultGhApi) {
33494
33512
  async function fetchRestPrSnapshot(prNumber, repo, gh = defaultGhApi) {
33495
33513
  return parseRestPrSnapshot(JSON.parse(await gh([`repos/${repo}/pulls/${prNumber}`])));
33496
33514
  }
33497
- async function fetchRestRepoMergeSettings(repo, gh = defaultGhApi) {
33498
- const payload = JSON.parse(await gh([`repos/${repo}`]));
33499
- return typeof payload.delete_branch_on_merge === "boolean" ? { deleteBranchOnMerge: payload.delete_branch_on_merge } : {};
33515
+ async function readRepoMergeSettingsOnce(repo, gh) {
33516
+ let raw;
33517
+ try {
33518
+ raw = await gh([`repos/${repo}`]);
33519
+ } catch (e) {
33520
+ return { proven: false, reason: `read failed: ${e.message}` };
33521
+ }
33522
+ let payload;
33523
+ try {
33524
+ payload = JSON.parse(raw);
33525
+ } catch {
33526
+ return { proven: false, reason: "payload was not JSON" };
33527
+ }
33528
+ if (typeof payload.delete_branch_on_merge === "boolean") {
33529
+ return { proven: true, deleteBranchOnMerge: payload.delete_branch_on_merge };
33530
+ }
33531
+ const apiMessage = typeof payload.message === "string" ? ` (${payload.message})` : "";
33532
+ return { proven: false, reason: `payload carried no delete_branch_on_merge${apiMessage}` };
33533
+ }
33534
+ async function ambientGhApi(args) {
33535
+ const { stdout } = await execFileP("gh", ["api", ...args], { timeout: REST_GH_TIMEOUT_MS });
33536
+ return stdout;
33537
+ }
33538
+ async function fetchRestRepoMergeSettings(repo, gh = defaultGhApi, ambientGh = ambientGhApi) {
33539
+ const primary = await readRepoMergeSettingsOnce(repo, gh);
33540
+ if (primary.proven) return primary;
33541
+ const fallback = await readRepoMergeSettingsOnce(repo, ambientGh);
33542
+ if (fallback.proven) return fallback;
33543
+ return { proven: false, reason: `${primary.reason}; ambient fallback: ${fallback.reason}` };
33500
33544
  }
33501
33545
  var PR_SNAPSHOT_READ_RETRIES = 3;
33502
33546
  var PR_SNAPSHOT_READ_DELAY_MS = 2e3;
@@ -36124,6 +36168,10 @@ async function advanceClosedIssuesToDone(deps) {
36124
36168
  const issue = `#${ref.number}`;
36125
36169
  try {
36126
36170
  const outcome = await deps.moveIssueToDone(issue);
36171
+ if (outcome.archived) {
36172
+ entries.push({ issue, moved: true, status: "archived-terminal" });
36173
+ continue;
36174
+ }
36127
36175
  if (!outcome.moved) {
36128
36176
  entries.push({ issue, moved: false, status: "failed", error: outcome.error });
36129
36177
  continue;
@@ -36988,7 +37036,7 @@ var PR_MERGE_BASE_RACE_RETRY_DELAY_MS = 3e3;
36988
37036
  function assertRemoteBranchPreservedOnMerge(repo, deleteBranchOnMerge) {
36989
37037
  if (deleteBranchOnMerge === false) return;
36990
37038
  throw new Error(
36991
- `pr merge: ${repo} does not prove delete_branch_on_merge=false. Automatic deletion can irreversibly close stacked PRs. Run \`mmi-cli devops ci reconcile --repo ${repo} --apply\`, then retry; remote branch cleanup is delayed.`
37039
+ `pr merge: ${repo} does not prove delete_branch_on_merge=false. Automatic deletion can irreversibly close stacked PRs. Run \`mmi-cli devops ci reconcile --repo ${repo} --apply\`, then retry; when that proof cannot be settled (or an active batch must merge now), rerun with --preserve-worktree \u2014 the designed settings-proof bypass (#6210). Remote branch cleanup is delayed either way.`
36992
37040
  );
36993
37041
  }
36994
37042
  function mergeMethodFromParentCount(parentCount) {
@@ -37031,6 +37079,7 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
37031
37079
  },
37032
37080
  moveIssueToDone: async (ref) => {
37033
37081
  const moved = await moveBoardItem({ config: await loadConfigForBoardSelector2(ref, repoOption), selector: ref, status: "Done", repo: repoOption, allowPartial: true });
37082
+ if (moved.archived) return { moved: true, archived: true };
37034
37083
  return moved.partial ? { moved: false, error: moved.warning } : { moved: true };
37035
37084
  },
37036
37085
  // #5317: after a successful move, re-read the board to verify Done actually holds — a move the API
@@ -37647,11 +37696,35 @@ async function checkDocsIndexAtHead(opts, deps) {
37647
37696
  }
37648
37697
 
37649
37698
  // src/pr-create-claim-guard.ts
37699
+ var CLAIM_GUARD_RATE_LIMIT_WAIT_CAP_MS = 3e4;
37700
+ function withRateLimitRetry(client, seams = {}) {
37701
+ const throttledPaths = /* @__PURE__ */ new Set();
37702
+ const retry = (path2, op) => runWithRateLimitBackoff(op, {
37703
+ context: `pr create claim guard: GET ${path2}`,
37704
+ retryCommand: "re-run `mmi-cli devops pr create` after the rate limit resets",
37705
+ capMs: seams.rateLimitWaitCapMs ?? CLAIM_GUARD_RATE_LIMIT_WAIT_CAP_MS,
37706
+ now: seams.now,
37707
+ sleep: seams.sleep,
37708
+ log: seams.log
37709
+ }).catch((e) => {
37710
+ if (isRateLimitedError(e)) throttledPaths.add(path2);
37711
+ throw e;
37712
+ });
37713
+ return {
37714
+ // The guard path issues only REST reads; graphql passes through untouched.
37715
+ client: {
37716
+ rest: (method, path2, init) => retry(path2, () => client.rest(method, path2, init)),
37717
+ restPaginate: (path2, init) => retry(path2, () => client.restPaginate(path2, init)),
37718
+ graphql: (query, variables, init) => client.graphql(query, variables, init)
37719
+ },
37720
+ throttledPaths
37721
+ };
37722
+ }
37650
37723
  async function prCreateClaimRefusal(body, repoOption, deps = {}) {
37651
37724
  const issues = [...new Set(findClosingMentions(body).map((mention) => mention.issue))];
37652
37725
  if (!issues.length) return void 0;
37653
37726
  const repo = await requireRepo(repoOption);
37654
- const client = deps.client ?? defaultGitHubClient();
37727
+ const { client, throttledPaths } = withRateLimitRetry(deps.client ?? defaultGitHubClient(), deps);
37655
37728
  const actor = deps.actor ?? describeSessionIdentity();
37656
37729
  const checkContest = deps.checkContest ?? checkLaneContest;
37657
37730
  for (const number of issues) {
@@ -37660,7 +37733,8 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
37660
37733
  const contest = await checkContest(client, { repository: repo, number }, actor);
37661
37734
  if (!contest.contested) continue;
37662
37735
  const ref = `${repo}#${number}`;
37663
- const holder = contest.marker ? `lane ${describeClaimMarker(contest.marker)}` : "another lane";
37736
+ const commentsPath = `repos/${repo}/issues/${number}/comments`;
37737
+ const holder = contest.marker ? `lane ${describeClaimMarker(contest.marker)}` : contest.unverifiable.includes("comments") ? `an unreadable lane (its claim-marker comments could not be read${throttledPaths.has(commentsPath) ? " \u2014 GitHub rate-limited the read (HTTP 403) and the bounded reset-aware retry could not clear it" : ""}; the holder may be this very lane)` : "another lane";
37664
37738
  return `pr create: REFUSED \u2014 ${ref} is held by ${holder} with live or unreadable work evidence; run \`mmi-cli oracle board claim ${ref} --force\` (move it to In Progress first if it sits In Review) or drop the closing line before creating this PR`;
37665
37739
  }
37666
37740
  return void 0;
@@ -39612,7 +39686,7 @@ ${list}`);
39612
39686
  else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
39613
39687
  if (result.status === "failed") process.exitCode = 1;
39614
39688
  });
39615
- jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); archives gitignored tmp/** before worktree teardown; on no-ci repos run pr ci-policy / checks-wait first (#1432, #5679)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo); from a foreign checkout the remote probe/delete address this repo and local cleanup runs only in the verified sibling checkout ../<repo>, else localBranch reports skipped-foreign-cwd and the receipt carries foreignCwd: true (#6148)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").addOption(new Option("--disable-auto", "disable a queued auto-merge without merging").conflicts(["auto", "wait", "squash", "merge", "rebase", "preserveWorktree", "gc", "squashBodyFile", "force"])).option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m) \u2014 run as a background/monitor task or under a shell timeout above that budget; a short foreground timeout (e.g. 120s) kills it after checks pass and leaves the PR open (#6027)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888)").option("--gc", "acknowledge deleting unarchived gitignored tmp/** evidence newer than the branch base (#5679)").option("--squash-body-file <path>", "squash commit body (overrides GitHub COMMIT_MESSAGES); use when a pushed commit mentions close/fix/resolve + #N that must not close (#5723)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
39689
+ jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); archives gitignored tmp/** before worktree teardown; on no-ci repos run pr ci-policy / checks-wait first (#1432, #5679)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo); from a foreign checkout the remote probe/delete address this repo and local cleanup runs only in the verified sibling checkout ../<repo>, else localBranch reports skipped-foreign-cwd and the receipt carries foreignCwd: true (#6148)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").addOption(new Option("--disable-auto", "disable a queued auto-merge without merging").conflicts(["auto", "wait", "squash", "merge", "rebase", "preserveWorktree", "gc", "squashBodyFile", "force"])).option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m) \u2014 run as a background/monitor task or under a shell timeout above that budget; a short foreground timeout (e.g. 120s) kills it after checks pass and leaves the PR open (#6027)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888); also the merge settings-proof bypass when delete_branch_on_merge cannot be proven (#6210)").option("--gc", "acknowledge deleting unarchived gitignored tmp/** evidence newer than the branch base (#5679)").option("--squash-body-file <path>", "squash commit body (overrides GitHub COMMIT_MESSAGES); use when a pushed commit mentions close/fix/resolve + #N that must not close (#5723)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
39616
39690
  const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
39617
39691
  const repoArgs = o.repo ? ["--repo", o.repo] : [];
39618
39692
  if (o.disableAuto) {
@@ -39667,6 +39741,9 @@ ${list}`);
39667
39741
  if (prMeta.state !== "MERGED" && !o.preserveWorktree) {
39668
39742
  if (!repoForPostCleanup) throw new Error("pr merge: repository is unreadable; cannot prove remote branch preservation");
39669
39743
  const repoSettings = await fetchRestRepoMergeSettings(repoForPostCleanup);
39744
+ if (!repoSettings.proven) {
39745
+ throw new Error(`pr merge: ${repoForPostCleanup} cannot prove delete_branch_on_merge=false \u2014 ${repoSettings.reason}. Automatic deletion can irreversibly close stacked PRs (#5789). When the proof cannot be settled (or an active batch must merge now), rerun with --preserve-worktree \u2014 the designed settings-proof bypass (#6210).`);
39746
+ }
39670
39747
  assertRemoteBranchPreservedOnMerge(repoForPostCleanup, repoSettings.deleteBranchOnMerge);
39671
39748
  }
39672
39749
  const startingPath = (await execFileP("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.11",
3
+ "version": "4.3.13",
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",