@mutmutco/cli 4.6.12 → 4.6.14
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 +282 -55
- package/dist/runbook-sweep.cjs +10 -8
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -1674,6 +1674,7 @@ __export(board_write_exports, {
|
|
|
1674
1674
|
BoardAttachError: () => BoardAttachError,
|
|
1675
1675
|
addIssueToProjectViaHub: () => addIssueToProjectViaHub,
|
|
1676
1676
|
boardAttachHttpError: () => boardAttachHttpError,
|
|
1677
|
+
createBoardIssueViaHub: () => createBoardIssueViaHub,
|
|
1677
1678
|
writeHubBoardField: () => writeHubBoardField
|
|
1678
1679
|
});
|
|
1679
1680
|
async function writeHubBoardField(request) {
|
|
@@ -1715,6 +1716,18 @@ function boardAttachHttpError(status, detail) {
|
|
|
1715
1716
|
const code = status === 401 ? "hub_session_missing" : status === 403 ? "forbidden" : status === 429 ? "rate_limited" : status >= 500 ? "hub_unavailable" : "refused";
|
|
1716
1717
|
return new BoardAttachError(code, status, detail);
|
|
1717
1718
|
}
|
|
1719
|
+
async function createBoardIssueViaHub(request) {
|
|
1720
|
+
const config = await loadConfig();
|
|
1721
|
+
const res = await fetch(`${config.sagaApiUrl?.replace(/\/$/, "")}/board/issues`, {
|
|
1722
|
+
method: "POST",
|
|
1723
|
+
headers: await hubHeaders({ "content-type": "application/json" }),
|
|
1724
|
+
body: JSON.stringify(request),
|
|
1725
|
+
signal: AbortSignal.timeout(25e3)
|
|
1726
|
+
});
|
|
1727
|
+
const body = await res.json().catch(() => null);
|
|
1728
|
+
if (!res.ok || !body?.number || !body.url) throw new Error(`board issue create HTTP ${res.status}: ${body?.error ?? "unconfirmed result"}`);
|
|
1729
|
+
return { number: body.number, url: body.url, projectItemId: body.projectItemId, onBoard: Boolean(body.onBoard) };
|
|
1730
|
+
}
|
|
1718
1731
|
var BOARD_ATTACH_HINTS, BoardAttachError;
|
|
1719
1732
|
var init_board_write = __esm({
|
|
1720
1733
|
"src/board-write.ts"() {
|
|
@@ -1723,7 +1736,7 @@ var init_board_write = __esm({
|
|
|
1723
1736
|
BOARD_ATTACH_HINTS = {
|
|
1724
1737
|
// #8965: honest, not a promise — attach did NOT run. App automation never has a Hub session by design.
|
|
1725
1738
|
hub_session_missing: "No Hub session (an App installation token cannot open one). Hub adds new issues in registered repos to their board automatically when they are opened; check membership with `mmi-cli oracle board show <n> --repo <owner/repo>`. A human login or MMI_HUB_TOKEN (Actions /auth/session-ci) is needed only to attach older or transferred issues.",
|
|
1726
|
-
forbidden: "the Hub denied this login: attach needs org-owner
|
|
1739
|
+
forbidden: "the Hub denied this login: attach needs org-owner, repo-admin or verified repository write authority on <owner/repo>",
|
|
1727
1740
|
hub_unavailable: "the Hub or GitHub failed transiently; the add is idempotent, so rerun `mmi-cli oracle board attach <issue>`",
|
|
1728
1741
|
rate_limited: "GitHub quota is exhausted; rerun `mmi-cli oracle board attach <issue>` after the reset",
|
|
1729
1742
|
refused: "the Hub refused this attach; check the board registration for <owner/repo>"
|
|
@@ -13867,6 +13880,49 @@ function boardClaimStatusFlagRecovery(flag, canonicalPath, argv) {
|
|
|
13867
13880
|
corrected: `mmi-cli ${kept.join(" ")}`
|
|
13868
13881
|
};
|
|
13869
13882
|
}
|
|
13883
|
+
function boardClaimLaneFlagRecovery(flag, canonicalPath, argv) {
|
|
13884
|
+
if (canonicalPath !== "oracle board claim") return void 0;
|
|
13885
|
+
if (!/^--(?:agent|actor|on-behalf)(?:=|$)/.test(flag)) return void 0;
|
|
13886
|
+
const baseFlag = flag.split("=")[0];
|
|
13887
|
+
const kept = [];
|
|
13888
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13889
|
+
const tok = argv[i];
|
|
13890
|
+
if (tok.startsWith(`${baseFlag}=`)) {
|
|
13891
|
+
kept.push(`--for=${tok.slice(baseFlag.length + 1)}`);
|
|
13892
|
+
continue;
|
|
13893
|
+
}
|
|
13894
|
+
if (tok === baseFlag) {
|
|
13895
|
+
kept.push("--for");
|
|
13896
|
+
if (i + 1 >= argv.length || argv[i + 1].startsWith("-")) kept.push("<login>");
|
|
13897
|
+
continue;
|
|
13898
|
+
}
|
|
13899
|
+
kept.push(tok);
|
|
13900
|
+
}
|
|
13901
|
+
return {
|
|
13902
|
+
message: `unknown option '${baseFlag}' \u2014 \`oracle board claim\` declares lane attribution as \`--for <login>\` ('assign to this login instead of @me \u2014 agent claims on behalf of the master')`,
|
|
13903
|
+
corrected: `mmi-cli ${kept.join(" ")}`
|
|
13904
|
+
};
|
|
13905
|
+
}
|
|
13906
|
+
var BOARD_CLAIM_AGENT_FLAGS = /* @__PURE__ */ new Set(["--agent", "--lane", "--by"]);
|
|
13907
|
+
function boardClaimAgentFlagRecovery(flag, canonicalPath, argv) {
|
|
13908
|
+
if (canonicalPath !== "oracle board claim") return void 0;
|
|
13909
|
+
const bare = flag.split("=")[0];
|
|
13910
|
+
if (!BOARD_CLAIM_AGENT_FLAGS.has(bare)) return void 0;
|
|
13911
|
+
const kept = [];
|
|
13912
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13913
|
+
const tok = argv[i];
|
|
13914
|
+
if (tok.startsWith(`${bare}=`)) continue;
|
|
13915
|
+
if (tok === bare) {
|
|
13916
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) i++;
|
|
13917
|
+
continue;
|
|
13918
|
+
}
|
|
13919
|
+
kept.push(tok);
|
|
13920
|
+
}
|
|
13921
|
+
return {
|
|
13922
|
+
message: `unknown option '${bare}' \u2014 \`oracle board claim\` stamps the claiming session itself \u2014 drop ${bare} and its value; \`--for <login>\` assigns on behalf of another login`,
|
|
13923
|
+
corrected: `mmi-cli ${kept.join(" ")}`
|
|
13924
|
+
};
|
|
13925
|
+
}
|
|
13870
13926
|
function issueCommentAppendOnlyRecovery(flag, canonicalPath, argv) {
|
|
13871
13927
|
if (canonicalPath !== "oracle issue comment") return void 0;
|
|
13872
13928
|
const hasEditVerb = argv.some((t, idx) => idx >= 3 && !t.startsWith("-") && /^(?:edit|update|patch|modify)$/i.test(t));
|
|
@@ -18983,9 +19039,10 @@ function surfaceInferenceSuffix(inferred) {
|
|
|
18983
19039
|
}
|
|
18984
19040
|
function uncertainSurfaceRefusal(inferred, command = "issue create") {
|
|
18985
19041
|
if (!inferred.uncertain) return void 0;
|
|
19042
|
+
const bareName = inferred.label.toLowerCase().startsWith(SURFACE_PREFIX) ? inferred.label.slice(SURFACE_PREFIX.length) : inferred.label;
|
|
18986
19043
|
const alternatives = inferred.alternatives?.length ? ` Other candidates: ${inferred.alternatives.join(", ")}.` : "";
|
|
18987
19044
|
return {
|
|
18988
|
-
message: `${command}: surface inference is uncertain (${inferred.reason}) \u2014 pass --surface <value> to choose explicitly or --no-surface only when the filing is genuinely exempt.${alternatives}`,
|
|
19045
|
+
message: `${command}: surface inference is uncertain (${inferred.reason}) \u2014 pass --surface <value> to choose explicitly or --no-surface only when the filing is genuinely exempt. The closest match was ${inferred.label} \u2014 re-run the same create with --surface ${bareName}.${alternatives}`,
|
|
18989
19046
|
payload: {
|
|
18990
19047
|
code: ERROR_CODES.ERR_MISSING_FLAG,
|
|
18991
19048
|
offending_flag: "--surface",
|
|
@@ -25459,7 +25516,7 @@ var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
|
25459
25516
|
var BLESSED_KIT_AGENT_FILES_SHA = "e976df208d891b215844386c1cac0f161946b6fa";
|
|
25460
25517
|
var BLESSED_JEV_FAILURES_SHA = "d5278c6133318b33727483ddfdfddc8f841e3ef6";
|
|
25461
25518
|
var BLESSED_VERDICT_RECEIPT_SHA = "";
|
|
25462
|
-
var BLESSED_RUNNER_CHOICE_SHA = "
|
|
25519
|
+
var BLESSED_RUNNER_CHOICE_SHA = "cf35db6cdf053746bc5b2cd75f241a7d93cf7f48";
|
|
25463
25520
|
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
25464
25521
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
25465
25522
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
@@ -27010,6 +27067,24 @@ async function reconcileOrgNoAgentFilesRuleset(plan, client, org = ORG_LOGIN) {
|
|
|
27010
27067
|
};
|
|
27011
27068
|
}
|
|
27012
27069
|
|
|
27070
|
+
// src/registry-repositories.ts
|
|
27071
|
+
function qualifyRegistryRepo(raw) {
|
|
27072
|
+
return raw.includes("/") ? raw : `mutmutco/${raw}`;
|
|
27073
|
+
}
|
|
27074
|
+
function registryRepoKey(repo) {
|
|
27075
|
+
return repo.toLowerCase();
|
|
27076
|
+
}
|
|
27077
|
+
function repoProjectClaim(project2, repoKey) {
|
|
27078
|
+
if (!project2) return 0;
|
|
27079
|
+
const slug = typeof project2.slug === "string" ? project2.slug.toLowerCase() : void 0;
|
|
27080
|
+
if (slug !== void 0 && slug === repoKey.split("/")[1]) return 3;
|
|
27081
|
+
if (project2.projectType !== "hub-service") return 2;
|
|
27082
|
+
return 1;
|
|
27083
|
+
}
|
|
27084
|
+
function preferPrimaryRepoProject(current, candidate, repoKey) {
|
|
27085
|
+
return repoProjectClaim(candidate, repoKey) > repoProjectClaim(current, repoKey) ? candidate : current;
|
|
27086
|
+
}
|
|
27087
|
+
|
|
27013
27088
|
// src/safe-json.ts
|
|
27014
27089
|
function safeJson(text, fallback) {
|
|
27015
27090
|
try {
|
|
@@ -27291,17 +27366,23 @@ async function auditOrgAccess(targets, deps, matrix = {}, dataAccess, sanctioned
|
|
|
27291
27366
|
}
|
|
27292
27367
|
function loadAccessTargets(projectsJson) {
|
|
27293
27368
|
const projects = safeJson(projectsJson, {}).projects ?? [];
|
|
27294
|
-
const
|
|
27295
|
-
const targets = [];
|
|
27369
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
27296
27370
|
for (const project2 of projects) {
|
|
27297
27371
|
for (const repo of project2.repos ?? []) {
|
|
27298
|
-
|
|
27299
|
-
|
|
27300
|
-
|
|
27301
|
-
|
|
27302
|
-
|
|
27372
|
+
const key = repo.toLowerCase();
|
|
27373
|
+
const existing = byRepo.get(key);
|
|
27374
|
+
byRepo.set(key, {
|
|
27375
|
+
repo: existing?.repo ?? repo,
|
|
27376
|
+
project: preferPrimaryRepoProject(existing?.project, project2, key)
|
|
27377
|
+
});
|
|
27303
27378
|
}
|
|
27304
27379
|
}
|
|
27380
|
+
const targets = [];
|
|
27381
|
+
for (const { repo, project: project2 } of byRepo.values()) {
|
|
27382
|
+
const cls = project2.class === "content" || project2.deployModel === "content" ? "content" : "deployable";
|
|
27383
|
+
const releaseTrack = cls === "content" ? "trunk" : resolveReleaseTrack(project2, void 0, repo);
|
|
27384
|
+
targets.push({ repo, class: cls, releaseTrack });
|
|
27385
|
+
}
|
|
27305
27386
|
return targets;
|
|
27306
27387
|
}
|
|
27307
27388
|
function resolveOrgAuditTargets(registryProjects) {
|
|
@@ -27665,10 +27746,10 @@ var rollout_plan_default = {
|
|
|
27665
27746
|
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)."
|
|
27666
27747
|
},
|
|
27667
27748
|
baseline: {
|
|
27668
|
-
version: "4.6.
|
|
27669
|
-
tag: "v4.6.
|
|
27670
|
-
commit: "
|
|
27671
|
-
npm: "@mutmutco/cli@4.6.
|
|
27749
|
+
version: "4.6.14",
|
|
27750
|
+
tag: "v4.6.14",
|
|
27751
|
+
commit: "5bbc73222ade",
|
|
27752
|
+
npm: "@mutmutco/cli@4.6.14"
|
|
27672
27753
|
},
|
|
27673
27754
|
exitCriterion: "fleet-n-of-n",
|
|
27674
27755
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27685,14 +27766,14 @@ var rollout_plan_default = {
|
|
|
27685
27766
|
repo: "mutmutco/mmi-hub",
|
|
27686
27767
|
role: "canary",
|
|
27687
27768
|
schedule: "train",
|
|
27688
|
-
v3Target: "v4.6.
|
|
27769
|
+
v3Target: "v4.6.14"
|
|
27689
27770
|
}
|
|
27690
27771
|
],
|
|
27691
27772
|
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.",
|
|
27692
27773
|
rollback: {
|
|
27693
27774
|
independent: true,
|
|
27694
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
27695
|
-
v3Target: "v4.6.
|
|
27775
|
+
mechanism: "npm dist-tag latest -> 4.6.14 and redeploy the Hub Lambda from tag v4.6.14 (5bbc73222ade); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
27776
|
+
v3Target: "v4.6.14 (@mutmutco/cli@4.6.14, tag commit 5bbc73222ade \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
27696
27777
|
}
|
|
27697
27778
|
},
|
|
27698
27779
|
{
|
|
@@ -27923,7 +28004,10 @@ function buildOrgVersionsReport(projects, probes, generatedAt) {
|
|
|
27923
28004
|
name: String(project2.name ?? project2.slug ?? "?")
|
|
27924
28005
|
});
|
|
27925
28006
|
}
|
|
27926
|
-
for (const repo of repos2)
|
|
28007
|
+
for (const repo of repos2) {
|
|
28008
|
+
const key = repo.trim().toLowerCase();
|
|
28009
|
+
projectByRepo.set(key, preferPrimaryRepoProject(projectByRepo.get(key), project2, key));
|
|
28010
|
+
}
|
|
27927
28011
|
}
|
|
27928
28012
|
const repos = orgVersionRepos(projects).map((repo) => {
|
|
27929
28013
|
const project2 = projectByRepo.get(repo.toLowerCase());
|
|
@@ -28942,7 +29026,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
28942
29026
|
}
|
|
28943
29027
|
for (const repo of repos2) {
|
|
28944
29028
|
const key = repo.trim().toLowerCase();
|
|
28945
|
-
projectByRepo.set(key, projectByRepo.get(key)
|
|
29029
|
+
projectByRepo.set(key, preferPrimaryRepoProject(projectByRepo.get(key), project2, key));
|
|
28946
29030
|
}
|
|
28947
29031
|
}
|
|
28948
29032
|
const repos = orgVersionRepos(projects).map((repo) => {
|
|
@@ -29030,15 +29114,16 @@ function defaultFleetTrackInventoryDeps(consistent = false) {
|
|
|
29030
29114
|
async function readFleetTrackInventory(deps = defaultFleetTrackInventoryDeps()) {
|
|
29031
29115
|
const projects = await deps.projects();
|
|
29032
29116
|
const repos = orgVersionRepos(projects);
|
|
29033
|
-
const
|
|
29117
|
+
const projectByRepo = /* @__PURE__ */ new Map();
|
|
29034
29118
|
for (const project2 of projects) {
|
|
29035
29119
|
for (const repo of (project2.repos ?? []).filter(validRepo2)) {
|
|
29036
29120
|
const key = repo.trim().toLowerCase();
|
|
29037
|
-
|
|
29038
|
-
branchByRepo.set(key, typeof project2.branch === "string" ? project2.branch : void 0);
|
|
29039
|
-
}
|
|
29121
|
+
projectByRepo.set(key, preferPrimaryRepoProject(projectByRepo.get(key), project2, key));
|
|
29040
29122
|
}
|
|
29041
29123
|
}
|
|
29124
|
+
const branchByRepo = new Map(
|
|
29125
|
+
[...projectByRepo].map(([key, project2]) => [key, typeof project2.branch === "string" ? project2.branch : void 0])
|
|
29126
|
+
);
|
|
29042
29127
|
const [releaseProbes, pluginProbes, onboardingProbes] = await Promise.all([
|
|
29043
29128
|
mapBounded2(repos, 6, (repo) => readOrgVersionProbe(deps.query, repo)),
|
|
29044
29129
|
mapBounded2(repos, 6, (repo) => readFleetPluginProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase()))),
|
|
@@ -32267,7 +32352,7 @@ var STANDARD_HOSTED_ALLOWLIST = [
|
|
|
32267
32352
|
["mutmutco/MMI-Hub", ".github/workflows/launcher-build.yml", "build", "windows-latest"],
|
|
32268
32353
|
["mutmutco/Jerv-JervCode", ".github/workflows/release-archives.yml", "release-platform-arm64", "windows-11-arm"]
|
|
32269
32354
|
];
|
|
32270
|
-
var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*'([^']+)'\s*\|\|\s*)?fromJSON\(needs\.([\w-]+)\.outputs\.(
|
|
32355
|
+
var HOSTED_SPILL_RE = /^\$\{\{\s*(?:matrix\.[\w-]+\s*==\s*'[\w-]+'\s*&&\s*(?:'(?<leg>[^']+)'|fromJSON\(needs\.(?<legDecider>[\w-]+)\.outputs\.(?<legOutput>\w+_runs_on)\s*\|\|\s*(?<legOwned>'[^']+')\))\s*\|\|\s*)?fromJSON\(needs\.(?<decider>[\w-]+)\.outputs\.(?<output>\w+_runs_on)\s*\|\|\s*(?<owned>'[^']+')\)\s*\}\}$/;
|
|
32271
32356
|
var RUNNER_CHOICE_ACTION = "mutmutco/MMI-Hub/.github/actions/runner-choice";
|
|
32272
32357
|
function runnerChoiceProduces(job, decider, output, source) {
|
|
32273
32358
|
const needs = job.needs;
|
|
@@ -32297,8 +32382,11 @@ function classifyJobRunnerCost(job, allowance2, source) {
|
|
|
32297
32382
|
if (approved(raw) || allowance2 === "windows-11-arm" && raw === allowance2) return "standard-hosted";
|
|
32298
32383
|
const fallback = /^\$\{\{\s*vars\.MMI_RUNNER_FALLBACK\s*==\s*'hosted'\s*&&\s*'([^']+)'\s*\|\|\s*(fromJSON\('[^']+'\))\s*\}\}$/.exec(raw);
|
|
32299
32384
|
if (fallback && approved(fallback[1]) && classifyRunnerCost("${{ " + fallback[2] + " }}") === "self-hosted") return "standard-hosted";
|
|
32300
|
-
const
|
|
32301
|
-
if (
|
|
32385
|
+
const layerZero = /^\$\{\{\s*vars\.[\w-]+\s*==\s*'off'\s*&&\s*(fromJSON\('[^']+'\))\s*\|\|\s*'([^']+)'\s*\}\}$/.exec(raw);
|
|
32386
|
+
if (layerZero && classifyRunnerCost("${{ " + layerZero[1] + " }}") === "self-hosted" && STANDARD_HOSTED_LABEL_RE.test(layerZero[2])) return "standard-hosted";
|
|
32387
|
+
const spill = HOSTED_SPILL_RE.exec(raw)?.groups;
|
|
32388
|
+
const legOk = (g) => g.legDecider ? classifyRunnerCost(`\${{ fromJSON(${g.legOwned}) }}`) === "self-hosted" && Boolean(source) && runnerChoiceProduces(job, g.legDecider, g.legOutput, source) : !g.leg || STANDARD_HOSTED_LABEL_RE.test(g.leg);
|
|
32389
|
+
if (spill && source && legOk(spill) && classifyRunnerCost(`\${{ fromJSON(${spill.owned}) }}`) === "self-hosted" && runnerChoiceProduces(job, spill.decider, spill.output, source)) return "standard-hosted";
|
|
32302
32390
|
const direct = classifyRunnerCost(raw);
|
|
32303
32391
|
const matrix = /^\$\{\{\s*matrix\.([\w-]+)(?:\.([\w-]+))?\s*\}\}$/.exec(raw);
|
|
32304
32392
|
if (!matrix || direct === "hosted") return direct;
|
|
@@ -39110,14 +39198,6 @@ function renderPinDiffs(tick) {
|
|
|
39110
39198
|
return tick.diffs.map((d) => [`${d.repo}:`, ...d.files.flatMap((f) => f.changes.map((c) => ` ${f.path}:${c.line} ${c.action} ${c.from === "development" ? "@development" : c.from.slice(0, 12)} -> ${c.to.slice(0, 12)}`))].join("\n")).join("\n");
|
|
39111
39199
|
}
|
|
39112
39200
|
|
|
39113
|
-
// src/registry-repositories.ts
|
|
39114
|
-
function qualifyRegistryRepo(raw) {
|
|
39115
|
-
return raw.includes("/") ? raw : `mutmutco/${raw}`;
|
|
39116
|
-
}
|
|
39117
|
-
function registryRepoKey(repo) {
|
|
39118
|
-
return repo.toLowerCase();
|
|
39119
|
-
}
|
|
39120
|
-
|
|
39121
39201
|
// src/ci-audit.ts
|
|
39122
39202
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
39123
39203
|
var HUB_REPO5 = "mutmutco/MMI-Hub";
|
|
@@ -39186,6 +39266,92 @@ function auditRunnerFallbacks(repo, files, closure) {
|
|
|
39186
39266
|
remediation: f.cost === "hosted" || f.cost === "unknown" ? "Resolve source reads and unsupported selectors/callees; preserve checks and artifacts. See gh-runner-runbook.md." : void 0
|
|
39187
39267
|
}));
|
|
39188
39268
|
}
|
|
39269
|
+
var yamlStrings = (value) => typeof value === "string" ? [value] : value && typeof value === "object" ? Object.values(value).flatMap(yamlStrings) : [];
|
|
39270
|
+
var survivesDeciderFailure = (job) => {
|
|
39271
|
+
const guard = typeof job.if === "string" ? job.if.replace(/^\s*\$\{\{\s*|\s*\}\}\s*$/g, "").trim() : "";
|
|
39272
|
+
return (guard === "!cancelled()" || guard.startsWith("!cancelled() &&") || /\balways\(\)/.test(guard)) && !/\bsuccess\(\)/.test(guard);
|
|
39273
|
+
};
|
|
39274
|
+
function auditDeciderSkipTrap(repo, files) {
|
|
39275
|
+
const checks = [];
|
|
39276
|
+
for (const { path: path2, body } of files) {
|
|
39277
|
+
let doc;
|
|
39278
|
+
try {
|
|
39279
|
+
doc = (0, import_yaml2.parse)(body);
|
|
39280
|
+
} catch {
|
|
39281
|
+
continue;
|
|
39282
|
+
}
|
|
39283
|
+
const jobs = plainRecord(doc) && plainRecord(doc.jobs) ? Object.entries(doc.jobs).filter(([, j]) => plainRecord(j)) : [];
|
|
39284
|
+
const deciders = /* @__PURE__ */ new Set();
|
|
39285
|
+
for (const [id, job] of jobs) {
|
|
39286
|
+
const selector = [
|
|
39287
|
+
typeof job["runs-on"] === "string" ? job["runs-on"] : "",
|
|
39288
|
+
...typeof job.uses === "string" ? yamlStrings(job.with) : []
|
|
39289
|
+
].join("\n");
|
|
39290
|
+
const found = [...new Set([...selector.matchAll(/needs\.([\w-]+)\.outputs\.\w+_runs_on/g)].map((m) => m[1]))];
|
|
39291
|
+
if (!found.length) continue;
|
|
39292
|
+
for (const d of found) deciders.add(d);
|
|
39293
|
+
const guard = typeof job.if === "string" ? job.if.replace(/^\s*\$\{\{\s*|\s*\}\}\s*$/g, "").trim() : "";
|
|
39294
|
+
const guarded = survivesDeciderFailure(job);
|
|
39295
|
+
checks.push({
|
|
39296
|
+
ok: guarded,
|
|
39297
|
+
label: `job reading decider ${found.map((d) => `needs.${d}`).join(", ")} outputs survives a failed decider (#9670): ${repo}/${path2}:${id}`,
|
|
39298
|
+
detail: guarded ? void 0 : `${id}: ${guard ? `if: \`${guard}\`` : "no if:"} lets a failed decider SKIP this job, and a skipped required check counts as passing`,
|
|
39299
|
+
remediation: guarded ? void 0 : `Start the job's if: with \`!cancelled() &&\` (or use \`always()\`), never \`success()\`, so it runs on the owned fallback when ${found[0]} fails. See gh-runner-runbook.md.`
|
|
39300
|
+
});
|
|
39301
|
+
if (typeof job.uses === "string") {
|
|
39302
|
+
const uses = job.uses;
|
|
39303
|
+
const callee = uses.startsWith("./.github/workflows/") ? files.find((file) => file.path === uses.slice(2)) : void 0;
|
|
39304
|
+
let target;
|
|
39305
|
+
if (callee) {
|
|
39306
|
+
try {
|
|
39307
|
+
target = (0, import_yaml2.parse)(callee.body);
|
|
39308
|
+
} catch {
|
|
39309
|
+
}
|
|
39310
|
+
}
|
|
39311
|
+
const targetJobs = plainRecord(target) && plainRecord(target.jobs) ? target.jobs : void 0;
|
|
39312
|
+
if (!targetJobs) checks.push({
|
|
39313
|
+
ok: false,
|
|
39314
|
+
label: `unverified decider consumer (#9678): ${repo}/${path2}:${id}`,
|
|
39315
|
+
detail: `${id}: reusable workflow ${job.uses} is unresolved; decider runner inputs cannot be verified`
|
|
39316
|
+
});
|
|
39317
|
+
else {
|
|
39318
|
+
const inputs = plainRecord(job.with) ? Object.entries(job.with).filter(([, value]) => yamlStrings(value).some((text) => /needs\.[\w-]+\.outputs\.\w+_runs_on/.test(text))).map(([name]) => name) : [];
|
|
39319
|
+
for (const [targetId, targetJob] of Object.entries(targetJobs)) {
|
|
39320
|
+
if (!plainRecord(targetJob) || typeof targetJob["runs-on"] !== "string") continue;
|
|
39321
|
+
if (!inputs.some((name) => new RegExp(`inputs\\.${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`).test(targetJob["runs-on"]))) continue;
|
|
39322
|
+
const ok = survivesDeciderFailure(targetJob);
|
|
39323
|
+
checks.push({
|
|
39324
|
+
ok,
|
|
39325
|
+
label: `reusable decider consumer survives a failed decider (#9678): ${repo}/${callee.path}:${targetId}`,
|
|
39326
|
+
detail: ok ? void 0 : `${id} -> ${targetId}: callee runner input needs !cancelled() or always(), never success()`
|
|
39327
|
+
});
|
|
39328
|
+
}
|
|
39329
|
+
}
|
|
39330
|
+
}
|
|
39331
|
+
}
|
|
39332
|
+
if (deciders.size) {
|
|
39333
|
+
const possibleResult = /\btoJSON\s*\(\s*needs\s*\)|\bneeds\s*\.\s*\*\s*\.\s*result\b|\bneeds(?:\.[\w-]+)?\s*\[\s*(?!['"][\w-]+['"]\s*\])/;
|
|
39334
|
+
for (const [id, job] of jobs) if (yamlStrings(job).some((text) => possibleResult.test(text))) {
|
|
39335
|
+
checks.push({
|
|
39336
|
+
ok: false,
|
|
39337
|
+
label: `possible decider result read (#9678): ${repo}/${path2}:${id}`,
|
|
39338
|
+
detail: `${id}: toJSON(needs), wildcard result or dynamic needs read may expose a failed decider result; use only explicit decider outputs`
|
|
39339
|
+
});
|
|
39340
|
+
}
|
|
39341
|
+
}
|
|
39342
|
+
for (const decider of deciders) {
|
|
39343
|
+
const read = new RegExp(String.raw`needs(?:\.${decider}|\[\s*['"]${decider}['"]\s*\])(?:\.result|\[\s*['"]result['"]\s*\])`);
|
|
39344
|
+
const readers = jobs.filter(([, job]) => yamlStrings(job).some((text) => read.test(text))).map(([id]) => id);
|
|
39345
|
+
checks.push({
|
|
39346
|
+
ok: readers.length === 0,
|
|
39347
|
+
label: `needs.${decider}.result is never read (#9670): ${repo}/${path2}`,
|
|
39348
|
+
detail: readers.length ? `${readers.join(", ")} read needs.${decider}.result; a decider that failed to start has no result to trust` : void 0,
|
|
39349
|
+
remediation: readers.length ? `Remove the needs.${decider}.result read from ${readers.join(", ")}; the empty ${decider} output already selects the owned fallback. See gh-runner-runbook.md.` : void 0
|
|
39350
|
+
});
|
|
39351
|
+
}
|
|
39352
|
+
}
|
|
39353
|
+
return checks;
|
|
39354
|
+
}
|
|
39189
39355
|
var LIVE_SELF_HOSTED_LINUX_LABELS = /* @__PURE__ */ new Set([
|
|
39190
39356
|
"self-hosted",
|
|
39191
39357
|
"linux",
|
|
@@ -39426,6 +39592,11 @@ function rulesetStatusChecks(rulesets) {
|
|
|
39426
39592
|
function sortedUnique(values) {
|
|
39427
39593
|
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
39428
39594
|
}
|
|
39595
|
+
function isGitHub404(e) {
|
|
39596
|
+
const err = e;
|
|
39597
|
+
if (err?.status === 404) return true;
|
|
39598
|
+
return /\b404\b|\bnot found\b/i.test(String(err?.message ?? ""));
|
|
39599
|
+
}
|
|
39429
39600
|
async function restJson2(deps, path2, fallback) {
|
|
39430
39601
|
try {
|
|
39431
39602
|
return await deps.client.rest("GET", path2) ?? fallback;
|
|
@@ -39795,7 +39966,23 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
39795
39966
|
const registryRequiredChecks = meta?.requiredChecks;
|
|
39796
39967
|
const explicitNoCi = registryCi === "none" || registryRequiredChecks === null || Array.isArray(registryRequiredChecks) && registryRequiredChecks.length === 0;
|
|
39797
39968
|
const deployableGated = repoClass === "deployable" && !explicitNoCi;
|
|
39798
|
-
|
|
39969
|
+
let info;
|
|
39970
|
+
try {
|
|
39971
|
+
info = await deps.client.rest("GET", `repos/${repo}`) ?? {};
|
|
39972
|
+
} catch (e) {
|
|
39973
|
+
if (isGitHub404(e)) {
|
|
39974
|
+
return {
|
|
39975
|
+
repo,
|
|
39976
|
+
class: repoClass,
|
|
39977
|
+
mergePolicy: "wait-for-checks",
|
|
39978
|
+
ok: false,
|
|
39979
|
+
checks: [],
|
|
39980
|
+
explicitNoCi,
|
|
39981
|
+
excludedReason: "GitHub repository returned 404 (not found)"
|
|
39982
|
+
};
|
|
39983
|
+
}
|
|
39984
|
+
info = {};
|
|
39985
|
+
}
|
|
39799
39986
|
if (info.archived === true) {
|
|
39800
39987
|
return {
|
|
39801
39988
|
repo,
|
|
@@ -40140,6 +40327,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
40140
40327
|
} catch (e) {
|
|
40141
40328
|
checks.push({ ok: false, label: "runner workflow source is readable (#8958)", detail: e instanceof WorkflowSourceError ? e.message : "workflow source: read-failed" });
|
|
40142
40329
|
}
|
|
40330
|
+
checks.push(...auditDeciderSkipTrap(repo, workflows));
|
|
40143
40331
|
checks.push(...auditSelfHostedRunnerHygiene(workflows));
|
|
40144
40332
|
checks.push(...auditJevOutcomes(workflows));
|
|
40145
40333
|
checks.push(...auditVerdictReceipt(workflows));
|
|
@@ -41876,8 +42064,9 @@ async function runPrLandOnce(prNumber, options, deps) {
|
|
|
41876
42064
|
if (!verdict.ok) {
|
|
41877
42065
|
return { ...base, error: `train authority unavailable: ${verdict.error}` };
|
|
41878
42066
|
}
|
|
41879
|
-
base.train = { role: verdict.authority.role, train: verdict.authority.train };
|
|
41880
|
-
|
|
42067
|
+
base.train = { role: verdict.authority.role, train: verdict.authority.train, ...verdict.authority.developmentMerge !== void 0 ? { developmentMerge: verdict.authority.developmentMerge } : {} };
|
|
42068
|
+
const canMerge = verdict.authority.train || resolved.base === "development" && verdict.authority.developmentMerge === true;
|
|
42069
|
+
if (!canMerge) {
|
|
41881
42070
|
return {
|
|
41882
42071
|
...base,
|
|
41883
42072
|
error: `@${verdict.authority.login ?? "caller"} is ${verdict.authority.role} \u2014 train not authorized on ${repo}; cannot land PR`
|
|
@@ -41917,11 +42106,13 @@ async function runPrLandOnce(prNumber, options, deps) {
|
|
|
41917
42106
|
const preflight2 = await deps.preflightQueue(prNumber, repo, pinned);
|
|
41918
42107
|
if (preflight2 && preflight2.mergeStatus === "failed") {
|
|
41919
42108
|
const rawError = preflight2.error ?? "queue preparation needed";
|
|
42109
|
+
const nextAction = preflight2.preparation?.nextAction;
|
|
42110
|
+
const error = nextAction ? rawError.includes(nextAction) ? rawError : `${rawError} \u2014 ${nextAction}` : !rawError.includes("read pr queue") ? `${rawError} \u2014 ${prQueueReadHint(prNumber, repo)}` : rawError;
|
|
41920
42111
|
return {
|
|
41921
42112
|
...base,
|
|
41922
42113
|
mergeStatus: "failed",
|
|
41923
42114
|
...preflight2.preparation ? { preparation: preflight2.preparation } : {},
|
|
41924
|
-
error
|
|
42115
|
+
error
|
|
41925
42116
|
};
|
|
41926
42117
|
}
|
|
41927
42118
|
}
|
|
@@ -41975,7 +42166,8 @@ async function runPrLandOnce(prNumber, options, deps) {
|
|
|
41975
42166
|
}
|
|
41976
42167
|
if (merge.mergeStatus === "failed") {
|
|
41977
42168
|
const rawError = merge.error ?? "merge failed";
|
|
41978
|
-
const
|
|
42169
|
+
const nextAction = merge.preparation?.nextAction;
|
|
42170
|
+
const error = nextAction ? rawError.includes(nextAction) ? rawError : `${rawError} \u2014 ${nextAction}` : hasQueue && !rawError.includes("read pr queue") ? `${rawError} \u2014 ${prQueueReadHint(prNumber, repo)}` : rawError;
|
|
41979
42171
|
return { ...base, ...merge.preparation ? { preparation: merge.preparation } : {}, ...merge.outcomeUnknown ? { outcomeUnknown: true } : {}, error };
|
|
41980
42172
|
}
|
|
41981
42173
|
if (hasQueue) {
|
|
@@ -44362,7 +44554,7 @@ function refuseNotClaimable(e, json) {
|
|
|
44362
44554
|
process.exitCode = 1;
|
|
44363
44555
|
return true;
|
|
44364
44556
|
}
|
|
44365
|
-
var BOARD_READ_HELP = "\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\nWhen using Windows PowerShell 5.1, avoid shell redirects: 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\nThe --json report is an OBJECT \u2014 never an array, and there is no top-level `items` list:\n {project, viewer, repo, primary, secondary, warnings, partial[, source, builtAt, eventCursor, ...]}\n`primary` is this repo and `secondary` the other repos in scope; each is a bucket object\n {userOwned, claimable, taken, unownedInFlight}\nand each bucket is an array of items. Filter the BUCKET, not the report:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n node -e \"const b=require('./.jerv/tmp/board.json');console.log(b.primary.claimable.length)\"\n\nAdd --flat with --json for one array across both scopes: {ref,number,title,status,assignees,labels,bucket,claim,openPullRequests}\n(#9595: claim is the parsed mmi-claim evidence object, null when unclaimed; openPullRequests lists the OPEN\n PRs whose closing-issue body links name the item. On the FULL JSON object that list lives at\n `report.pullRequests` \u2014 there is no top-level `openPullRequests`, and #9596 hand-written projections\n that assume one crash on `.filter`.)\n\nA cached snapshot read renders `source: cache \xB7 built <timestamp> \xB7 fresh|stale` (#9584) and carries\n`builtAt` under --json, so a same-turn re-read of the same builtAt buys nothing new.\n\nUnder --json, parse stdout ONLY and check the exit code first. A failure still prints a\nhuman-readable reason on stderr, so never pipe 2>&1 into a JSON parser (#9151) \u2014 the merged\ntext is not JSON and hides the real board failure.\n";
|
|
44557
|
+
var BOARD_READ_HELP = "\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\nWhen using Windows PowerShell 5.1, avoid shell redirects: 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\nThe --json report is an OBJECT \u2014 never an array, and there is no top-level `items` list:\n {project, viewer, repo, primary, secondary, warnings, partial[, source, builtAt, eventCursor, ...]}\n`primary` is this repo and `secondary` the other repos in scope; each is a bucket object\n {userOwned, claimable, taken, unownedInFlight}\nand each bucket is an array of items. Filter the BUCKET, not the report:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n node -e \"const b=require('./.jerv/tmp/board.json');console.log(b.primary.claimable.length)\"\n\nAdd --flat with --json for one array across both scopes: {ref,number,title,status,assignees,labels,bucket,claim,openPullRequests}\n(#9595: claim is the parsed mmi-claim evidence object, null when unclaimed; openPullRequests lists the OPEN\n PRs whose closing-issue body links name the item. On the FULL JSON object that list lives at\n `report.pullRequests` \u2014 there is no top-level `openPullRequests`, and #9596 hand-written projections\n that assume one crash on `.filter`.)\n\nA cached snapshot read renders `source: cache \xB7 built <timestamp> \xB7 fresh|stale` (#9584) and carries\n`builtAt` under --json, so a same-turn re-read of the same builtAt buys nothing new.\n\nUnder --json, parse stdout ONLY and check the exit code first. A failure still prints a\nhuman-readable reason on stderr, so never pipe 2>&1 into a JSON parser (#9151) \u2014 the merged\ntext is not JSON and hides the real board failure.\n\nA zero-byte or empty .jerv/tmp/*.json means the read FAILED \u2014 `Unexpected end of JSON input` is\nthe symptom of shell-redirected empty stdout, not malformed JSON \u2014 re-run with --out (which writes\nthe file only on success) and check the exit code before parsing.\n\nChanging an existing item's Status is a move, never a hand-written `gh api graphql`\n`updateProjectV2ItemFieldValue` mutation with copied `PVT_`/`PVTI_` node ids and a manual Status\nfield-id lookup:\n mmi-cli oracle board move <status> <ref>\n";
|
|
44366
44558
|
function flattenBoardReport(report) {
|
|
44367
44559
|
const closingByRef = /* @__PURE__ */ new Map();
|
|
44368
44560
|
for (const pull of report.pullRequests ?? []) {
|
|
@@ -48676,7 +48868,11 @@ async function propagationScope(classes, what) {
|
|
|
48676
48868
|
} catch (e) {
|
|
48677
48869
|
return fail(e.message);
|
|
48678
48870
|
}
|
|
48679
|
-
const bySlugMeta =
|
|
48871
|
+
const bySlugMeta = /* @__PURE__ */ new Map();
|
|
48872
|
+
for (const p of projects) for (const r of p.repos ?? []) {
|
|
48873
|
+
const key = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
48874
|
+
bySlugMeta.set(key, preferPrimaryRepoProject(bySlugMeta.get(key), p, key));
|
|
48875
|
+
}
|
|
48680
48876
|
const classOf = (repo) => bySlugMeta.get(repo.toLowerCase())?.class ?? "deployable";
|
|
48681
48877
|
const seedClasses = classes ?? [...new Set(rosterRepos.map(classOf))];
|
|
48682
48878
|
const inClassRepos = rosterRepos.filter((repo) => seedClasses.includes(classOf(repo)));
|
|
@@ -48889,13 +49085,12 @@ function registerBootstrapCommands(program3) {
|
|
|
48889
49085
|
return failGraceful("bootstrap drift: the registry roster is unreadable or empty \u2014 refusing to report a fleet verdict from a scope this command could not establish (#3808)");
|
|
48890
49086
|
}
|
|
48891
49087
|
targets = collectRegistryRepos(projects);
|
|
48892
|
-
const byRepo = /* @__PURE__ */ new Map();
|
|
48893
49088
|
const metaByRepo = /* @__PURE__ */ new Map();
|
|
48894
49089
|
for (const p of projects) for (const r of p.repos ?? []) {
|
|
48895
49090
|
const key = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
48896
|
-
|
|
48897
|
-
metaByRepo.set(key, p);
|
|
49091
|
+
metaByRepo.set(key, preferPrimaryRepoProject(metaByRepo.get(key), p, key));
|
|
48898
49092
|
}
|
|
49093
|
+
const byRepo = new Map([...metaByRepo].map(([key, p]) => [key, p.class ?? "deployable"]));
|
|
48899
49094
|
classOf = (repo) => byRepo.get(repo.toLowerCase()) ?? "deployable";
|
|
48900
49095
|
metaOf = (repo) => metaByRepo.get(repo.toLowerCase()) ?? null;
|
|
48901
49096
|
}
|
|
@@ -55094,6 +55289,9 @@ async function openSuitePinPr(deps, input) {
|
|
|
55094
55289
|
}
|
|
55095
55290
|
}
|
|
55096
55291
|
|
|
55292
|
+
// src/command-register-collaboration.ts
|
|
55293
|
+
init_board_write();
|
|
55294
|
+
|
|
55097
55295
|
// src/pr-title-check.ts
|
|
55098
55296
|
var CONVENTIONAL_PR_TITLE = /^(feat|fix|docs|chore|refactor|perf|test|build|ci|revert|style)(\([^)]+\))?!?: .+/;
|
|
55099
55297
|
var EXEMPT_PR_TITLE = /^Merge pull request /;
|
|
@@ -59500,8 +59698,25 @@ function registerCollaborationCommands(program3) {
|
|
|
59500
59698
|
}
|
|
59501
59699
|
if (refusal && !surfaceWaived()) return fail(refusal.message, refusal.payload);
|
|
59502
59700
|
}
|
|
59503
|
-
|
|
59504
|
-
|
|
59701
|
+
let created;
|
|
59702
|
+
try {
|
|
59703
|
+
await ensureLabelsExist(extraLabels, targetRepo2);
|
|
59704
|
+
created = await ghCreate(args);
|
|
59705
|
+
} catch (error) {
|
|
59706
|
+
if (o.parent !== void 0 || !/Could not resolve to a Repository|HTTP 404|Not Found/i.test(error.message)) throw error;
|
|
59707
|
+
const filed = await createBoardIssueViaHub({ repo: targetRepo2, title, body, type: issueType, priority, labels: extraLabels });
|
|
59708
|
+
const nextStep = filed.onBoard ? void 0 : `mmi-cli oracle board attach ${targetRepo2}#${filed.number}`;
|
|
59709
|
+
console.log(JSON.stringify({
|
|
59710
|
+
status: filed.onBoard ? "created" : "partial",
|
|
59711
|
+
...filed,
|
|
59712
|
+
label: issueType,
|
|
59713
|
+
priority,
|
|
59714
|
+
credential: "app",
|
|
59715
|
+
...nextStep ? { boardAttach: "failed", nextStep, nextSteps: [nextStep] } : {},
|
|
59716
|
+
...surfaceInferred ? { surface_inferred: true, surface: surfaceInferred.label, surface_reason: surfaceInferred.reason } : {}
|
|
59717
|
+
}));
|
|
59718
|
+
return;
|
|
59719
|
+
}
|
|
59505
59720
|
if (isGhCreateRateLimited(created)) {
|
|
59506
59721
|
console.log(JSON.stringify(created));
|
|
59507
59722
|
process.exitCode = 1;
|
|
@@ -66144,12 +66359,12 @@ async function readRunLog(id, o, runner) {
|
|
|
66144
66359
|
const base = `repos/${repo}/actions/runs/${runId}`;
|
|
66145
66360
|
const pages = JSON.parse(await readAppApi(`${base}${attempt === void 0 ? "" : `/attempts/${attempt}`}/jobs`, runner));
|
|
66146
66361
|
const jobs = pages.flatMap((page) => page.jobs);
|
|
66147
|
-
const selected = jobs.filter((job) => jobName === void 0 ? FAILED2.has(job.conclusion ?? "") : job.name === jobName);
|
|
66362
|
+
const selected = jobs.filter((job) => jobName === void 0 ? FAILED2.has(job.conclusion ?? "") : job.name === jobName || String(job.id) === jobName);
|
|
66148
66363
|
const result = { runId, repo, jobs: [] };
|
|
66149
66364
|
if (!selected.length && jobName === void 0) return { result, exitCode: 1, message: `read run log: no matching job for run ${runId} in ${repo}; conclusions seen: ${[...new Set(jobs.map((j) => j.conclusion ?? "pending"))].join(", ") || "none"}` };
|
|
66150
66365
|
if (!selected.length) {
|
|
66151
66366
|
const listed = jobs.slice(0, 30).map((j) => `${j.name} (${j.id})`).join(", ");
|
|
66152
|
-
return { result, exitCode: 1, message: `read run log: --job takes the exact job name
|
|
66367
|
+
return { result, exitCode: 1, message: `read run log: --job takes the exact job name or numeric job id; jobs in run ${runId}: ${listed || "none"}${jobs.length > 30 ? `, \u2026+${jobs.length - 30} more` : ""}` };
|
|
66153
66368
|
}
|
|
66154
66369
|
for (const job of selected) {
|
|
66155
66370
|
const text = await readAppApi(`repos/${repo}/actions/jobs/${parseBrokerId(String(job.id), "job id")}/logs`, runner);
|
|
@@ -66158,7 +66373,7 @@ async function readRunLog(id, o, runner) {
|
|
|
66158
66373
|
return { result, exitCode: 0 };
|
|
66159
66374
|
}
|
|
66160
66375
|
function registerRunLogRead(run, runner) {
|
|
66161
|
-
run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3)), "200").option("--attempt <n>", "run attempt (1-1000; default latest)", (v) => String(parseBrokerInt(v, "--attempt", 1, 1e3))).option("--json", "emit runId, repo and jobs with failedStep/errorLines/tail").argument("<runId>", "workflow run number", (v) => parseBrokerId(v, "run id")).action(async (id, o) => {
|
|
66376
|
+
run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name or numeric job id").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3)), "200").option("--attempt <n>", "run attempt (1-1000; default latest)", (v) => String(parseBrokerInt(v, "--attempt", 1, 1e3))).option("--json", "emit runId, repo and jobs with failedStep/errorLines/tail").argument("<runId>", "workflow run number", (v) => parseBrokerId(v, "run id")).action(async (id, o) => {
|
|
66162
66377
|
requireBrokerApp("read run log");
|
|
66163
66378
|
const env = brokerChildEnv();
|
|
66164
66379
|
try {
|
|
@@ -66246,6 +66461,11 @@ function localJqEnv(base = process.env) {
|
|
|
66246
66461
|
}
|
|
66247
66462
|
return env;
|
|
66248
66463
|
}
|
|
66464
|
+
function formatJqFailureMessage(code, stderr) {
|
|
66465
|
+
const base = `--jq expression failed (jq exit ${code}): ${stderr.trim()}`;
|
|
66466
|
+
if (!/Invalid escape/i.test(stderr)) return base;
|
|
66467
|
+
return `${base} \u2014 the expression is handed to jq verbatim (mmi-cli never re-quotes it), so if a shell or seat layer collapsed a backslash, use an escape-free form such as \`test("bundle[.]css")\` or \`contains("bundle.css")\`, or drop --jq and filter the JSON locally`;
|
|
66468
|
+
}
|
|
66249
66469
|
var systemJqRunner = (expr, input, env) => new Promise((resolveOut, reject2) => {
|
|
66250
66470
|
const child2 = (0, import_node_child_process20.spawn)("jq", [...process.platform === "win32" ? ["--binary"] : [], "-r", "-c", expr], { env, stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
66251
66471
|
let stdout = "";
|
|
@@ -66257,7 +66477,7 @@ var systemJqRunner = (expr, input, env) => new Promise((resolveOut, reject2) =>
|
|
|
66257
66477
|
stderr += chunk;
|
|
66258
66478
|
});
|
|
66259
66479
|
child2.on("error", (e) => reject2(e.code === "ENOENT" ? new Error("--jq needs the jq binary on PATH and none was found \u2014 install jq, or drop --jq and filter the --json output locally") : e));
|
|
66260
|
-
child2.on("close", (code) => code === 0 ? resolveOut(stdout) : reject2(new Error(
|
|
66480
|
+
child2.on("close", (code) => code === 0 ? resolveOut(stdout) : reject2(new Error(formatJqFailureMessage(code, stderr))));
|
|
66261
66481
|
child2.stdin.on("error", () => {
|
|
66262
66482
|
});
|
|
66263
66483
|
child2.stdin.end(input);
|
|
@@ -66871,7 +67091,7 @@ var RELEASE_LATEST_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/latest$`);
|
|
|
66871
67091
|
var RELEASE_TAG_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/tags/(${NAME})$`);
|
|
66872
67092
|
var RELEASE_ASSET_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/assets/\\d+$`);
|
|
66873
67093
|
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/issues/comments/{id}, repos/{owner}/{repo}/issues/{n}/comments[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs[?per_page=1-100&page=<n>&status=&branch=&event=&head_sha=<40-hex>&created=<date-range>&actor=, each key at most once], repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/actions/runs/{id}/artifacts[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>), repos/{owner}/{repo}/releases[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/releases/latest, repos/{owner}/{repo}/releases/tags/{tag}, repos/{owner}/{repo}/releases/assets/{id} (needs --out <file>)";
|
|
66874
|
-
var API_GET_RECOVERY = " No other GET read exists \u2014 do not retry path variants; use a dedicated mmi-cli subcommand or ask a human token holder. This refusal exits 1 on stderr with nothing on stdout: check the exit code and never merge stderr into stdout (2>&1) before JSON-parsing.";
|
|
67094
|
+
var API_GET_RECOVERY = " No other GET read exists \u2014 do not retry path variants; use a dedicated mmi-cli subcommand or ask a human token holder. This refusal exits 1 on stderr with nothing on stdout: check the exit code and never merge stderr into stdout (2>&1) before JSON-parsing. Piping empty stdout into JSON.parse hides the stderr refusal; check the exit code before parsing.";
|
|
66875
67095
|
var TYPED_READ_ROUTE_RE = new RegExp(`^repos/(${NAME})/(${NAME})/(issues|pulls)(?:/(comments/\\d+|\\d+)(?:/([a-z_]+))?)?$`);
|
|
66876
67096
|
function typedReadHint(path2) {
|
|
66877
67097
|
const match = TYPED_READ_ROUTE_RE.exec(path2.split("?")[0]);
|
|
@@ -66892,7 +67112,7 @@ function typedReadHint(path2) {
|
|
|
66892
67112
|
}
|
|
66893
67113
|
if (!item) return say(`mmi-cli devops read pr list --repo ${slug} --json number,title,state,headRefName`);
|
|
66894
67114
|
if (item.startsWith("comments/")) return say(`mmi-cli devops read pr view <pr> --repo ${slug} --json comments,reviews`);
|
|
66895
|
-
return say(`mmi-cli devops read pr view ${item} --repo ${slug} --json title,state,comments,reviews,commits,statusCheckRollup`);
|
|
67115
|
+
return say(`mmi-cli devops read pr view ${item} --repo ${slug} --json title,state,comments,reviews,commits,statusCheckRollup,mergedAt,mergeCommit,headRefOid`, "; `mergedAt`, `mergeCommit` and `headRefOid` carry the merge and head evidence, and `--json` is a field list callers may narrow or extend");
|
|
66896
67116
|
}
|
|
66897
67117
|
function runsReadHint(path2) {
|
|
66898
67118
|
const q = path2.indexOf("?");
|
|
@@ -66937,11 +67157,18 @@ function checkRunsReadHint(path2) {
|
|
|
66937
67157
|
const slug = `${match[1]}/${match[2]}`;
|
|
66938
67158
|
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).`;
|
|
66939
67159
|
}
|
|
67160
|
+
function workflowRunsReadHint(path2) {
|
|
67161
|
+
const match = new RegExp(`^repos/(${NAME})/(${NAME})/actions/workflows(?:/([^/?#]+)(?:/runs)?)?(?:[/?#]|$)`).exec(path2.split("?")[0]);
|
|
67162
|
+
if (!match) return "";
|
|
67163
|
+
const [, owner, repo, workflow] = match;
|
|
67164
|
+
const slug = `${owner}/${repo}`;
|
|
67165
|
+
return ` Workflow routes are outside the App allowlist \u2014 use the typed workflow runs read \`MMI_ACTOR=app mmi-cli devops read run list --repo ${slug} --workflow ${workflow ?? "<workflow.yml>"} --branch <branch> --limit <n> --json databaseId,workflowName,event,status,conclusion\`, or the allowlisted REST runs list \`MMI_ACTOR=app mmi-cli devops api get "repos/${slug}/actions/runs?branch=<branch>&per_page=<n>"\`.`;
|
|
67166
|
+
}
|
|
66940
67167
|
function parseApiGetPath(rawPath) {
|
|
66941
67168
|
const path2 = rawPath.trim();
|
|
66942
67169
|
const refuse = () => {
|
|
66943
67170
|
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." : "";
|
|
66944
|
-
const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${commitsReadHint(path2)}${checkRunsReadHint(path2)}${hint}`;
|
|
67171
|
+
const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${commitsReadHint(path2)}${checkRunsReadHint(path2)}${workflowRunsReadHint(path2)}${hint}`;
|
|
66945
67172
|
return new Error(`api get: refusing '${rawPath}' \u2014${routeHints}${API_GET_RECOVERY} Only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.`);
|
|
66946
67173
|
};
|
|
66947
67174
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
@@ -67001,7 +67228,7 @@ function registerApiReadCommands(program3, overrides) {
|
|
|
67001
67228
|
if (!zip && o.out !== void 0) {
|
|
67002
67229
|
return fail(`${shapes} \u2014 drop --out and let the JSON body go to stdout (pipe it into node/jq, or use --jq <expr>), or use the typed read (e.g. \`mmi-cli devops read run list --repo <owner/repo> --json databaseId,workflowName,event,status,conclusion\`). This refusal exits 1 on stderr with nothing written: check the exit code and never discard stderr (2>&1 / >/dev/null) before parsing.`);
|
|
67003
67230
|
}
|
|
67004
|
-
return fail(shapes);
|
|
67231
|
+
return fail(`${shapes} \u2014 append \`--out <file>\` to this command (e.g. \`MMI_ACTOR=app mmi-cli devops api get ${req.path} --out .jerv/tmp/job.log\`), or for job logs use the typed read \`MMI_ACTOR=app mmi-cli devops read run log <runId> --repo <owner/repo> --job <name|id>\`. Shell redirection (\`>\` / \`2>&1\`) is not a substitute: this refusal exits 1 on stderr with nothing written to stdout, so \`>\` leaves a zero-byte file \u2014 delete it and check the exit code.`);
|
|
67005
67232
|
}
|
|
67006
67233
|
const appRoute = req.kind.startsWith("actions-") || req.kind.startsWith("release-");
|
|
67007
67234
|
if (appRoute) requireBrokerApp("api get");
|
|
@@ -70572,7 +70799,7 @@ function envelopeAwareWriteErr(str2) {
|
|
|
70572
70799
|
const invoked = resolveCommandFromArgv(program2, argv);
|
|
70573
70800
|
const invokedPath = invoked ? canonicalPathFor(commandPath3(invoked)) ?? commandPath3(invoked) : void 0;
|
|
70574
70801
|
const canonicalArgv = canonicalArgvFor(argv);
|
|
70575
|
-
const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, invokedPath, canonicalArgv) ?? issueCommentAppendOnlyRecovery(flag, invokedPath, canonicalArgv) ?? trainPlanRedundantFlagRecovery(flag, invokedPath, canonicalArgv) : void 0;
|
|
70802
|
+
const claimStatus = invoked ? boardClaimStatusFlagRecovery(flag, invokedPath, canonicalArgv) ?? boardClaimLaneFlagRecovery(flag, invokedPath, canonicalArgv) ?? boardClaimAgentFlagRecovery(flag, invokedPath, canonicalArgv) ?? issueCommentAppendOnlyRecovery(flag, invokedPath, canonicalArgv) ?? trainPlanRedundantFlagRecovery(flag, invokedPath, canonicalArgv) : void 0;
|
|
70576
70803
|
if (claimStatus) {
|
|
70577
70804
|
if (argvWantsJson3()) {
|
|
70578
70805
|
process.stderr.write(
|
package/dist/runbook-sweep.cjs
CHANGED
|
@@ -8585,6 +8585,16 @@ init_github_quota();
|
|
|
8585
8585
|
|
|
8586
8586
|
// src/fleet-lockstep-enforce.ts
|
|
8587
8587
|
init_github_client();
|
|
8588
|
+
|
|
8589
|
+
// src/registry-repositories.ts
|
|
8590
|
+
function qualifyRegistryRepo(raw) {
|
|
8591
|
+
return raw.includes("/") ? raw : `mutmutco/${raw}`;
|
|
8592
|
+
}
|
|
8593
|
+
function registryRepoKey(repo) {
|
|
8594
|
+
return repo.toLowerCase();
|
|
8595
|
+
}
|
|
8596
|
+
|
|
8597
|
+
// src/fleet-lockstep-enforce.ts
|
|
8588
8598
|
init_clean_exit();
|
|
8589
8599
|
init_cli_shared();
|
|
8590
8600
|
|
|
@@ -13200,14 +13210,6 @@ function message(e) {
|
|
|
13200
13210
|
return e instanceof Error ? e.message : String(e);
|
|
13201
13211
|
}
|
|
13202
13212
|
|
|
13203
|
-
// src/registry-repositories.ts
|
|
13204
|
-
function qualifyRegistryRepo(raw) {
|
|
13205
|
-
return raw.includes("/") ? raw : `mutmutco/${raw}`;
|
|
13206
|
-
}
|
|
13207
|
-
function registryRepoKey(repo) {
|
|
13208
|
-
return repo.toLowerCase();
|
|
13209
|
-
}
|
|
13210
|
-
|
|
13211
13213
|
// src/ci-audit.ts
|
|
13212
13214
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
13213
13215
|
var HUB_REPO4 = "mutmutco/MMI-Hub";
|
package/package.json
CHANGED