@mutmutco/cli 4.2.4 → 4.2.6
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 +434 -107
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5844,13 +5844,20 @@ async function waitForPrChecks(deps) {
|
|
|
5844
5844
|
failureStreak += 1;
|
|
5845
5845
|
if (failureStreak >= PR_CHECKS_FAILURE_CONFIRMATIONS) {
|
|
5846
5846
|
const diagnosis = deps.diagnoseFailure ? await deps.diagnoseFailure().catch(() => null) : null;
|
|
5847
|
+
const diagnosticCommand = diagnosis?.failedChecks.find((check) => check.diagnosticCommand)?.diagnosticCommand ?? deps.checksDiagnosticCommand;
|
|
5848
|
+
const failureReceipt = {
|
|
5849
|
+
failedChecks: diagnosis?.failedChecks ?? [],
|
|
5850
|
+
...deps.checksUrl ? { checksUrl: deps.checksUrl } : {},
|
|
5851
|
+
...diagnosticCommand ? { diagnosticCommand } : {},
|
|
5852
|
+
...diagnosis?.checksFailureDetail ? { checksFailureDetail: diagnosis.checksFailureDetail } : {}
|
|
5853
|
+
};
|
|
5847
5854
|
if (diagnosis?.cause === "stale-head") {
|
|
5848
|
-
return { policy, status: "failure", reason: diagnosis.reason, detail: "stale-head" };
|
|
5855
|
+
return { policy, status: "failure", reason: diagnosis.reason, detail: "stale-head", ...failureReceipt };
|
|
5849
5856
|
}
|
|
5850
5857
|
if (diagnosis?.cause === "runner-infra") {
|
|
5851
|
-
return { policy, status: "failure", reason: diagnosis.reason, detail: "runner-infra" };
|
|
5858
|
+
return { policy, status: "failure", reason: diagnosis.reason, detail: "runner-infra", ...failureReceipt };
|
|
5852
5859
|
}
|
|
5853
|
-
return { policy, status: "failure", reason, detail: "checks-failure" };
|
|
5860
|
+
return { policy, status: "failure", reason, detail: "checks-failure", ...failureReceipt };
|
|
5854
5861
|
}
|
|
5855
5862
|
lastDetail = "confirming-failure";
|
|
5856
5863
|
await deps.sleep(PR_CHECKS_POLL_MS);
|
|
@@ -9340,6 +9347,27 @@ function boardConfigFromProject(meta, floor = {}) {
|
|
|
9340
9347
|
};
|
|
9341
9348
|
}
|
|
9342
9349
|
|
|
9350
|
+
// src/issue-ref.ts
|
|
9351
|
+
var ISSUE_REF_SHAPES = "123, #123, owner/repo#123, or a GitHub issue or PR URL";
|
|
9352
|
+
function invalidReference(ref) {
|
|
9353
|
+
return new Error(`invalid reference "${ref}" \u2014 expected ${ISSUE_REF_SHAPES}`);
|
|
9354
|
+
}
|
|
9355
|
+
function parseIssueRef(ref, expectedRepo) {
|
|
9356
|
+
const trimmed = ref.trim();
|
|
9357
|
+
const url = trimmed.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/(?:issues|pull)\/(\d+)$/i);
|
|
9358
|
+
const qualified = trimmed.match(/^([^/\s#]+\/[^/\s#]+)#(\d+)$/);
|
|
9359
|
+
const bare = trimmed.match(/^#?(\d+)$/);
|
|
9360
|
+
const match = url ?? qualified ?? bare;
|
|
9361
|
+
if (!match) throw invalidReference(ref);
|
|
9362
|
+
const number = Number(match[match.length - 1]);
|
|
9363
|
+
if (!Number.isInteger(number) || number <= 0) throw invalidReference(ref);
|
|
9364
|
+
const repo = (url ?? qualified)?.[1];
|
|
9365
|
+
if (repo && expectedRepo && repo.toLowerCase() !== expectedRepo.toLowerCase()) {
|
|
9366
|
+
throw new Error(`reference "${ref}" names ${repo}, which does not match --repo ${expectedRepo} \u2014 expected ${ISSUE_REF_SHAPES}`);
|
|
9367
|
+
}
|
|
9368
|
+
return repo ? { repo, number } : { number };
|
|
9369
|
+
}
|
|
9370
|
+
|
|
9343
9371
|
// src/repo-resolve.ts
|
|
9344
9372
|
function slugOf(repoOrSlug) {
|
|
9345
9373
|
return (repoOrSlug.includes("/") ? repoOrSlug.split("/").pop() : repoOrSlug).toLowerCase();
|
|
@@ -9349,11 +9377,11 @@ function repoFromRemoteUrl(remoteUrl) {
|
|
|
9349
9377
|
return m ? `${m[1]}/${m[2]}` : void 0;
|
|
9350
9378
|
}
|
|
9351
9379
|
function repoFromSelector(selector) {
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9380
|
+
try {
|
|
9381
|
+
return parseIssueRef(selector).repo;
|
|
9382
|
+
} catch {
|
|
9383
|
+
return void 0;
|
|
9384
|
+
}
|
|
9357
9385
|
}
|
|
9358
9386
|
async function resolveRepo(repo) {
|
|
9359
9387
|
if (repo) return repo;
|
|
@@ -11802,10 +11830,10 @@ var rollout_plan_default = {
|
|
|
11802
11830
|
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)."
|
|
11803
11831
|
},
|
|
11804
11832
|
baseline: {
|
|
11805
|
-
version: "4.2.
|
|
11806
|
-
tag: "v4.2.
|
|
11807
|
-
commit: "
|
|
11808
|
-
npm: "@mutmutco/cli@4.2.
|
|
11833
|
+
version: "4.2.6",
|
|
11834
|
+
tag: "v4.2.6",
|
|
11835
|
+
commit: "608eebe09477",
|
|
11836
|
+
npm: "@mutmutco/cli@4.2.6"
|
|
11809
11837
|
},
|
|
11810
11838
|
exitCriterion: "fleet-n-of-n",
|
|
11811
11839
|
hubOnlyShortcut: "forbidden",
|
|
@@ -11822,14 +11850,14 @@ var rollout_plan_default = {
|
|
|
11822
11850
|
repo: "mutmutco/mmi-hub",
|
|
11823
11851
|
role: "canary",
|
|
11824
11852
|
schedule: "train",
|
|
11825
|
-
v3Target: "v4.2.
|
|
11853
|
+
v3Target: "v4.2.6"
|
|
11826
11854
|
}
|
|
11827
11855
|
],
|
|
11828
11856
|
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.",
|
|
11829
11857
|
rollback: {
|
|
11830
11858
|
independent: true,
|
|
11831
|
-
mechanism: "npm dist-tag latest -> 4.2.
|
|
11832
|
-
v3Target: "v4.2.
|
|
11859
|
+
mechanism: "npm dist-tag latest -> 4.2.6 and redeploy the Hub Lambda from tag v4.2.6 (608eebe09477); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
11860
|
+
v3Target: "v4.2.6 (@mutmutco/cli@4.2.6, tag commit 608eebe09477 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
11833
11861
|
}
|
|
11834
11862
|
},
|
|
11835
11863
|
{
|
|
@@ -12136,16 +12164,6 @@ function formatOrgVersions(report) {
|
|
|
12136
12164
|
}
|
|
12137
12165
|
|
|
12138
12166
|
// src/sub-issue.ts
|
|
12139
|
-
function parseIssueRef(ref) {
|
|
12140
|
-
const trimmed = ref.trim();
|
|
12141
|
-
const url = trimmed.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)$/i);
|
|
12142
|
-
if (url) return { repo: url[1], number: Number(url[2]) };
|
|
12143
|
-
const qualified = trimmed.match(/^([^/\s#]+\/[^/\s#]+)#(\d+)$/);
|
|
12144
|
-
if (qualified) return { repo: qualified[1], number: Number(qualified[2]) };
|
|
12145
|
-
const bare = trimmed.match(/^#?(\d+)$/);
|
|
12146
|
-
if (bare) return { number: Number(bare[1]) };
|
|
12147
|
-
throw new Error(`invalid issue reference "${ref}" \u2014 expected #123, 123, owner/repo#123, or an issue URL`);
|
|
12148
|
-
}
|
|
12149
12167
|
function buildResolveIdArgs(ref) {
|
|
12150
12168
|
const args = ["issue", "view", String(ref.number), "--json", "id", "--jq", ".id"];
|
|
12151
12169
|
if (ref.repo) args.push("--repo", ref.repo);
|
|
@@ -14855,19 +14873,19 @@ async function discoverRequiredCheckContexts(deps, ctx, branch) {
|
|
|
14855
14873
|
}
|
|
14856
14874
|
return [...contexts];
|
|
14857
14875
|
}
|
|
14858
|
-
async function
|
|
14859
|
-
const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", target, "--head", "main", "--state", "
|
|
14876
|
+
async function findAlignmentPr(deps, ctx, target) {
|
|
14877
|
+
const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", target, "--head", "main", "--state", "all", "--json", "number,url,state"]));
|
|
14860
14878
|
if (!out) return void 0;
|
|
14861
14879
|
const rows = JSON.parse(out);
|
|
14862
|
-
const row = rows.
|
|
14863
|
-
return row
|
|
14880
|
+
const row = rows.filter((r) => typeof r.number === "number" && typeof r.url === "string" && (r.state === "OPEN" || r.state === "MERGED")).sort((a, b) => b.number - a.number)[0];
|
|
14881
|
+
return row;
|
|
14864
14882
|
}
|
|
14865
14883
|
function parsePrNumber(url) {
|
|
14866
14884
|
const n = Number.parseInt(url.split("/").pop() ?? "", 10);
|
|
14867
14885
|
return Number.isFinite(n) ? n : void 0;
|
|
14868
14886
|
}
|
|
14869
14887
|
async function rollDevelopmentForward(deps, ctx, tag) {
|
|
14870
|
-
await runGitRemoteRead(deps, ["fetch", "origin", "main"]);
|
|
14888
|
+
await runGitRemoteRead(deps, ["fetch", "origin", "main", "development"]);
|
|
14871
14889
|
const required = await discoverRequiredCheckContexts(deps, ctx, "development");
|
|
14872
14890
|
if (required.length === 0) {
|
|
14873
14891
|
await deps.run("git", ["checkout", "development"]);
|
|
@@ -14876,13 +14894,17 @@ async function rollDevelopmentForward(deps, ctx, tag) {
|
|
|
14876
14894
|
await deps.run("git", ["push", "origin", "development"]);
|
|
14877
14895
|
return { status: "pushed", note: "development rolled forward to the released main (development has no required checks)" };
|
|
14878
14896
|
}
|
|
14879
|
-
const existing = await findOpenAlignmentPr(deps, ctx, "development");
|
|
14880
|
-
if (existing) {
|
|
14881
|
-
return enqueueAlignmentAutoMerge(deps, ctx, existing.number, existing.url, `alignment PR already open: ${existing.url}`);
|
|
14882
|
-
}
|
|
14883
14897
|
const ahead = clean2(await deps.run("git", ["rev-list", "--count", "origin/development..origin/main"]));
|
|
14898
|
+
const existing = await findAlignmentPr(deps, ctx, "development");
|
|
14884
14899
|
if (ahead === "0") {
|
|
14885
|
-
return {
|
|
14900
|
+
return {
|
|
14901
|
+
status: "aligned",
|
|
14902
|
+
alignment: "already-merged",
|
|
14903
|
+
note: existing?.state === "MERGED" ? `alignment: already-merged \u2014 development already contains main via alignment PR #${existing.number}` : "alignment: already-merged \u2014 development already contains the released main; nothing to roll forward"
|
|
14904
|
+
};
|
|
14905
|
+
}
|
|
14906
|
+
if (existing?.state === "OPEN") {
|
|
14907
|
+
return enqueueAlignmentAutoMerge(deps, ctx, existing.number, existing.url, `alignment PR already open: ${existing.url}`);
|
|
14886
14908
|
}
|
|
14887
14909
|
const body = `Carries the ${tag} release (including the version fold) from \`main\` back to \`development\`.
|
|
14888
14910
|
|
|
@@ -14919,7 +14941,7 @@ async function enqueueAlignmentAutoMerge(deps, ctx, prNumber, prUrl, openedNote)
|
|
|
14919
14941
|
return { ...base, note: manual };
|
|
14920
14942
|
}
|
|
14921
14943
|
async function alignRcForward(deps, ctx, tag) {
|
|
14922
|
-
await runGitRemoteRead(deps, ["fetch", "origin", "main"]);
|
|
14944
|
+
await runGitRemoteRead(deps, ["fetch", "origin", "main", "rc"]);
|
|
14923
14945
|
const required = await discoverRequiredCheckContexts(deps, ctx, "rc");
|
|
14924
14946
|
if (required.length === 0) {
|
|
14925
14947
|
const ahead2 = clean2(await deps.run("git", ["rev-list", "--count", "origin/rc..origin/main"]));
|
|
@@ -14929,13 +14951,17 @@ async function alignRcForward(deps, ctx, tag) {
|
|
|
14929
14951
|
await runGitPush(deps, ["push", "origin", "origin/main:refs/heads/rc"]);
|
|
14930
14952
|
return { status: "pushed", note: "rc aligned to the released main (rc has no required checks)" };
|
|
14931
14953
|
}
|
|
14932
|
-
const existing = await findOpenAlignmentPr(deps, ctx, "rc");
|
|
14933
|
-
if (existing) {
|
|
14934
|
-
return enqueueAlignmentAutoMerge(deps, ctx, existing.number, existing.url, `rc alignment PR already open: ${existing.url}`);
|
|
14935
|
-
}
|
|
14936
14954
|
const ahead = clean2(await deps.run("git", ["rev-list", "--count", "origin/rc..origin/main"]));
|
|
14955
|
+
const existing = await findAlignmentPr(deps, ctx, "rc");
|
|
14937
14956
|
if (ahead === "0") {
|
|
14938
|
-
return {
|
|
14957
|
+
return {
|
|
14958
|
+
status: "aligned",
|
|
14959
|
+
alignment: "already-merged",
|
|
14960
|
+
note: existing?.state === "MERGED" ? `alignment: already-merged \u2014 rc already contains main via alignment PR #${existing.number}` : "alignment: already-merged \u2014 rc already contains the released main; nothing to align"
|
|
14961
|
+
};
|
|
14962
|
+
}
|
|
14963
|
+
if (existing?.state === "OPEN") {
|
|
14964
|
+
return enqueueAlignmentAutoMerge(deps, ctx, existing.number, existing.url, `rc alignment PR already open: ${existing.url}`);
|
|
14939
14965
|
}
|
|
14940
14966
|
const body = `Carries the ${tag} release from \`main\` back to \`rc\`.
|
|
14941
14967
|
|
|
@@ -15847,10 +15873,16 @@ function resolveGreenGateNpmFromGateWorkflows(files) {
|
|
|
15847
15873
|
return pin ? bundledNpmForNodePin(pin) : void 0;
|
|
15848
15874
|
}
|
|
15849
15875
|
function parsePublishWorkflowNpmPin(workflowBody) {
|
|
15850
|
-
const match = /(?:^|[^\w])npm@(\d
|
|
15876
|
+
const match = /(?:^|[^\w])npm@(\d+(?:\.\d+\.\d+)?)/m.exec(workflowBody);
|
|
15851
15877
|
return match?.[1];
|
|
15852
15878
|
}
|
|
15879
|
+
function parseGateNpmVersionPins(workflowBody) {
|
|
15880
|
+
if (!/runner-node-toolchain/.test(workflowBody)) return [];
|
|
15881
|
+
return [...workflowBody.matchAll(/^\s*npm-version:\s*['"]?(\d+(?:\.\d+\.\d+)?)/gm)].map((m) => m[1]);
|
|
15882
|
+
}
|
|
15853
15883
|
function resolveExpectedCiNpmFromWorkflows(gateFiles, allFiles) {
|
|
15884
|
+
const declared = gateFiles?.flatMap((file) => parseGateNpmVersionPins(file.body)) ?? [];
|
|
15885
|
+
if (declared.length) return declared[0];
|
|
15854
15886
|
const fromGate = resolveGreenGateNpmFromGateWorkflows(gateFiles);
|
|
15855
15887
|
if (fromGate) return fromGate;
|
|
15856
15888
|
const files = allFiles ?? gateFiles;
|
|
@@ -20683,15 +20715,9 @@ function resolveBoardConfig(cfg) {
|
|
|
20683
20715
|
priorityOptions: cfg.priorityOptions
|
|
20684
20716
|
};
|
|
20685
20717
|
}
|
|
20686
|
-
function parseIssueSelector(selector, defaultRepo) {
|
|
20687
|
-
const
|
|
20688
|
-
|
|
20689
|
-
if (url) return { repo: url[1], number: Number(url[2]) };
|
|
20690
|
-
const qualified = trimmed.match(/^([^/\s#]+\/[^/\s#]+)#(\d+)$/);
|
|
20691
|
-
if (qualified) return { repo: qualified[1], number: Number(qualified[2]) };
|
|
20692
|
-
const local = trimmed.match(/^#?(\d+)$/);
|
|
20693
|
-
if (local) return { repo: defaultRepo, number: Number(local[1]) };
|
|
20694
|
-
throw new Error(`expected an issue selector like 123, #123, owner/repo#123, or a GitHub issue URL`);
|
|
20718
|
+
function parseIssueSelector(selector, defaultRepo, expectedRepo) {
|
|
20719
|
+
const parsed = parseIssueRef(selector, expectedRepo);
|
|
20720
|
+
return { repo: parsed.repo ?? defaultRepo, number: parsed.number };
|
|
20695
20721
|
}
|
|
20696
20722
|
function sameRepo(itemRepo, selectorKey) {
|
|
20697
20723
|
const repo = itemRepo.toLowerCase();
|
|
@@ -21016,7 +21042,7 @@ async function moveBoardItem(options, deps = {}) {
|
|
|
21016
21042
|
const cfg = resolveBoardConfig(options.config);
|
|
21017
21043
|
const client = deps.client ?? defaultGitHubClient();
|
|
21018
21044
|
const currentRepo = await resolveCurrentRepo(options, deps);
|
|
21019
|
-
const selector = parseIssueSelector(options.selector, currentRepo);
|
|
21045
|
+
const selector = parseIssueSelector(options.selector, currentRepo, options.repo);
|
|
21020
21046
|
const lookup = await fetchIssueProjectItem(client, cfg, selector);
|
|
21021
21047
|
const item = lookup.item;
|
|
21022
21048
|
if (!item) {
|
|
@@ -21090,7 +21116,7 @@ async function showBoardItem(options, deps = {}) {
|
|
|
21090
21116
|
const cfg = resolveBoardConfig(options.config);
|
|
21091
21117
|
const client = deps.client ?? defaultGitHubClient();
|
|
21092
21118
|
const currentRepo = await resolveCurrentRepo(options, deps);
|
|
21093
|
-
const selector = parseIssueSelector(options.selector, currentRepo);
|
|
21119
|
+
const selector = parseIssueSelector(options.selector, currentRepo, options.repo);
|
|
21094
21120
|
const { item } = await fetchIssueProjectItem(client, cfg, selector);
|
|
21095
21121
|
if (!item) throw boardNotFoundError(`${selector.repo}#${selector.number}`, { owner: cfg.projectOwner, number: cfg.projectNumber });
|
|
21096
21122
|
if (item.contentType === "Issue") {
|
|
@@ -21181,6 +21207,15 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
21181
21207
|
}
|
|
21182
21208
|
const assignee = options.assignee ?? "@me";
|
|
21183
21209
|
const assignedLogin = assignee === "@me" ? report.viewer : assignee.replace(/^@/, "");
|
|
21210
|
+
const holder = {
|
|
21211
|
+
login: assignedLogin,
|
|
21212
|
+
session: ctx.session.session,
|
|
21213
|
+
surface: ctx.session.surface,
|
|
21214
|
+
host: ctx.session.host
|
|
21215
|
+
};
|
|
21216
|
+
let previousHolder;
|
|
21217
|
+
const claimedReceipt = () => previousHolder ? { outcome: "took-over", holder, previousHolder } : { outcome: "claimed", holder };
|
|
21218
|
+
const heldReceipt = () => previousHolder ? { outcome: "took-over", holder, previousHolder } : { outcome: "held", holder };
|
|
21184
21219
|
if (flatItem.contentType !== "Issue") throw new Error(`${flatItem.ref} is not an issue`);
|
|
21185
21220
|
const pre = evaluateClaim(flatItem, assignedLogin);
|
|
21186
21221
|
if (!pre.ok) throw new Error(pre.reason);
|
|
@@ -21191,20 +21226,28 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
21191
21226
|
if (!verdict.ok) throw new Error(verdict.reason);
|
|
21192
21227
|
item = fresh;
|
|
21193
21228
|
const refuseIfContested = async () => {
|
|
21194
|
-
if (options.force) return;
|
|
21195
21229
|
const contest = await checkLaneContest(client, item, ctx.session);
|
|
21196
|
-
if (contest.contested)
|
|
21230
|
+
if (!contest.contested) return;
|
|
21231
|
+
if (!options.force) throw new Error(laneContestMessage(item.ref, contest, "claim"));
|
|
21232
|
+
previousHolder = {
|
|
21233
|
+
login: assignedLogin,
|
|
21234
|
+
...contest.marker ? {
|
|
21235
|
+
session: contest.marker.session,
|
|
21236
|
+
surface: contest.marker.surface,
|
|
21237
|
+
host: contest.marker.host
|
|
21238
|
+
} : {}
|
|
21239
|
+
};
|
|
21197
21240
|
};
|
|
21198
21241
|
await refuseIfContested();
|
|
21199
21242
|
if (verdict.alreadyClaimed) {
|
|
21200
21243
|
if (options.check) {
|
|
21201
|
-
return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true, checked: true };
|
|
21244
|
+
return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, ...heldReceipt(), alreadyClaimed: true, checked: true };
|
|
21202
21245
|
}
|
|
21203
21246
|
await postClaimMarkerComment(client, item, ctx.session);
|
|
21204
|
-
return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true };
|
|
21247
|
+
return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, ...heldReceipt(), alreadyClaimed: true };
|
|
21205
21248
|
}
|
|
21206
21249
|
if (options.check) {
|
|
21207
|
-
return { item, viewer: report.viewer, repo: report.repo, status: item.status, partial: false, checked: true };
|
|
21250
|
+
return { item, viewer: report.viewer, repo: report.repo, status: item.status, partial: false, ...claimedReceipt(), checked: true };
|
|
21208
21251
|
}
|
|
21209
21252
|
await refuseIfContested();
|
|
21210
21253
|
try {
|
|
@@ -21218,7 +21261,7 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
21218
21261
|
} catch (e) {
|
|
21219
21262
|
const warning = `partial claim: ${item.ref} was assigned to @${assignedLogin}, but Status was not moved to In Progress (${ghError(e)})`;
|
|
21220
21263
|
if (!options.allowPartial) throw new Error(warning);
|
|
21221
|
-
return { item, viewer: report.viewer, repo: report.repo, status: "Todo", partial: true, warning };
|
|
21264
|
+
return { item, viewer: report.viewer, repo: report.repo, status: "Todo", partial: true, warning, ...claimedReceipt() };
|
|
21222
21265
|
}
|
|
21223
21266
|
return {
|
|
21224
21267
|
item: {
|
|
@@ -21230,13 +21273,14 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
21230
21273
|
viewer: report.viewer,
|
|
21231
21274
|
repo: report.repo,
|
|
21232
21275
|
status: "In Progress",
|
|
21233
|
-
partial: false
|
|
21276
|
+
partial: false,
|
|
21277
|
+
...claimedReceipt()
|
|
21234
21278
|
};
|
|
21235
21279
|
}
|
|
21236
21280
|
async function claimBoardIssue(options, deps = {}) {
|
|
21237
21281
|
const cfg = resolveBoardConfig(options.config);
|
|
21238
21282
|
const collected = await collectBoardItems(cfg, { repo: options.repo, allowPartial: options.allowPartial, activeOnly: true }, deps);
|
|
21239
|
-
const selector = parseIssueSelector(options.selector, collected.repo);
|
|
21283
|
+
const selector = parseIssueSelector(options.selector, collected.repo, options.repo);
|
|
21240
21284
|
const ctx = await prepareClaimContext(options, [selector], deps, collected);
|
|
21241
21285
|
return claimOneBoardItem(ctx, selector, options);
|
|
21242
21286
|
}
|
|
@@ -21246,7 +21290,7 @@ async function claimBoardIssues(options, deps = {}) {
|
|
|
21246
21290
|
const selectors = [];
|
|
21247
21291
|
const seen = /* @__PURE__ */ new Set();
|
|
21248
21292
|
for (const raw of options.selectors) {
|
|
21249
|
-
const selector = parseIssueSelector(raw, collected.repo);
|
|
21293
|
+
const selector = parseIssueSelector(raw, collected.repo, options.repo);
|
|
21250
21294
|
const key = `${selector.repo.toLowerCase()}#${selector.number}`;
|
|
21251
21295
|
if (seen.has(key)) continue;
|
|
21252
21296
|
seen.add(key);
|
|
@@ -21262,7 +21306,7 @@ async function claimBoardIssues(options, deps = {}) {
|
|
|
21262
21306
|
const ref = `${selector.repo}#${selector.number}`;
|
|
21263
21307
|
try {
|
|
21264
21308
|
const result = await claimOneBoardItem(ctx, selector, options);
|
|
21265
|
-
results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, alreadyClaimed: result.alreadyClaimed, checked: result.checked };
|
|
21309
|
+
results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, outcome: result.outcome, holder: result.holder, previousHolder: result.previousHolder, alreadyClaimed: result.alreadyClaimed, checked: result.checked };
|
|
21266
21310
|
} catch (e) {
|
|
21267
21311
|
results[index] = { ref, claimed: false, reason: e.message };
|
|
21268
21312
|
}
|
|
@@ -21283,7 +21327,7 @@ async function moveBoardIssues(options, deps = {}) {
|
|
|
21283
21327
|
const selectors = [];
|
|
21284
21328
|
const seen = /* @__PURE__ */ new Set();
|
|
21285
21329
|
for (const raw of options.selectors) {
|
|
21286
|
-
const selector = parseIssueSelector(raw, currentRepo);
|
|
21330
|
+
const selector = parseIssueSelector(raw, currentRepo, options.repo);
|
|
21287
21331
|
const key = `${selector.repo.toLowerCase()}#${selector.number}`;
|
|
21288
21332
|
if (seen.has(key)) continue;
|
|
21289
21333
|
seen.add(key);
|
|
@@ -21363,7 +21407,7 @@ async function unclaimBoardIssue(options, deps = {}) {
|
|
|
21363
21407
|
const cfg = resolveBoardConfig(options.config);
|
|
21364
21408
|
const client = deps.client ?? defaultGitHubClient();
|
|
21365
21409
|
const currentRepo = await resolveCurrentRepo(options, deps);
|
|
21366
|
-
const selector = parseIssueSelector(options.selector, currentRepo);
|
|
21410
|
+
const selector = parseIssueSelector(options.selector, currentRepo, options.repo);
|
|
21367
21411
|
const { viewer, item } = await fetchIssueProjectItem(client, cfg, selector);
|
|
21368
21412
|
if (!item) {
|
|
21369
21413
|
throw boardNotFoundError(`${selector.repo}#${selector.number}`, { owner: cfg.projectOwner, number: cfg.projectNumber });
|
|
@@ -22485,6 +22529,20 @@ var REQUIRED_OPTION_HELP = {
|
|
|
22485
22529
|
optionDescription(option) {
|
|
22486
22530
|
const described = Help.prototype.optionDescription.call(this, option);
|
|
22487
22531
|
return option.mandatory ? `(required) ${described}` : described;
|
|
22532
|
+
},
|
|
22533
|
+
// #5874: resolveHouseShim removes the house token before Commander renders help. Its stock usage
|
|
22534
|
+
// therefore teaches a removed flat alias; render the manifest's canonical house-qualified path instead.
|
|
22535
|
+
commandUsage(command) {
|
|
22536
|
+
const parts = [];
|
|
22537
|
+
for (let node = command; node && node.parent; node = node.parent) {
|
|
22538
|
+
parts.unshift(node.name());
|
|
22539
|
+
}
|
|
22540
|
+
if (!parts.length) return Help.prototype.commandUsage.call(this, command);
|
|
22541
|
+
const flatPath = parts.join(" ");
|
|
22542
|
+
const canonicalPath = canonicalPathFor(flatPath) ?? flatPath;
|
|
22543
|
+
const alias = command.alias();
|
|
22544
|
+
const name = alias ? `${command.name()}|${alias}` : command.name();
|
|
22545
|
+
return `mmi-cli ${canonicalPath.slice(0, -command.name().length)}${name} ${command.usage()}`;
|
|
22488
22546
|
}
|
|
22489
22547
|
};
|
|
22490
22548
|
function classifyTree(command, path2, inherited, hideFromParent) {
|
|
@@ -31295,6 +31353,69 @@ async function remoteBranchExists2(branch, options = {}) {
|
|
|
31295
31353
|
return void 0;
|
|
31296
31354
|
}
|
|
31297
31355
|
}
|
|
31356
|
+
async function deleteMergedRemoteBranch(options) {
|
|
31357
|
+
const remediation = `git push origin --delete ${options.branch}`;
|
|
31358
|
+
if (!options.branch) {
|
|
31359
|
+
return {
|
|
31360
|
+
branch: options.branch,
|
|
31361
|
+
existedBefore: options.existedBefore,
|
|
31362
|
+
attempted: false,
|
|
31363
|
+
status: "failed",
|
|
31364
|
+
error: "missing PR head branch",
|
|
31365
|
+
remediation
|
|
31366
|
+
};
|
|
31367
|
+
}
|
|
31368
|
+
if (options.existedBefore === false) {
|
|
31369
|
+
const exists2 = await options.branchExists(options.branch);
|
|
31370
|
+
if (exists2 === false) return { branch: options.branch, existedBefore: false, attempted: false, status: "already-gone" };
|
|
31371
|
+
return {
|
|
31372
|
+
branch: options.branch,
|
|
31373
|
+
existedBefore: false,
|
|
31374
|
+
attempted: false,
|
|
31375
|
+
status: "failed",
|
|
31376
|
+
error: exists2 ? `origin reports ${options.branch} after merge` : `could not verify absence of origin/${options.branch}`,
|
|
31377
|
+
remediation
|
|
31378
|
+
};
|
|
31379
|
+
}
|
|
31380
|
+
try {
|
|
31381
|
+
await options.execGit(["push", "origin", "--delete", options.branch]);
|
|
31382
|
+
} catch (e) {
|
|
31383
|
+
const exists2 = await options.branchExists(options.branch);
|
|
31384
|
+
if (exists2 === false) {
|
|
31385
|
+
return {
|
|
31386
|
+
branch: options.branch,
|
|
31387
|
+
existedBefore: options.existedBefore,
|
|
31388
|
+
attempted: true,
|
|
31389
|
+
status: "deleted"
|
|
31390
|
+
};
|
|
31391
|
+
}
|
|
31392
|
+
return {
|
|
31393
|
+
branch: options.branch,
|
|
31394
|
+
existedBefore: options.existedBefore,
|
|
31395
|
+
attempted: true,
|
|
31396
|
+
status: "failed",
|
|
31397
|
+
error: e instanceof Error ? e.message : String(e),
|
|
31398
|
+
remediation
|
|
31399
|
+
};
|
|
31400
|
+
}
|
|
31401
|
+
const exists = await options.branchExists(options.branch);
|
|
31402
|
+
if (exists === false) {
|
|
31403
|
+
return {
|
|
31404
|
+
branch: options.branch,
|
|
31405
|
+
existedBefore: options.existedBefore,
|
|
31406
|
+
attempted: true,
|
|
31407
|
+
status: "deleted"
|
|
31408
|
+
};
|
|
31409
|
+
}
|
|
31410
|
+
return {
|
|
31411
|
+
branch: options.branch,
|
|
31412
|
+
existedBefore: options.existedBefore,
|
|
31413
|
+
attempted: true,
|
|
31414
|
+
status: "failed",
|
|
31415
|
+
error: exists ? `origin still reports ${options.branch} after deletion` : `could not verify deletion of origin/${options.branch}`,
|
|
31416
|
+
remediation
|
|
31417
|
+
};
|
|
31418
|
+
}
|
|
31298
31419
|
|
|
31299
31420
|
// src/worktree-merge-cleanup.ts
|
|
31300
31421
|
var import_node_fs36 = require("node:fs");
|
|
@@ -31484,6 +31605,7 @@ function removeResidueDirectory(wtPath) {
|
|
|
31484
31605
|
} catch {
|
|
31485
31606
|
}
|
|
31486
31607
|
(0, import_node_fs36.rmSync)(wtPath, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
31608
|
+
if ((0, import_node_fs36.existsSync)(wtPath)) return { ok: false, error: `directory remains after removal: ${wtPath}` };
|
|
31487
31609
|
return { ok: true };
|
|
31488
31610
|
} catch (e) {
|
|
31489
31611
|
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
@@ -31776,8 +31898,11 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
31776
31898
|
if (residue.ok) {
|
|
31777
31899
|
report.worktree.residue = "swept";
|
|
31778
31900
|
} else {
|
|
31901
|
+
report.worktree.status = "failed";
|
|
31902
|
+
report.worktree.reason = "residue-remains";
|
|
31779
31903
|
report.worktree.residue = "left";
|
|
31780
31904
|
report.worktree.residueError = residue.error;
|
|
31905
|
+
report.worktree.remediation = `Remove-Item -LiteralPath '${wtPath.replace(/'/g, "''")}' -Recurse -Force`;
|
|
31781
31906
|
}
|
|
31782
31907
|
}
|
|
31783
31908
|
try {
|
|
@@ -31808,7 +31933,7 @@ function renderPrMergeCleanupLines(cleanup) {
|
|
|
31808
31933
|
lines.push(`pr merge: preserved worktree ${wt.path} (--preserve-worktree)`);
|
|
31809
31934
|
}
|
|
31810
31935
|
if (wt.residue === "left") {
|
|
31811
|
-
lines.push(`pr merge: worktree ${wt.path} registration is gone but residue remains \u2014
|
|
31936
|
+
lines.push(`pr merge: worktree ${wt.path} registration is gone but residue remains \u2014 ${wt.residueError ?? wt.path}; remediate: ${wt.remediation ?? wt.path}`);
|
|
31812
31937
|
}
|
|
31813
31938
|
if (wt.artifactsArchive?.status === "archived" && wt.artifactsArchive.path) {
|
|
31814
31939
|
lines.push(`pr merge: archived worktree evidence to ${wt.artifactsArchive.path}`);
|
|
@@ -31863,8 +31988,24 @@ function registerBoardCommands(program3) {
|
|
|
31863
31988
|
return failGraceful(`board read failed: ${withDiscoverMissDetail(e.message)}`);
|
|
31864
31989
|
}
|
|
31865
31990
|
}
|
|
31866
|
-
function
|
|
31867
|
-
|
|
31991
|
+
function formatClaimHolder(holder) {
|
|
31992
|
+
const lane = holder.surface && holder.session && holder.host ? ` (${holder.surface}/${holder.session}@${holder.host})` : "";
|
|
31993
|
+
return `@${holder.login}${lane}`;
|
|
31994
|
+
}
|
|
31995
|
+
function claimVerdict(ref, result) {
|
|
31996
|
+
const holder = formatClaimHolder(result.holder);
|
|
31997
|
+
const previousHolder = result.previousHolder ? formatClaimHolder(result.previousHolder) : "another lane";
|
|
31998
|
+
if (result.checked) {
|
|
31999
|
+
if (result.outcome === "held") return `Check ${ref}: held by ${holder} - claim would renew the lease (nothing written)`;
|
|
32000
|
+
if (result.outcome === "took-over") return `Check ${ref}: held by ${previousHolder} - --force claim would take it over for ${holder} (nothing written)`;
|
|
32001
|
+
return `Check ${ref}: free - claim would proceed for ${holder} (nothing written)`;
|
|
32002
|
+
}
|
|
32003
|
+
if (result.partial) {
|
|
32004
|
+
return result.outcome === "took-over" ? `Partially took over ${ref} from ${previousHolder}: ${result.warning}` : `Partially claimed ${ref} for ${holder}: ${result.warning}`;
|
|
32005
|
+
}
|
|
32006
|
+
if (result.outcome === "took-over") return `Took over ${ref} from ${previousHolder} for ${holder} - In Progress`;
|
|
32007
|
+
if (result.outcome === "held") return `${ref} is held by ${holder} - In Progress`;
|
|
32008
|
+
return `Claimed ${ref} for ${holder} - In Progress`;
|
|
31868
32009
|
}
|
|
31869
32010
|
const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
31870
32011
|
board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nNever capture the JSON with a shell redirect on Windows: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n").action((o) => runBoardRead(o));
|
|
@@ -31886,7 +32027,7 @@ function registerBoardCommands(program3) {
|
|
|
31886
32027
|
});
|
|
31887
32028
|
if (!result.checked) invalidateStatuslineBoardCache();
|
|
31888
32029
|
if (o.json) return console.log(JSON.stringify(result));
|
|
31889
|
-
console.log(
|
|
32030
|
+
console.log(claimVerdict(result.item.ref, result));
|
|
31890
32031
|
} catch (e) {
|
|
31891
32032
|
if (refuseRateLimited(e, o.json)) return;
|
|
31892
32033
|
return failGraceful(`board claim failed: ${e.message}`);
|
|
@@ -31908,7 +32049,7 @@ function registerBoardCommands(program3) {
|
|
|
31908
32049
|
console.log(JSON.stringify(bulk.results));
|
|
31909
32050
|
} else {
|
|
31910
32051
|
for (const result of bulk.results) {
|
|
31911
|
-
console.log(result.claimed ?
|
|
32052
|
+
console.log(result.claimed ? claimVerdict(result.ref, result) : `Skipped ${result.ref}: ${result.reason}`);
|
|
31912
32053
|
}
|
|
31913
32054
|
}
|
|
31914
32055
|
if (bulk.failed > 0) process.exitCode = 1;
|
|
@@ -32004,7 +32145,7 @@ function registerBoardCommands(program3) {
|
|
|
32004
32145
|
}
|
|
32005
32146
|
try {
|
|
32006
32147
|
const defaultRepo = await resolveRepo(o.repo) ?? "";
|
|
32007
|
-
const selector = parseIssueSelector(issueRef, defaultRepo);
|
|
32148
|
+
const selector = parseIssueSelector(issueRef, defaultRepo, o.repo);
|
|
32008
32149
|
if (!selector.repo) {
|
|
32009
32150
|
return fail("board set-priority failed: could not resolve the repo \u2014 pass owner/repo#123 or use --repo");
|
|
32010
32151
|
}
|
|
@@ -32249,7 +32390,7 @@ async function fetchRestClosingGuardPayload(prNumber, repo, gh = defaultGhApi) {
|
|
|
32249
32390
|
return { pr: pr2, commits };
|
|
32250
32391
|
}
|
|
32251
32392
|
async function fetchHeadCheckRuns(headSha, repo, gh) {
|
|
32252
|
-
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}"]);
|
|
32393
|
+
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, details_url}"]);
|
|
32253
32394
|
return dedupeLatestCheckRuns(parseNdjsonLines(runsOut));
|
|
32254
32395
|
}
|
|
32255
32396
|
async function fetchHeadCheckEntries(headSha, repo, gh) {
|
|
@@ -32321,6 +32462,29 @@ var RUNNER_INFRA_PRESTART_CONCLUSIONS = /* @__PURE__ */ new Set([
|
|
|
32321
32462
|
"cancelled",
|
|
32322
32463
|
"startup_failure"
|
|
32323
32464
|
]);
|
|
32465
|
+
var BARE_CHECKS_FAILURE_MESSAGE = "GitHub reported a checks failure but exposed no per-check data.";
|
|
32466
|
+
function actionRunIdFromDetailsUrl(detailsUrl) {
|
|
32467
|
+
const match = detailsUrl?.match(/\/actions\/runs\/(\d+)(?:\/|$)/);
|
|
32468
|
+
if (!match) return void 0;
|
|
32469
|
+
const runId = Number(match[1]);
|
|
32470
|
+
return Number.isSafeInteger(runId) ? runId : void 0;
|
|
32471
|
+
}
|
|
32472
|
+
function failedCheckReceipt(run, repo) {
|
|
32473
|
+
const name = run.name ?? `check-run ${run.id ?? "unknown"}`;
|
|
32474
|
+
const conclusion = run.conclusion ?? "unknown";
|
|
32475
|
+
const detailsUrl = typeof run.details_url === "string" && run.details_url ? run.details_url : void 0;
|
|
32476
|
+
const runId = actionRunIdFromDetailsUrl(detailsUrl);
|
|
32477
|
+
return {
|
|
32478
|
+
name,
|
|
32479
|
+
conclusion,
|
|
32480
|
+
...detailsUrl ? { detailsUrl } : {},
|
|
32481
|
+
...runId !== void 0 ? {
|
|
32482
|
+
runId,
|
|
32483
|
+
runUrl: `https://github.com/${repo}/actions/runs/${runId}`,
|
|
32484
|
+
diagnosticCommand: `gh run view ${runId} --log-failed --repo ${repo}`
|
|
32485
|
+
} : {}
|
|
32486
|
+
};
|
|
32487
|
+
}
|
|
32324
32488
|
function isErrorAnnotation(a) {
|
|
32325
32489
|
const level = a.annotation_level?.toLowerCase();
|
|
32326
32490
|
return level === "failure" || level === "error";
|
|
@@ -32353,6 +32517,7 @@ function classifyFailedChecks(failing) {
|
|
|
32353
32517
|
cause: "runner-infra",
|
|
32354
32518
|
infraFailures,
|
|
32355
32519
|
otherFailures,
|
|
32520
|
+
failedChecks: [],
|
|
32356
32521
|
reason: `${infraFailures.length} check(s) failed on RUNNER INFRASTRUCTURE, not your diff: ${infraFailures.join(", ")}. No test failed \u2014 the shared runner hit a wall-clock budget kill, ran out of disk, was missing a toolchain, could not acquire an mmi-live lane, or Actions returned Service Unavailable before checkout (the check annotation / conclusion names which). Re-run once the runner drains/heals (gh run rerun --failed); do not debug the diff.`
|
|
32357
32522
|
};
|
|
32358
32523
|
}
|
|
@@ -32360,6 +32525,7 @@ function classifyFailedChecks(failing) {
|
|
|
32360
32525
|
cause: "checks-failure",
|
|
32361
32526
|
infraFailures,
|
|
32362
32527
|
otherFailures,
|
|
32528
|
+
failedChecks: [],
|
|
32363
32529
|
reason: infraFailures.length ? `checks failed: ${otherFailures.join(", ")}; separately, ${infraFailures.join(", ")} failed on runner infrastructure, not a test.` : `checks failed: ${otherFailures.join(", ") || "unknown"}.`
|
|
32364
32530
|
};
|
|
32365
32531
|
}
|
|
@@ -32380,6 +32546,7 @@ async function diagnoseFailedRestChecks(prNumber, repo, gh = defaultGhApi) {
|
|
|
32380
32546
|
cause: "stale-head",
|
|
32381
32547
|
infraFailures: [],
|
|
32382
32548
|
otherFailures: [],
|
|
32549
|
+
failedChecks: [],
|
|
32383
32550
|
reason: `the PR head (${snapshot.headSha.slice(0, 7)}) is BEHIND the branch tip (${tip.sha.slice(0, 7)}) \u2014 GitHub likely dropped a \`synchronize\` event, so the reported red is a superseded commit and NO run exists for your current push. This is not your diff failing. Re-push (an empty commit is enough) or close and reopen the PR to re-sync the head and trigger a fresh run.`
|
|
32384
32551
|
}
|
|
32385
32552
|
};
|
|
@@ -32392,15 +32559,20 @@ async function diagnoseFailedRestChecks(prNumber, repo, gh = defaultGhApi) {
|
|
|
32392
32559
|
return { state: "failed", error: `check-runs read failed for ${snapshot.headSha.slice(0, 7)} on ${repo}: ${readErrorText(e)}` };
|
|
32393
32560
|
}
|
|
32394
32561
|
if (!failing.length) return { state: "absent", reason: "no failing check-run on the current head to diagnose" };
|
|
32562
|
+
const failedChecks = failing.map((run) => failedCheckReceipt(run, repo));
|
|
32395
32563
|
try {
|
|
32396
32564
|
const annotated = await Promise.all(failing.map(async (run) => ({
|
|
32397
32565
|
name: run.name ?? `check-run ${run.id}`,
|
|
32398
32566
|
conclusion: run.conclusion ?? null,
|
|
32399
32567
|
annotations: JSON.parse(await gh([`repos/${repo}/check-runs/${run.id}/annotations`]))
|
|
32400
32568
|
})));
|
|
32401
|
-
return { state: "ok", diagnosis: classifyFailedChecks(annotated) };
|
|
32569
|
+
return { state: "ok", diagnosis: { ...classifyFailedChecks(annotated), failedChecks } };
|
|
32402
32570
|
} catch (e) {
|
|
32403
|
-
return {
|
|
32571
|
+
return {
|
|
32572
|
+
state: "failed",
|
|
32573
|
+
error: `annotations read failed for a failing check-run on ${repo}: ${readErrorText(e)}`,
|
|
32574
|
+
failedChecks
|
|
32575
|
+
};
|
|
32404
32576
|
}
|
|
32405
32577
|
}
|
|
32406
32578
|
function isNotFoundError2(e) {
|
|
@@ -33240,6 +33412,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
33240
33412
|
(opts, args) => ({ command: "issue edit", ref: args[0], fields: Object.keys(opts).filter((k) => k !== "repo" && opts[k] !== void 0) })
|
|
33241
33413
|
).action(async (ref, o) => {
|
|
33242
33414
|
try {
|
|
33415
|
+
parseIssueRef(ref, o.repo);
|
|
33243
33416
|
const defaultRepo = await resolveRepo(o.repo);
|
|
33244
33417
|
const result = await editIssue(defaultGitHubClient(), {
|
|
33245
33418
|
ref,
|
|
@@ -33284,6 +33457,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
33284
33457
|
).action(async (ref, o) => {
|
|
33285
33458
|
const parsed = parseCloseReason(o.reason);
|
|
33286
33459
|
try {
|
|
33460
|
+
parseIssueRef(ref, o.repo);
|
|
33287
33461
|
const defaultRepo = await resolveRepo(o.repo);
|
|
33288
33462
|
const result = await closeIssue(defaultGitHubClient(), {
|
|
33289
33463
|
ref,
|
|
@@ -33303,6 +33477,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
33303
33477
|
(_opts, args) => ({ command: "issue reopen", ref: args[0] })
|
|
33304
33478
|
).action(async (ref, o) => {
|
|
33305
33479
|
try {
|
|
33480
|
+
parseIssueRef(ref, o.repo);
|
|
33306
33481
|
const defaultRepo = await resolveRepo(o.repo);
|
|
33307
33482
|
const result = await reopenIssue(defaultGitHubClient(), ref, defaultRepo);
|
|
33308
33483
|
console.log(JSON.stringify(result));
|
|
@@ -33315,6 +33490,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
33315
33490
|
(_opts, args) => ({ command: "issue assign", ref: args[0], login: args[1] })
|
|
33316
33491
|
).action(async (ref, login, o) => {
|
|
33317
33492
|
try {
|
|
33493
|
+
parseIssueRef(ref, o.repo);
|
|
33318
33494
|
const defaultRepo = await resolveRepo(o.repo);
|
|
33319
33495
|
const result = await assignIssue(defaultGitHubClient(), ref, login, defaultRepo);
|
|
33320
33496
|
console.log(JSON.stringify(result));
|
|
@@ -33327,6 +33503,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
33327
33503
|
(_opts, args) => ({ command: "issue unassign", ref: args[0], login: args[1] })
|
|
33328
33504
|
).action(async (ref, login, o) => {
|
|
33329
33505
|
try {
|
|
33506
|
+
parseIssueRef(ref, o.repo);
|
|
33330
33507
|
const defaultRepo = await resolveRepo(o.repo);
|
|
33331
33508
|
const result = await unassignIssue(defaultGitHubClient(), ref, login, defaultRepo);
|
|
33332
33509
|
console.log(JSON.stringify(result));
|
|
@@ -33339,6 +33516,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
33339
33516
|
(opts, args) => ({ command: "issue relocate", ref: args[0], to: opts.to })
|
|
33340
33517
|
).action(async (ref, o) => {
|
|
33341
33518
|
try {
|
|
33519
|
+
parseIssueRef(ref, o.repo);
|
|
33342
33520
|
const defaultRepo = await resolveRepo(o.repo);
|
|
33343
33521
|
const result = await relocateIssue(defaultGitHubClient(), ref, o.to, defaultRepo);
|
|
33344
33522
|
console.log(JSON.stringify(result));
|
|
@@ -33350,8 +33528,10 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
33350
33528
|
issue2.command("unlink-child <parent> <child>").description("unlink a child issue from its parent (inverse of link-child) and print {parentNumber,subIssueNumber,totalCount} JSON").option("--repo <owner/repo>", "repo for bare refs on either side (defaults to the current repo)"),
|
|
33351
33529
|
(_opts, args) => ({ command: "issue unlink-child", parent: args[0], child: args[1] })
|
|
33352
33530
|
).action(async (parentRef, childRef, o) => {
|
|
33353
|
-
const defaultRepo = await resolveRepo(o.repo);
|
|
33354
33531
|
try {
|
|
33532
|
+
parseIssueRef(parentRef, o.repo);
|
|
33533
|
+
parseIssueRef(childRef, o.repo);
|
|
33534
|
+
const defaultRepo = await resolveRepo(o.repo);
|
|
33355
33535
|
const result = await unlinkSubIssue(ghRunner, parentRef, childRef, defaultRepo);
|
|
33356
33536
|
console.log(JSON.stringify(result));
|
|
33357
33537
|
} catch (e) {
|
|
@@ -34985,7 +35165,7 @@ function registerLearningPickupCommand(program3) {
|
|
|
34985
35165
|
let selector;
|
|
34986
35166
|
try {
|
|
34987
35167
|
if (ref) {
|
|
34988
|
-
selector = parseIssueSelector(ref, defaultRepo);
|
|
35168
|
+
selector = parseIssueSelector(ref, defaultRepo, o.repo);
|
|
34989
35169
|
} else if (o.number) {
|
|
34990
35170
|
selector = { repo: defaultRepo, number: Number.parseInt(o.number, 10) };
|
|
34991
35171
|
if (!Number.isFinite(selector.number)) throw new Error("invalid --number");
|
|
@@ -39659,7 +39839,7 @@ withExamples(mutating(
|
|
|
39659
39839
|
repo: targetRepo2,
|
|
39660
39840
|
labels: extraLabels.length ? extraLabels : void 0
|
|
39661
39841
|
});
|
|
39662
|
-
if (o.parent !== void 0) parseIssueRef(o.parent);
|
|
39842
|
+
if (o.parent !== void 0) parseIssueRef(o.parent, o.repo);
|
|
39663
39843
|
} catch (e) {
|
|
39664
39844
|
return fail(`issue create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
39665
39845
|
}
|
|
@@ -39741,10 +39921,15 @@ async function readParentField(number, repo) {
|
|
|
39741
39921
|
}
|
|
39742
39922
|
return resolveParentField(payload);
|
|
39743
39923
|
}
|
|
39744
|
-
issue.command("view <
|
|
39745
|
-
|
|
39746
|
-
|
|
39747
|
-
|
|
39924
|
+
issue.command("view <ref>").aliases(["show", "get"]).description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894). `show` and `get` alias view (#5483/#5706)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (ref, o) => {
|
|
39925
|
+
let parsed;
|
|
39926
|
+
try {
|
|
39927
|
+
parsed = parseIssueRef(ref, o.repo);
|
|
39928
|
+
} catch (e) {
|
|
39929
|
+
return fail(`issue view: ${e.message}`);
|
|
39930
|
+
}
|
|
39931
|
+
const n = parsed.number;
|
|
39932
|
+
const repo = await resolveRepo(parsed.repo ?? o.repo);
|
|
39748
39933
|
if (!repo) return fail("issue view: could not resolve repo (pass --repo <owner/repo>)");
|
|
39749
39934
|
const fields = normalizeIssueViewJsonFields(o.json);
|
|
39750
39935
|
try {
|
|
@@ -39804,12 +39989,14 @@ issue.command("discover-related").description("find related issues for an existi
|
|
|
39804
39989
|
jsonParity(issue.command("link-child <parent> <child>").description("link an existing issue as a native sub-issue of a parent and print {parentNumber,subIssueNumber,totalCount} JSON").option("--repo <owner/repo>", "repo for bare refs on either side (defaults to the current repo)")).action(async (parentRef, childRef, o) => {
|
|
39805
39990
|
const defaultRepo = await resolveRepo(o.repo);
|
|
39806
39991
|
try {
|
|
39992
|
+
parseIssueRef(parentRef, o.repo);
|
|
39993
|
+
parseIssueRef(childRef, o.repo);
|
|
39807
39994
|
const result = await linkSubIssue(ghRunner2, parentRef, childRef, defaultRepo);
|
|
39808
39995
|
console.log(JSON.stringify(result));
|
|
39809
39996
|
} catch (e) {
|
|
39810
39997
|
let conflict;
|
|
39811
39998
|
try {
|
|
39812
|
-
const child2 = parseIssueRef(childRef);
|
|
39999
|
+
const child2 = parseIssueRef(childRef, o.repo);
|
|
39813
40000
|
const childRepo = child2.repo ?? defaultRepo;
|
|
39814
40001
|
if (childRepo) conflict = await classifyReparentFailure(e, ghRunner2, childRepo, child2.number, parentRef);
|
|
39815
40002
|
} catch {
|
|
@@ -39824,7 +40011,7 @@ jsonParity(issue.command("link-child <parent> <child>").description("link an exi
|
|
|
39824
40011
|
jsonParity(issue.command("comment <ref>").description("post a Markdown comment to an issue and print {number,repo,url,commentUrl} JSON").option("--body <body>", "comment body (markdown; prefer --body-file for multiline Markdown)").option("--body-file <path|->", "read comment body from a UTF-8 file, or from stdin with -").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)")).action(async (ref, o) => {
|
|
39825
40012
|
let parsed;
|
|
39826
40013
|
try {
|
|
39827
|
-
parsed = parseIssueRef(ref);
|
|
40014
|
+
parsed = parseIssueRef(ref, o.repo);
|
|
39828
40015
|
} catch (e) {
|
|
39829
40016
|
return fail(`issue comment: ${e.message}`);
|
|
39830
40017
|
}
|
|
@@ -39847,7 +40034,7 @@ jsonParity(issue.command("comment <ref>").description("post a Markdown comment t
|
|
|
39847
40034
|
jsonParity(issue.command("check <ref>").description("tick (or with --off untick) a task-list checkbox in an issue/epic body by its item text and print {number,repo,item,checked,changed} JSON").requiredOption("--item <text>", "the checklist item to match \u2014 exact item text, else a unique substring").option("--off", "untick the item ([x] \u2014 [ ]) instead of ticking it").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)")).action(async (ref, o) => {
|
|
39848
40035
|
let parsed;
|
|
39849
40036
|
try {
|
|
39850
|
-
parsed = parseIssueRef(ref);
|
|
40037
|
+
parsed = parseIssueRef(ref, o.repo);
|
|
39851
40038
|
} catch (e) {
|
|
39852
40039
|
return fail(`issue check: ${e.message}`);
|
|
39853
40040
|
}
|
|
@@ -40034,10 +40221,15 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
40034
40221
|
"Use --body-file for multiline PR bodies instead of shell-escaped inline markdown.",
|
|
40035
40222
|
"Write that file under .jerv/ inside the host workspace (#4405); host policy governs untracked files."
|
|
40036
40223
|
]);
|
|
40037
|
-
pr.command("view <
|
|
40038
|
-
|
|
40039
|
-
|
|
40040
|
-
|
|
40224
|
+
pr.command("view <ref>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) \u2014 the mmi-cli read path (#2347). --comments folds in every comment; --context also adds linkedIssues (the issues it closes/references, #2894)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').option("--comments", "include every comment (body + comments in one call) \u2014 read the whole PR before landing it (#2894)").option("--context", "full working context in one call: implies --comments and also adds linkedIssues (the issues the PR closes/references) (#2894)").action(async (ref, o) => {
|
|
40225
|
+
let parsed;
|
|
40226
|
+
try {
|
|
40227
|
+
parsed = parseIssueRef(ref, o.repo);
|
|
40228
|
+
} catch (e) {
|
|
40229
|
+
return fail(`pr view: ${e.message}`);
|
|
40230
|
+
}
|
|
40231
|
+
const n = parsed.number;
|
|
40232
|
+
const repo = await resolveRepo(parsed.repo ?? o.repo);
|
|
40041
40233
|
if (!repo) return fail("pr view: could not resolve repo (pass --repo <owner/repo>)");
|
|
40042
40234
|
const defaultPrFields = "number,title,state,url,isDraft,mergeable,mergedAt,mergeCommit,headRefName,baseRefName,author,labels";
|
|
40043
40235
|
const effective = resolvePrViewFields(o.json, defaultPrFields);
|
|
@@ -40146,13 +40338,44 @@ async function waitLoopCorePool(label) {
|
|
|
40146
40338
|
async function waitLoopDiagnosis(label, prNumber, repo) {
|
|
40147
40339
|
const read = await diagnoseFailedRestChecks(prNumber, repo);
|
|
40148
40340
|
if (read.state === "ok") return read.diagnosis;
|
|
40341
|
+
if (read.state === "absent") {
|
|
40342
|
+
return {
|
|
40343
|
+
cause: "checks-failure",
|
|
40344
|
+
infraFailures: [],
|
|
40345
|
+
otherFailures: [],
|
|
40346
|
+
failedChecks: [],
|
|
40347
|
+
checksFailureDetail: BARE_CHECKS_FAILURE_MESSAGE,
|
|
40348
|
+
reason: BARE_CHECKS_FAILURE_MESSAGE
|
|
40349
|
+
};
|
|
40350
|
+
}
|
|
40149
40351
|
if (read.state === "failed") {
|
|
40150
40352
|
console.warn(
|
|
40151
40353
|
`${label}: CI failure diagnosis read FAILED (${read.error}) \u2014 the red verdict stands UNDIAGNOSED; this is not evidence the failure is a real test failure. Re-read it with \`gh pr checks ${prNumber} --repo ${repo}\`.`
|
|
40152
40354
|
);
|
|
40355
|
+
return {
|
|
40356
|
+
cause: "checks-failure",
|
|
40357
|
+
infraFailures: [],
|
|
40358
|
+
otherFailures: [],
|
|
40359
|
+
failedChecks: read.failedChecks ?? [],
|
|
40360
|
+
reason: "checks failure diagnosis unavailable"
|
|
40361
|
+
};
|
|
40153
40362
|
}
|
|
40154
40363
|
return null;
|
|
40155
40364
|
}
|
|
40365
|
+
function failedChecksReceiptLines(context, wait) {
|
|
40366
|
+
if (wait.detail !== "checks-failure") return [];
|
|
40367
|
+
const lines = [];
|
|
40368
|
+
for (const check of wait.failedChecks ?? []) {
|
|
40369
|
+
lines.push(`${context}: failed check: ${check.name} (${check.conclusion})${check.detailsUrl ? ` \u2014 ${check.detailsUrl}` : ""}`);
|
|
40370
|
+
if (check.diagnosticCommand) lines.push(`${context}: next diagnostic: ${check.diagnosticCommand}`);
|
|
40371
|
+
}
|
|
40372
|
+
if (!wait.failedChecks?.length && wait.checksFailureDetail) lines.push(`${context}: ${wait.checksFailureDetail}`);
|
|
40373
|
+
if (!wait.failedChecks?.some((check) => check.diagnosticCommand) && wait.diagnosticCommand) {
|
|
40374
|
+
lines.push(`${context}: next diagnostic: ${wait.diagnosticCommand}`);
|
|
40375
|
+
}
|
|
40376
|
+
if (wait.checksUrl) lines.push(`${context}: PR checks: ${wait.checksUrl}`);
|
|
40377
|
+
return lines;
|
|
40378
|
+
}
|
|
40156
40379
|
async function waitLoopHeadWorkflowRunCount(label, prNumber, repo) {
|
|
40157
40380
|
let snapshot;
|
|
40158
40381
|
try {
|
|
@@ -40206,6 +40429,8 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
|
|
|
40206
40429
|
// #3388: on a confirmed red, read the failing runs' annotations so a wall-clock budget kill stops
|
|
40207
40430
|
// reading as "your tests failed". One call per failing run, only at the verdict.
|
|
40208
40431
|
diagnoseFailure: () => waitLoopDiagnosis("pr checks-wait", number, repo),
|
|
40432
|
+
checksUrl: `https://github.com/${repo}/pull/${number}/checks`,
|
|
40433
|
+
checksDiagnosticCommand: `gh pr checks ${number} --repo ${repo}`,
|
|
40209
40434
|
// #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
|
|
40210
40435
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
|
|
40211
40436
|
baseBranch,
|
|
@@ -40232,21 +40457,21 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
|
|
|
40232
40457
|
printLine(`pr checks-wait: failure (stale PR head, NOT a test failure) \u2014 ${result.reason}`);
|
|
40233
40458
|
} else if (result.detail === "zero-runs") {
|
|
40234
40459
|
printLine(`pr checks-wait: failure (zero workflow runs delivered, NOT pending checks) \u2014 ${result.reason}`);
|
|
40235
|
-
} else
|
|
40460
|
+
} else {
|
|
40461
|
+
printLine(`pr checks-wait: ${result.status}${result.reason ? ` \u2014 ${result.reason}` : ""}${result.detail ? ` (${result.detail})` : ""}`);
|
|
40462
|
+
for (const line of failedChecksReceiptLines("pr checks-wait", result)) printLine(line);
|
|
40463
|
+
}
|
|
40236
40464
|
if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
|
|
40237
40465
|
if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
|
|
40238
40466
|
});
|
|
40239
40467
|
pr.command("land <number>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").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").action(async (number, o) => {
|
|
40240
|
-
|
|
40241
|
-
|
|
40242
|
-
|
|
40243
|
-
|
|
40244
|
-
if (
|
|
40245
|
-
|
|
40246
|
-
|
|
40247
|
-
return;
|
|
40248
|
-
}
|
|
40249
|
-
o.repo = refMatch[1];
|
|
40468
|
+
if (/^(?:[^/]+\/[^/]+)?#\d+$/.test(number.trim())) {
|
|
40469
|
+
try {
|
|
40470
|
+
const parsed = parseIssueRef(number, o.repo);
|
|
40471
|
+
number = String(parsed.number);
|
|
40472
|
+
if (parsed.repo) o.repo = parsed.repo;
|
|
40473
|
+
} catch (e) {
|
|
40474
|
+
return fail(`pr land: ${e.message}`);
|
|
40250
40475
|
}
|
|
40251
40476
|
}
|
|
40252
40477
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
@@ -40308,6 +40533,8 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
40308
40533
|
// #3388: `pr land` is the batch path — the one most likely to self-DOS the shared runner and
|
|
40309
40534
|
// then read its own wall-clock kill as a broken diff.
|
|
40310
40535
|
diagnoseFailure: () => waitLoopDiagnosis("pr land", prNumber, repo),
|
|
40536
|
+
checksUrl: `https://github.com/${repo}/pull/${prNumber}/checks`,
|
|
40537
|
+
checksDiagnosticCommand: `gh pr checks ${prNumber} --repo ${repo}`,
|
|
40311
40538
|
// #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
|
|
40312
40539
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
|
|
40313
40540
|
baseBranch: "development",
|
|
@@ -40490,6 +40717,8 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40490
40717
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
40491
40718
|
pollRateLimit: () => waitLoopCorePool("pr merge --wait"),
|
|
40492
40719
|
diagnoseFailure: () => waitLoopDiagnosis("pr merge --wait", number, repo),
|
|
40720
|
+
checksUrl: `https://github.com/${repo}/pull/${number}/checks`,
|
|
40721
|
+
checksDiagnosticCommand: `gh pr checks ${number} --repo ${repo}`,
|
|
40493
40722
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
|
|
40494
40723
|
baseBranch,
|
|
40495
40724
|
sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
|
|
@@ -40498,7 +40727,20 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40498
40727
|
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
40499
40728
|
});
|
|
40500
40729
|
if (wait.status !== "success" && wait.status !== "skipped") {
|
|
40730
|
+
if (wait.detail === "checks-failure") {
|
|
40731
|
+
console.log(JSON.stringify({
|
|
40732
|
+
mergeStatus: "not-merged",
|
|
40733
|
+
reason: "checks-failure",
|
|
40734
|
+
pr: number,
|
|
40735
|
+
repo,
|
|
40736
|
+
failedChecks: wait.failedChecks ?? [],
|
|
40737
|
+
...wait.checksUrl ? { checksUrl: wait.checksUrl } : {},
|
|
40738
|
+
...wait.diagnosticCommand ? { diagnosticCommand: wait.diagnosticCommand } : {},
|
|
40739
|
+
...wait.checksFailureDetail ? { checksFailureDetail: wait.checksFailureDetail } : {}
|
|
40740
|
+
}));
|
|
40741
|
+
}
|
|
40501
40742
|
console.warn(`pr merge: --wait stopped before merge \u2014 ${wait.status}${wait.reason ? `: ${wait.reason}` : ""}${wait.detail ? ` (${wait.detail})` : ""}`);
|
|
40743
|
+
for (const line of failedChecksReceiptLines("pr merge", wait)) console.warn(line);
|
|
40502
40744
|
process.exitCode = wait.status === "timeout" || wait.status === "rate-limited" ? PR_CHECKS_TIMEOUT_EXIT_CODE : 1;
|
|
40503
40745
|
return;
|
|
40504
40746
|
}
|
|
@@ -40610,7 +40852,6 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40610
40852
|
process.exitCode = 1;
|
|
40611
40853
|
return;
|
|
40612
40854
|
}
|
|
40613
|
-
const remoteBranch = { branch: headRef, existedBefore: remoteBefore, attempted: false, reason: remoteNotAttemptedReason };
|
|
40614
40855
|
const primaryRoot = beforeWorktrees[0]?.path ?? (startingPath || process.cwd());
|
|
40615
40856
|
let localCleanup;
|
|
40616
40857
|
try {
|
|
@@ -40635,6 +40876,28 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40635
40876
|
}
|
|
40636
40877
|
};
|
|
40637
40878
|
}
|
|
40879
|
+
const remoteBranch = await deleteMergedRemoteBranch({
|
|
40880
|
+
branch: headRef,
|
|
40881
|
+
existedBefore: remoteBefore,
|
|
40882
|
+
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
40883
|
+
branchExists: remoteBranchExists2
|
|
40884
|
+
});
|
|
40885
|
+
const worktreePartial = localCleanup?.worktree?.path && localCleanup.worktree.status !== "removed" ? {
|
|
40886
|
+
kind: "worktree-directory",
|
|
40887
|
+
path: localCleanup.worktree.path,
|
|
40888
|
+
reason: localCleanup.worktree.reason ?? localCleanup.worktree.status,
|
|
40889
|
+
error: localCleanup.worktree.error ?? localCleanup.worktree.residueError,
|
|
40890
|
+
remediation: localCleanup.worktree.remediation ?? `Remove-Item -LiteralPath '${localCleanup.worktree.path?.replace(/'/g, "''")}' -Recurse -Force`
|
|
40891
|
+
} : void 0;
|
|
40892
|
+
const partialCleanup = [
|
|
40893
|
+
...worktreePartial ? [worktreePartial] : [],
|
|
40894
|
+
...remoteBranch.status === "failed" ? [{
|
|
40895
|
+
kind: "remote-branch",
|
|
40896
|
+
branch: remoteBranch.branch,
|
|
40897
|
+
error: remoteBranch.error,
|
|
40898
|
+
remediation: remoteBranch.remediation
|
|
40899
|
+
}] : []
|
|
40900
|
+
];
|
|
40638
40901
|
const boardAdvance = await advanceClosedIssuesToDone2(number, repoForPostCleanup);
|
|
40639
40902
|
const crossRepoFilingIssue = repoForPostCleanup ? await reconcileCrossRepoFilingIssue(defaultGitHubClient(), repoForPostCleanup, Number(number)) : { status: "failed", error: "could not resolve the PR repo for cross-repo filing-issue reconciliation" };
|
|
40640
40903
|
const recovery = repoForPostCleanup ? buildPostMergeReconRecovery({
|
|
@@ -40672,12 +40935,13 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40672
40935
|
observedMethod
|
|
40673
40936
|
});
|
|
40674
40937
|
console.log(JSON.stringify({
|
|
40675
|
-
mergeStatus: "merged",
|
|
40938
|
+
mergeStatus: partialCleanup.length ? "partial-cleanup" : "merged",
|
|
40676
40939
|
merged: number,
|
|
40677
40940
|
branch: headRef,
|
|
40678
40941
|
...methodField ? { method: methodField } : {},
|
|
40679
40942
|
remoteBranch,
|
|
40680
40943
|
housekeeping,
|
|
40944
|
+
...partialCleanup.length ? { cleanupStatus: "partial", partialCleanup } : {},
|
|
40681
40945
|
...localCleanup?.worktree ? { worktree: localCleanup.worktree } : {},
|
|
40682
40946
|
...localCleanup?.localBranch ? { localBranch: localCleanup.localBranch } : {},
|
|
40683
40947
|
// `boardAdvance` keeps its published array shape; `boardAdvanceStatus` is the field a caller reads to
|
|
@@ -40701,7 +40965,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40701
40965
|
})) {
|
|
40702
40966
|
console.error(line);
|
|
40703
40967
|
}
|
|
40704
|
-
process.exitCode = prMergeLocalCleanupExitCode(localCleanup) ?? postMergeReconExitCode({ boardAdvance, crossRepoFilingIssue }) ?? process.exitCode;
|
|
40968
|
+
process.exitCode = (partialCleanup.length ? 1 : void 0) ?? prMergeLocalCleanupExitCode(localCleanup) ?? postMergeReconExitCode({ boardAdvance, crossRepoFilingIssue }) ?? process.exitCode;
|
|
40705
40969
|
});
|
|
40706
40970
|
registerQueryCommands(program2);
|
|
40707
40971
|
registerIssueLifecycleCommands(program2, { attach: attachToProject });
|
|
@@ -40844,6 +41108,52 @@ function renderAlignment(label, alignment) {
|
|
|
40844
41108
|
}
|
|
40845
41109
|
return `${label}: ALIGNMENT PR PENDING \u2014 land it with \`mmi-cli devops pr merge ${alignment.prNumber ?? "<number>"} --auto --merge\`${alignment.prUrl ? ` (${alignment.prUrl})` : ""}`;
|
|
40846
41110
|
}
|
|
41111
|
+
function followUpLegStatus(status) {
|
|
41112
|
+
return status === "failure" ? "failed" : status;
|
|
41113
|
+
}
|
|
41114
|
+
function releaseFollowUpLegs(result, projectInfoSync) {
|
|
41115
|
+
const legs = [
|
|
41116
|
+
{
|
|
41117
|
+
leg: "project-info",
|
|
41118
|
+
status: projectInfoSync && "error" in projectInfoSync ? "failed" : "success",
|
|
41119
|
+
...projectInfoSync && "error" in projectInfoSync ? { error: projectInfoSync.error } : {}
|
|
41120
|
+
}
|
|
41121
|
+
];
|
|
41122
|
+
const runs = result.workflowRuns ?? [];
|
|
41123
|
+
if (runs.length > 0) {
|
|
41124
|
+
legs.push(...runs.map((run) => ({
|
|
41125
|
+
leg: run.workflow,
|
|
41126
|
+
status: followUpLegStatus(run.conclusion),
|
|
41127
|
+
...run.conclusion === "failure" ? { error: run.workflow === "jerv-gateway" ? result.dispatch : `${run.workflow} reported failure` } : {}
|
|
41128
|
+
})));
|
|
41129
|
+
} else if (result.deployStatus !== "success") {
|
|
41130
|
+
legs.push({
|
|
41131
|
+
leg: "deploy",
|
|
41132
|
+
status: followUpLegStatus(result.deployStatus),
|
|
41133
|
+
...result.deployStatus === "failure" ? { error: result.dispatch } : {}
|
|
41134
|
+
});
|
|
41135
|
+
}
|
|
41136
|
+
if (result.rcRetirement) {
|
|
41137
|
+
legs.push({
|
|
41138
|
+
leg: "rc-retirement",
|
|
41139
|
+
status: result.rcRetirement === "failed" ? "failed" : "success",
|
|
41140
|
+
...result.rcRetirement === "failed" ? { error: result.rcRetirementNote ?? "rc retirement failed" } : {}
|
|
41141
|
+
});
|
|
41142
|
+
}
|
|
41143
|
+
if (result.devRollForward) {
|
|
41144
|
+
legs.push({
|
|
41145
|
+
leg: "development-alignment",
|
|
41146
|
+
status: result.devRollForward.status === "pr-pending" ? "pending" : "success"
|
|
41147
|
+
});
|
|
41148
|
+
}
|
|
41149
|
+
if (result.rcAlignment) {
|
|
41150
|
+
legs.push({
|
|
41151
|
+
leg: "rc-alignment",
|
|
41152
|
+
status: result.rcAlignment.status === "pr-pending" ? "pending" : "success"
|
|
41153
|
+
});
|
|
41154
|
+
}
|
|
41155
|
+
return legs;
|
|
41156
|
+
}
|
|
40847
41157
|
var JERV_POWERTOOLS_REPO = "mutmutco/Jerv-PowerTools";
|
|
40848
41158
|
async function runPostReleaseJervDoctor(repo) {
|
|
40849
41159
|
if (repo.toLowerCase() !== JERV_POWERTOOLS_REPO.toLowerCase()) return void 0;
|
|
@@ -40884,7 +41194,11 @@ function renderTrainApply(commandName, r) {
|
|
|
40884
41194
|
if (r.releaseVerdict) {
|
|
40885
41195
|
const v = r.releaseVerdict;
|
|
40886
41196
|
const verdict = v.followUpStatus === "failed" ? "PROMOTED, FOLLOW-UP FAILED" : v.followUpStatus === "pending" ? "PROMOTED, FOLLOW-UP PENDING" : "SUCCEEDED";
|
|
40887
|
-
|
|
41197
|
+
const failedLegs = v.legs.filter((leg) => leg.status === "failed");
|
|
41198
|
+
return [
|
|
41199
|
+
`${base}; release verdict: ${verdict}; promoted=${v.promoted}; tag=${v.tag}; release=${v.releaseUrl ?? "unreported"}; follow-up: ${v.followUpStatus.toUpperCase()}`,
|
|
41200
|
+
...failedLegs.map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`)
|
|
41201
|
+
].join("\n");
|
|
40888
41202
|
}
|
|
40889
41203
|
return base;
|
|
40890
41204
|
}
|
|
@@ -41055,7 +41369,8 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
41055
41369
|
followUpStatus,
|
|
41056
41370
|
promoted: result.promoted,
|
|
41057
41371
|
tag: result.tag,
|
|
41058
|
-
releaseUrl: result.release?.url ?? null
|
|
41372
|
+
releaseUrl: result.release?.url ?? null,
|
|
41373
|
+
legs: releaseFollowUpLegs(result, projectInfoSync)
|
|
41059
41374
|
} : void 0;
|
|
41060
41375
|
const reported = {
|
|
41061
41376
|
...result,
|
|
@@ -41105,7 +41420,7 @@ function renderHotfixRelease(r) {
|
|
|
41105
41420
|
` - ${r.tagNote}`,
|
|
41106
41421
|
` - ${r.releaseNote}`,
|
|
41107
41422
|
` - deploy: ${r.deployNote}`,
|
|
41108
|
-
...r.runs.map((run) => ` - ${run.workflow}: ${run.conclusion}${run.url ? ` (${run.url})` : ""}`),
|
|
41423
|
+
...r.runs.map((run) => ` - ${run.workflow}: ${run.conclusion}${run.conclusion === "failure" ? " \u2014 error: workflow reported failure" : ""}${run.url ? ` (${run.url})` : ""}`),
|
|
41109
41424
|
` - ${r.verifyNote}`,
|
|
41110
41425
|
...r.announceNote ? [` - announce: ${r.announceNote}`] : [],
|
|
41111
41426
|
` - fold: ${r.foldNote}`,
|
|
@@ -41116,7 +41431,7 @@ function renderHotfixStatus(r) {
|
|
|
41116
41431
|
return [
|
|
41117
41432
|
`mmi-cli devops hotfix status: ${r.tag} on ${r.repo} \u2014 ${r.state}`,
|
|
41118
41433
|
` - branch: ${r.branchExists ? "pushed" : "absent"} \u2014 PR: ${r.pr ? `#${r.pr.number} ${r.pr.state}` : "none"} \u2014 tag: ${r.tagPushed ? "pushed" : "absent"} \u2014 Release: ${r.releaseExists ? "exists" : "absent"}`,
|
|
41119
|
-
...r.runs.map((run) => ` - ${run.workflow}: ${run.conclusion}${run.url ? ` (${run.url})` : ""}`),
|
|
41434
|
+
...r.runs.map((run) => ` - ${run.workflow}: ${run.conclusion}${run.conclusion === "failure" ? " \u2014 error: workflow reported failure" : ""}${run.url ? ` (${run.url})` : ""}`),
|
|
41120
41435
|
` - npm @mutmutco/cli: ${r.npmVersion}`,
|
|
41121
41436
|
` - next: ${r.next}`,
|
|
41122
41437
|
...r.warnings.map((w) => ` - warning: ${w}`)
|
|
@@ -41127,13 +41442,25 @@ function hotfixRunOutcome(conclusion) {
|
|
|
41127
41442
|
if (conclusion === "failure") return "failure";
|
|
41128
41443
|
return "unresolved";
|
|
41129
41444
|
}
|
|
41445
|
+
function hotfixFollowUpLegs(runs, foldPort, foldNote) {
|
|
41446
|
+
const legs = runs.map((run) => ({
|
|
41447
|
+
leg: run.workflow,
|
|
41448
|
+
status: followUpLegStatus(run.conclusion === "failure" ? "failure" : run.conclusion === "success" ? "success" : "pending"),
|
|
41449
|
+
...run.conclusion === "failure" ? { error: "workflow reported failure" } : {}
|
|
41450
|
+
}));
|
|
41451
|
+
if (foldPort === "failure") {
|
|
41452
|
+
legs.push({ leg: "development-fold-port", status: "failed", error: foldNote ?? "development fold port failed" });
|
|
41453
|
+
}
|
|
41454
|
+
return legs;
|
|
41455
|
+
}
|
|
41130
41456
|
async function runHotfixSub(sub, body, json, render) {
|
|
41131
41457
|
try {
|
|
41132
41458
|
await requireFreshTrainCli("hotfix");
|
|
41133
41459
|
const result = await body();
|
|
41134
|
-
printLine(json ? JSON.stringify(result, null, 2) : render(result));
|
|
41135
41460
|
const runs = result.runs;
|
|
41136
41461
|
const foldPort = result.foldStatus ?? "ok";
|
|
41462
|
+
const legs = runs ? hotfixFollowUpLegs(runs, foldPort, result.foldNote) : void 0;
|
|
41463
|
+
printLine(json ? JSON.stringify(legs ? Object.assign({}, result, { legs }) : result, null, 2) : render(result));
|
|
41137
41464
|
if (runs) applyTrainFollowUpExit(deriveTrainFollowUpStatus({
|
|
41138
41465
|
projectInfo: "ok",
|
|
41139
41466
|
deploy: reduceFollowUpOutcomes(runs.map((r) => hotfixRunOutcome(r.conclusion))),
|
package/package.json
CHANGED