@mutmutco/cli 4.5.163 → 4.5.164
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 +57 -18
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -19051,10 +19051,10 @@ var rollout_plan_default = {
|
|
|
19051
19051
|
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)."
|
|
19052
19052
|
},
|
|
19053
19053
|
baseline: {
|
|
19054
|
-
version: "4.5.
|
|
19055
|
-
tag: "v4.5.
|
|
19056
|
-
commit: "
|
|
19057
|
-
npm: "@mutmutco/cli@4.5.
|
|
19054
|
+
version: "4.5.164",
|
|
19055
|
+
tag: "v4.5.164",
|
|
19056
|
+
commit: "a5f70231e564",
|
|
19057
|
+
npm: "@mutmutco/cli@4.5.164"
|
|
19058
19058
|
},
|
|
19059
19059
|
exitCriterion: "fleet-n-of-n",
|
|
19060
19060
|
hubOnlyShortcut: "forbidden",
|
|
@@ -19071,14 +19071,14 @@ var rollout_plan_default = {
|
|
|
19071
19071
|
repo: "mutmutco/mmi-hub",
|
|
19072
19072
|
role: "canary",
|
|
19073
19073
|
schedule: "train",
|
|
19074
|
-
v3Target: "v4.5.
|
|
19074
|
+
v3Target: "v4.5.164"
|
|
19075
19075
|
}
|
|
19076
19076
|
],
|
|
19077
19077
|
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.",
|
|
19078
19078
|
rollback: {
|
|
19079
19079
|
independent: true,
|
|
19080
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
19081
|
-
v3Target: "v4.5.
|
|
19080
|
+
mechanism: "npm dist-tag latest -> 4.5.164 and redeploy the Hub Lambda from tag v4.5.164 (a5f70231e564); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
19081
|
+
v3Target: "v4.5.164 (@mutmutco/cli@4.5.164, tag commit a5f70231e564 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
19082
19082
|
}
|
|
19083
19083
|
},
|
|
19084
19084
|
{
|
|
@@ -40963,6 +40963,22 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
40963
40963
|
}
|
|
40964
40964
|
const ciPolicy = await deps.resolveCiPolicy(repo);
|
|
40965
40965
|
base.ciPolicy = ciPolicy;
|
|
40966
|
+
const pinned = options.expectHead;
|
|
40967
|
+
if (pinned) base.pinnedHead = { expected: pinned };
|
|
40968
|
+
const headDrift = async (stage) => {
|
|
40969
|
+
if (!pinned) return void 0;
|
|
40970
|
+
if (!deps.readHeadSha) throw new Error("pinned head reader unavailable");
|
|
40971
|
+
const observed = await deps.readHeadSha(prNumber, repo);
|
|
40972
|
+
base.pinnedHead = { expected: pinned, observed };
|
|
40973
|
+
if (observed === pinned) return void 0;
|
|
40974
|
+
return {
|
|
40975
|
+
...base,
|
|
40976
|
+
pinnedHead: { ...base.pinnedHead, refused: "drift" },
|
|
40977
|
+
error: `pr land #${prNumber}: head ${observed} is not the pinned ${pinned} (${stage}) \u2014 refusing to land a head that was not checked (--expect-head)`
|
|
40978
|
+
};
|
|
40979
|
+
};
|
|
40980
|
+
const drift = await headDrift("before landing");
|
|
40981
|
+
if (drift) return drift;
|
|
40966
40982
|
const hasQueue = await deps.hasMergeQueue?.(repo, resolved.base) ?? false;
|
|
40967
40983
|
let queueSnapshot;
|
|
40968
40984
|
if (hasQueue) {
|
|
@@ -40986,7 +41002,16 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
40986
41002
|
base.checksWait = await deps.waitForChecks(prNumber, repo);
|
|
40987
41003
|
const waitError = checksWaitError(base.checksWait);
|
|
40988
41004
|
if (waitError) return { ...base, error: waitError };
|
|
41005
|
+
const driftAfterChecks = await headDrift("after checks");
|
|
41006
|
+
if (driftAfterChecks) return driftAfterChecks;
|
|
40989
41007
|
let prState = await deps.readPrState(prNumber, repo);
|
|
41008
|
+
if (pinned && prState.mergeStateStatus === "BEHIND") {
|
|
41009
|
+
return {
|
|
41010
|
+
...base,
|
|
41011
|
+
pinnedHead: { ...base.pinnedHead, refused: "behind" },
|
|
41012
|
+
error: `pr land #${prNumber}: head is BEHIND ${resolved.base} and pinned by --expect-head \u2014 refusing to update it. Update and re-check the head yourself, then land with the new SHA`
|
|
41013
|
+
};
|
|
41014
|
+
}
|
|
40990
41015
|
if (prState.mergeStateStatus === "BEHIND" && deps.updateBranch) {
|
|
40991
41016
|
try {
|
|
40992
41017
|
base.branchUpdated = await deps.updateBranch(prNumber, repo);
|
|
@@ -41080,6 +41105,7 @@ async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
|
|
|
41080
41105
|
const result2 = {
|
|
41081
41106
|
...base,
|
|
41082
41107
|
mergeQueue: { ...base.mergeQueue, state: "ejected", ...run ? { ejectedRun: run } : {} },
|
|
41108
|
+
...base.pinnedHead ? { pinnedHead: { ...base.pinnedHead, refused: "ejected" } } : {},
|
|
41083
41109
|
error: `merge queue ejected PR #${pr} \u2014 ${run ? `merge_group run ${run.id} ${run.conclusion} ${run.url}` : "merge_group run not found"}`
|
|
41084
41110
|
};
|
|
41085
41111
|
deps.queueProgress?.(result2);
|
|
@@ -41105,7 +41131,7 @@ async function landMany(prs, landOne, cleanup, hooks) {
|
|
|
41105
41131
|
const safe = async (pr) => landOne(pr).catch((e) => ({ status: "failed", repo: "", pr, base: "", error: e.message }));
|
|
41106
41132
|
return Promise.all(prs.map(async (pr) => {
|
|
41107
41133
|
let result = await safe(pr);
|
|
41108
|
-
if (prLandOutcome(result) === "ejected") {
|
|
41134
|
+
if (prLandOutcome(result) === "ejected" && !result.pinnedHead) {
|
|
41109
41135
|
hooks?.onRetry?.(pr, result);
|
|
41110
41136
|
result = { ...await safe(pr), retriedAfterEjection: true };
|
|
41111
41137
|
}
|
|
@@ -41985,6 +42011,7 @@ function buildPrMergeArgs(input) {
|
|
|
41985
42011
|
const args = ["pr", "merge", input.number, ...input.repoArgs, input.method];
|
|
41986
42012
|
if (input.deleteBranch !== false) args.push("--delete-branch");
|
|
41987
42013
|
if (input.bodyFile) args.push("--body-file", input.bodyFile);
|
|
42014
|
+
if (input.matchHeadCommit) args.push("--match-head-commit", input.matchHeadCommit);
|
|
41988
42015
|
if (input.auto) args.push("--auto");
|
|
41989
42016
|
return args;
|
|
41990
42017
|
}
|
|
@@ -42226,12 +42253,12 @@ async function ghMergeAutoEnqueue(prNumber, repo, method, extra) {
|
|
|
42226
42253
|
bodyText: method === "--squash" ? extra?.bodyText : void 0
|
|
42227
42254
|
});
|
|
42228
42255
|
try {
|
|
42229
|
-
return await mergeAutoEnqueueWithBody(prNumber, args, method, io, overrideBody?.path, extra?.mergeQueue);
|
|
42256
|
+
return await mergeAutoEnqueueWithBody(prNumber, args, method, io, overrideBody?.path, extra?.mergeQueue, extra?.matchHeadCommit);
|
|
42230
42257
|
} finally {
|
|
42231
42258
|
overrideBody?.cleanup();
|
|
42232
42259
|
}
|
|
42233
42260
|
}
|
|
42234
|
-
async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile, mergeQueue = false) {
|
|
42261
|
+
async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile, mergeQueue = false, matchHeadCommit) {
|
|
42235
42262
|
const headRef = (await io.gh(["pr", "view", prNumber, ...args, "--json", "headRefName", "--jq", ".headRefName"], GC_GH_TIMEOUT_MS).catch(() => "")).trim();
|
|
42236
42263
|
const deleteBranch = !mergeQueue && !isProtectedBranch(headRef);
|
|
42237
42264
|
const readMergeState = () => readGhPrStateWithRetry(
|
|
@@ -42251,7 +42278,7 @@ async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile, me
|
|
|
42251
42278
|
return s.ok && s.state === "MERGED";
|
|
42252
42279
|
},
|
|
42253
42280
|
reEnqueue: async () => {
|
|
42254
|
-
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
|
|
42281
|
+
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile, matchHeadCommit }), GH_MUTATION_TIMEOUT_MS);
|
|
42255
42282
|
}
|
|
42256
42283
|
});
|
|
42257
42284
|
if (confirmation === "merged") return { mergeStatus: "merged" };
|
|
@@ -42259,7 +42286,7 @@ async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile, me
|
|
|
42259
42286
|
return { mergeStatus: "failed", error: "auto-merge did not stick \u2014 GitHub reported no autoMergeRequest after enqueue; retry once the PR has a pending check" };
|
|
42260
42287
|
};
|
|
42261
42288
|
try {
|
|
42262
|
-
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
|
|
42289
|
+
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile, matchHeadCommit }), GH_MUTATION_TIMEOUT_MS);
|
|
42263
42290
|
} catch (e) {
|
|
42264
42291
|
const message2 = String(e.message || "");
|
|
42265
42292
|
if (/already been merged/i.test(message2)) return { mergeStatus: "merged" };
|
|
@@ -42267,7 +42294,7 @@ async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile, me
|
|
|
42267
42294
|
if (note) return { mergeStatus: "failed", error: note };
|
|
42268
42295
|
if (mergeAutoRejectedPrAlreadyClean(message2)) {
|
|
42269
42296
|
try {
|
|
42270
|
-
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: false, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
|
|
42297
|
+
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: false, deleteBranch, bodyFile, matchHeadCommit }), GH_MUTATION_TIMEOUT_MS);
|
|
42271
42298
|
} catch (e2) {
|
|
42272
42299
|
const m2 = String(e2.message || "");
|
|
42273
42300
|
if (/already been merged/i.test(m2)) return { mergeStatus: "merged" };
|
|
@@ -42278,7 +42305,7 @@ async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile, me
|
|
|
42278
42305
|
return { mergeStatus: "failed", error: `could not confirm PR state after clean-status fallback: ${afterDirect.error}` };
|
|
42279
42306
|
}
|
|
42280
42307
|
try {
|
|
42281
|
-
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
|
|
42308
|
+
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile, matchHeadCommit }), GH_MUTATION_TIMEOUT_MS);
|
|
42282
42309
|
} catch (e3) {
|
|
42283
42310
|
const m3 = String(e3.message || "");
|
|
42284
42311
|
if (/already been merged/i.test(m3)) return { mergeStatus: "merged" };
|
|
@@ -47844,9 +47871,14 @@ ${list}`);
|
|
|
47844
47871
|
}
|
|
47845
47872
|
class PrHeadBehindBaseError extends Error {
|
|
47846
47873
|
}
|
|
47847
|
-
pr.command("land <numbers...>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only. Several PR numbers land together (#8322): all are enqueued, waited on together, a queue-ejected PR is updated and re-enqueued once, a queue merge is followed by worktree/branch cleanup and board Done, and one table (or --json) reports each outcome").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--enqueue-only", "return once queue membership is confirmed; without a merge queue, land normally").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--force", "acknowledge and land 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").option("--without-review <reason>", "compatibility option; shared merge helpers do not require a review verdict").option("--timeout <minutes>", `merge-queue wait per PR (default ${PR_LAND_QUEUE_TIMEOUT_MS / 6e4} minutes, one window per ${PR_LAND_QUEUE_BATCH_SIZE} PRs \u2014 the queue's own check timeout is 60)`).option("--preserve-worktree", "after a queue merge keep the local worktree and branch (skips the post-merge cleanup)").action(async (numbers, opts) => {
|
|
47874
|
+
pr.command("land <numbers...>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only. Several PR numbers land together (#8322): all are enqueued, waited on together, a queue-ejected PR is updated and re-enqueued once, a queue merge is followed by worktree/branch cleanup and board Done, and one table (or --json) reports each outcome").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--enqueue-only", "return once queue membership is confirmed; without a merge queue, land normally").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--force", "acknowledge and land 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").option("--without-review <reason>", "compatibility option; shared merge helpers do not require a review verdict").option("--timeout <minutes>", `merge-queue wait per PR (default ${PR_LAND_QUEUE_TIMEOUT_MS / 6e4} minutes, one window per ${PR_LAND_QUEUE_BATCH_SIZE} PRs \u2014 the queue's own check timeout is 60)`).option("--preserve-worktree", "after a queue merge keep the local worktree and branch (skips the post-merge cleanup)").option("--expect-head <sha>", "pin the PR head to this full 40-hex SHA (#8456): checked before landing, after checks and by GitHub at enqueue (--match-head-commit); never updates a BEHIND head and never retries an ejection \u2014 it stops with pinnedHead.refused instead. One PR only").action(async (numbers, opts) => {
|
|
47848
47875
|
const timeoutMinutes = opts.timeout === void 0 ? void 0 : Number(opts.timeout);
|
|
47849
47876
|
if (timeoutMinutes !== void 0 && !(timeoutMinutes > 0)) return fail("pr land: --timeout must be a positive number of minutes");
|
|
47877
|
+
if (opts.expectHead !== void 0) {
|
|
47878
|
+
if (!/^[0-9a-f]{40}$/i.test(opts.expectHead)) return fail("pr land: --expect-head needs the full 40-hex head SHA");
|
|
47879
|
+
if (numbers.length !== 1) return fail("pr land: --expect-head pins one PR head; land one PR per call");
|
|
47880
|
+
opts.expectHead = opts.expectHead.toLowerCase();
|
|
47881
|
+
}
|
|
47850
47882
|
const queueTimeoutMs = timeoutMinutes !== void 0 ? Math.round(timeoutMinutes * 6e4) : PR_LAND_QUEUE_TIMEOUT_MS * Math.ceil(numbers.length / PR_LAND_QUEUE_BATCH_SIZE);
|
|
47851
47883
|
let inFlight = numbers.length;
|
|
47852
47884
|
const landOne = async (numberArg) => {
|
|
@@ -47887,7 +47919,12 @@ ${list}`);
|
|
|
47887
47919
|
}
|
|
47888
47920
|
if (landClosingGuardVerdict.message) console.warn(landClosingGuardVerdict.message);
|
|
47889
47921
|
let mergeQueueEnabled = false;
|
|
47890
|
-
const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false, enqueueOnly: o.enqueueOnly, queueTimeoutMs }, {
|
|
47922
|
+
const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false, enqueueOnly: o.enqueueOnly, queueTimeoutMs, expectHead: o.expectHead }, {
|
|
47923
|
+
readHeadSha: async (prNumber, repo) => {
|
|
47924
|
+
const sha = (await fetchRestPrSnapshot(prNumber, repo)).headSha;
|
|
47925
|
+
if (!sha) throw new Error(`pr land: could not read the head SHA of PR #${prNumber}`);
|
|
47926
|
+
return sha.toLowerCase();
|
|
47927
|
+
},
|
|
47891
47928
|
pollDelayMs: (attempt) => prLandPollDelayMs(attempt, inFlight),
|
|
47892
47929
|
hasMergeQueue: async (repo, base) => mergeQueueEnabled = await hasPrMergeQueue(repo, base),
|
|
47893
47930
|
readMergeQueue: readPrMergeQueue,
|
|
@@ -47985,9 +48022,11 @@ ${list}`);
|
|
|
47985
48022
|
mergeAuto: (prNumber, repo) => ghMergeAutoEnqueue(prNumber, repo, "--squash", {
|
|
47986
48023
|
allowedClosing: landClosingGuardInput?.closing,
|
|
47987
48024
|
bodyText: landSquashBody,
|
|
47988
|
-
mergeQueue: mergeQueueEnabled
|
|
48025
|
+
mergeQueue: mergeQueueEnabled,
|
|
48026
|
+
matchHeadCommit: o.expectHead
|
|
47989
48027
|
}),
|
|
47990
|
-
|
|
48028
|
+
// #8456: a pinned land has no update lane at all.
|
|
48029
|
+
...o.expectHead ? {} : { updateBranch: (prNumber, repo) => prLandUpdateBranch(prNumber, repo, Boolean(o.repo)) },
|
|
47991
48030
|
pollMerged: async (prNumber, repo, deadlineMs) => {
|
|
47992
48031
|
let lastFailure;
|
|
47993
48032
|
while (Date.now() < deadlineMs) {
|
package/package.json
CHANGED