@mutmutco/cli 4.6.6 → 4.6.8
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 +221 -40
- package/dist/runbook-sweep.cjs +4 -2
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -1636,11 +1636,13 @@ var init_cli_shared = __esm({
|
|
|
1636
1636
|
const pool = appEnv ? "app" : !callerEnv ? ambientPool() : !callerEnv.GH_TOKEN ? "personal" : callerEnv.MMI_GH_POOL === "app" ? "app" : callerEnv.GH_TOKEN === process.env.GH_TOKEN ? ambientPool() : "caller";
|
|
1637
1637
|
recordGhUsage("gh", pool, args.filter((a) => !a.startsWith("-")).slice(0, 2).join(" "));
|
|
1638
1638
|
}
|
|
1639
|
-
|
|
1639
|
+
const ambientEnv = options.env ?? process.env;
|
|
1640
|
+
const ambient = ambientEnv.MMI_GH_POOL === "app" || /^ghs_/.test(ambientEnv.GH_TOKEN || ambientEnv.GITHUB_TOKEN || "") ? "app_installation" : "user";
|
|
1641
|
+
if (!appEnv) return tag(run(), ambient);
|
|
1640
1642
|
try {
|
|
1641
1643
|
return await tag(run(appEnv), "app_installation");
|
|
1642
1644
|
} catch (e) {
|
|
1643
|
-
if (shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(),
|
|
1645
|
+
if (shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(), ambient);
|
|
1644
1646
|
throw e;
|
|
1645
1647
|
}
|
|
1646
1648
|
};
|
|
@@ -13871,6 +13873,45 @@ function boardClaimStatusFlagRecovery(flag, canonicalPath, argv) {
|
|
|
13871
13873
|
corrected: `mmi-cli ${kept.join(" ")}`
|
|
13872
13874
|
};
|
|
13873
13875
|
}
|
|
13876
|
+
function issueCommentAppendOnlyRecovery(flag, canonicalPath, argv) {
|
|
13877
|
+
if (canonicalPath !== "oracle issue comment") return void 0;
|
|
13878
|
+
const hasEditVerb = argv.some((t, idx) => idx >= 3 && !t.startsWith("-") && /^(?:edit|update|patch|modify)$/i.test(t));
|
|
13879
|
+
if (flag !== "--id" && !flag.startsWith("--id=") && !hasEditVerb) return void 0;
|
|
13880
|
+
const valueFlags = /* @__PURE__ */ new Set(["--repo", "--body", "--body-file"]);
|
|
13881
|
+
const kept = [];
|
|
13882
|
+
let sawTarget = false;
|
|
13883
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13884
|
+
const tok = argv[i];
|
|
13885
|
+
if (i < 3) {
|
|
13886
|
+
kept.push(tok);
|
|
13887
|
+
continue;
|
|
13888
|
+
}
|
|
13889
|
+
if (tok.startsWith("--id=")) continue;
|
|
13890
|
+
if (tok === "--id") {
|
|
13891
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) i++;
|
|
13892
|
+
continue;
|
|
13893
|
+
}
|
|
13894
|
+
if (tok.startsWith("-")) {
|
|
13895
|
+
kept.push(tok);
|
|
13896
|
+
const name = tok.includes("=") ? tok.slice(0, tok.indexOf("=")) : tok;
|
|
13897
|
+
if (!tok.includes("=") && valueFlags.has(name) && i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
|
|
13898
|
+
kept.push(argv[++i]);
|
|
13899
|
+
}
|
|
13900
|
+
continue;
|
|
13901
|
+
}
|
|
13902
|
+
if (/^(?:edit|update|patch|modify)$/i.test(tok)) continue;
|
|
13903
|
+
sawTarget = true;
|
|
13904
|
+
kept.push(tok);
|
|
13905
|
+
}
|
|
13906
|
+
if (!sawTarget) {
|
|
13907
|
+
kept.splice(3, 0, "<ref>");
|
|
13908
|
+
}
|
|
13909
|
+
const corrected = `mmi-cli ${kept.join(" ")}`;
|
|
13910
|
+
return {
|
|
13911
|
+
message: `unknown option '${flag}' \u2014 \`oracle issue comment\` is append-only (no comment edit/update or \`--id\` route); post a new comment with \`${corrected}\` or read existing comments with \`mmi-cli oracle issue view <ref> --json comments\``,
|
|
13912
|
+
corrected
|
|
13913
|
+
};
|
|
13914
|
+
}
|
|
13874
13915
|
var BOARD_CLAIM_OWN_FLAGS = ["--repo", "--for", "--force", "--check", "--allow-partial", "--json"];
|
|
13875
13916
|
function splitInlineSelectors(argv, flags) {
|
|
13876
13917
|
const out = [];
|
|
@@ -15016,6 +15057,15 @@ var GUIDES = /* @__PURE__ */ new Map([
|
|
|
15016
15057
|
did_you_mean: "oracle issue edit"
|
|
15017
15058
|
}
|
|
15018
15059
|
],
|
|
15060
|
+
// #9574: `oracle issue comment` is append-only; callers guessing `oracle issue comment edit` get the append-only route.
|
|
15061
|
+
[
|
|
15062
|
+
"oracle issue comment:edit",
|
|
15063
|
+
{
|
|
15064
|
+
hint: "no `oracle issue comment edit` \u2014 issue comments are append-only; post a new comment with `mmi-cli oracle issue comment <ref> --body-file <path>` or read existing comments with `mmi-cli oracle issue view <ref> --json comments`",
|
|
15065
|
+
expected: ["oracle issue comment", "oracle issue view"],
|
|
15066
|
+
did_you_mean: "oracle issue comment"
|
|
15067
|
+
}
|
|
15068
|
+
],
|
|
15019
15069
|
// Worktree creation belongs to Git or the selected host, not an MMI command.
|
|
15020
15070
|
[
|
|
15021
15071
|
"worktree:create",
|
|
@@ -15315,7 +15365,7 @@ async function mintFilingWriteToken(deps) {
|
|
|
15315
15365
|
try {
|
|
15316
15366
|
return await mintFromMember(
|
|
15317
15367
|
deps,
|
|
15318
|
-
{ metadata: "read", contents: "write", issues: "write", pull_requests: "write" },
|
|
15368
|
+
{ metadata: "read", contents: "write", issues: "write", pull_requests: "write", workflows: "write" },
|
|
15319
15369
|
APP_VAULT_KEYS
|
|
15320
15370
|
);
|
|
15321
15371
|
} catch (e) {
|
|
@@ -17811,7 +17861,7 @@ var DECLARED_ENTRIES = [
|
|
|
17811
17861
|
// #4118: Listening + systemd-active ≠ GitHub online after acquirejob 503. restart.conf cannot see
|
|
17812
17862
|
// it; an Actions workflow on mmi-live cannot heal it when half the fleet is offline. Root cron.
|
|
17813
17863
|
{ name: "mmi-runner-online-reconcile (mmi-runner)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; API-offline + _diag acquirejob, max 2 restarts/tick (verify: mmi-cli devops runtime box get mmi-runner --ssh)" },
|
|
17814
|
-
{ name: "mmi-runner-online-reconcile (mmi-runner2)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; scripts/runner2-online-reconcile.cron selects lanes
|
|
17864
|
+
{ name: "mmi-runner-online-reconcile (mmi-runner2)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; scripts/runner2-online-reconcile.cron selects lanes 13-16, disables credential files, _diag acquirejob only, max 2 restarts/tick; rollout #7859 (verify: mmi-cli devops runtime box get mmi-runner2 --ssh)" },
|
|
17815
17865
|
// #5292: a lane that holds a dispatched JOB but produces zero output past the budget honestly
|
|
17816
17866
|
// reports "busy", so the online reconcile never fires and GitHub will not re-dispatch while the
|
|
17817
17867
|
// run holds the assignment. This tick cancels + re-triggers those runs (max 1/tick, debounced).
|
|
@@ -27456,10 +27506,10 @@ var rollout_plan_default = {
|
|
|
27456
27506
|
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)."
|
|
27457
27507
|
},
|
|
27458
27508
|
baseline: {
|
|
27459
|
-
version: "4.6.
|
|
27460
|
-
tag: "v4.6.
|
|
27461
|
-
commit: "
|
|
27462
|
-
npm: "@mutmutco/cli@4.6.
|
|
27509
|
+
version: "4.6.8",
|
|
27510
|
+
tag: "v4.6.8",
|
|
27511
|
+
commit: "0dd075a0d2c1",
|
|
27512
|
+
npm: "@mutmutco/cli@4.6.8"
|
|
27463
27513
|
},
|
|
27464
27514
|
exitCriterion: "fleet-n-of-n",
|
|
27465
27515
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27476,14 +27526,14 @@ var rollout_plan_default = {
|
|
|
27476
27526
|
repo: "mutmutco/mmi-hub",
|
|
27477
27527
|
role: "canary",
|
|
27478
27528
|
schedule: "train",
|
|
27479
|
-
v3Target: "v4.6.
|
|
27529
|
+
v3Target: "v4.6.8"
|
|
27480
27530
|
}
|
|
27481
27531
|
],
|
|
27482
27532
|
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.",
|
|
27483
27533
|
rollback: {
|
|
27484
27534
|
independent: true,
|
|
27485
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
27486
|
-
v3Target: "v4.6.
|
|
27535
|
+
mechanism: "npm dist-tag latest -> 4.6.8 and redeploy the Hub Lambda from tag v4.6.8 (0dd075a0d2c1); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
27536
|
+
v3Target: "v4.6.8 (@mutmutco/cli@4.6.8, tag commit 0dd075a0d2c1 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
27487
27537
|
}
|
|
27488
27538
|
},
|
|
27489
27539
|
{
|
|
@@ -41374,6 +41424,20 @@ async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
|
|
|
41374
41424
|
state: "queued",
|
|
41375
41425
|
...read.mergeQueueEntry ? { position: read.mergeQueueEntry.position, entryState: read.mergeQueueEntry.state } : {}
|
|
41376
41426
|
};
|
|
41427
|
+
const groupSha = read.mergeQueueEntry?.headCommit?.oid;
|
|
41428
|
+
if (!options.enqueueOnly && groupSha && deps.findQueueStartupFailure) {
|
|
41429
|
+
const startup = await deps.findQueueStartupFailure(base.repo, groupSha).catch(() => void 0);
|
|
41430
|
+
if (startup) {
|
|
41431
|
+
const detail = startup.annotation ? `: ${startup.annotation}` : startup.url ? ` (${startup.url})` : "";
|
|
41432
|
+
const result2 = {
|
|
41433
|
+
...base,
|
|
41434
|
+
queueRunStartupFailure: startup,
|
|
41435
|
+
error: `queue-run-startup-failure: ${startup.workflow} run ${startup.runId} on ${startup.headSha}${detail}`
|
|
41436
|
+
};
|
|
41437
|
+
deps.queueProgress?.(result2);
|
|
41438
|
+
return result2;
|
|
41439
|
+
}
|
|
41440
|
+
}
|
|
41377
41441
|
deps.queueProgress?.(base);
|
|
41378
41442
|
if (options.enqueueOnly) return { ...base, status: "queued" };
|
|
41379
41443
|
} else if (read.state === "OPEN" && !read.autoMergeRequest) {
|
|
@@ -41678,6 +41742,17 @@ function readOverrideTrailer(trailer, body, cwd) {
|
|
|
41678
41742
|
}).split(/\r?\n/).filter((line) => line.trim().toLowerCase().startsWith(prefix)).map((line) => line.trim().slice(prefix.length).trim()).join("\n").trim();
|
|
41679
41743
|
return { value, messageTransform: footer[1] ? "strip-squash-separator-coauthors" : "strip-terminal-coauthors" };
|
|
41680
41744
|
}
|
|
41745
|
+
function readMessageOverride(message2, cwd) {
|
|
41746
|
+
const prefix = `${TRAILER_KEY.toLowerCase()}:`;
|
|
41747
|
+
const direct = (0, import_node_child_process10.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
41748
|
+
windowsHide: true,
|
|
41749
|
+
cwd,
|
|
41750
|
+
input: message2,
|
|
41751
|
+
encoding: "utf8",
|
|
41752
|
+
maxBuffer: 32 * 1024 * 1024
|
|
41753
|
+
}).split(/\r?\n/).filter((line) => line.trim().toLowerCase().startsWith(prefix)).map((line) => line.trim().slice(prefix.length).trim()).join("\n").trim();
|
|
41754
|
+
return readOverrideTrailer(direct, message2, cwd).value;
|
|
41755
|
+
}
|
|
41681
41756
|
function readOverride(base, cwd, head = "HEAD") {
|
|
41682
41757
|
const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
|
|
41683
41758
|
const refusals = [];
|
|
@@ -41706,6 +41781,9 @@ var HOTFIX_DIFF_BASE = "origin/main";
|
|
|
41706
41781
|
var TEST_RE = TEST_FILE_RE;
|
|
41707
41782
|
var PY_TEST_RE = PY_TEST_FILE_RE;
|
|
41708
41783
|
var GO_TEST_RE = GO_TEST_FILE_RE;
|
|
41784
|
+
function readMessageOverride2(message2, cwd) {
|
|
41785
|
+
return readMessageOverride(message2, cwd);
|
|
41786
|
+
}
|
|
41709
41787
|
function translate(glob) {
|
|
41710
41788
|
let out = "";
|
|
41711
41789
|
for (let i = 0; i < glob.length; i++) {
|
|
@@ -42531,33 +42609,38 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
|
|
|
42531
42609
|
});
|
|
42532
42610
|
const rejected = (reason) => admissionRejected(reason, repo, pr);
|
|
42533
42611
|
const reconcile = async (reason, recordEntry = false, resumingSaved = false) => {
|
|
42534
|
-
|
|
42535
|
-
|
|
42536
|
-
|
|
42537
|
-
if (
|
|
42538
|
-
|
|
42539
|
-
|
|
42540
|
-
|
|
42612
|
+
const maxReads = options.enqueueOnly ? 3 : 1;
|
|
42613
|
+
const sleep3 = options.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
42614
|
+
for (let readAttempt = 0; readAttempt < maxReads; readAttempt++) {
|
|
42615
|
+
if (readAttempt) await sleep3(readAttempt * 1e3);
|
|
42616
|
+
try {
|
|
42617
|
+
const observed = await readPrMergeQueue(pr, repo, read);
|
|
42618
|
+
if (observed.state === "MERGED" && observed.headRefOid === head) return { mergeStatus: "merged" };
|
|
42619
|
+
if (observed.isInMergeQueue && observed.headRefOid === head) {
|
|
42620
|
+
const entry = observed.mergeQueueEntry;
|
|
42621
|
+
if (recordEntry && receipt?.head === head && entry?.headCommit && entry.enqueuedAt) {
|
|
42622
|
+
journal.save("enqueued", receipt?.uuid, { headCommitOid: entry.headCommit.oid, enqueuedAt: entry.enqueuedAt });
|
|
42623
|
+
}
|
|
42624
|
+
return { mergeStatus: "auto-merge-enqueued" };
|
|
42541
42625
|
}
|
|
42542
|
-
|
|
42543
|
-
|
|
42544
|
-
|
|
42545
|
-
|
|
42546
|
-
|
|
42547
|
-
|
|
42548
|
-
|
|
42549
|
-
|
|
42550
|
-
|
|
42551
|
-
|
|
42552
|
-
|
|
42553
|
-
|
|
42554
|
-
|
|
42555
|
-
|
|
42556
|
-
|
|
42557
|
-
return rejected("the previous admission was ejected by GitHub; rerun to submit a new intent");
|
|
42626
|
+
const saved = recordEntry ? admissionJournal(repo, pr, options.receiptDirectory).receipt : void 0;
|
|
42627
|
+
const boundMs = saved?.queueEntry ? Date.parse(saved.queueEntry.enqueuedAt) : resumingSaved && saved?.state === "enqueued" && saved.queueWitnessed === void 0 ? saved.observedAt : void 0;
|
|
42628
|
+
if (saved && boundMs !== void 0 && Number.isFinite(boundMs) && observed.state === "OPEN" && !observed.isInMergeQueue && observed.headRefOid === saved.head) {
|
|
42629
|
+
const events = await readArrayPages(
|
|
42630
|
+
`repos/${repo}/issues/${pr}/timeline?per_page=100`,
|
|
42631
|
+
options.timelineRead ?? read,
|
|
42632
|
+
"queue ejection timeline unreadable"
|
|
42633
|
+
);
|
|
42634
|
+
const times = (name) => events.filter((event) => event.event === name).map((event) => Date.parse(event.created_at));
|
|
42635
|
+
const adds = times("added_to_merge_queue").filter((at) => at >= boundMs);
|
|
42636
|
+
const removals = adds.length === 1 ? times("removed_from_merge_queue").filter((at) => at > adds[0]) : [];
|
|
42637
|
+
if (removals.length === 1) {
|
|
42638
|
+
journal.save("ejected");
|
|
42639
|
+
return rejected("the previous admission was ejected by GitHub; rerun to submit a new intent");
|
|
42640
|
+
}
|
|
42558
42641
|
}
|
|
42642
|
+
} catch {
|
|
42559
42643
|
}
|
|
42560
|
-
} catch {
|
|
42561
42644
|
}
|
|
42562
42645
|
return unknown(reason);
|
|
42563
42646
|
};
|
|
@@ -42738,6 +42821,19 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
|
|
|
42738
42821
|
error: "GitHub accepted queue admission but has not confirmed queue membership. Rerun pr land to resume the saved operation read-only."
|
|
42739
42822
|
};
|
|
42740
42823
|
}
|
|
42824
|
+
function githubQueueSquashMessage(title, pr, commits) {
|
|
42825
|
+
const list = commits.map((commit) => `* ${commit.headline}
|
|
42826
|
+
|
|
42827
|
+
${commit.body}`.trim()).join("\n\n");
|
|
42828
|
+
return `${title} (#${pr})
|
|
42829
|
+
|
|
42830
|
+
${list}
|
|
42831
|
+
|
|
42832
|
+
---------
|
|
42833
|
+
|
|
42834
|
+
Co-authored-by: GitHub <noreply@github.com>
|
|
42835
|
+
`;
|
|
42836
|
+
}
|
|
42741
42837
|
async function enqueuePrMergeQueue(pr, repo, options = {}) {
|
|
42742
42838
|
if (!/^\d+$/.test(pr)) throw new Error("merge queue enqueue requires a PR number");
|
|
42743
42839
|
pr = pr.replace(/^0+(?=\d)/, "");
|
|
@@ -42853,6 +42949,19 @@ ${pull.body}`;
|
|
|
42853
42949
|
])];
|
|
42854
42950
|
return preparation("queue-message", targets, `GitHub's unchanged ${policy.body} message is unsafe for ${targets.map((number) => `${repo}#${number}`).join(", ")}. ${verdict.message}`);
|
|
42855
42951
|
}
|
|
42952
|
+
if (method === "SQUASH" && policy.body === "COMMIT_MESSAGES" && messages.length > 1 && options.bodyText == null && messages.some((message2) => /^\s*Test-Policy-Override\s*:/im.test(`${message2.headline}
|
|
42953
|
+
${message2.body}`)) && !readMessageOverride2(githubQueueSquashMessage(pull.title, pr, messages), process.cwd())) {
|
|
42954
|
+
return {
|
|
42955
|
+
mergeStatus: "failed",
|
|
42956
|
+
preparation: {
|
|
42957
|
+
status: "preparation-needed",
|
|
42958
|
+
reason: "queue-message-override",
|
|
42959
|
+
issueTargets: [],
|
|
42960
|
+
nextAction: "PR author: this repository squashes with COMMIT_MESSAGES, and GitHub composes a multi-commit squash as a list of every commit message followed by its own co-author block, so this Test-Policy-Override trailer would not be in the final trailer block the queue check reads. Put the change in ONE commit whose message ends with a trailer block carrying the waiver (no force-push: push a new branch whose single commit has the same tree, parented on the PR base; open it as a new PR and close this one), or make the waiver the final trailer block of the LAST commit, then rerun pr land. Keep repository settings unchanged."
|
|
42961
|
+
},
|
|
42962
|
+
error: "queue preparation needed: the multi-commit COMMIT_MESSAGES squash would bury the Test-Policy-Override waiver mid-message, so the queue check would fail malformed-override-trailer"
|
|
42963
|
+
};
|
|
42964
|
+
}
|
|
42856
42965
|
const requiredBody = options.bodyText ?? composedBody;
|
|
42857
42966
|
const prBodyPreservesOverrides = requiredBody != null && method === "SQUASH" && policy.body === "PR_BODY" && options.bodyText == null && prBodyPreservesCommitOverrides(messages, headline, pull.body, process.cwd());
|
|
42858
42967
|
if (requiredBody && !prBodyPreservesOverrides && (method !== "SQUASH" || requiredBody.trim() !== body.trim())) {
|
|
@@ -42904,6 +43013,54 @@ async function findPrMergeGroupRun(pr, repo, base, gh2 = readApi) {
|
|
|
42904
43013
|
const run = pages.flatMap((page) => page.workflow_runs).filter((run2) => run2.head_branch?.startsWith(prefix)).sort((a, b) => b.created_at.localeCompare(a.created_at) || b.id - a.id)[0];
|
|
42905
43014
|
return run ? { id: run.id, url: run.html_url, conclusion: run.conclusion } : void 0;
|
|
42906
43015
|
}
|
|
43016
|
+
async function findPrMergeQueueStartupFailure(repo, groupHeadSha, gh2 = readApi) {
|
|
43017
|
+
if (!/^[a-f0-9]{40}$/i.test(groupHeadSha)) return void 0;
|
|
43018
|
+
try {
|
|
43019
|
+
const body = JSON.parse(await gh2([`repos/${repo}/actions/runs?head_sha=${groupHeadSha}&per_page=100`]));
|
|
43020
|
+
const runs2 = (body.workflow_runs ?? []).filter((r) => r.head_sha?.toLowerCase() === groupHeadSha.toLowerCase() && r.status === "completed");
|
|
43021
|
+
for (const run of runs2) {
|
|
43022
|
+
let isStartupFailure = run.conclusion === "startup_failure";
|
|
43023
|
+
if (!isStartupFailure && run.conclusion === "failure") {
|
|
43024
|
+
const jobsBody = JSON.parse(await gh2([`repos/${repo}/actions/runs/${run.id}/jobs?per_page=1`]));
|
|
43025
|
+
if (jobsBody.total_count === 0 && Array.isArray(jobsBody.jobs) && jobsBody.jobs.length === 0) {
|
|
43026
|
+
isStartupFailure = true;
|
|
43027
|
+
}
|
|
43028
|
+
}
|
|
43029
|
+
if (!isStartupFailure) continue;
|
|
43030
|
+
const workflow = (run.path?.replace(/^\.github\/workflows\//, "") || run.name || "workflow").trim();
|
|
43031
|
+
let annotation;
|
|
43032
|
+
if (run.check_suite_id) {
|
|
43033
|
+
try {
|
|
43034
|
+
const checkRuns = JSON.parse(await gh2([`repos/${repo}/check-suites/${run.check_suite_id}/check-runs?per_page=20`]));
|
|
43035
|
+
for (const cr of checkRuns.check_runs ?? []) {
|
|
43036
|
+
const summary = [cr.output?.title, cr.output?.summary].filter(Boolean).join(" \u2014 ").trim();
|
|
43037
|
+
if (summary) {
|
|
43038
|
+
annotation = summary;
|
|
43039
|
+
break;
|
|
43040
|
+
}
|
|
43041
|
+
const anns = JSON.parse(await gh2([`repos/${repo}/check-runs/${cr.id}/annotations?per_page=10`]));
|
|
43042
|
+
const first = Array.isArray(anns) ? anns.find((a) => a?.message || a?.title) : void 0;
|
|
43043
|
+
if (first) {
|
|
43044
|
+
annotation = (first.message || first.title || "").trim();
|
|
43045
|
+
break;
|
|
43046
|
+
}
|
|
43047
|
+
}
|
|
43048
|
+
} catch {
|
|
43049
|
+
}
|
|
43050
|
+
}
|
|
43051
|
+
return {
|
|
43052
|
+
workflow,
|
|
43053
|
+
runId: run.id,
|
|
43054
|
+
headSha: groupHeadSha,
|
|
43055
|
+
...run.html_url ? { url: run.html_url } : {},
|
|
43056
|
+
...annotation ? { annotation } : {}
|
|
43057
|
+
};
|
|
43058
|
+
}
|
|
43059
|
+
} catch {
|
|
43060
|
+
return void 0;
|
|
43061
|
+
}
|
|
43062
|
+
return void 0;
|
|
43063
|
+
}
|
|
42907
43064
|
function workflowNeeds(text) {
|
|
42908
43065
|
const doc = (0, import_yaml3.parse)(text);
|
|
42909
43066
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -42999,6 +43156,9 @@ async function readBaseRedVerdict(result, gh2 = appOnlyReadApi) {
|
|
|
42999
43156
|
return mine && base && failuresAllOnBase(mine, base) ? { baseRunId: String(baseRun.id) } : void 0;
|
|
43000
43157
|
}
|
|
43001
43158
|
function prMergeQueueLine(result) {
|
|
43159
|
+
if (result.queueRunStartupFailure) {
|
|
43160
|
+
return `pr land: PR #${result.pr} ${result.error ?? `queue-run-startup-failure: ${result.queueRunStartupFailure.workflow} run ${result.queueRunStartupFailure.runId} on ${result.queueRunStartupFailure.headSha}`}`;
|
|
43161
|
+
}
|
|
43002
43162
|
const queue = result.mergeQueue;
|
|
43003
43163
|
if (!queue) return void 0;
|
|
43004
43164
|
switch (queue.state) {
|
|
@@ -59501,6 +59661,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59501
59661
|
readMergeQueue: readPrMergeQueue,
|
|
59502
59662
|
observeQueued: (prNumber, repo, snapshot) => observeQueuedAdmission(repo, prNumber, snapshot),
|
|
59503
59663
|
findMergeGroupRun: findPrMergeGroupRun,
|
|
59664
|
+
findQueueStartupFailure: findPrMergeQueueStartupFailure,
|
|
59504
59665
|
queueProgress: (receipt) => {
|
|
59505
59666
|
const line = prMergeQueueLine(receipt);
|
|
59506
59667
|
if (line) (o.json ? console.warn : printLine)(line);
|
|
@@ -65676,15 +65837,18 @@ function formatBrokerRunListRecovery(spec, jq) {
|
|
|
65676
65837
|
}
|
|
65677
65838
|
function brokerGhFailureHint(failureText, args, jq) {
|
|
65678
65839
|
const target = args[0] === "pr" && args[1] === "view" ? args[2] : void 0;
|
|
65840
|
+
const repoIdx = args.indexOf("--repo");
|
|
65841
|
+
const repo = repoIdx >= 0 && args[repoIdx + 1] && !args[repoIdx + 1].startsWith("-") ? args[repoIdx + 1] : void 0;
|
|
65679
65842
|
const unknownField = /Unknown JSON field: "([^"]+)"/.exec(failureText)?.[1] ?? /Unknown JSON field: ([A-Za-z][A-Za-z0-9]*)/.exec(failureText)?.[1];
|
|
65680
65843
|
if (unknownField === "mergeQueueEntry" || unknownField === "isInMergeQueue") {
|
|
65681
|
-
return `'${unknownField}' is not a gh pr view --json field \u2014 read the merge queue with \`mmi-cli devops read pr queue ${target ?? "<number>"} --repo <owner/repo> --json\` and drop it from --json; repeating this read cannot succeed`;
|
|
65844
|
+
return `'${unknownField}' is not a gh pr view --json field \u2014 read the merge queue with \`mmi-cli devops read pr queue ${target ?? "<number>"} --repo ${repo ?? "<owner/repo>"} --json\` and drop it from --json; repeating this read cannot succeed`;
|
|
65682
65845
|
}
|
|
65683
65846
|
if (unknownField) {
|
|
65684
65847
|
return `gh rejected --json field '${unknownField}' \u2014 drop it or use the typed read whose fields cover it (the "Available fields" list gh printed is its own vocabulary)`;
|
|
65685
65848
|
}
|
|
65686
65849
|
if (/Could not resolve to a PullRequest/i.test(failureText)) {
|
|
65687
|
-
|
|
65850
|
+
const slug = repo ?? "<owner/repo>";
|
|
65851
|
+
return `GitHub could not resolve the PR${target ? ` #${target}` : ""}${repo ? ` in ${repo}` : ""} \u2014 verify the number and that --repo names the repo that owns it before retrying, or list PRs with \`mmi-cli devops read pr list --repo ${slug} --state all --json number,title,headRefName\`; an identical re-run fails the same way`;
|
|
65688
65852
|
}
|
|
65689
65853
|
if (args[0] === "run" && args[1] === "list") {
|
|
65690
65854
|
return formatBrokerRunListRecovery({ program: "gh", args: [...args], timeoutMs: SINGLE_SHOT_TIMEOUT_MS }, jq);
|
|
@@ -66062,11 +66226,26 @@ function branchRulesReadHint(path2) {
|
|
|
66062
66226
|
const slug = `${match[1]}/${match[2]}`;
|
|
66063
66227
|
return ` No raw rules, rulesets or branch-protection read is on the App allowlist, so retrying path or query variants cannot work \u2014 inspect a PR's required status contexts with \`MMI_ACTOR=app mmi-cli devops pr checks-required <pr> --repo ${slug} --json\`, or verify repository ruleset and branch-protection health with \`mmi-cli devops bootstrap verify --repo ${slug} --json\` and \`mmi-cli devops ci audit --repo ${slug} --json\`.`;
|
|
66064
66228
|
}
|
|
66229
|
+
function commitsReadHint(path2) {
|
|
66230
|
+
const clean4 = path2.split("?")[0];
|
|
66231
|
+
if (branchRulesReadHint(clean4) || checkRunsReadHint(clean4)) return "";
|
|
66232
|
+
const match = new RegExp(`^repos/(${NAME})/(${NAME})/(?:commits|branches)(?:[/?#]|$)`).exec(clean4);
|
|
66233
|
+
if (!match) return "";
|
|
66234
|
+
const slug = `${match[1]}/${match[2]}`;
|
|
66235
|
+
return ` No raw commits or branches endpoint is on the App allowlist \u2014 to compare branch tips, ahead/behind counts, and commit SHAs/dates (e.g. whether main is current with development), use the allowlisted compare read \`MMI_ACTOR=app mmi-cli devops api get "repos/${slug}/compare/main...development"\` (returns \`status\`, \`ahead_by\`, \`behind_by\`, \`base_commit\`, \`merge_base_commit\`, and \`commits\`), or inspect remote branch SHAs directly with \`git ls-remote --heads https://github.com/${slug}.git main development\`.`;
|
|
66236
|
+
}
|
|
66237
|
+
function checkRunsReadHint(path2) {
|
|
66238
|
+
const clean4 = path2.split("?")[0];
|
|
66239
|
+
const match = new RegExp(`^repos/(${NAME})/(${NAME})/(?:check-suites(?:/|$)|check-runs(?:/|$)|commits/[^/?#]+/(?:check-runs|check-suites)(?:/|$))`).exec(clean4);
|
|
66240
|
+
if (!match) return "";
|
|
66241
|
+
const slug = `${match[1]}/${match[2]}`;
|
|
66242
|
+
return ` Check-suite and check-run routes are deliberately outside the App allowlist \u2014 read per-check / per-job conclusions with the allowlisted \`MMI_ACTOR=app mmi-cli devops api get "repos/${slug}/actions/runs/<id>/jobs?per_page=100"\` or \`mmi-cli devops read run view <id> --repo ${slug} --json databaseId,workflowName,event,status,conclusion,jobs\`; check-run annotations and output text have no App-token read (ask a human token holder).`;
|
|
66243
|
+
}
|
|
66065
66244
|
function parseApiGetPath(rawPath) {
|
|
66066
66245
|
const path2 = rawPath.trim();
|
|
66067
66246
|
const refuse = () => {
|
|
66068
66247
|
const hint = /^[A-Za-z]:[/\\]/.test(path2) ? " Git Bash/MSYS may have rewritten a leading-slash argument; retry with MSYS_NO_PATHCONV=1 and the slash-free repos/... path. The endpoint must still be allowlisted." : "";
|
|
66069
|
-
const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${hint}`;
|
|
66248
|
+
const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${commitsReadHint(path2)}${checkRunsReadHint(path2)}${hint}`;
|
|
66070
66249
|
return new Error(`api get: refusing '${rawPath}' \u2014${routeHints}${API_GET_RECOVERY} Only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.`);
|
|
66071
66250
|
};
|
|
66072
66251
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
@@ -69695,13 +69874,15 @@ function envelopeAwareWriteErr(str2) {
|
|
|
69695
69874
|
const flag = match[1];
|
|
69696
69875
|
const argv = process.argv.slice(2);
|
|
69697
69876
|
const invoked = resolveCommandFromArgv(program2, argv);
|
|
69698
|
-
const
|
|
69877
|
+
const invokedPath = invoked ? canonicalPathFor(commandPath3(invoked)) ?? commandPath3(invoked) : void 0;
|
|
69878
|
+
const canonicalArgv = canonicalArgvFor(argv);
|
|
69879
|
+
const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, invokedPath, canonicalArgv) ?? issueCommentAppendOnlyRecovery(flag, invokedPath, canonicalArgv) : void 0;
|
|
69699
69880
|
if (claimStatus) {
|
|
69700
69881
|
if (argvWantsJson3()) {
|
|
69701
69882
|
process.stderr.write(
|
|
69702
69883
|
formatErrorEnvelope(claimStatus.message, {
|
|
69703
69884
|
code: ERROR_CODES.ERR_UNKNOWN_FLAG,
|
|
69704
|
-
offending_flag: "
|
|
69885
|
+
offending_flag: flag.split("=")[0],
|
|
69705
69886
|
corrected_command: claimStatus.corrected
|
|
69706
69887
|
}) + "\n"
|
|
69707
69888
|
);
|
package/dist/runbook-sweep.cjs
CHANGED
|
@@ -455,11 +455,13 @@ var init_cli_shared = __esm({
|
|
|
455
455
|
const pool = appEnv ? "app" : !callerEnv ? ambientPool() : !callerEnv.GH_TOKEN ? "personal" : callerEnv.MMI_GH_POOL === "app" ? "app" : callerEnv.GH_TOKEN === process.env.GH_TOKEN ? ambientPool() : "caller";
|
|
456
456
|
recordGhUsage("gh", pool, args.filter((a) => !a.startsWith("-")).slice(0, 2).join(" "));
|
|
457
457
|
}
|
|
458
|
-
|
|
458
|
+
const ambientEnv = options.env ?? process.env;
|
|
459
|
+
const ambient = ambientEnv.MMI_GH_POOL === "app" || /^ghs_/.test(ambientEnv.GH_TOKEN || ambientEnv.GITHUB_TOKEN || "") ? "app_installation" : "user";
|
|
460
|
+
if (!appEnv) return tag(run(), ambient);
|
|
459
461
|
try {
|
|
460
462
|
return await tag(run(appEnv), "app_installation");
|
|
461
463
|
} catch (e) {
|
|
462
|
-
if (shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(),
|
|
464
|
+
if (shouldRetryIntegrationRefusalOnAmbient(e)) return tag(run(), ambient);
|
|
463
465
|
throw e;
|
|
464
466
|
}
|
|
465
467
|
};
|
package/package.json
CHANGED