@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.
- package/dist/main.cjs +93 -16
- 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.
|
|
15466
|
-
tag: "v4.3.
|
|
15467
|
-
commit: "
|
|
15468
|
-
npm: "@mutmutco/cli@4.3.
|
|
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.
|
|
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.
|
|
15492
|
-
v3Target: "v4.3.
|
|
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
|
|
33498
|
-
|
|
33499
|
-
|
|
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;
|
|
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
|
|
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