@mutmutco/cli 4.0.16 → 4.0.17
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 +143 -16
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5035,16 +5035,26 @@ function isAgentScratchPath(path2) {
|
|
|
5035
5035
|
return /^tmp_[^/]+$/.test(normalized);
|
|
5036
5036
|
}
|
|
5037
5037
|
var PUSH_WALL_AGENT_CONFIG_DIRS = [".claude/", ".cursor/rules/", ".codex/", ".agents/"];
|
|
5038
|
+
var LIVE_SESSION_DIRS = [".jerv/", ".pi/"];
|
|
5039
|
+
function pathUnderDirPrefix(path2, dir) {
|
|
5040
|
+
return path2 === dir || path2 === dir.slice(0, -1) || path2.startsWith(dir) || path2.includes(`/${dir}`);
|
|
5041
|
+
}
|
|
5038
5042
|
function isUncommittableAgentConfigPath(path2) {
|
|
5039
5043
|
const normalized = path2.replace(/\\/g, "/").trim();
|
|
5040
|
-
return PUSH_WALL_AGENT_CONFIG_DIRS.some((dir) => normalized
|
|
5044
|
+
return PUSH_WALL_AGENT_CONFIG_DIRS.some((dir) => pathUnderDirPrefix(normalized, dir));
|
|
5045
|
+
}
|
|
5046
|
+
function isLiveSessionPath(path2) {
|
|
5047
|
+
const normalized = path2.replace(/\\/g, "/").trim();
|
|
5048
|
+
return LIVE_SESSION_DIRS.some((dir) => pathUnderDirPrefix(normalized, dir));
|
|
5041
5049
|
}
|
|
5042
5050
|
function splitPorcelainLine(line) {
|
|
5043
5051
|
return { status: line.slice(0, 2), path: line.slice(3).split(" -> ")[0]?.trim() ?? "" };
|
|
5044
5052
|
}
|
|
5045
5053
|
function isExemptPorcelainLine(status, path2) {
|
|
5046
5054
|
if (isAgentScratchPath(path2)) return true;
|
|
5047
|
-
|
|
5055
|
+
if (status !== "??") return false;
|
|
5056
|
+
if (isUncommittableAgentConfigPath(path2)) return true;
|
|
5057
|
+
return isLiveSessionPath(path2);
|
|
5048
5058
|
}
|
|
5049
5059
|
function porcelainBlockingPaths(porcelain) {
|
|
5050
5060
|
const blocking = [];
|
|
@@ -5059,12 +5069,21 @@ function porcelainHasBlockingChanges(porcelain) {
|
|
|
5059
5069
|
return porcelainBlockingPaths(porcelain).length > 0;
|
|
5060
5070
|
}
|
|
5061
5071
|
var BLOCKING_PATH_PREVIEW_LIMIT = 10;
|
|
5072
|
+
function porcelainHasBlockingUntracked(porcelain) {
|
|
5073
|
+
for (const line of porcelain.split("\n")) {
|
|
5074
|
+
if (!line.trim()) continue;
|
|
5075
|
+
const { status, path: path2 } = splitPorcelainLine(line);
|
|
5076
|
+
if (path2 !== "" && status === "??" && !isExemptPorcelainLine(status, path2)) return true;
|
|
5077
|
+
}
|
|
5078
|
+
return false;
|
|
5079
|
+
}
|
|
5062
5080
|
function describeBlockingPaths(porcelain) {
|
|
5063
5081
|
const paths = porcelainBlockingPaths(porcelain);
|
|
5064
5082
|
if (paths.length === 0) return "";
|
|
5065
5083
|
const shown = paths.slice(0, BLOCKING_PATH_PREVIEW_LIMIT);
|
|
5066
5084
|
const rest = paths.length - shown.length;
|
|
5067
|
-
|
|
5085
|
+
const hint = porcelainHasBlockingUntracked(porcelain) ? " (untracked: gitignore it, or add it to .git/info/exclude)" : "";
|
|
5086
|
+
return ` \u2014 blocking: ${shown.join(", ")}${rest > 0 ? `, \u2026 and ${rest} more` : ""}${hint}`;
|
|
5068
5087
|
}
|
|
5069
5088
|
|
|
5070
5089
|
// src/local-train-sync.ts
|
|
@@ -5614,6 +5633,12 @@ var PR_CHECKS_FAILURE_CONFIRMATIONS = 2;
|
|
|
5614
5633
|
var PR_CHECKS_RATELIMIT_FLOOR = 50;
|
|
5615
5634
|
var PR_CHECKS_RATELIMIT_JITTER_MAX_MS = 5e3;
|
|
5616
5635
|
var NO_CI_LIVE_CHECKS_CONTRADICTION_REASON = "ci:none contradicted by live check runs on PR head";
|
|
5636
|
+
var PR_CHECKS_ZERO_RUNS_GRACE_MS = 2 * 6e4;
|
|
5637
|
+
var PR_CHECKS_ZERO_RUNS_CONFIRMATIONS = 2;
|
|
5638
|
+
function zeroRunsDeliveryMessage(headSha) {
|
|
5639
|
+
const tip = headSha ? ` (${headSha.slice(0, 7)})` : "";
|
|
5640
|
+
return `GitHub delivered ZERO workflow runs for this PR head${tip} \u2014 not "checks still pending". Sibling PRs on the same repo can still be healthy while this head is silent. Close/reopen is insufficient. Recourse: push an empty commit on the same branch (\`git commit --allow-empty -m "ci: re-trigger gates" && git push\`) or re-branch onto a new head and open a fresh PR. See docs/Guides/gh-runner-runbook.md \xA7 Zero workflow runs on a PR head.`;
|
|
5641
|
+
}
|
|
5617
5642
|
function decidePrMergeNoCiGuard(checks, policyReason = "registry META ci:none") {
|
|
5618
5643
|
if (checks === "no-checks-reported") return { action: "proceed" };
|
|
5619
5644
|
const staleNote = `merge CI policy says no-ci (${policyReason}), but live PR checks exist`;
|
|
@@ -5661,6 +5686,7 @@ async function waitForPrChecks(deps) {
|
|
|
5661
5686
|
let lastDetail = "pending";
|
|
5662
5687
|
let successStreak = 0;
|
|
5663
5688
|
let failureStreak = 0;
|
|
5689
|
+
let zeroRunsStreak = 0;
|
|
5664
5690
|
report("starting");
|
|
5665
5691
|
while (now() < deadline) {
|
|
5666
5692
|
if (deps.pollRateLimit) {
|
|
@@ -5694,6 +5720,7 @@ async function waitForPrChecks(deps) {
|
|
|
5694
5720
|
report(state);
|
|
5695
5721
|
if (state !== "success") successStreak = 0;
|
|
5696
5722
|
if (state !== "failure") failureStreak = 0;
|
|
5723
|
+
if (state !== "no-checks-reported") zeroRunsStreak = 0;
|
|
5697
5724
|
if (state === "failure") {
|
|
5698
5725
|
failureStreak += 1;
|
|
5699
5726
|
if (failureStreak >= PR_CHECKS_FAILURE_CONFIRMATIONS) {
|
|
@@ -5726,6 +5753,30 @@ async function waitForPrChecks(deps) {
|
|
|
5726
5753
|
}
|
|
5727
5754
|
if (state === "no-checks-reported") {
|
|
5728
5755
|
lastDetail = "no-checks-reported (waiting for workflow to queue)";
|
|
5756
|
+
const elapsed = now() - started;
|
|
5757
|
+
if (deps.pollHeadWorkflowRunCount && elapsed >= PR_CHECKS_ZERO_RUNS_GRACE_MS) {
|
|
5758
|
+
const count = await deps.pollHeadWorkflowRunCount().catch(() => null);
|
|
5759
|
+
if (count === 0) {
|
|
5760
|
+
zeroRunsStreak += 1;
|
|
5761
|
+
if (zeroRunsStreak >= PR_CHECKS_ZERO_RUNS_CONFIRMATIONS) {
|
|
5762
|
+
return {
|
|
5763
|
+
policy,
|
|
5764
|
+
status: "failure",
|
|
5765
|
+
reason: zeroRunsDeliveryMessage(),
|
|
5766
|
+
detail: "zero-runs",
|
|
5767
|
+
waitedMs: elapsed
|
|
5768
|
+
};
|
|
5769
|
+
}
|
|
5770
|
+
lastDetail = "no-checks-reported (confirming zero workflow runs on head)";
|
|
5771
|
+
} else {
|
|
5772
|
+
zeroRunsStreak = 0;
|
|
5773
|
+
if (count !== null && count > 0) {
|
|
5774
|
+
lastDetail = `no-checks-reported (${count} workflow run(s) on head; waiting for check-runs)`;
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
} else {
|
|
5778
|
+
zeroRunsStreak = 0;
|
|
5779
|
+
}
|
|
5729
5780
|
await deps.sleep(PR_CHECKS_POLL_MS);
|
|
5730
5781
|
continue;
|
|
5731
5782
|
}
|
|
@@ -10832,10 +10883,10 @@ var rollout_plan_default = {
|
|
|
10832
10883
|
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)."
|
|
10833
10884
|
},
|
|
10834
10885
|
baseline: {
|
|
10835
|
-
version: "4.0.
|
|
10836
|
-
tag: "v4.0.
|
|
10837
|
-
commit: "
|
|
10838
|
-
npm: "@mutmutco/cli@4.0.
|
|
10886
|
+
version: "4.0.17",
|
|
10887
|
+
tag: "v4.0.17",
|
|
10888
|
+
commit: "df1e866df4f8",
|
|
10889
|
+
npm: "@mutmutco/cli@4.0.17"
|
|
10839
10890
|
},
|
|
10840
10891
|
exitCriterion: "fleet-n-of-n",
|
|
10841
10892
|
hubOnlyShortcut: "forbidden",
|
|
@@ -10852,14 +10903,14 @@ var rollout_plan_default = {
|
|
|
10852
10903
|
repo: "mutmutco/mmi-hub",
|
|
10853
10904
|
role: "canary",
|
|
10854
10905
|
schedule: "train",
|
|
10855
|
-
v3Target: "v4.0.
|
|
10906
|
+
v3Target: "v4.0.17"
|
|
10856
10907
|
}
|
|
10857
10908
|
],
|
|
10858
10909
|
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.",
|
|
10859
10910
|
rollback: {
|
|
10860
10911
|
independent: true,
|
|
10861
|
-
mechanism: "npm dist-tag latest -> 4.0.
|
|
10862
|
-
v3Target: "v4.0.
|
|
10912
|
+
mechanism: "npm dist-tag latest -> 4.0.17 and redeploy the Hub Lambda from tag v4.0.17 (df1e866df4f8); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
10913
|
+
v3Target: "v4.0.17 (@mutmutco/cli@4.0.17, tag commit df1e866df4f8 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
10863
10914
|
}
|
|
10864
10915
|
},
|
|
10865
10916
|
{
|
|
@@ -14659,7 +14710,11 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
|
|
|
14659
14710
|
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag, stage: "main", deployModel });
|
|
14660
14711
|
}
|
|
14661
14712
|
if (trueMergeGateNote) checks = `${trueMergeGateNote}; ${checks}`;
|
|
14662
|
-
|
|
14713
|
+
try {
|
|
14714
|
+
await runGitPush(deps, ["push", "origin", "main"]);
|
|
14715
|
+
} catch (e) {
|
|
14716
|
+
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag, stage: "main", deployModel });
|
|
14717
|
+
}
|
|
14663
14718
|
const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
14664
14719
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
|
|
14665
14720
|
const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
@@ -15045,7 +15100,11 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
15045
15100
|
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag: tag2, stage: "rc", deployModel: deployModel2 });
|
|
15046
15101
|
}
|
|
15047
15102
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
15048
|
-
|
|
15103
|
+
try {
|
|
15104
|
+
await runGitPush(deps, ["push", "origin", "rc"]);
|
|
15105
|
+
} catch (e) {
|
|
15106
|
+
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag: tag2, stage: "rc", deployModel: deployModel2 });
|
|
15107
|
+
}
|
|
15049
15108
|
const d2 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha);
|
|
15050
15109
|
return { ...ctx, command, stage: "rc", ref: "rc", tag: tag2, bumpIntent, deployModel: deployModel2, promoted: true, checks: checks2, resumeNote, dispatch: d2.note, runId: d2.runId, runUrl: d2.runUrl, workflowRuns: d2.workflowRuns, deployStatus: d2.deployStatus };
|
|
15051
15110
|
}
|
|
@@ -29772,6 +29831,14 @@ function ghPrMergeLocalBranchDeleteWarning(message) {
|
|
|
29772
29831
|
function mergeAutoRejectedPrAlreadyClean(message) {
|
|
29773
29832
|
return /clean status \(enablePullRequestAutoMerge\)|is in clean status/i.test(message);
|
|
29774
29833
|
}
|
|
29834
|
+
function mergeMethodFromParentCount(parentCount) {
|
|
29835
|
+
if (parentCount >= 2) return "merge";
|
|
29836
|
+
return void 0;
|
|
29837
|
+
}
|
|
29838
|
+
function reportedMergeMethod(input) {
|
|
29839
|
+
if (!input.alreadyMerged) return input.requestedMethod;
|
|
29840
|
+
return input.observedMethod;
|
|
29841
|
+
}
|
|
29775
29842
|
|
|
29776
29843
|
// src/merge-cleanup.ts
|
|
29777
29844
|
var GC_GH_TIMEOUT_MS = 2e4;
|
|
@@ -30410,6 +30477,25 @@ async function pollRestPrMergeable(prNumber, repo, gh = defaultGhApi) {
|
|
|
30410
30477
|
return "UNKNOWN";
|
|
30411
30478
|
}
|
|
30412
30479
|
}
|
|
30480
|
+
async function countHeadWorkflowRuns(headSha, repo, gh = defaultGhApi) {
|
|
30481
|
+
if (!headSha) return { state: "failed", error: "no head SHA" };
|
|
30482
|
+
let raw;
|
|
30483
|
+
try {
|
|
30484
|
+
raw = await gh([`repos/${repo}/actions/runs?head_sha=${encodeURIComponent(headSha)}&per_page=1`]);
|
|
30485
|
+
} catch (e) {
|
|
30486
|
+
return { state: "failed", error: `actions/runs read failed for ${headSha.slice(0, 7)} on ${repo}: ${readErrorText(e)}` };
|
|
30487
|
+
}
|
|
30488
|
+
let count;
|
|
30489
|
+
try {
|
|
30490
|
+
count = JSON.parse(raw).total_count;
|
|
30491
|
+
} catch (e) {
|
|
30492
|
+
return { state: "failed", error: `actions/runs payload for ${headSha.slice(0, 7)} was not JSON: ${readErrorText(e)}` };
|
|
30493
|
+
}
|
|
30494
|
+
if (typeof count !== "number" || !Number.isFinite(count) || count < 0) {
|
|
30495
|
+
return { state: "failed", error: `actions/runs payload for ${headSha.slice(0, 7)} carried no usable total_count` };
|
|
30496
|
+
}
|
|
30497
|
+
return { state: "ok", count };
|
|
30498
|
+
}
|
|
30413
30499
|
async function pollRestPrMerged(prNumber, repo, gh = defaultGhApi) {
|
|
30414
30500
|
try {
|
|
30415
30501
|
return (await fetchRestPrSnapshot(prNumber, repo, gh)).merged ? { state: "merged" } : { state: "open" };
|
|
@@ -38057,12 +38143,29 @@ async function waitLoopDiagnosis(label, prNumber, repo) {
|
|
|
38057
38143
|
}
|
|
38058
38144
|
return null;
|
|
38059
38145
|
}
|
|
38146
|
+
async function waitLoopHeadWorkflowRunCount(label, prNumber, repo) {
|
|
38147
|
+
let snapshot;
|
|
38148
|
+
try {
|
|
38149
|
+
snapshot = await fetchRestPrSnapshot(prNumber, repo);
|
|
38150
|
+
} catch (e) {
|
|
38151
|
+
console.warn(
|
|
38152
|
+
`${label}: zero-runs probe FAILED (pulls read: ${e?.message ?? e}) \u2014 not treating as delivery failure this cycle`
|
|
38153
|
+
);
|
|
38154
|
+
return null;
|
|
38155
|
+
}
|
|
38156
|
+
const read = await countHeadWorkflowRuns(snapshot.headSha, repo);
|
|
38157
|
+
if (read.state === "ok") return read.count;
|
|
38158
|
+
console.warn(
|
|
38159
|
+
`${label}: zero-runs probe FAILED (${read.error}) \u2014 not treating as delivery failure this cycle`
|
|
38160
|
+
);
|
|
38161
|
+
return null;
|
|
38162
|
+
}
|
|
38060
38163
|
pr.command("ci-policy").description("report merge CI policy: wait-for-checks vs no-ci (for grind/build agents)").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current checkout)").action(async (o) => {
|
|
38061
38164
|
const result = await resolveMergeCiPolicyForCheckout(o.repo);
|
|
38062
38165
|
if (o.json) return printLine(JSON.stringify(result));
|
|
38063
38166
|
printLine(`merge CI policy: ${result.policy} (${result.reason})`);
|
|
38064
38167
|
});
|
|
38065
|
-
pr.command("checks-wait <number>").description(`bounded wait for ALL checks on the PR head, required or not (#5336); skips immediately on no-ci repos (#1432), fails immediately on a CONFLICTING PR \u2014 GitHub never queues checks for one (#2970). REST-only polling with a pool floor (#3024). Default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m; --timeout raises it. Exit 1 = a check FAILED
|
|
38168
|
+
pr.command("checks-wait <number>").description(`bounded wait for ALL checks on the PR head, required or not (#5336); skips immediately on no-ci repos (#1432), fails immediately on a CONFLICTING PR \u2014 GitHub never queues checks for one (#2970), and fails early on a confirmed zero-workflow-runs head instead of burning the full budget as pending (#5400). REST-only polling with a pool floor (#3024). Default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m; --timeout raises it. Exit 1 = a check FAILED, the PR is CONFLICTING, or GitHub delivered zero runs; exit ${PR_CHECKS_TIMEOUT_EXIT_CODE} = the wait window expired or the API pool ran dry (re-arm)`).option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--timeout <minutes>", `wait budget in minutes (default ${PR_CHECKS_TIMEOUT_MS / 6e4}) \u2014 raise it for serial self-hosted e2e queues`).action(async (number, o) => {
|
|
38066
38169
|
let timeoutMs;
|
|
38067
38170
|
if (o.timeout !== void 0) {
|
|
38068
38171
|
const minutes = Number(o.timeout);
|
|
@@ -38088,6 +38191,8 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
|
|
|
38088
38191
|
// #3388: on a confirmed red, read the failing runs' annotations so a wall-clock budget kill stops
|
|
38089
38192
|
// reading as "your tests failed". One call per failing run, only at the verdict.
|
|
38090
38193
|
diagnoseFailure: () => waitLoopDiagnosis("pr checks-wait", number, repo),
|
|
38194
|
+
// #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
|
|
38195
|
+
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
|
|
38091
38196
|
baseBranch,
|
|
38092
38197
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
38093
38198
|
log: (message) => console.warn(message),
|
|
@@ -38102,7 +38207,7 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
|
|
|
38102
38207
|
} else if (result.status === "timeout") {
|
|
38103
38208
|
const stuckQueuing = /no-checks-reported/i.test(result.detail ?? "");
|
|
38104
38209
|
printLine(
|
|
38105
|
-
`pr checks-wait: timeout \u2014 waited ${Math.round((result.waitedMs ?? 0) / 6e4)}m, last state: ${result.detail ?? "pending"}. No check failed; re-run to keep waiting, or pass --timeout <minutes>.` + (stuckQueuing ? " Tip:
|
|
38210
|
+
`pr checks-wait: timeout \u2014 waited ${Math.round((result.waitedMs ?? 0) / 6e4)}m, last state: ${result.detail ?? "pending"}. No check failed; re-run to keep waiting, or pass --timeout <minutes>.` + (stuckQueuing ? " Tip: check-runs never appeared \u2014 if `gh api repos/<owner>/<repo>/actions/runs?head_sha=<sha>` shows total_count=0, see docs/Guides/gh-runner-runbook.md \xA7 Zero workflow runs; otherwise mmi-live may be saturated or Actions may be failing before checkout (docs/Guides/gh-runner-runbook.md)." : "")
|
|
38106
38211
|
);
|
|
38107
38212
|
} else if (result.status === "rate-limited") {
|
|
38108
38213
|
printLine(`pr checks-wait: rate-limited \u2014 ${result.reason ?? "REST pool below floor"}. No check failed; re-run after the pool resets.`);
|
|
@@ -38110,6 +38215,8 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
|
|
|
38110
38215
|
printLine(`pr checks-wait: failure (runner infrastructure, NOT a test failure) \u2014 ${result.reason}`);
|
|
38111
38216
|
} else if (result.detail === "stale-head") {
|
|
38112
38217
|
printLine(`pr checks-wait: failure (stale PR head, NOT a test failure) \u2014 ${result.reason}`);
|
|
38218
|
+
} else if (result.detail === "zero-runs") {
|
|
38219
|
+
printLine(`pr checks-wait: failure (zero workflow runs delivered, NOT pending checks) \u2014 ${result.reason}`);
|
|
38113
38220
|
} else printLine(`pr checks-wait: ${result.status}${result.reason ? ` \u2014 ${result.reason}` : ""}${result.detail ? ` (${result.detail})` : ""}`);
|
|
38114
38221
|
if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
|
|
38115
38222
|
if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
|
|
@@ -38180,6 +38287,8 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
38180
38287
|
// #3388: `pr land` is the batch path — the one most likely to self-DOS the shared runner and
|
|
38181
38288
|
// then read its own wall-clock kill as a broken diff.
|
|
38182
38289
|
diagnoseFailure: () => waitLoopDiagnosis("pr land", prNumber, repo),
|
|
38290
|
+
// #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
|
|
38291
|
+
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
|
|
38183
38292
|
baseBranch: "development",
|
|
38184
38293
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
38185
38294
|
log: (message) => console.warn(message),
|
|
@@ -38302,6 +38411,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
38302
38411
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
38303
38412
|
pollRateLimit: () => waitLoopCorePool("pr merge --wait"),
|
|
38304
38413
|
diagnoseFailure: () => waitLoopDiagnosis("pr merge --wait", number, repo),
|
|
38414
|
+
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
|
|
38305
38415
|
baseBranch,
|
|
38306
38416
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
38307
38417
|
log: (message) => console.warn(message),
|
|
@@ -38421,11 +38531,27 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
38421
38531
|
invalidateStatuslineBoardCache();
|
|
38422
38532
|
const devDeploy = devDeployPlan.applicable && devDeployDeps && remoteNotAttemptedReason !== "pr-already-merged" ? await dispatchDevDeploy(repoForPostCleanup, devDeployDeps) : void 0;
|
|
38423
38533
|
const devDeployManual = devDeployPlan.applicable ? devDeployPlan.manualPointer : void 0;
|
|
38534
|
+
let observedMethod;
|
|
38535
|
+
if (remoteNotAttemptedReason === "pr-already-merged" && repoForPostCleanup) {
|
|
38536
|
+
try {
|
|
38537
|
+
const mergeSha = (await defaultGitHubClient().rest("GET", `repos/${repoForPostCleanup}/pulls/${number}`)).merge_commit_sha;
|
|
38538
|
+
if (mergeSha) {
|
|
38539
|
+
const commit = await defaultGitHubClient().rest("GET", `repos/${repoForPostCleanup}/commits/${mergeSha}`);
|
|
38540
|
+
observedMethod = mergeMethodFromParentCount(Array.isArray(commit.parents) ? commit.parents.length : 0);
|
|
38541
|
+
}
|
|
38542
|
+
} catch {
|
|
38543
|
+
}
|
|
38544
|
+
}
|
|
38545
|
+
const methodField = reportedMergeMethod({
|
|
38546
|
+
requestedMethod: method.slice(2),
|
|
38547
|
+
alreadyMerged: remoteNotAttemptedReason === "pr-already-merged",
|
|
38548
|
+
observedMethod
|
|
38549
|
+
});
|
|
38424
38550
|
console.log(JSON.stringify({
|
|
38425
38551
|
mergeStatus: "merged",
|
|
38426
38552
|
merged: number,
|
|
38427
38553
|
branch: headRef,
|
|
38428
|
-
method:
|
|
38554
|
+
...methodField ? { method: methodField } : {},
|
|
38429
38555
|
remoteBranch,
|
|
38430
38556
|
housekeeping,
|
|
38431
38557
|
// `boardAdvance` keeps its published array shape; `boardAdvanceStatus` is the field a caller reads to
|
|
@@ -38807,7 +38933,8 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
38807
38933
|
applyTrainFollowUpExit(followUpStatus);
|
|
38808
38934
|
return;
|
|
38809
38935
|
} catch (e) {
|
|
38810
|
-
|
|
38936
|
+
process.exitCode = 1;
|
|
38937
|
+
return await failGraceful(`${commandName}: ${e.message}`);
|
|
38811
38938
|
}
|
|
38812
38939
|
}
|
|
38813
38940
|
const repo = o.repo ?? await resolveRepo();
|
package/package.json
CHANGED