@mutmutco/cli 4.5.122 → 4.5.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +434 -82
- package/dist/runbook-sweep.cjs +5536 -0
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5119,8 +5119,8 @@ function survivingFlagArgs(argv, rejectedFlag, ownFlags) {
|
|
|
5119
5119
|
return out;
|
|
5120
5120
|
}
|
|
5121
5121
|
function formatPositionalTarget(commandPath3, argName, opts = {}) {
|
|
5122
|
-
const
|
|
5123
|
-
const target =
|
|
5122
|
+
const concrete2 = opts.flag && opts.argv ? targetFlagValueFromArgv(opts.flag, opts.argv) : void 0;
|
|
5123
|
+
const target = concrete2 ?? `<${argName}>`;
|
|
5124
5124
|
const kept = opts.flag && opts.argv && opts.ownFlags ? survivingFlagArgs(opts.argv, opts.flag, opts.ownFlags) : [];
|
|
5125
5125
|
return [`mmi-cli ${commandPath3}`, target, ...kept].join(" ");
|
|
5126
5126
|
}
|
|
@@ -5656,7 +5656,7 @@ function resolveVerbAliasShim(argv) {
|
|
|
5656
5656
|
|
|
5657
5657
|
// src/command-composition.ts
|
|
5658
5658
|
var import_node_os25 = require("node:os");
|
|
5659
|
-
var
|
|
5659
|
+
var import_node_path55 = require("node:path");
|
|
5660
5660
|
|
|
5661
5661
|
// src/board-read.ts
|
|
5662
5662
|
var import_node_fs14 = require("node:fs");
|
|
@@ -5789,6 +5789,8 @@ var APP_ELIGIBLE_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
5789
5789
|
"read run view",
|
|
5790
5790
|
"read run list",
|
|
5791
5791
|
"read run watch",
|
|
5792
|
+
"read run log",
|
|
5793
|
+
// #8136: bounded jobs/logs REST read, same polling credential.
|
|
5792
5794
|
"read pr view",
|
|
5793
5795
|
"read pr checks",
|
|
5794
5796
|
"read issue view",
|
|
@@ -10108,18 +10110,18 @@ function isGhHttpNotFound(err) {
|
|
|
10108
10110
|
function isForkStyleHead(head) {
|
|
10109
10111
|
return head.includes(":");
|
|
10110
10112
|
}
|
|
10111
|
-
async function defaultReadRemoteHead(
|
|
10113
|
+
async function defaultReadRemoteHead(exec3, repo, head) {
|
|
10112
10114
|
try {
|
|
10113
|
-
await
|
|
10115
|
+
await exec3("gh", ["api", `repos/${repo}/git/ref/heads/${encodeURIComponent(head)}`], { timeout: 15e3 });
|
|
10114
10116
|
return "present";
|
|
10115
10117
|
} catch (e) {
|
|
10116
10118
|
if (isGhHttpNotFound(e)) return "absent";
|
|
10117
10119
|
return "unknown";
|
|
10118
10120
|
}
|
|
10119
10121
|
}
|
|
10120
|
-
async function defaultCompareRefs(
|
|
10122
|
+
async function defaultCompareRefs(exec3, repo, base, head) {
|
|
10121
10123
|
try {
|
|
10122
|
-
const { stdout } = await
|
|
10124
|
+
const { stdout } = await exec3(
|
|
10123
10125
|
"gh",
|
|
10124
10126
|
["api", `repos/${repo}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`],
|
|
10125
10127
|
{ timeout: 15e3 }
|
|
@@ -10130,11 +10132,11 @@ async function defaultCompareRefs(exec2, repo, base, head) {
|
|
|
10130
10132
|
return "unknown";
|
|
10131
10133
|
}
|
|
10132
10134
|
}
|
|
10133
|
-
async function publishAbsentPrHeadIfOwned(repo, head,
|
|
10135
|
+
async function publishAbsentPrHeadIfOwned(repo, head, exec3) {
|
|
10134
10136
|
if (isForkStyleHead(head)) return false;
|
|
10135
10137
|
let originRepo;
|
|
10136
10138
|
try {
|
|
10137
|
-
const { stdout } = await
|
|
10139
|
+
const { stdout } = await exec3("git", ["remote", "get-url", "--push", "origin"], { timeout: GIT_TIMEOUT_MS });
|
|
10138
10140
|
originRepo = repoFromRemoteUrl(stdout.trim());
|
|
10139
10141
|
} catch {
|
|
10140
10142
|
return false;
|
|
@@ -10142,20 +10144,20 @@ async function publishAbsentPrHeadIfOwned(repo, head, exec2) {
|
|
|
10142
10144
|
if (!originRepo || originRepo.toLowerCase() !== repo.toLowerCase()) return false;
|
|
10143
10145
|
let currentBranch2;
|
|
10144
10146
|
try {
|
|
10145
|
-
currentBranch2 = (await
|
|
10147
|
+
currentBranch2 = (await exec3("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
10146
10148
|
} catch {
|
|
10147
10149
|
return false;
|
|
10148
10150
|
}
|
|
10149
10151
|
if (!currentBranch2 || currentBranch2 !== head) return false;
|
|
10150
10152
|
let commit;
|
|
10151
10153
|
try {
|
|
10152
|
-
commit = (await
|
|
10154
|
+
commit = (await exec3("git", ["rev-parse", "--verify", "HEAD^{commit}"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
10153
10155
|
} catch {
|
|
10154
10156
|
return false;
|
|
10155
10157
|
}
|
|
10156
10158
|
if (!commit) return false;
|
|
10157
10159
|
try {
|
|
10158
|
-
await
|
|
10160
|
+
await exec3("git", ["push", "origin", `${commit}:refs/heads/${head}`], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10159
10161
|
return true;
|
|
10160
10162
|
} catch {
|
|
10161
10163
|
return false;
|
|
@@ -10285,22 +10287,22 @@ function upstreamFaultMessage(verb, stderr) {
|
|
|
10285
10287
|
const advice = mayRetryCreate(verb) ? "Retrying is the right action" : "The write may have completed server-side before the error \u2014 VERIFY before retrying, or a retry will duplicate it";
|
|
10286
10288
|
return `gh ${verb} create failed: GitHub's API is failing (server-side 5xx) \u2014 this is NOT a problem with your diff or arguments. GitHub request ID: ${requestId ?? "unavailable"}. ${advice}; note githubstatus.com may still read green while this is happening.`;
|
|
10287
10289
|
}
|
|
10288
|
-
async function resolveRepoForCreate(args,
|
|
10290
|
+
async function resolveRepoForCreate(args, exec3) {
|
|
10289
10291
|
const fromFlag = flagValue(args, "--repo");
|
|
10290
10292
|
if (fromFlag) return fromFlag;
|
|
10291
10293
|
try {
|
|
10292
|
-
const { stdout } = await
|
|
10294
|
+
const { stdout } = await exec3("git", ["remote", "get-url", "origin"], { timeout: 1e4 });
|
|
10293
10295
|
const fromOrigin = repoFromRemoteUrl(stdout.trim());
|
|
10294
10296
|
if (fromOrigin) return fromOrigin;
|
|
10295
10297
|
} catch {
|
|
10296
10298
|
}
|
|
10297
10299
|
return repoFromRemoteUrl(originUrlFromGitConfig() ?? "") || void 0;
|
|
10298
10300
|
}
|
|
10299
|
-
async function resolveHeadForCreate(args,
|
|
10301
|
+
async function resolveHeadForCreate(args, exec3) {
|
|
10300
10302
|
const fromFlag = flagValue(args, "--head");
|
|
10301
10303
|
if (fromFlag) return fromFlag;
|
|
10302
10304
|
try {
|
|
10303
|
-
const { stdout } = await
|
|
10305
|
+
const { stdout } = await exec3("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 1e4 });
|
|
10304
10306
|
const head = stdout.trim();
|
|
10305
10307
|
return head && head !== "HEAD" ? head : void 0;
|
|
10306
10308
|
} catch {
|
|
@@ -10312,13 +10314,13 @@ async function resolveBodyForCreate(args, read) {
|
|
|
10312
10314
|
if (bodyFile !== void 0) return read(bodyFile, "utf8");
|
|
10313
10315
|
return flagValue(args, "--body") ?? "";
|
|
10314
10316
|
}
|
|
10315
|
-
async function defaultFindOpenPr(
|
|
10317
|
+
async function defaultFindOpenPr(exec3, input) {
|
|
10316
10318
|
const owner = input.repo.split("/")[0];
|
|
10317
10319
|
if (!owner) return void 0;
|
|
10318
10320
|
const qs = new URLSearchParams({ state: "open", head: `${owner}:${input.head}` });
|
|
10319
10321
|
if (input.base) qs.set("base", input.base);
|
|
10320
10322
|
try {
|
|
10321
|
-
const { stdout } = await
|
|
10323
|
+
const { stdout } = await exec3("gh", ["api", `repos/${input.repo}/pulls?${qs.toString()}`], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10322
10324
|
const rows = JSON.parse(stdout);
|
|
10323
10325
|
const first = Array.isArray(rows) ? rows[0] : void 0;
|
|
10324
10326
|
if (typeof first?.number === "number" && typeof first.html_url === "string") {
|
|
@@ -10328,7 +10330,7 @@ async function defaultFindOpenPr(exec2, input) {
|
|
|
10328
10330
|
}
|
|
10329
10331
|
return void 0;
|
|
10330
10332
|
}
|
|
10331
|
-
async function defaultRestCreatePr(
|
|
10333
|
+
async function defaultRestCreatePr(exec3, input, deps = {}) {
|
|
10332
10334
|
const write = deps.write ?? import_promises.writeFile;
|
|
10333
10335
|
const remove2 = deps.remove ?? import_promises.unlink;
|
|
10334
10336
|
const ensureDir = deps.ensureDir ?? import_promises.mkdir;
|
|
@@ -10353,7 +10355,7 @@ async function defaultRestCreatePr(exec2, input, deps = {}) {
|
|
|
10353
10355
|
file
|
|
10354
10356
|
];
|
|
10355
10357
|
try {
|
|
10356
|
-
const { stdout } = await
|
|
10358
|
+
const { stdout } = await exec3("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10357
10359
|
const parsed = JSON.parse(stdout);
|
|
10358
10360
|
if (typeof parsed.number === "number" && typeof parsed.html_url === "string") {
|
|
10359
10361
|
return { number: parsed.number, url: parsed.html_url };
|
|
@@ -10365,7 +10367,7 @@ async function defaultRestCreatePr(exec2, input, deps = {}) {
|
|
|
10365
10367
|
const fromText = parseExistingPr(text);
|
|
10366
10368
|
if (fromText) return { ...fromText, existing: true };
|
|
10367
10369
|
if (/already exists/i.test(text)) {
|
|
10368
|
-
const found = await defaultFindOpenPr(
|
|
10370
|
+
const found = await defaultFindOpenPr(exec3, { repo: input.repo, head: input.head, base: input.base });
|
|
10369
10371
|
if (found) return found;
|
|
10370
10372
|
}
|
|
10371
10373
|
throw e;
|
|
@@ -10376,7 +10378,7 @@ async function defaultRestCreatePr(exec2, input, deps = {}) {
|
|
|
10376
10378
|
}
|
|
10377
10379
|
}
|
|
10378
10380
|
}
|
|
10379
|
-
async function defaultRestCreateIssue(
|
|
10381
|
+
async function defaultRestCreateIssue(exec3, input) {
|
|
10380
10382
|
const args = [
|
|
10381
10383
|
"api",
|
|
10382
10384
|
"--method",
|
|
@@ -10391,7 +10393,7 @@ async function defaultRestCreateIssue(exec2, input) {
|
|
|
10391
10393
|
args.push("-f", `labels[]=${label}`);
|
|
10392
10394
|
}
|
|
10393
10395
|
try {
|
|
10394
|
-
const { stdout } = await
|
|
10396
|
+
const { stdout } = await exec3("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10395
10397
|
const parsed = JSON.parse(stdout);
|
|
10396
10398
|
if (typeof parsed.number === "number" && typeof parsed.html_url === "string") {
|
|
10397
10399
|
return { number: parsed.number, url: parsed.html_url };
|
|
@@ -10410,9 +10412,9 @@ ${err.stdout ?? ""}`;
|
|
|
10410
10412
|
throw e;
|
|
10411
10413
|
}
|
|
10412
10414
|
}
|
|
10413
|
-
async function defaultReadRateLimit(
|
|
10415
|
+
async function defaultReadRateLimit(exec3) {
|
|
10414
10416
|
try {
|
|
10415
|
-
const { stdout } = await
|
|
10417
|
+
const { stdout } = await exec3("gh", ["api", "rate_limit"], { timeout: 15e3 });
|
|
10416
10418
|
return parseRateLimitPools(stdout);
|
|
10417
10419
|
} catch {
|
|
10418
10420
|
return {};
|
|
@@ -10585,17 +10587,17 @@ async function createPrViaRestFallback(args, swappedArgs, deps, knownPools) {
|
|
|
10585
10587
|
}
|
|
10586
10588
|
}
|
|
10587
10589
|
async function ghCreate(args, deps = {}) {
|
|
10588
|
-
const
|
|
10590
|
+
const exec3 = deps.exec ?? execFileP;
|
|
10589
10591
|
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
10590
10592
|
const now = deps.now ?? Date.now;
|
|
10591
10593
|
const read = deps.readFile ?? import_promises.readFile;
|
|
10592
|
-
const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(
|
|
10593
|
-
const restCreateIssue = deps.restCreateIssue ?? ((input) => defaultRestCreateIssue(
|
|
10594
|
-
const findOpenPr = deps.findOpenPr ?? ((input) => defaultFindOpenPr(
|
|
10595
|
-
const readRateLimit = deps.readRateLimit ?? (() => defaultReadRateLimit(
|
|
10594
|
+
const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(exec3, input));
|
|
10595
|
+
const restCreateIssue = deps.restCreateIssue ?? ((input) => defaultRestCreateIssue(exec3, input));
|
|
10596
|
+
const findOpenPr = deps.findOpenPr ?? ((input) => defaultFindOpenPr(exec3, input));
|
|
10597
|
+
const readRateLimit = deps.readRateLimit ?? (() => defaultReadRateLimit(exec3));
|
|
10596
10598
|
if (args[0] === "pr") {
|
|
10597
10599
|
const refusal = await preflightPrCreateRemoteHead(args, {
|
|
10598
|
-
exec:
|
|
10600
|
+
exec: exec3,
|
|
10599
10601
|
readRemoteHead: deps.readRemoteHead,
|
|
10600
10602
|
compareRefs: deps.compareRefs
|
|
10601
10603
|
});
|
|
@@ -10603,7 +10605,7 @@ async function ghCreate(args, deps = {}) {
|
|
|
10603
10605
|
}
|
|
10604
10606
|
const swapped = await bodyArgsViaFile(args);
|
|
10605
10607
|
const restDeps = {
|
|
10606
|
-
exec:
|
|
10608
|
+
exec: exec3,
|
|
10607
10609
|
sleep: sleep2,
|
|
10608
10610
|
now,
|
|
10609
10611
|
readFile: read,
|
|
@@ -10646,7 +10648,7 @@ async function ghCreate(args, deps = {}) {
|
|
|
10646
10648
|
}
|
|
10647
10649
|
for (let attempt = 1; attempt <= GH_CREATE_UPSTREAM_RETRIES; attempt++) {
|
|
10648
10650
|
try {
|
|
10649
|
-
const { stdout } = await
|
|
10651
|
+
const { stdout } = await exec3("gh", swapped.args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10650
10652
|
return parseCreatedUrl(stdout);
|
|
10651
10653
|
} catch (e) {
|
|
10652
10654
|
const err = e;
|
|
@@ -10664,7 +10666,7 @@ async function ghCreate(args, deps = {}) {
|
|
|
10664
10666
|
if (args[0] === "issue" && isGhRateLimitError(errText)) {
|
|
10665
10667
|
try {
|
|
10666
10668
|
const result = await createIssueViaRestFallback(args, swapped.args, {
|
|
10667
|
-
exec:
|
|
10669
|
+
exec: exec3,
|
|
10668
10670
|
sleep: sleep2,
|
|
10669
10671
|
now,
|
|
10670
10672
|
readFile: read,
|
|
@@ -15785,6 +15787,135 @@ function checkCommands(docs, commandPaths) {
|
|
|
15785
15787
|
}
|
|
15786
15788
|
return { ok: findings.length === 0, findings, warnings: [] };
|
|
15787
15789
|
}
|
|
15790
|
+
function isRunbook(doc, body) {
|
|
15791
|
+
return doc === "docs/Guides/operator.md" || body.split(/\r?\n/).slice(0, 20).some((line) => line.includes("<!-- runbook -->"));
|
|
15792
|
+
}
|
|
15793
|
+
function shellClaims(body) {
|
|
15794
|
+
const claims = [];
|
|
15795
|
+
let fence = "";
|
|
15796
|
+
let enabled = false;
|
|
15797
|
+
body.split(/\r?\n/).forEach((text, index) => {
|
|
15798
|
+
const marker = /^\s*(`{3,}|~{3,})(.*)$/.exec(text);
|
|
15799
|
+
if (marker) {
|
|
15800
|
+
if (!fence) {
|
|
15801
|
+
fence = marker[1];
|
|
15802
|
+
enabled = /^(bash|sh|shell|console)?$/.test(marker[2].trim().toLowerCase());
|
|
15803
|
+
} else if (marker[1][0] === fence[0] && marker[1].length >= fence.length && !marker[2].trim()) {
|
|
15804
|
+
fence = "";
|
|
15805
|
+
enabled = false;
|
|
15806
|
+
}
|
|
15807
|
+
return;
|
|
15808
|
+
}
|
|
15809
|
+
if (!enabled || /^\s*#/.test(text)) return;
|
|
15810
|
+
const parts = text.replace(/^\s*\$ /, "").match(/(?:"[^"\n]*"|'[^'\n]*'|<[^>\n]*>|[^\s;&|])+|&&|\|\||[;|]/g) ?? [];
|
|
15811
|
+
let tokens2 = [];
|
|
15812
|
+
const emit = () => {
|
|
15813
|
+
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens2[0] ?? "")) tokens2.shift();
|
|
15814
|
+
if (tokens2.length) claims.push({ tokens: tokens2, line: index + 1 });
|
|
15815
|
+
tokens2 = [];
|
|
15816
|
+
};
|
|
15817
|
+
for (const part of parts) {
|
|
15818
|
+
if (part.startsWith("#")) break;
|
|
15819
|
+
if (["&&", "||", ";", "|"].includes(part)) emit();
|
|
15820
|
+
else tokens2.push(part.replace(/^(["'])(.*)\1$/, "$2"));
|
|
15821
|
+
}
|
|
15822
|
+
emit();
|
|
15823
|
+
});
|
|
15824
|
+
return claims;
|
|
15825
|
+
}
|
|
15826
|
+
function checkRunbookClaims(root, docs, deps) {
|
|
15827
|
+
const findings = [];
|
|
15828
|
+
const placeholder = (token) => token.includes("<");
|
|
15829
|
+
const paths = deps.commandPaths ?? /* @__PURE__ */ new Set();
|
|
15830
|
+
for (const [doc, body] of Object.entries(docs)) {
|
|
15831
|
+
for (const { tokens: tokens2, line } of shellClaims(body)) {
|
|
15832
|
+
const [cmd, ...args] = tokens2;
|
|
15833
|
+
const report = (kind, detail) => {
|
|
15834
|
+
findings.push({ kind, doc, line, detail });
|
|
15835
|
+
};
|
|
15836
|
+
if (cmd === "mmi-cli" && deps.commandPaths) {
|
|
15837
|
+
let resolved = "";
|
|
15838
|
+
let unknown = false;
|
|
15839
|
+
for (const token of args) {
|
|
15840
|
+
if (token.startsWith("-") || placeholder(token)) break;
|
|
15841
|
+
const next = resolved ? `${resolved} ${token}` : token;
|
|
15842
|
+
if ([...paths].some((path2) => path2 === next || path2.startsWith(`${next} `))) resolved = next;
|
|
15843
|
+
else if (paths.has(resolved)) break;
|
|
15844
|
+
else {
|
|
15845
|
+
report("unknown-command", `mmi-cli ${next}`);
|
|
15846
|
+
unknown = true;
|
|
15847
|
+
break;
|
|
15848
|
+
}
|
|
15849
|
+
}
|
|
15850
|
+
for (const token of unknown ? [] : args) {
|
|
15851
|
+
if (token === "--") break;
|
|
15852
|
+
const flag = token.split("=")[0];
|
|
15853
|
+
if (/^--[\w-]+$/.test(flag) && flag !== "--help" && !placeholder(flag) && !deps.commandOptions?.get(resolved)?.has(flag)) {
|
|
15854
|
+
report("unknown-flag", `mmi-cli ${resolved}: ${flag}`);
|
|
15855
|
+
}
|
|
15856
|
+
}
|
|
15857
|
+
} else if (cmd === "npm") {
|
|
15858
|
+
const run = args.indexOf("run");
|
|
15859
|
+
if (run < 0) continue;
|
|
15860
|
+
const prefix = args.indexOf("--prefix");
|
|
15861
|
+
const dir = prefix >= 0 ? args[prefix + 1] : ".";
|
|
15862
|
+
const script = args.slice(run + 1).find((token, i, tail) => !token.startsWith("-") && tail[i - 1] !== "--prefix");
|
|
15863
|
+
if (!dir || !script || placeholder(dir) || placeholder(script)) continue;
|
|
15864
|
+
const manifest = deps.readFile((0, import_node_path13.join)(root, dir, "package.json"));
|
|
15865
|
+
const scripts = manifest ? JSON.parse(manifest).scripts : void 0;
|
|
15866
|
+
if (!scripts || !Object.hasOwn(scripts, script)) report("missing-script", `npm run ${script} (${dir}/package.json)`);
|
|
15867
|
+
} else if ((cmd === "python" || cmd === "python3") && args[0] === "-m") {
|
|
15868
|
+
const module2 = args[1];
|
|
15869
|
+
if (!module2 || placeholder(module2)) continue;
|
|
15870
|
+
const rel = module2.replaceAll(".", "/");
|
|
15871
|
+
const first = rel.split("/")[0];
|
|
15872
|
+
const tracked = deps.trackedFirstSegments([first, `${first}.py`, `src/${first}`, `src/${first}.py`]);
|
|
15873
|
+
const roots = ["", "src/"].filter((prefix) => {
|
|
15874
|
+
if (!prefix) return tracked ? tracked.has(first) || tracked.has(`${first}.py`) : deps.exists((0, import_node_path13.join)(root, first)) || deps.exists((0, import_node_path13.join)(root, `${first}.py`));
|
|
15875
|
+
return tracked ? tracked.has(`src/${first}`) || tracked.has(`src/${first}.py`) : deps.exists((0, import_node_path13.join)(root, "src", first)) || deps.exists((0, import_node_path13.join)(root, "src", `${first}.py`));
|
|
15876
|
+
});
|
|
15877
|
+
if (roots.length && !roots.some((prefix) => [".py", "/__init__.py", "/__main__.py"].some((suffix) => deps.exists((0, import_node_path13.join)(root, `${prefix}${rel}${suffix}`))))) {
|
|
15878
|
+
report("missing-module", module2);
|
|
15879
|
+
}
|
|
15880
|
+
} else if (["node", "bash", "sh"].includes(cmd)) {
|
|
15881
|
+
const rel = args[0];
|
|
15882
|
+
if (!rel || rel.startsWith("-") || placeholder(rel) || /^(\/|[A-Za-z]:|\.\.\/)/.test(rel)) continue;
|
|
15883
|
+
const first = refFirstSegment(rel);
|
|
15884
|
+
const tracked = deps.trackedFirstSegments([first]);
|
|
15885
|
+
if ((tracked ? tracked.has(first) : deps.exists((0, import_node_path13.join)(root, first))) && !deps.exists((0, import_node_path13.join)(root, rel)) && !deps.isIgnored([rel]).has(rel)) report("missing-path", rel);
|
|
15886
|
+
}
|
|
15887
|
+
}
|
|
15888
|
+
}
|
|
15889
|
+
return findings;
|
|
15890
|
+
}
|
|
15891
|
+
function checkRunbookCoverage(docs, base, git3) {
|
|
15892
|
+
const changed = git3(["diff", "--name-only", `${base}...HEAD`]).split(/\r?\n/).filter(Boolean);
|
|
15893
|
+
const findings = [];
|
|
15894
|
+
const matches = (glob, path2) => {
|
|
15895
|
+
const pattern = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "\0").replace(/\*\*/g, "").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replaceAll("\0", "(?:.*/)?").replaceAll("", ".*");
|
|
15896
|
+
return new RegExp(`^${pattern}$`).test(path2);
|
|
15897
|
+
};
|
|
15898
|
+
for (const [doc, body] of Object.entries(docs)) {
|
|
15899
|
+
const lines2 = stripFences(body);
|
|
15900
|
+
const headings = lines2.flatMap((text, i) => {
|
|
15901
|
+
const match = /^(#{1,6})\s+(.+)$/.exec(text);
|
|
15902
|
+
return match ? [{ level: match[1].length, title: match[2], start: i + 1 }] : [];
|
|
15903
|
+
});
|
|
15904
|
+
const diff = git3(["diff", "-U0", `${base}...HEAD`, "--", doc]);
|
|
15905
|
+
const ranges = [...diff.matchAll(/^@@ .*?\+(\d+)(?:,(\d+))? @@/gm)].map((m) => ({ start: Number(m[1]), count: m[2] === void 0 ? 1 : Number(m[2]) }));
|
|
15906
|
+
for (let i = 0; i < headings.length; i++) {
|
|
15907
|
+
const heading = headings[i];
|
|
15908
|
+
const end = headings.slice(i + 1).find((h) => h.level <= heading.level)?.start ?? lines2.length + 1;
|
|
15909
|
+
const markerEnd = headings[i + 1]?.start ?? end;
|
|
15910
|
+
const globs = lines2.slice(heading.start, markerEnd - 1).flatMap((text) => /<!--\s*runbook-covers:\s*(.*?)\s*-->/.exec(text)?.[1].split(",").map((s) => s.trim()) ?? []);
|
|
15911
|
+
const affected = changed.filter((path2) => globs.some((glob) => matches(glob, path2)));
|
|
15912
|
+
if (affected.length && !ranges.some((r) => r.count > 0 && r.start < end && r.start + r.count > heading.start)) {
|
|
15913
|
+
findings.push({ kind: "runbook-section-stale", doc, line: heading.start, detail: `${heading.title}: changed ${affected.join(", ")}; update this section or declare Runbook-Unaffected: <reason>` });
|
|
15914
|
+
}
|
|
15915
|
+
}
|
|
15916
|
+
}
|
|
15917
|
+
return findings;
|
|
15918
|
+
}
|
|
15788
15919
|
function readFileOrNull(path2) {
|
|
15789
15920
|
return (0, import_node_fs15.existsSync)(path2) ? (0, import_node_fs15.readFileSync)(path2, "utf8") : null;
|
|
15790
15921
|
}
|
|
@@ -15806,11 +15937,11 @@ function defaultListDocs(root) {
|
|
|
15806
15937
|
return [...ROOT_DOCS.filter((rel) => (0, import_node_fs15.existsSync)((0, import_node_path13.join)(root, rel))), ...docs];
|
|
15807
15938
|
}
|
|
15808
15939
|
var CHECK_IGNORE_MAX_BUFFER = 32 * 1024 * 1024;
|
|
15809
|
-
function defaultIsIgnored(root, relPaths,
|
|
15940
|
+
function defaultIsIgnored(root, relPaths, exec3 = import_node_child_process6.execFileSync) {
|
|
15810
15941
|
const inRepo = relPaths.filter((p) => !p.startsWith(".."));
|
|
15811
15942
|
if (inRepo.length === 0) return /* @__PURE__ */ new Set();
|
|
15812
15943
|
try {
|
|
15813
|
-
const out =
|
|
15944
|
+
const out = exec3("git", ["check-ignore", "--stdin"], {
|
|
15814
15945
|
cwd: root,
|
|
15815
15946
|
input: inRepo.join("\n"),
|
|
15816
15947
|
encoding: "utf8",
|
|
@@ -15829,17 +15960,19 @@ function defaultIsIgnored(root, relPaths, exec2 = import_node_child_process6.exe
|
|
|
15829
15960
|
throw error;
|
|
15830
15961
|
}
|
|
15831
15962
|
}
|
|
15832
|
-
function defaultTrackedFirstSegments(root, firstSegments,
|
|
15963
|
+
function defaultTrackedFirstSegments(root, firstSegments, exec3 = import_node_child_process6.execFileSync) {
|
|
15833
15964
|
if (firstSegments.length === 0) return /* @__PURE__ */ new Set();
|
|
15834
15965
|
try {
|
|
15835
|
-
const out =
|
|
15966
|
+
const out = exec3("git", ["ls-files", "-z", "--", ...firstSegments.map((s) => `:(literal)${s}`)], {
|
|
15836
15967
|
cwd: root,
|
|
15837
15968
|
encoding: "utf8",
|
|
15838
15969
|
maxBuffer: CHECK_IGNORE_MAX_BUFFER
|
|
15839
15970
|
});
|
|
15840
15971
|
const tracked = /* @__PURE__ */ new Set();
|
|
15841
15972
|
for (const path2 of out.split("\0")) {
|
|
15842
|
-
if (path2)
|
|
15973
|
+
if (path2) for (const segment of firstSegments) {
|
|
15974
|
+
if (path2 === segment || path2.startsWith(`${segment}/`)) tracked.add(segment);
|
|
15975
|
+
}
|
|
15843
15976
|
}
|
|
15844
15977
|
return tracked;
|
|
15845
15978
|
} catch (error) {
|
|
@@ -15863,7 +15996,7 @@ function runDocRefs(root, deps = {}) {
|
|
|
15863
15996
|
const walked = listDocs(root);
|
|
15864
15997
|
const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
|
|
15865
15998
|
const docs = Object.fromEntries(
|
|
15866
|
-
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile10((0, import_node_path13.join)(root, rel))]).filter(([, body]) => body != null)
|
|
15999
|
+
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile10((0, import_node_path13.join)(root, rel))]).filter(([rel, body]) => body != null && (!deps.runbooksOnly || isRunbook(rel, body)))
|
|
15867
16000
|
);
|
|
15868
16001
|
const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs);
|
|
15869
16002
|
const findings = [
|
|
@@ -15880,10 +16013,22 @@ function runDocRefs(root, deps = {}) {
|
|
|
15880
16013
|
} else {
|
|
15881
16014
|
findings.push(...checkCommands(docs, commandPaths).findings);
|
|
15882
16015
|
}
|
|
16016
|
+
const runbooks = Object.fromEntries(Object.entries(docs).filter(([doc, body]) => isRunbook(doc, body)));
|
|
16017
|
+
findings.push(...checkRunbookClaims(root, runbooks, { readFile: readFile10, exists, isIgnored, trackedFirstSegments, commandPaths, commandOptions: deps.commandOptions }));
|
|
16018
|
+
const warnings = [...refResult.warnings];
|
|
16019
|
+
if (deps.runbooksOnly && !Object.keys(runbooks).length) {
|
|
16020
|
+
warnings.push({ kind: "runbook-missing", doc: "docs/Guides/operator.md", line: 0, detail: "no operator runbook or <!-- runbook --> document exists" });
|
|
16021
|
+
}
|
|
16022
|
+
if (deps.base) {
|
|
16023
|
+
const git3 = deps.git ?? ((args) => (0, import_node_child_process6.execFileSync)("git", args, { cwd: root, encoding: "utf8", maxBuffer: CHECK_IGNORE_MAX_BUFFER, windowsHide: true }));
|
|
16024
|
+
const stale = checkRunbookCoverage(runbooks, deps.base, git3);
|
|
16025
|
+
const override = /^Runbook-Unaffected:[ \t]*\S.*$/m.test(git3(["log", "--format=%B", `${deps.base}..HEAD`]));
|
|
16026
|
+
(override ? warnings : findings).push(...stale);
|
|
16027
|
+
}
|
|
15883
16028
|
return {
|
|
15884
16029
|
ok: findings.length === 0,
|
|
15885
16030
|
findings,
|
|
15886
|
-
warnings
|
|
16031
|
+
warnings,
|
|
15887
16032
|
docCount: Object.keys(docs).length
|
|
15888
16033
|
};
|
|
15889
16034
|
}
|
|
@@ -16001,6 +16146,7 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
16001
16146
|
// src/gate-budget.ts
|
|
16002
16147
|
var BLESSED_RUN_WITH_BUDGET_SHA = "5ce541add569e54e7ca96de67a768577d3d604e4";
|
|
16003
16148
|
var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
16149
|
+
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
16004
16150
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
16005
16151
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
16006
16152
|
var FULL_SHA = /^[0-9a-f]{40}$/;
|
|
@@ -16375,6 +16521,8 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
16375
16521
|
// The pin is the CLI's blessed SHA, not an operator knob — one central place to bump (#3178).
|
|
16376
16522
|
GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA,
|
|
16377
16523
|
GATE_RUNNER_SHA: BLESSED_RUNNER_GATE_SHA,
|
|
16524
|
+
// #8129: the guard and its bundled CLI must come from the same immutable Hub revision.
|
|
16525
|
+
GATE_RUNBOOK_SHA: BLESSED_RUNBOOK_GUARD_SHA,
|
|
16378
16526
|
// #7407: repo-declared service containers and extra check steps; empty renders today's gate byte-for-byte.
|
|
16379
16527
|
GATE_SERVICES_YAML: "",
|
|
16380
16528
|
GATE_EXTRA_STEPS_YAML: ""
|
|
@@ -16412,6 +16560,8 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
16412
16560
|
}
|
|
16413
16561
|
function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranches, requiredChecks) {
|
|
16414
16562
|
const out = { ...vars };
|
|
16563
|
+
out.OWNER ??= parsed.owner;
|
|
16564
|
+
out.REPO ??= parsed.name;
|
|
16415
16565
|
out.REPO_NAME ??= parsed.name;
|
|
16416
16566
|
out.REPO_SLUG ??= parsed.slug;
|
|
16417
16567
|
out.CLASS ??= cls;
|
|
@@ -18093,10 +18243,10 @@ var rollout_plan_default = {
|
|
|
18093
18243
|
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)."
|
|
18094
18244
|
},
|
|
18095
18245
|
baseline: {
|
|
18096
|
-
version: "4.5.
|
|
18097
|
-
tag: "v4.5.
|
|
18098
|
-
commit: "
|
|
18099
|
-
npm: "@mutmutco/cli@4.5.
|
|
18246
|
+
version: "4.5.124",
|
|
18247
|
+
tag: "v4.5.124",
|
|
18248
|
+
commit: "255e8e974e8e",
|
|
18249
|
+
npm: "@mutmutco/cli@4.5.124"
|
|
18100
18250
|
},
|
|
18101
18251
|
exitCriterion: "fleet-n-of-n",
|
|
18102
18252
|
hubOnlyShortcut: "forbidden",
|
|
@@ -18113,14 +18263,14 @@ var rollout_plan_default = {
|
|
|
18113
18263
|
repo: "mutmutco/mmi-hub",
|
|
18114
18264
|
role: "canary",
|
|
18115
18265
|
schedule: "train",
|
|
18116
|
-
v3Target: "v4.5.
|
|
18266
|
+
v3Target: "v4.5.124"
|
|
18117
18267
|
}
|
|
18118
18268
|
],
|
|
18119
18269
|
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.",
|
|
18120
18270
|
rollback: {
|
|
18121
18271
|
independent: true,
|
|
18122
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
18123
|
-
v3Target: "v4.5.
|
|
18272
|
+
mechanism: "npm dist-tag latest -> 4.5.124 and redeploy the Hub Lambda from tag v4.5.124 (255e8e974e8e); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
18273
|
+
v3Target: "v4.5.124 (@mutmutco/cli@4.5.124, tag commit 255e8e974e8e \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
18124
18274
|
}
|
|
18125
18275
|
},
|
|
18126
18276
|
{
|
|
@@ -20977,6 +21127,14 @@ async function isStrayUnreleasedTag(deps, tag, remoteSha, repo) {
|
|
|
20977
21127
|
}
|
|
20978
21128
|
return deps.run("gh", ["release", "view", tag, "--repo", repo, "--json", "tagName"]).then(() => false).catch((e) => /not found|HTTP 404/i.test(e instanceof Error ? e.message : String(e)));
|
|
20979
21129
|
}
|
|
21130
|
+
async function isPromotedUnreleasedTag(deps, tag, remoteSha, repo) {
|
|
21131
|
+
try {
|
|
21132
|
+
await deps.run("git", ["merge-base", "--is-ancestor", remoteSha, "origin/main"]);
|
|
21133
|
+
} catch {
|
|
21134
|
+
return false;
|
|
21135
|
+
}
|
|
21136
|
+
return deps.run("gh", ["release", "view", tag, "--repo", repo, "--json", "tagName"]).then(() => false).catch((e) => /not found|HTTP 404/i.test(e instanceof Error ? e.message : String(e)));
|
|
21137
|
+
}
|
|
20980
21138
|
function cmpReleaseTag(a, b) {
|
|
20981
21139
|
const parse2 = (t) => {
|
|
20982
21140
|
const m = /^v(\d+)\.(\d+)\.(\d+)$/.exec(t.trim());
|
|
@@ -27609,7 +27767,9 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
27609
27767
|
for (const note of reverifyNotes) deps.warn?.(`release ledger: ${note}`);
|
|
27610
27768
|
}
|
|
27611
27769
|
const ledgerMode = persisted ? "strict" : "migration";
|
|
27612
|
-
const
|
|
27770
|
+
const preMainPartial = await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo);
|
|
27771
|
+
const promotedPartial = !preMainPartial && await isPromotedUnreleasedTag(deps, tag, tagSha, ctx.repo);
|
|
27772
|
+
const partialRelease = preMainPartial || promotedPartial;
|
|
27613
27773
|
if (partialRelease && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
|
|
27614
27774
|
if (!partialRelease) {
|
|
27615
27775
|
try {
|
|
@@ -27726,7 +27886,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
27726
27886
|
note: state2 === "failed" ? `release ${tag} was already published, but its deploy/publish on the release SHA FAILED \u2014 re-run the failed job (do NOT re-tag); the protected alignment PRs were reused` : state2 === "pending" ? `release ${tag} was already published; reused its protected alignment PRs, but a deploy/publish or alignment follow-up is unresolved \u2014 watch it to completion` : `release ${tag} was already published; alignment is complete and the deploy on the release SHA is proven green`
|
|
27727
27887
|
};
|
|
27728
27888
|
}
|
|
27729
|
-
if (!await probeAncestor(deps, "origin/main", tagSha, "release --resume: origin/main fast-forward ancestry")) {
|
|
27889
|
+
if (!promotedPartial && !await probeAncestor(deps, "origin/main", tagSha, "release --resume: origin/main fast-forward ancestry")) {
|
|
27730
27890
|
throw new Error(
|
|
27731
27891
|
`release --resume: origin/main is not an ancestor of ${tag} (${tagSha.slice(0, 12)}) \u2014 main has moved on since the partial release, so finishing it here would not be a fast-forward. Resolve by hand; nothing was written.`
|
|
27732
27892
|
);
|
|
@@ -27772,9 +27932,9 @@ ${recovery.note}`), recoveryInput);
|
|
|
27772
27932
|
}
|
|
27773
27933
|
await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified the required-check wall live on this resume" }), { strict: ledgerMode === "strict", landed: "the immutable tag on origin at the resumed SHA" });
|
|
27774
27934
|
deps.warn?.("Release: Promoting the verified release");
|
|
27775
|
-
const versionEnv = await pushAfterVercelVersionEnv(deps, meta, "main", tag, ["push", "origin", `${tagSha}:refs/heads/main`]);
|
|
27776
|
-
steps.push(`pushed ${tagSha.slice(0, 12)} to origin/main`);
|
|
27777
|
-
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: `resumed: ${tagSha.slice(0, 12)} pushed to origin/main (tag preserved, never re-cut)` }), { strict: ledgerMode === "strict", landed: "the immutable tag and the re-proven required-check wall" });
|
|
27935
|
+
const versionEnv = promotedPartial ? { status: "skipped", note: "" } : await pushAfterVercelVersionEnv(deps, meta, "main", tag, ["push", "origin", `${tagSha}:refs/heads/main`]);
|
|
27936
|
+
steps.push(promotedPartial ? `origin/main already contains ${tagSha.slice(0, 12)} (the main push had landed): not pushed again` : `pushed ${tagSha.slice(0, 12)} to origin/main`);
|
|
27937
|
+
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: promotedPartial ? "re-verified live: origin/main contains the immutable tag SHA (#8128)" : `resumed: ${tagSha.slice(0, 12)} pushed to origin/main (tag preserved, never re-cut)` }), { strict: ledgerMode === "strict", landed: "the immutable tag and the re-proven required-check wall" });
|
|
27778
27938
|
deps.warn?.("Release: Creating the GitHub Release");
|
|
27779
27939
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
27780
27940
|
const cutBy = (await ledger.load(anchors))?.cutBy ?? describeSessionIdentity();
|
|
@@ -38048,8 +38208,8 @@ async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
|
|
|
38048
38208
|
if (sources.length === 0) return { failures: [], skipped: [] };
|
|
38049
38209
|
const { mkdtemp: mkdtemp2, rm: rm2 } = await import("node:fs/promises");
|
|
38050
38210
|
const { tmpdir: tmpdir12 } = await import("node:os");
|
|
38051
|
-
const { join:
|
|
38052
|
-
const dir = await mkdtemp2(
|
|
38211
|
+
const { join: join49 } = await import("node:path");
|
|
38212
|
+
const dir = await mkdtemp2(join49(tmpdir12(), "mmi-hotfix-preflight-"));
|
|
38053
38213
|
const wt = { run: (file, args) => deps.run(file, file === "git" ? ["-C", dir, ...args] : args) };
|
|
38054
38214
|
const failures = [];
|
|
38055
38215
|
const skipped = [];
|
|
@@ -52015,6 +52175,65 @@ function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
|
|
|
52015
52175
|
// src/api-read.ts
|
|
52016
52176
|
var import_node_fs50 = require("node:fs");
|
|
52017
52177
|
|
|
52178
|
+
// src/run-log-read.ts
|
|
52179
|
+
init_cli_shared();
|
|
52180
|
+
var FAILED2 = /* @__PURE__ */ new Set(["failure", "cancelled", "timed_out"]);
|
|
52181
|
+
function logRepo(raw) {
|
|
52182
|
+
const repo = parseBrokerRepo(raw, "--repo");
|
|
52183
|
+
if (repo.split("/").some((s) => s === "." || s === "..")) throw new Error("read run log: --repo refuses dot segments");
|
|
52184
|
+
return repo;
|
|
52185
|
+
}
|
|
52186
|
+
function logJob(raw) {
|
|
52187
|
+
if (!raw.trim() || /[\r\n\0]/.test(raw)) throw new Error("read run log: --job must be a non-empty job name");
|
|
52188
|
+
return raw;
|
|
52189
|
+
}
|
|
52190
|
+
function sliceRunLog(text, tail) {
|
|
52191
|
+
const lines2 = text.replace(/\r\n/g, "\n").split("\n");
|
|
52192
|
+
if (lines2.at(-1) === "") lines2.pop();
|
|
52193
|
+
const context = /* @__PURE__ */ new Set();
|
|
52194
|
+
lines2.forEach((line, i) => {
|
|
52195
|
+
if (line.includes("##[error]")) for (let j = Math.max(0, i - 3); j <= Math.min(lines2.length - 1, i + 3); j++) context.add(j);
|
|
52196
|
+
});
|
|
52197
|
+
return { errorLines: lines2.filter((_, i) => context.has(i)), tail: lines2.slice(-tail) };
|
|
52198
|
+
}
|
|
52199
|
+
async function readRunLog(id, o, runner) {
|
|
52200
|
+
const runId = parseBrokerId(id, "run id");
|
|
52201
|
+
const repo = logRepo(o.repo);
|
|
52202
|
+
const tail = parseBrokerInt(o.tail ?? "200", "--tail", 1, 2e3);
|
|
52203
|
+
const attempt = o.attempt === void 0 ? void 0 : parseBrokerInt(o.attempt, "--attempt", 1, 1e3);
|
|
52204
|
+
const jobName = o.job === void 0 ? void 0 : logJob(o.job);
|
|
52205
|
+
if (o.failed && jobName !== void 0) throw new Error("read run log: --failed and --job are mutually exclusive");
|
|
52206
|
+
const base = `repos/${repo}/actions/runs/${runId}`;
|
|
52207
|
+
const pages = JSON.parse(await readAppApi(`${base}${attempt === void 0 ? "" : `/attempts/${attempt}`}/jobs`, runner));
|
|
52208
|
+
const jobs = pages.flatMap((page) => page.jobs);
|
|
52209
|
+
const selected = jobs.filter((job) => jobName === void 0 ? FAILED2.has(job.conclusion ?? "") : job.name === jobName);
|
|
52210
|
+
const result = { runId, repo, jobs: [] };
|
|
52211
|
+
if (!selected.length) 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"}` };
|
|
52212
|
+
for (const job of selected) {
|
|
52213
|
+
const text = await readAppApi(`repos/${repo}/actions/jobs/${parseBrokerId(String(job.id), "job id")}/logs`, runner);
|
|
52214
|
+
result.jobs.push({ id: job.id, name: job.name, conclusion: job.conclusion, failedStep: job.steps?.find((s) => s.conclusion === "failure")?.name ?? null, ...sliceRunLog(text, tail) });
|
|
52215
|
+
}
|
|
52216
|
+
return { result, exitCode: 0 };
|
|
52217
|
+
}
|
|
52218
|
+
function registerRunLogRead(run, runner) {
|
|
52219
|
+
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) => {
|
|
52220
|
+
requireBrokerApp("read run log");
|
|
52221
|
+
const env = brokerChildEnv();
|
|
52222
|
+
try {
|
|
52223
|
+
const { result, message: message2, exitCode } = await readRunLog(id, o, runner);
|
|
52224
|
+
if (message2) printLine(message2);
|
|
52225
|
+
else if (o.json) printLine(redactPrivateCredential(JSON.stringify(result), env));
|
|
52226
|
+
else for (const job of result.jobs) {
|
|
52227
|
+
printLine(redactPrivateCredential(`== ${job.name} (${job.id}) ${job.conclusion}: failing step "${job.failedStep ?? "none"}"`, env));
|
|
52228
|
+
for (const line of [...job.errorLines, ...job.tail]) printLine(redactPrivateCredential(line, env));
|
|
52229
|
+
}
|
|
52230
|
+
process.exitCode = exitCode;
|
|
52231
|
+
} catch (e) {
|
|
52232
|
+
return fail(redactPrivateCredential(`read run log: App-pool read failed (${e.message}) \u2014 re-run the same command; never falls back to personal`, env));
|
|
52233
|
+
}
|
|
52234
|
+
});
|
|
52235
|
+
}
|
|
52236
|
+
|
|
52018
52237
|
// src/app-read-broker.ts
|
|
52019
52238
|
init_cli_shared();
|
|
52020
52239
|
var OWNER_REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
@@ -52301,7 +52520,8 @@ function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
|
|
|
52301
52520
|
const runStatusOpt = (v) => parseBrokerRunStatus(v);
|
|
52302
52521
|
const assigneeOpt = (v) => parseBrokerAssignee(v);
|
|
52303
52522
|
const read = program3.command("read").description(`closed typed GitHub reads on the App pool (${BROKER_JSON_FLAGS_NOTE})`);
|
|
52304
|
-
const run = read.command("run").description("workflow-run reads (view/list/watch
|
|
52523
|
+
const run = read.command("run").description("workflow-run reads (view/list/watch pass gh through; log is a bounded App REST read)");
|
|
52524
|
+
registerRunLogRead(run, overrides?.apiRunner);
|
|
52305
52525
|
run.command("view").description("read a workflow run (attempt/exit-status/json; formatters refused)").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--json [fields]", "machine-readable output (comma-separated gh fields)", jsonOpt).option("--exit-status", "exit non-zero when the run failed").option("--attempt <n>", "the attempt number of the run (1-1000)", intOpt("--attempt", 1, 1e3)).argument("<id>", "workflow run number", idOpt("run id")).action(async (id, o) => {
|
|
52306
52526
|
let spec;
|
|
52307
52527
|
try {
|
|
@@ -52391,11 +52611,11 @@ var CONTENTS_BASE_RE = new RegExp(`^repos/${NAME}/${NAME}/contents/${FREE}$`);
|
|
|
52391
52611
|
var CONTENTS_QUERY_RE = /^ref=[^&\s]+$/;
|
|
52392
52612
|
var ACTIONS_RUNS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs$`);
|
|
52393
52613
|
var ACTIONS_RUN_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+$`);
|
|
52394
|
-
var ACTIONS_JOBS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d
|
|
52614
|
+
var ACTIONS_JOBS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+(?:/attempts/\\d+)?/jobs$`);
|
|
52395
52615
|
var ACTIONS_ARTIFACTS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+/artifacts$`);
|
|
52396
52616
|
var ACTIONS_ARTIFACT_ZIP_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/artifacts/\\d+/zip$`);
|
|
52397
52617
|
var ACTIONS_JOB_LOGS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/jobs/\\d+/logs$`);
|
|
52398
|
-
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs, repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>)";
|
|
52618
|
+
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs, repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>)";
|
|
52399
52619
|
function parseApiGetPath(rawPath) {
|
|
52400
52620
|
const path2 = rawPath.trim();
|
|
52401
52621
|
const refuse = () => new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
|
|
@@ -52429,8 +52649,16 @@ function buildApiGetArgs(path2, accept) {
|
|
|
52429
52649
|
async function runApiGet(run, path2, accept) {
|
|
52430
52650
|
return run(buildApiGetArgs(path2, accept), GIT_TIMEOUT_MS);
|
|
52431
52651
|
}
|
|
52652
|
+
var apiReadRunner = async (args, timeoutMs, env, binary) => (await execFileP("gh", args, { timeout: timeoutMs, ...binary ? { encoding: "latin1", maxBuffer: ZIP_MAX_BUFFER } : {}, ...env ? { env } : {} })).stdout;
|
|
52653
|
+
async function readAppApi(path2, runner = apiReadRunner) {
|
|
52654
|
+
const req = parseApiGetPath(path2);
|
|
52655
|
+
requireBrokerApp("read run log");
|
|
52656
|
+
const log = req.kind === "actions-job-logs";
|
|
52657
|
+
const body = await runApiGet((args, timeout) => runner(req.kind === "actions-jobs" ? [...args, "--slurp"] : args, log ? ZIP_TIMEOUT_MS : timeout, brokerChildEnv(), log || void 0), path2);
|
|
52658
|
+
return log ? Buffer.from(body, "latin1").toString("utf8") : body;
|
|
52659
|
+
}
|
|
52432
52660
|
function registerApiReadCommands(program3, overrides) {
|
|
52433
|
-
const spawn6 = overrides?.runner ??
|
|
52661
|
+
const spawn6 = overrides?.runner ?? apiReadRunner;
|
|
52434
52662
|
const api = program3.command("api").description("bounded GitHub REST reads for automation \u2014 GET only, allowlisted paths (pull files, compare, contents, actions runs/jobs/artifacts)");
|
|
52435
52663
|
api.command("get").description("GET one allowlisted REST path and print the response body to stdout (JSON, or raw file bytes with --accept raw); refuses everything outside the allowlist").option("--accept <type>", "response media type: json (default) or raw (file bytes)", (v) => {
|
|
52436
52664
|
acceptHeaderFor(v);
|
|
@@ -54053,7 +54281,7 @@ async function resolveWhoami(deps) {
|
|
|
54053
54281
|
}
|
|
54054
54282
|
|
|
54055
54283
|
// src/command-register-developer.ts
|
|
54056
|
-
var
|
|
54284
|
+
var import_node_path54 = require("node:path");
|
|
54057
54285
|
init_house_map();
|
|
54058
54286
|
init_hub_url();
|
|
54059
54287
|
|
|
@@ -54132,6 +54360,7 @@ function digestPackedFiles(packageRoot, packedFiles) {
|
|
|
54132
54360
|
var DIST_ARTIFACTS = [
|
|
54133
54361
|
{ path: "cli/dist/index.cjs", packageDir: "cli", output: "index.cjs" },
|
|
54134
54362
|
{ path: "cli/dist/main.cjs", packageDir: "cli", output: "main.cjs" },
|
|
54363
|
+
{ path: "cli/dist/runbook-sweep.cjs", packageDir: "cli", output: "runbook-sweep.cjs" },
|
|
54135
54364
|
{ path: "updater/dist/index.cjs", packageDir: "updater", output: "index.cjs" }
|
|
54136
54365
|
];
|
|
54137
54366
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
@@ -54272,6 +54501,123 @@ function runDistStatus(root) {
|
|
|
54272
54501
|
}
|
|
54273
54502
|
}
|
|
54274
54503
|
|
|
54504
|
+
// src/doc-refs-live.ts
|
|
54505
|
+
var import_node_path51 = require("node:path");
|
|
54506
|
+
var concrete = (value) => !!value && !/[<>{}$]/.test(value);
|
|
54507
|
+
var liveOption = (tokens2, flag) => {
|
|
54508
|
+
const index = tokens2.indexOf(flag);
|
|
54509
|
+
return index >= 0 ? tokens2[index + 1] : tokens2.find((token) => token.startsWith(`${flag}=`))?.slice(flag.length + 1);
|
|
54510
|
+
};
|
|
54511
|
+
async function checkRunbookLive(docs, deps) {
|
|
54512
|
+
const findings = [];
|
|
54513
|
+
const warnings = [];
|
|
54514
|
+
const cache = /* @__PURE__ */ new Map();
|
|
54515
|
+
const names = (type, scope) => {
|
|
54516
|
+
const key = `${type}:${scope}`;
|
|
54517
|
+
if (!cache.has(key)) cache.set(key, Promise.resolve().then(() => deps[type](scope)));
|
|
54518
|
+
return cache.get(key);
|
|
54519
|
+
};
|
|
54520
|
+
for (const [doc, body] of Object.entries(docs)) {
|
|
54521
|
+
if (!isRunbook(doc, body)) continue;
|
|
54522
|
+
for (const { tokens: raw, line } of shellClaims(body)) {
|
|
54523
|
+
if (raw[0] !== "mmi-cli") continue;
|
|
54524
|
+
const end = raw.indexOf("--");
|
|
54525
|
+
const tokens2 = end < 0 ? raw : raw.slice(0, end);
|
|
54526
|
+
const task = liveOption(tokens2, "--task");
|
|
54527
|
+
const repo = liveOption(tokens2, "--repo") ?? tokens2.find((token) => /^[\w.-]+\/[\w.-]+$/.test(token));
|
|
54528
|
+
const key = liveOption(tokens2, "--key");
|
|
54529
|
+
const scope = liveOption(tokens2, "--repo") ?? (liveOption(tokens2, "--slug") === "_org" ? "_org" : void 0);
|
|
54530
|
+
const checks = [];
|
|
54531
|
+
if (tokens2.includes("run-task") && concrete(repo) && concrete(task)) checks.push({ type: "tasks", scope: repo, name: task, kind: "unknown-task" });
|
|
54532
|
+
if (tokens2[1] === "vault" && tokens2[2] === "secrets" && ["use", "set", "rm"].includes(tokens2[3]) && concrete(scope) && concrete(key)) checks.push({ type: "vaultKeys", scope, name: key, kind: "unknown-vault-key" });
|
|
54533
|
+
for (const check of checks) {
|
|
54534
|
+
const detail = `${check.scope}: ${check.name}`;
|
|
54535
|
+
try {
|
|
54536
|
+
if (!(await names(check.type, check.scope)).has(check.name)) findings.push({ kind: check.kind, doc, line, detail });
|
|
54537
|
+
} catch {
|
|
54538
|
+
warnings.push({ kind: "live-unverified", doc, line, detail: `${check.type} ${check.scope}: remote names unreadable (auth/transport or unsupported scope)` });
|
|
54539
|
+
}
|
|
54540
|
+
}
|
|
54541
|
+
}
|
|
54542
|
+
let fence = "";
|
|
54543
|
+
const seen = /* @__PURE__ */ new Set();
|
|
54544
|
+
for (const [index, text] of body.split(/\r?\n/).entries()) {
|
|
54545
|
+
const marker = /^\s*(`{3,}|~{3,})(.*)$/.exec(text);
|
|
54546
|
+
if (marker) {
|
|
54547
|
+
if (!fence) fence = marker[1];
|
|
54548
|
+
else if (marker[1][0] === fence[0] && marker[1].length >= fence.length && !marker[2].trim()) fence = "";
|
|
54549
|
+
continue;
|
|
54550
|
+
}
|
|
54551
|
+
const spans = fence ? [text] : [...text.matchAll(/`([^`]+)`/g)].map((match) => match[1]);
|
|
54552
|
+
for (const span of spans) for (const match of span.matchAll(/https:\/\/[^\s`"']+/g)) {
|
|
54553
|
+
const url = match[0].replace(/[),.;]+$/, "");
|
|
54554
|
+
if (!concrete(url) || seen.has(url)) continue;
|
|
54555
|
+
let host;
|
|
54556
|
+
try {
|
|
54557
|
+
host = new URL(url).hostname;
|
|
54558
|
+
} catch {
|
|
54559
|
+
continue;
|
|
54560
|
+
}
|
|
54561
|
+
if (/(^|\.)example\./i.test(host)) continue;
|
|
54562
|
+
seen.add(url);
|
|
54563
|
+
try {
|
|
54564
|
+
const status = await deps.endpoint(url);
|
|
54565
|
+
if (status >= 500) findings.push({ kind: "endpoint-down", doc, line: index + 1, detail: `${url}: HTTP ${status}` });
|
|
54566
|
+
} catch {
|
|
54567
|
+
findings.push({ kind: "endpoint-down", doc, line: index + 1, detail: `${url}: network error or 10s timeout` });
|
|
54568
|
+
}
|
|
54569
|
+
}
|
|
54570
|
+
}
|
|
54571
|
+
}
|
|
54572
|
+
return { findings, warnings };
|
|
54573
|
+
}
|
|
54574
|
+
async function runLiveDocRefs(root, deps, live) {
|
|
54575
|
+
const offline = runDocRefs(root, { ...deps, runbooksOnly: true });
|
|
54576
|
+
const read = deps.readFile ?? readFileOrNull;
|
|
54577
|
+
const docs = Object.fromEntries((deps.listDocs ?? defaultListDocs)(root).flatMap((doc) => {
|
|
54578
|
+
const body = read((0, import_node_path51.join)(root, doc));
|
|
54579
|
+
return body !== null && isRunbook(doc, body) && !deps.isIgnored?.([doc]).has(doc) ? [[doc, body]] : [];
|
|
54580
|
+
}));
|
|
54581
|
+
const remote = await checkRunbookLive(docs, live);
|
|
54582
|
+
const findings = [...offline.findings, ...remote.findings];
|
|
54583
|
+
return { ...offline, findings, warnings: [...offline.warnings, ...remote.warnings], ok: findings.length === 0 };
|
|
54584
|
+
}
|
|
54585
|
+
|
|
54586
|
+
// src/doc-refs-live-io.ts
|
|
54587
|
+
var import_node_child_process20 = require("node:child_process");
|
|
54588
|
+
var import_node_util7 = require("node:util");
|
|
54589
|
+
var exec2 = (0, import_node_util7.promisify)(import_node_child_process20.execFile);
|
|
54590
|
+
function createRunbookLiveDeps(cliBin) {
|
|
54591
|
+
const json = async (args) => {
|
|
54592
|
+
const { stdout } = await exec2(process.execPath, [cliBin, ...args, "--json"], { windowsHide: true, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 });
|
|
54593
|
+
return JSON.parse(stdout);
|
|
54594
|
+
};
|
|
54595
|
+
return {
|
|
54596
|
+
tasks: async (repo) => {
|
|
54597
|
+
const project2 = await json(["oracle", "org", "project", "get", repo]);
|
|
54598
|
+
if (!project2 || typeof project2 !== "object" || Array.isArray(project2)) throw new Error("unreadable META");
|
|
54599
|
+
return new Set(Object.keys(project2.tenantTasks ?? {}));
|
|
54600
|
+
},
|
|
54601
|
+
vaultKeys: async (scope) => {
|
|
54602
|
+
if (scope === "_org") throw new Error("org names listing unsupported");
|
|
54603
|
+
const result = await json(["vault", "secrets", "list", "--repo", scope]);
|
|
54604
|
+
if (!Array.isArray(result?.secrets) || result.secrets.some((item) => typeof item.key !== "string")) throw new Error("unreadable names");
|
|
54605
|
+
return new Set(result.secrets.map((item) => item.key));
|
|
54606
|
+
},
|
|
54607
|
+
endpoint: async (url) => {
|
|
54608
|
+
const probe = async () => {
|
|
54609
|
+
const response = await fetch(url, { method: "GET", signal: AbortSignal.timeout(1e4) });
|
|
54610
|
+
await response.body?.cancel();
|
|
54611
|
+
return response.status;
|
|
54612
|
+
};
|
|
54613
|
+
const first = await probe().catch(() => 599);
|
|
54614
|
+
if (first < 500) return first;
|
|
54615
|
+
await new Promise((resolve7) => setTimeout(resolve7, 2e4));
|
|
54616
|
+
return probe();
|
|
54617
|
+
}
|
|
54618
|
+
};
|
|
54619
|
+
}
|
|
54620
|
+
|
|
54275
54621
|
// src/edge-commands.ts
|
|
54276
54622
|
init_cli_shared();
|
|
54277
54623
|
|
|
@@ -54369,7 +54715,7 @@ init_hub_auth();
|
|
|
54369
54715
|
|
|
54370
54716
|
// src/schedules-lift-command.ts
|
|
54371
54717
|
var import_promises12 = require("node:fs/promises");
|
|
54372
|
-
var
|
|
54718
|
+
var import_node_path52 = require("node:path");
|
|
54373
54719
|
init_clean_exit();
|
|
54374
54720
|
init_cli_shared();
|
|
54375
54721
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -54398,7 +54744,7 @@ async function readWorkflowFiles(dir) {
|
|
|
54398
54744
|
const files = [];
|
|
54399
54745
|
for (const name of names.sort()) {
|
|
54400
54746
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
54401
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0,
|
|
54747
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0, import_node_path52.join)(dir, name), "utf8") });
|
|
54402
54748
|
}
|
|
54403
54749
|
return files;
|
|
54404
54750
|
}
|
|
@@ -54480,9 +54826,9 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
54480
54826
|
}
|
|
54481
54827
|
|
|
54482
54828
|
// src/spawn-policy-core.ts
|
|
54483
|
-
var
|
|
54829
|
+
var import_node_child_process21 = require("node:child_process");
|
|
54484
54830
|
var import_node_fs55 = require("node:fs");
|
|
54485
|
-
var
|
|
54831
|
+
var import_node_path53 = require("node:path");
|
|
54486
54832
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
54487
54833
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
54488
54834
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -54551,7 +54897,7 @@ function findViolationsInSource(raw) {
|
|
|
54551
54897
|
return found;
|
|
54552
54898
|
}
|
|
54553
54899
|
function policedFiles(root) {
|
|
54554
|
-
const r = (0,
|
|
54900
|
+
const r = (0, import_node_child_process21.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
54555
54901
|
cwd: root,
|
|
54556
54902
|
encoding: "utf8",
|
|
54557
54903
|
windowsHide: true,
|
|
@@ -54568,7 +54914,7 @@ function runSpawnPolicy(root) {
|
|
|
54568
54914
|
for (const file of files) {
|
|
54569
54915
|
let raw;
|
|
54570
54916
|
try {
|
|
54571
|
-
raw = (0, import_node_fs55.readFileSync)((0,
|
|
54917
|
+
raw = (0, import_node_fs55.readFileSync)((0, import_node_path53.join)(root, file), "utf8");
|
|
54572
54918
|
} catch {
|
|
54573
54919
|
continue;
|
|
54574
54920
|
}
|
|
@@ -54588,7 +54934,7 @@ function runSpawnPolicy(root) {
|
|
|
54588
54934
|
function registerDeveloperCommands(program3) {
|
|
54589
54935
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
54590
54936
|
rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
|
|
54591
|
-
const path2 = (0,
|
|
54937
|
+
const path2 = (0, import_node_path54.join)(process.cwd(), ".gitignore");
|
|
54592
54938
|
const current = (0, import_node_fs56.existsSync)(path2) ? (0, import_node_fs56.readFileSync)(path2, "utf8") : null;
|
|
54593
54939
|
const plan = planManagedGitignore(current);
|
|
54594
54940
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
@@ -54734,17 +55080,23 @@ function registerDeveloperCommands(program3) {
|
|
|
54734
55080
|
await failGraceful(e.message);
|
|
54735
55081
|
}
|
|
54736
55082
|
});
|
|
54737
|
-
docs.command("refs").description("deterministic doc reference gate: repo paths, markdown links, mmi-cli command refs, and pins across docs, org-guide, mmi-help, README and architecture must resolve, or exit 1").option("--json", "machine-readable findings list: { ok, docCount, findings[], warnings[] }").addOption(new Option("--repo <owner/repo>", "not accepted \u2014 docs refs reads the current checkout").hideHelp()).action(async (o) => {
|
|
55083
|
+
docs.command("refs").description("deterministic doc reference gate: repo paths, markdown links, mmi-cli command refs, and pins across docs, org-guide, mmi-help, README and architecture must resolve, or exit 1").option("--json", "machine-readable findings list: { ok, docCount, findings[], warnings[] }").addOption(new Option("--repo <owner/repo>", "not accepted \u2014 docs refs reads the current checkout").hideHelp()).option("--base <ref>", "require covered runbook sections to be touched against this merge base (#8126)").option("--runbooks-only", "check only operator runbooks (#8126)").option("--live", "check operator runbooks against registry tasks, vault names and HTTPS endpoints (implies --runbooks-only)").action(async (o) => {
|
|
54738
55084
|
try {
|
|
54739
55085
|
if (o.repo) {
|
|
54740
55086
|
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli oracle docs refs${o.json ? " --json" : ""}`, "docs refs");
|
|
54741
55087
|
if (!guard.ok) return failGraceful(guard.message);
|
|
54742
55088
|
}
|
|
54743
55089
|
const root = await repoRoot();
|
|
54744
|
-
const
|
|
54745
|
-
|
|
54746
|
-
);
|
|
54747
|
-
const
|
|
55090
|
+
const manifest = buildCommandManifest(program3);
|
|
55091
|
+
const commandPaths = new Set(manifest.index.map((entry) => entry.path));
|
|
55092
|
+
const commandOptions = /* @__PURE__ */ new Map();
|
|
55093
|
+
const collect = (entry) => {
|
|
55094
|
+
commandOptions.set(entry.path, new Set(entry.options.flatMap((option) => option.flags.match(/--[\w-]+/g) ?? [])));
|
|
55095
|
+
entry.subcommands.forEach(collect);
|
|
55096
|
+
};
|
|
55097
|
+
collect(manifest.tree);
|
|
55098
|
+
const deps = { commandPaths, commandOptions, base: o.base, runbooksOnly: o.runbooksOnly };
|
|
55099
|
+
const result = o.live ? await runLiveDocRefs(root, deps, createRunbookLiveDeps(process.argv[1])) : runDocRefs(root, deps);
|
|
54748
55100
|
if (o.json) {
|
|
54749
55101
|
consoleIo.log(JSON.stringify({ ok: result.ok, docCount: result.docCount, findings: result.findings, warnings: result.warnings }, null, 2));
|
|
54750
55102
|
if (!result.ok) process.exitCode = 1;
|
|
@@ -54863,7 +55215,7 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
54863
55215
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
54864
55216
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
54865
55217
|
function envHealLockPath(home) {
|
|
54866
|
-
return (0,
|
|
55218
|
+
return (0, import_node_path55.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
54867
55219
|
}
|
|
54868
55220
|
async function withEnvHealLock(what, run) {
|
|
54869
55221
|
try {
|
|
@@ -55035,7 +55387,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
55035
55387
|
// has no generated routing index would
|
|
55036
55388
|
// get a permanent — demanding an artifact it never asked for.
|
|
55037
55389
|
docsIndexState: (root) => {
|
|
55038
|
-
if (!(0, import_node_fs57.existsSync)((0,
|
|
55390
|
+
if (!(0, import_node_fs57.existsSync)((0, import_node_path55.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
55039
55391
|
const real = createDocsIndexDeps(root);
|
|
55040
55392
|
let docs;
|
|
55041
55393
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -55044,7 +55396,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
55044
55396
|
},
|
|
55045
55397
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
55046
55398
|
healDocsIndex: (root) => {
|
|
55047
|
-
if (!(0, import_node_fs57.existsSync)((0,
|
|
55399
|
+
if (!(0, import_node_fs57.existsSync)((0, import_node_path55.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
55048
55400
|
const real = createDocsIndexDeps(root);
|
|
55049
55401
|
let docs;
|
|
55050
55402
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -56152,16 +56504,16 @@ function ciAuditDeps2() {
|
|
|
56152
56504
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
56153
56505
|
readSeedFile: (path2) => {
|
|
56154
56506
|
if (!root) return null;
|
|
56155
|
-
const fullPath = (0,
|
|
56507
|
+
const fullPath = (0, import_node_path55.join)(root, path2);
|
|
56156
56508
|
return (0, import_node_fs57.existsSync)(fullPath) ? (0, import_node_fs57.readFileSync)(fullPath, "utf8") : null;
|
|
56157
56509
|
}
|
|
56158
56510
|
};
|
|
56159
56511
|
}
|
|
56160
56512
|
function hubRoot2() {
|
|
56161
|
-
const fromPkg = (0,
|
|
56513
|
+
const fromPkg = (0, import_node_path55.join)(__dirname, "..", "..");
|
|
56162
56514
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
56163
|
-
if ((0, import_node_fs57.existsSync)((0,
|
|
56164
|
-
if ((0, import_node_fs57.existsSync)((0,
|
|
56515
|
+
if ((0, import_node_fs57.existsSync)((0, import_node_path55.join)(fromPkg, marker))) return fromPkg;
|
|
56516
|
+
if ((0, import_node_fs57.existsSync)((0, import_node_path55.join)(process.cwd(), marker))) return process.cwd();
|
|
56165
56517
|
return null;
|
|
56166
56518
|
}
|
|
56167
56519
|
registerQueryCommands(program2);
|
|
@@ -56330,7 +56682,7 @@ function directoryBytes(path2) {
|
|
|
56330
56682
|
return 0;
|
|
56331
56683
|
}
|
|
56332
56684
|
for (const entry of entries) {
|
|
56333
|
-
const child2 = (0,
|
|
56685
|
+
const child2 = (0, import_node_path55.join)(path2, entry.name);
|
|
56334
56686
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
56335
56687
|
else {
|
|
56336
56688
|
try {
|
|
@@ -56360,7 +56712,7 @@ function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
|
56360
56712
|
dirBytes,
|
|
56361
56713
|
listStagingDirs: (root) => (0, import_node_fs57.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
56362
56714
|
try {
|
|
56363
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
56715
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path55.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs57.statSync)(p).mtimeMs) };
|
|
56364
56716
|
} catch {
|
|
56365
56717
|
return { name: d.name, mtimeMs: Date.now() };
|
|
56366
56718
|
}
|
|
@@ -56374,7 +56726,7 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
56374
56726
|
return {
|
|
56375
56727
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
56376
56728
|
mtimeMs: (name) => {
|
|
56377
|
-
const p = (0,
|
|
56729
|
+
const p = (0, import_node_path55.join)(stagingRoot, name);
|
|
56378
56730
|
if (!(0, import_node_fs57.existsSync)(p)) return null;
|
|
56379
56731
|
try {
|
|
56380
56732
|
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs57.statSync)(q).mtimeMs);
|