@mutmutco/cli 4.6.7 → 4.6.9
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 +295 -48
- package/dist/runbook-sweep.cjs +11 -4
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -1236,7 +1236,7 @@ var init_house_map = __esm({
|
|
|
1236
1236
|
api: "devops",
|
|
1237
1237
|
// allowlisted GitHub REST GET reads for automation (pull files, compare, contents)
|
|
1238
1238
|
run: "devops",
|
|
1239
|
-
// #9479: one named workflow run —
|
|
1239
|
+
// #9479: one named workflow run — the bounded `run cancel <id>` and `run rerun <id>` writes
|
|
1240
1240
|
ci: "devops",
|
|
1241
1241
|
// the CI/gate audit
|
|
1242
1242
|
jev: "devops",
|
|
@@ -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",
|
|
@@ -15297,6 +15347,8 @@ var APP_WRITE_COMMANDS = /* @__PURE__ */ new Map([
|
|
|
15297
15347
|
["ci cancel-orphans", { metadata: "read", actions: "write" }],
|
|
15298
15348
|
// #9479: one named run cancelled under the same actions:write profile as the orphan sweep.
|
|
15299
15349
|
["run cancel", { metadata: "read", actions: "write" }],
|
|
15350
|
+
// #9587: one named run rerun (all or failed jobs) — same actions:write profile, nothing broader.
|
|
15351
|
+
["run rerun", { metadata: "read", actions: "write" }],
|
|
15300
15352
|
["issue comment", { metadata: "read", issues: "write", pull_requests: "write" }],
|
|
15301
15353
|
// #8401: the detached related-issue lookup (issue list/view reads + one marker comment) — no identity contract.
|
|
15302
15354
|
["issue discover-related", { metadata: "read", issues: "write" }]
|
|
@@ -17811,7 +17863,7 @@ var DECLARED_ENTRIES = [
|
|
|
17811
17863
|
// #4118: Listening + systemd-active ≠ GitHub online after acquirejob 503. restart.conf cannot see
|
|
17812
17864
|
// it; an Actions workflow on mmi-live cannot heal it when half the fleet is offline. Root cron.
|
|
17813
17865
|
{ 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
|
|
17866
|
+
{ 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
17867
|
// #5292: a lane that holds a dispatched JOB but produces zero output past the budget honestly
|
|
17816
17868
|
// reports "busy", so the online reconcile never fires and GitHub will not re-dispatch while the
|
|
17817
17869
|
// run holds the assignment. This tick cancels + re-triggers those runs (max 1/tick, debounced).
|
|
@@ -24989,10 +25041,15 @@ function checkRunbookCoverage(docs, base, git3) {
|
|
|
24989
25041
|
const heading = headings[i];
|
|
24990
25042
|
const end = headings.slice(i + 1).find((h) => h.level <= heading.level)?.start ?? lines2.length + 1;
|
|
24991
25043
|
const markerEnd = headings[i + 1]?.start ?? end;
|
|
24992
|
-
const
|
|
25044
|
+
const markers = lines2.slice(heading.start, markerEnd - 1).flatMap((text, j) => {
|
|
25045
|
+
const found = /<!--\s*runbook-covers:\s*(.*?)\s*-->/.exec(text)?.[1].split(",").map((s) => s.trim());
|
|
25046
|
+
return found ? [{ line: heading.start + 1 + j, globs: found }] : [];
|
|
25047
|
+
});
|
|
25048
|
+
const globs = markers.flatMap((m) => m.globs);
|
|
24993
25049
|
const affected = changed.filter((path2) => globs.some((glob) => matches(glob, path2)) && !changesOnlyUses(path2));
|
|
24994
25050
|
if (affected.length && !ranges.some((r) => r.count > 0 && r.start < end && r.start + r.count > heading.start)) {
|
|
24995
|
-
|
|
25051
|
+
const markerLine = markers.find((m) => m.globs.some((glob) => affected.some((path2) => matches(glob, path2)))).line;
|
|
25052
|
+
findings.push({ kind: "runbook-section-stale", doc, line: markerLine, detail: `${heading.title} (lines ${heading.start}\u2013${end - 1}, covers marker at line ${markerLine}): changed ${affected.join(", ")}; update this section's text (lines ${heading.start}\u2013${end - 1}) or runbook-covers list, or add a \`Runbook-Unaffected: <reason>\` trailer to a commit message in this PR`, section: { heading: heading.title, level: heading.level, startLine: heading.start, endLine: end - 1, covers: globs }, sources: affected });
|
|
24996
25053
|
}
|
|
24997
25054
|
}
|
|
24998
25055
|
}
|
|
@@ -27456,10 +27513,10 @@ var rollout_plan_default = {
|
|
|
27456
27513
|
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
27514
|
},
|
|
27458
27515
|
baseline: {
|
|
27459
|
-
version: "4.6.
|
|
27460
|
-
tag: "v4.6.
|
|
27461
|
-
commit: "
|
|
27462
|
-
npm: "@mutmutco/cli@4.6.
|
|
27516
|
+
version: "4.6.9",
|
|
27517
|
+
tag: "v4.6.9",
|
|
27518
|
+
commit: "7cbf84926132",
|
|
27519
|
+
npm: "@mutmutco/cli@4.6.9"
|
|
27463
27520
|
},
|
|
27464
27521
|
exitCriterion: "fleet-n-of-n",
|
|
27465
27522
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27476,14 +27533,14 @@ var rollout_plan_default = {
|
|
|
27476
27533
|
repo: "mutmutco/mmi-hub",
|
|
27477
27534
|
role: "canary",
|
|
27478
27535
|
schedule: "train",
|
|
27479
|
-
v3Target: "v4.6.
|
|
27536
|
+
v3Target: "v4.6.9"
|
|
27480
27537
|
}
|
|
27481
27538
|
],
|
|
27482
27539
|
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
27540
|
rollback: {
|
|
27484
27541
|
independent: true,
|
|
27485
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
27486
|
-
v3Target: "v4.6.
|
|
27542
|
+
mechanism: "npm dist-tag latest -> 4.6.9 and redeploy the Hub Lambda from tag v4.6.9 (7cbf84926132); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
27543
|
+
v3Target: "v4.6.9 (@mutmutco/cli@4.6.9, tag commit 7cbf84926132 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
27487
27544
|
}
|
|
27488
27545
|
},
|
|
27489
27546
|
{
|
|
@@ -32056,8 +32113,11 @@ function classifyRunnerCost(selector) {
|
|
|
32056
32113
|
}
|
|
32057
32114
|
var STANDARD_HOSTED_ALLOWLIST = [
|
|
32058
32115
|
["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"],
|
|
32059
|
-
["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"]
|
|
32116
|
+
["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"],
|
|
32117
|
+
// #9571 (MM-Work#8894 chooser): owned mmi-heavy first, included ubuntu-latest only on a deep backlog.
|
|
32118
|
+
["mutmutco/MMI-Hub", ".github/workflows/gate.yml", "cli-part", "hosted-spill"]
|
|
32060
32119
|
];
|
|
32120
|
+
var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*'([^']+)'\s*\|\|\s*)?fromJSON\(needs\.[\w-]+\.outputs\.heavy_runs_on\s*\|\|\s*('[^']+')\)\s*\}\}$/;
|
|
32061
32121
|
function standardHostedLabelFor(repo, path2, jobId) {
|
|
32062
32122
|
if (repo === "mutmutco/Jerv-Hub") return ["ubuntu-latest", "windows-latest", "macos-14"];
|
|
32063
32123
|
return STANDARD_HOSTED_ALLOWLIST.find(([r, p, j]) => r === repo && p === path2 && j === jobId)?.[3];
|
|
@@ -32072,6 +32132,8 @@ function classifyJobRunnerCost(job, allowance2) {
|
|
|
32072
32132
|
if (approved(raw) || allowance2 === "windows-11-arm" && raw === allowance2) return "standard-hosted";
|
|
32073
32133
|
const fallback = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'([^']+)'\s*\|\|\s*(fromJSON\('[^']+'\))\s*\}\}$/.exec(raw);
|
|
32074
32134
|
if (fallback && approved(fallback[1]) && classifyRunnerCost("${{ " + fallback[2] + " }}") === "self-hosted") return "standard-hosted";
|
|
32135
|
+
const spill = allowance2 === "hosted-spill" ? HOSTED_SPILL_RE.exec(raw) : null;
|
|
32136
|
+
if (spill) return (!spill[1] || spill[1] === "ubuntu-latest") && classifyRunnerCost(`\${{ fromJSON(${spill[2]}) }}`) === "self-hosted" ? "standard-hosted" : "unknown";
|
|
32075
32137
|
const direct = classifyRunnerCost(raw);
|
|
32076
32138
|
const matrix = /^\$\{\{\s*matrix\.([\w-]+)(?:\.([\w-]+))?\s*\}\}$/.exec(raw);
|
|
32077
32139
|
if (!matrix || direct === "hosted") return direct;
|
|
@@ -38593,7 +38655,7 @@ function runsOnLabelSets(job) {
|
|
|
38593
38655
|
if (plainRecord(selector)) selector = selector.labels;
|
|
38594
38656
|
if (typeof selector !== "string" || !selector.includes("${{")) return [toSet(selector)].filter((s) => Boolean(s));
|
|
38595
38657
|
const sets = [];
|
|
38596
|
-
for (const m of selector.matchAll(/fromJSON\('([^']+)'\)/g)) {
|
|
38658
|
+
for (const m of selector.matchAll(/fromJSON\((?:needs\.[\w-]+\.outputs\.[\w-]+\s*\|\|\s*)?'([^']+)'\)/g)) {
|
|
38597
38659
|
try {
|
|
38598
38660
|
const parsed = toSet(JSON.parse(m[1]));
|
|
38599
38661
|
if (parsed) sets.push(parsed);
|
|
@@ -41374,6 +41436,20 @@ async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
|
|
|
41374
41436
|
state: "queued",
|
|
41375
41437
|
...read.mergeQueueEntry ? { position: read.mergeQueueEntry.position, entryState: read.mergeQueueEntry.state } : {}
|
|
41376
41438
|
};
|
|
41439
|
+
const groupSha = read.mergeQueueEntry?.headCommit?.oid;
|
|
41440
|
+
if (!options.enqueueOnly && groupSha && deps.findQueueStartupFailure) {
|
|
41441
|
+
const startup = await deps.findQueueStartupFailure(base.repo, groupSha).catch(() => void 0);
|
|
41442
|
+
if (startup) {
|
|
41443
|
+
const detail = startup.annotation ? `: ${startup.annotation}` : startup.url ? ` (${startup.url})` : "";
|
|
41444
|
+
const result2 = {
|
|
41445
|
+
...base,
|
|
41446
|
+
queueRunStartupFailure: startup,
|
|
41447
|
+
error: `queue-run-startup-failure: ${startup.workflow} run ${startup.runId} on ${startup.headSha}${detail}`
|
|
41448
|
+
};
|
|
41449
|
+
deps.queueProgress?.(result2);
|
|
41450
|
+
return result2;
|
|
41451
|
+
}
|
|
41452
|
+
}
|
|
41377
41453
|
deps.queueProgress?.(base);
|
|
41378
41454
|
if (options.enqueueOnly) return { ...base, status: "queued" };
|
|
41379
41455
|
} else if (read.state === "OPEN" && !read.autoMergeRequest) {
|
|
@@ -41678,6 +41754,17 @@ function readOverrideTrailer(trailer, body, cwd) {
|
|
|
41678
41754
|
}).split(/\r?\n/).filter((line) => line.trim().toLowerCase().startsWith(prefix)).map((line) => line.trim().slice(prefix.length).trim()).join("\n").trim();
|
|
41679
41755
|
return { value, messageTransform: footer[1] ? "strip-squash-separator-coauthors" : "strip-terminal-coauthors" };
|
|
41680
41756
|
}
|
|
41757
|
+
function readMessageOverride(message2, cwd) {
|
|
41758
|
+
const prefix = `${TRAILER_KEY.toLowerCase()}:`;
|
|
41759
|
+
const direct = (0, import_node_child_process10.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
41760
|
+
windowsHide: true,
|
|
41761
|
+
cwd,
|
|
41762
|
+
input: message2,
|
|
41763
|
+
encoding: "utf8",
|
|
41764
|
+
maxBuffer: 32 * 1024 * 1024
|
|
41765
|
+
}).split(/\r?\n/).filter((line) => line.trim().toLowerCase().startsWith(prefix)).map((line) => line.trim().slice(prefix.length).trim()).join("\n").trim();
|
|
41766
|
+
return readOverrideTrailer(direct, message2, cwd).value;
|
|
41767
|
+
}
|
|
41681
41768
|
function readOverride(base, cwd, head = "HEAD") {
|
|
41682
41769
|
const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
|
|
41683
41770
|
const refusals = [];
|
|
@@ -41706,6 +41793,9 @@ var HOTFIX_DIFF_BASE = "origin/main";
|
|
|
41706
41793
|
var TEST_RE = TEST_FILE_RE;
|
|
41707
41794
|
var PY_TEST_RE = PY_TEST_FILE_RE;
|
|
41708
41795
|
var GO_TEST_RE = GO_TEST_FILE_RE;
|
|
41796
|
+
function readMessageOverride2(message2, cwd) {
|
|
41797
|
+
return readMessageOverride(message2, cwd);
|
|
41798
|
+
}
|
|
41709
41799
|
function translate(glob) {
|
|
41710
41800
|
let out = "";
|
|
41711
41801
|
for (let i = 0; i < glob.length; i++) {
|
|
@@ -42531,33 +42621,38 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
|
|
|
42531
42621
|
});
|
|
42532
42622
|
const rejected = (reason) => admissionRejected(reason, repo, pr);
|
|
42533
42623
|
const reconcile = async (reason, recordEntry = false, resumingSaved = false) => {
|
|
42534
|
-
|
|
42535
|
-
|
|
42536
|
-
|
|
42537
|
-
if (
|
|
42538
|
-
|
|
42539
|
-
|
|
42540
|
-
|
|
42624
|
+
const maxReads = options.enqueueOnly ? 3 : 1;
|
|
42625
|
+
const sleep3 = options.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
42626
|
+
for (let readAttempt = 0; readAttempt < maxReads; readAttempt++) {
|
|
42627
|
+
if (readAttempt) await sleep3(readAttempt * 1e3);
|
|
42628
|
+
try {
|
|
42629
|
+
const observed = await readPrMergeQueue(pr, repo, read);
|
|
42630
|
+
if (observed.state === "MERGED" && observed.headRefOid === head) return { mergeStatus: "merged" };
|
|
42631
|
+
if (observed.isInMergeQueue && observed.headRefOid === head) {
|
|
42632
|
+
const entry = observed.mergeQueueEntry;
|
|
42633
|
+
if (recordEntry && receipt?.head === head && entry?.headCommit && entry.enqueuedAt) {
|
|
42634
|
+
journal.save("enqueued", receipt?.uuid, { headCommitOid: entry.headCommit.oid, enqueuedAt: entry.enqueuedAt });
|
|
42635
|
+
}
|
|
42636
|
+
return { mergeStatus: "auto-merge-enqueued" };
|
|
42541
42637
|
}
|
|
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");
|
|
42638
|
+
const saved = recordEntry ? admissionJournal(repo, pr, options.receiptDirectory).receipt : void 0;
|
|
42639
|
+
const boundMs = saved?.queueEntry ? Date.parse(saved.queueEntry.enqueuedAt) : resumingSaved && saved?.state === "enqueued" && saved.queueWitnessed === void 0 ? saved.observedAt : void 0;
|
|
42640
|
+
if (saved && boundMs !== void 0 && Number.isFinite(boundMs) && observed.state === "OPEN" && !observed.isInMergeQueue && observed.headRefOid === saved.head) {
|
|
42641
|
+
const events = await readArrayPages(
|
|
42642
|
+
`repos/${repo}/issues/${pr}/timeline?per_page=100`,
|
|
42643
|
+
options.timelineRead ?? read,
|
|
42644
|
+
"queue ejection timeline unreadable"
|
|
42645
|
+
);
|
|
42646
|
+
const times = (name) => events.filter((event) => event.event === name).map((event) => Date.parse(event.created_at));
|
|
42647
|
+
const adds = times("added_to_merge_queue").filter((at) => at >= boundMs);
|
|
42648
|
+
const removals = adds.length === 1 ? times("removed_from_merge_queue").filter((at) => at > adds[0]) : [];
|
|
42649
|
+
if (removals.length === 1) {
|
|
42650
|
+
journal.save("ejected");
|
|
42651
|
+
return rejected("the previous admission was ejected by GitHub; rerun to submit a new intent");
|
|
42652
|
+
}
|
|
42558
42653
|
}
|
|
42654
|
+
} catch {
|
|
42559
42655
|
}
|
|
42560
|
-
} catch {
|
|
42561
42656
|
}
|
|
42562
42657
|
return unknown(reason);
|
|
42563
42658
|
};
|
|
@@ -42738,6 +42833,19 @@ async function submitQueueAdmission(pr, repo, head, read, write, options, resume
|
|
|
42738
42833
|
error: "GitHub accepted queue admission but has not confirmed queue membership. Rerun pr land to resume the saved operation read-only."
|
|
42739
42834
|
};
|
|
42740
42835
|
}
|
|
42836
|
+
function githubQueueSquashMessage(title, pr, commits) {
|
|
42837
|
+
const list = commits.map((commit) => `* ${commit.headline}
|
|
42838
|
+
|
|
42839
|
+
${commit.body}`.trim()).join("\n\n");
|
|
42840
|
+
return `${title} (#${pr})
|
|
42841
|
+
|
|
42842
|
+
${list}
|
|
42843
|
+
|
|
42844
|
+
---------
|
|
42845
|
+
|
|
42846
|
+
Co-authored-by: GitHub <noreply@github.com>
|
|
42847
|
+
`;
|
|
42848
|
+
}
|
|
42741
42849
|
async function enqueuePrMergeQueue(pr, repo, options = {}) {
|
|
42742
42850
|
if (!/^\d+$/.test(pr)) throw new Error("merge queue enqueue requires a PR number");
|
|
42743
42851
|
pr = pr.replace(/^0+(?=\d)/, "");
|
|
@@ -42853,6 +42961,19 @@ ${pull.body}`;
|
|
|
42853
42961
|
])];
|
|
42854
42962
|
return preparation("queue-message", targets, `GitHub's unchanged ${policy.body} message is unsafe for ${targets.map((number) => `${repo}#${number}`).join(", ")}. ${verdict.message}`);
|
|
42855
42963
|
}
|
|
42964
|
+
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}
|
|
42965
|
+
${message2.body}`)) && !readMessageOverride2(githubQueueSquashMessage(pull.title, pr, messages), process.cwd())) {
|
|
42966
|
+
return {
|
|
42967
|
+
mergeStatus: "failed",
|
|
42968
|
+
preparation: {
|
|
42969
|
+
status: "preparation-needed",
|
|
42970
|
+
reason: "queue-message-override",
|
|
42971
|
+
issueTargets: [],
|
|
42972
|
+
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."
|
|
42973
|
+
},
|
|
42974
|
+
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"
|
|
42975
|
+
};
|
|
42976
|
+
}
|
|
42856
42977
|
const requiredBody = options.bodyText ?? composedBody;
|
|
42857
42978
|
const prBodyPreservesOverrides = requiredBody != null && method === "SQUASH" && policy.body === "PR_BODY" && options.bodyText == null && prBodyPreservesCommitOverrides(messages, headline, pull.body, process.cwd());
|
|
42858
42979
|
if (requiredBody && !prBodyPreservesOverrides && (method !== "SQUASH" || requiredBody.trim() !== body.trim())) {
|
|
@@ -42904,6 +43025,54 @@ async function findPrMergeGroupRun(pr, repo, base, gh2 = readApi) {
|
|
|
42904
43025
|
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
43026
|
return run ? { id: run.id, url: run.html_url, conclusion: run.conclusion } : void 0;
|
|
42906
43027
|
}
|
|
43028
|
+
async function findPrMergeQueueStartupFailure(repo, groupHeadSha, gh2 = readApi) {
|
|
43029
|
+
if (!/^[a-f0-9]{40}$/i.test(groupHeadSha)) return void 0;
|
|
43030
|
+
try {
|
|
43031
|
+
const body = JSON.parse(await gh2([`repos/${repo}/actions/runs?head_sha=${groupHeadSha}&per_page=100`]));
|
|
43032
|
+
const runs2 = (body.workflow_runs ?? []).filter((r) => r.head_sha?.toLowerCase() === groupHeadSha.toLowerCase() && r.status === "completed");
|
|
43033
|
+
for (const run of runs2) {
|
|
43034
|
+
let isStartupFailure = run.conclusion === "startup_failure";
|
|
43035
|
+
if (!isStartupFailure && run.conclusion === "failure") {
|
|
43036
|
+
const jobsBody = JSON.parse(await gh2([`repos/${repo}/actions/runs/${run.id}/jobs?per_page=1`]));
|
|
43037
|
+
if (jobsBody.total_count === 0 && Array.isArray(jobsBody.jobs) && jobsBody.jobs.length === 0) {
|
|
43038
|
+
isStartupFailure = true;
|
|
43039
|
+
}
|
|
43040
|
+
}
|
|
43041
|
+
if (!isStartupFailure) continue;
|
|
43042
|
+
const workflow = (run.path?.replace(/^\.github\/workflows\//, "") || run.name || "workflow").trim();
|
|
43043
|
+
let annotation;
|
|
43044
|
+
if (run.check_suite_id) {
|
|
43045
|
+
try {
|
|
43046
|
+
const checkRuns = JSON.parse(await gh2([`repos/${repo}/check-suites/${run.check_suite_id}/check-runs?per_page=20`]));
|
|
43047
|
+
for (const cr of checkRuns.check_runs ?? []) {
|
|
43048
|
+
const summary = [cr.output?.title, cr.output?.summary].filter(Boolean).join(" \u2014 ").trim();
|
|
43049
|
+
if (summary) {
|
|
43050
|
+
annotation = summary;
|
|
43051
|
+
break;
|
|
43052
|
+
}
|
|
43053
|
+
const anns = JSON.parse(await gh2([`repos/${repo}/check-runs/${cr.id}/annotations?per_page=10`]));
|
|
43054
|
+
const first = Array.isArray(anns) ? anns.find((a) => a?.message || a?.title) : void 0;
|
|
43055
|
+
if (first) {
|
|
43056
|
+
annotation = (first.message || first.title || "").trim();
|
|
43057
|
+
break;
|
|
43058
|
+
}
|
|
43059
|
+
}
|
|
43060
|
+
} catch {
|
|
43061
|
+
}
|
|
43062
|
+
}
|
|
43063
|
+
return {
|
|
43064
|
+
workflow,
|
|
43065
|
+
runId: run.id,
|
|
43066
|
+
headSha: groupHeadSha,
|
|
43067
|
+
...run.html_url ? { url: run.html_url } : {},
|
|
43068
|
+
...annotation ? { annotation } : {}
|
|
43069
|
+
};
|
|
43070
|
+
}
|
|
43071
|
+
} catch {
|
|
43072
|
+
return void 0;
|
|
43073
|
+
}
|
|
43074
|
+
return void 0;
|
|
43075
|
+
}
|
|
42907
43076
|
function workflowNeeds(text) {
|
|
42908
43077
|
const doc = (0, import_yaml3.parse)(text);
|
|
42909
43078
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -42999,6 +43168,9 @@ async function readBaseRedVerdict(result, gh2 = appOnlyReadApi) {
|
|
|
42999
43168
|
return mine && base && failuresAllOnBase(mine, base) ? { baseRunId: String(baseRun.id) } : void 0;
|
|
43000
43169
|
}
|
|
43001
43170
|
function prMergeQueueLine(result) {
|
|
43171
|
+
if (result.queueRunStartupFailure) {
|
|
43172
|
+
return `pr land: PR #${result.pr} ${result.error ?? `queue-run-startup-failure: ${result.queueRunStartupFailure.workflow} run ${result.queueRunStartupFailure.runId} on ${result.queueRunStartupFailure.headSha}`}`;
|
|
43173
|
+
}
|
|
43002
43174
|
const queue = result.mergeQueue;
|
|
43003
43175
|
if (!queue) return void 0;
|
|
43004
43176
|
switch (queue.state) {
|
|
@@ -43206,6 +43378,57 @@ function registerRunCancel(run, writeToken) {
|
|
|
43206
43378
|
else console.log(`run cancel: ${receipt.repo} run ${receipt.runId} \u2014 ${receipt.force ? "force-cancel" : "cancel"} requested (status: ${receipt.status}${receipt.runStatus ? `, runStatus: ${receipt.runStatus}` : ""})`);
|
|
43207
43379
|
});
|
|
43208
43380
|
}
|
|
43381
|
+
var RUN_RERUN_STATUS = "rerun-requested";
|
|
43382
|
+
async function rerunWorkflowRun(repo, id, deps, failed2 = false) {
|
|
43383
|
+
const raw = String(id).trim();
|
|
43384
|
+
if (!/^[1-9]\d*$/.test(raw)) throw new Error(`run rerun: <id> must be a positive workflow run id (received '${String(id)}')`);
|
|
43385
|
+
const runId = Number(raw);
|
|
43386
|
+
if (!Number.isSafeInteger(runId)) throw new Error(`run rerun: <id> ${raw} is beyond the safe integer range; refusing to address a different run`);
|
|
43387
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(repo)) throw new Error(`run rerun: --repo must be owner/repo (received '${repo}')`);
|
|
43388
|
+
const projects = await deps.projects();
|
|
43389
|
+
if (!projects) throw new Error("run rerun: the Hub project registry is unreadable; refusing to rerun on a repo this command cannot prove registered");
|
|
43390
|
+
const key = registryRepoKey(repo);
|
|
43391
|
+
if (!projects.some((p) => (p.repos ?? []).some((r) => typeof r === "string" && registryRepoKey(qualifyRegistryRepo(r)) === key))) {
|
|
43392
|
+
throw new Error(`run rerun: ${repo} is not a registered org repo; App writes are limited to repos in the Hub registry`);
|
|
43393
|
+
}
|
|
43394
|
+
const mode = failed2 ? "failed" : "all";
|
|
43395
|
+
let httpStatus;
|
|
43396
|
+
try {
|
|
43397
|
+
httpStatus = await deps.post(`repos/${repo}/actions/runs/${runId}/${failed2 ? "rerun-failed-jobs" : "rerun"}`);
|
|
43398
|
+
} catch (error) {
|
|
43399
|
+
const e = error;
|
|
43400
|
+
const detail = String(e.stderr ?? error);
|
|
43401
|
+
const code = /\bHTTP(?:\/[\d.]+)? (\d{3})\b/.exec(`${detail} ${e.stdout ?? ""}`)?.[1];
|
|
43402
|
+
if (code === "403") throw new Error(`run rerun: GitHub App denied (HTTP 403) on ${repo}; actions:write permission is required; no personal-token fallback`);
|
|
43403
|
+
if (code === "404") throw new Error(`run rerun: run ${runId} not found on ${repo} (HTTP 404), or the App cannot see that repo`);
|
|
43404
|
+
if (code === "409" || code === "422") throw new Error(`run rerun: run ${runId} is not rerunnable (HTTP ${code}); it may still be in progress, or be past GitHub's rerun window`);
|
|
43405
|
+
throw new Error(`run rerun: ${detail}`);
|
|
43406
|
+
}
|
|
43407
|
+
return { ok: true, repo, runId, mode, httpStatus, credential: "app_installation", status: RUN_RERUN_STATUS };
|
|
43408
|
+
}
|
|
43409
|
+
function appRunPost(writeToken, exec3 = execFileP) {
|
|
43410
|
+
let token;
|
|
43411
|
+
return async (path2) => {
|
|
43412
|
+
token ??= await writeToken();
|
|
43413
|
+
const { stdout } = await exec3("gh", ["api", "--include", "--method", "POST", path2], {
|
|
43414
|
+
timeout: 2e4,
|
|
43415
|
+
env: { ...process.env, GH_TOKEN: token, MMI_GH_POOL: "app" }
|
|
43416
|
+
});
|
|
43417
|
+
return Number(/^HTTP\/[\d.]+ (\d{3})/.exec(stdout)?.[1] ?? 0);
|
|
43418
|
+
};
|
|
43419
|
+
}
|
|
43420
|
+
function registerRunRerun(run, writeToken, projects) {
|
|
43421
|
+
run.command("rerun <id>").description("rerun one workflow run on the org App (actions:write), registered repos only; --failed reruns only the failed jobs; prints a receipt").requiredOption("--repo <owner/repo>", "registered repository that owns the run").option("--failed", "rerun only the failed jobs (POST .../rerun-failed-jobs)").option("--json", "machine-readable receipt").action(async (id, options) => {
|
|
43422
|
+
let receipt;
|
|
43423
|
+
try {
|
|
43424
|
+
receipt = await rerunWorkflowRun(options.repo, id, { projects, post: appRunPost(writeToken) }, options.failed === true);
|
|
43425
|
+
} catch (error) {
|
|
43426
|
+
return fail(error.message);
|
|
43427
|
+
}
|
|
43428
|
+
if (options.json) console.log(JSON.stringify(receipt));
|
|
43429
|
+
else console.log(`run rerun: ${receipt.repo} run ${receipt.runId} \u2014 ${receipt.mode === "failed" ? "failed jobs" : "all jobs"} rerun requested (HTTP ${receipt.httpStatus}, credential: ${receipt.credential})`);
|
|
43430
|
+
});
|
|
43431
|
+
}
|
|
43209
43432
|
|
|
43210
43433
|
// src/board-commands.ts
|
|
43211
43434
|
init_cli_shared();
|
|
@@ -59501,6 +59724,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
|
|
|
59501
59724
|
readMergeQueue: readPrMergeQueue,
|
|
59502
59725
|
observeQueued: (prNumber, repo, snapshot) => observeQueuedAdmission(repo, prNumber, snapshot),
|
|
59503
59726
|
findMergeGroupRun: findPrMergeGroupRun,
|
|
59727
|
+
findQueueStartupFailure: findPrMergeQueueStartupFailure,
|
|
59504
59728
|
queueProgress: (receipt) => {
|
|
59505
59729
|
const line = prMergeQueueLine(receipt);
|
|
59506
59730
|
if (line) (o.json ? console.warn : printLine)(line);
|
|
@@ -65676,15 +65900,18 @@ function formatBrokerRunListRecovery(spec, jq) {
|
|
|
65676
65900
|
}
|
|
65677
65901
|
function brokerGhFailureHint(failureText, args, jq) {
|
|
65678
65902
|
const target = args[0] === "pr" && args[1] === "view" ? args[2] : void 0;
|
|
65903
|
+
const repoIdx = args.indexOf("--repo");
|
|
65904
|
+
const repo = repoIdx >= 0 && args[repoIdx + 1] && !args[repoIdx + 1].startsWith("-") ? args[repoIdx + 1] : void 0;
|
|
65679
65905
|
const unknownField = /Unknown JSON field: "([^"]+)"/.exec(failureText)?.[1] ?? /Unknown JSON field: ([A-Za-z][A-Za-z0-9]*)/.exec(failureText)?.[1];
|
|
65680
65906
|
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`;
|
|
65907
|
+
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
65908
|
}
|
|
65683
65909
|
if (unknownField) {
|
|
65684
65910
|
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
65911
|
}
|
|
65686
65912
|
if (/Could not resolve to a PullRequest/i.test(failureText)) {
|
|
65687
|
-
|
|
65913
|
+
const slug = repo ?? "<owner/repo>";
|
|
65914
|
+
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
65915
|
}
|
|
65689
65916
|
if (args[0] === "run" && args[1] === "list") {
|
|
65690
65917
|
return formatBrokerRunListRecovery({ program: "gh", args: [...args], timeoutMs: SINGLE_SHOT_TIMEOUT_MS }, jq);
|
|
@@ -66062,11 +66289,26 @@ function branchRulesReadHint(path2) {
|
|
|
66062
66289
|
const slug = `${match[1]}/${match[2]}`;
|
|
66063
66290
|
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
66291
|
}
|
|
66292
|
+
function commitsReadHint(path2) {
|
|
66293
|
+
const clean4 = path2.split("?")[0];
|
|
66294
|
+
if (branchRulesReadHint(clean4) || checkRunsReadHint(clean4)) return "";
|
|
66295
|
+
const match = new RegExp(`^repos/(${NAME})/(${NAME})/(?:commits|branches)(?:[/?#]|$)`).exec(clean4);
|
|
66296
|
+
if (!match) return "";
|
|
66297
|
+
const slug = `${match[1]}/${match[2]}`;
|
|
66298
|
+
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\`.`;
|
|
66299
|
+
}
|
|
66300
|
+
function checkRunsReadHint(path2) {
|
|
66301
|
+
const clean4 = path2.split("?")[0];
|
|
66302
|
+
const match = new RegExp(`^repos/(${NAME})/(${NAME})/(?:check-suites(?:/|$)|check-runs(?:/|$)|commits/[^/?#]+/(?:check-runs|check-suites)(?:/|$))`).exec(clean4);
|
|
66303
|
+
if (!match) return "";
|
|
66304
|
+
const slug = `${match[1]}/${match[2]}`;
|
|
66305
|
+
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).`;
|
|
66306
|
+
}
|
|
66065
66307
|
function parseApiGetPath(rawPath) {
|
|
66066
66308
|
const path2 = rawPath.trim();
|
|
66067
66309
|
const refuse = () => {
|
|
66068
66310
|
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}`;
|
|
66311
|
+
const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${commitsReadHint(path2)}${checkRunsReadHint(path2)}${hint}`;
|
|
66070
66312
|
return new Error(`api get: refusing '${rawPath}' \u2014${routeHints}${API_GET_RECOVERY} Only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.`);
|
|
66071
66313
|
};
|
|
66072
66314
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
@@ -69695,13 +69937,15 @@ function envelopeAwareWriteErr(str2) {
|
|
|
69695
69937
|
const flag = match[1];
|
|
69696
69938
|
const argv = process.argv.slice(2);
|
|
69697
69939
|
const invoked = resolveCommandFromArgv(program2, argv);
|
|
69698
|
-
const
|
|
69940
|
+
const invokedPath = invoked ? canonicalPathFor(commandPath3(invoked)) ?? commandPath3(invoked) : void 0;
|
|
69941
|
+
const canonicalArgv = canonicalArgvFor(argv);
|
|
69942
|
+
const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, invokedPath, canonicalArgv) ?? issueCommentAppendOnlyRecovery(flag, invokedPath, canonicalArgv) : void 0;
|
|
69699
69943
|
if (claimStatus) {
|
|
69700
69944
|
if (argvWantsJson3()) {
|
|
69701
69945
|
process.stderr.write(
|
|
69702
69946
|
formatErrorEnvelope(claimStatus.message, {
|
|
69703
69947
|
code: ERROR_CODES.ERR_UNKNOWN_FLAG,
|
|
69704
|
-
offending_flag: "
|
|
69948
|
+
offending_flag: flag.split("=")[0],
|
|
69705
69949
|
corrected_command: claimStatus.corrected
|
|
69706
69950
|
}) + "\n"
|
|
69707
69951
|
);
|
|
@@ -69784,7 +70028,7 @@ registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand
|
|
|
69784
70028
|
appActorCommand = commandPath4;
|
|
69785
70029
|
setAuditCommand(commandPath4);
|
|
69786
70030
|
if (commandPath4 !== "whoami diagnose-hub") emitCliCallTelemetry(commandPath4);
|
|
69787
|
-
if (commandPath4 === "ci cancel-orphans" || commandPath4 === "run cancel") return;
|
|
70031
|
+
if (commandPath4 === "ci cancel-orphans" || commandPath4 === "run cancel" || commandPath4 === "run rerun") return;
|
|
69788
70032
|
try {
|
|
69789
70033
|
await activateAppActor(commandPath4, process.env, () => APP_WRITE_COMMANDS.has(commandPath4) ? mintCommandWriteToken(appActorDeps(), commandPath4) : mintInstallationToken(appActorDeps()));
|
|
69790
70034
|
} catch (e) {
|
|
@@ -70619,9 +70863,12 @@ ci.command("audit").description("read-only fleet scan: gate workflow, ruleset co
|
|
|
70619
70863
|
});
|
|
70620
70864
|
registerCiChecksWait(ci);
|
|
70621
70865
|
registerCancelOrphans(ci, async () => (await mintCommandWriteToken(appActorDeps(), "ci cancel-orphans")).token);
|
|
70622
|
-
|
|
70623
|
-
|
|
70624
|
-
|
|
70866
|
+
var runRoot = program2.command("run").description("bounded workflow-run writes \u2014 `run cancel <id>` and `run rerun <id> [--failed]`, both on the org App actions:write profile");
|
|
70867
|
+
registerRunCancel(runRoot, async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run cancel")).token);
|
|
70868
|
+
registerRunRerun(
|
|
70869
|
+
runRoot,
|
|
70870
|
+
async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run rerun")).token,
|
|
70871
|
+
async () => fetchProjectsList(registryClientDeps(await loadConfig()))
|
|
70625
70872
|
);
|
|
70626
70873
|
ci.command("reconcile").description("audit + optionally apply merge settings and product ruleset activation (master-admin)").option("--json", "machine-readable output").option("--repo <owner/repo>", "reconcile one repo instead of the full registry").option("--apply", "PATCH merge settings + activate product ruleset when missing (master role required); activation is skipped while the repo's gate has never passed (#3694)").option("--park-ruleset", "stop the product ruleset enforcing without deleting it \u2014 keeps its required contexts (master role required)").action(async (o) => {
|
|
70627
70874
|
if (o.apply && o.parkRuleset) {
|
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
|
};
|
|
@@ -8224,10 +8226,15 @@ function checkRunbookCoverage(docs, base, git) {
|
|
|
8224
8226
|
const heading = headings[i];
|
|
8225
8227
|
const end = headings.slice(i + 1).find((h) => h.level <= heading.level)?.start ?? lines.length + 1;
|
|
8226
8228
|
const markerEnd = headings[i + 1]?.start ?? end;
|
|
8227
|
-
const
|
|
8229
|
+
const markers = lines.slice(heading.start, markerEnd - 1).flatMap((text, j) => {
|
|
8230
|
+
const found = /<!--\s*runbook-covers:\s*(.*?)\s*-->/.exec(text)?.[1].split(",").map((s) => s.trim());
|
|
8231
|
+
return found ? [{ line: heading.start + 1 + j, globs: found }] : [];
|
|
8232
|
+
});
|
|
8233
|
+
const globs = markers.flatMap((m) => m.globs);
|
|
8228
8234
|
const affected = changed.filter((path2) => globs.some((glob) => matches(glob, path2)) && !changesOnlyUses(path2));
|
|
8229
8235
|
if (affected.length && !ranges.some((r) => r.count > 0 && r.start < end && r.start + r.count > heading.start)) {
|
|
8230
|
-
|
|
8236
|
+
const markerLine = markers.find((m) => m.globs.some((glob) => affected.some((path2) => matches(glob, path2)))).line;
|
|
8237
|
+
findings.push({ kind: "runbook-section-stale", doc, line: markerLine, detail: `${heading.title} (lines ${heading.start}\u2013${end - 1}, covers marker at line ${markerLine}): changed ${affected.join(", ")}; update this section's text (lines ${heading.start}\u2013${end - 1}) or runbook-covers list, or add a \`Runbook-Unaffected: <reason>\` trailer to a commit message in this PR`, section: { heading: heading.title, level: heading.level, startLine: heading.start, endLine: end - 1, covers: globs }, sources: affected });
|
|
8231
8238
|
}
|
|
8232
8239
|
}
|
|
8233
8240
|
}
|
package/package.json
CHANGED