@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 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 concrete = opts.flag && opts.argv ? targetFlagValueFromArgv(opts.flag, opts.argv) : void 0;
5123
- const target = concrete ?? `<${argName}>`;
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 import_node_path54 = require("node:path");
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(exec2, repo, head) {
10113
+ async function defaultReadRemoteHead(exec3, repo, head) {
10112
10114
  try {
10113
- await exec2("gh", ["api", `repos/${repo}/git/ref/heads/${encodeURIComponent(head)}`], { timeout: 15e3 });
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(exec2, repo, base, head) {
10122
+ async function defaultCompareRefs(exec3, repo, base, head) {
10121
10123
  try {
10122
- const { stdout } = await exec2(
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, exec2) {
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 exec2("git", ["remote", "get-url", "--push", "origin"], { timeout: GIT_TIMEOUT_MS });
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 exec2("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
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 exec2("git", ["rev-parse", "--verify", "HEAD^{commit}"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
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 exec2("git", ["push", "origin", `${commit}:refs/heads/${head}`], { timeout: GH_MUTATION_TIMEOUT_MS });
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, exec2) {
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 exec2("git", ["remote", "get-url", "origin"], { timeout: 1e4 });
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, exec2) {
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 exec2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 1e4 });
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(exec2, input) {
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 exec2("gh", ["api", `repos/${input.repo}/pulls?${qs.toString()}`], { timeout: GH_MUTATION_TIMEOUT_MS });
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(exec2, input, deps = {}) {
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 exec2("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS });
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(exec2, { repo: input.repo, head: input.head, base: input.base });
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(exec2, input) {
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 exec2("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS });
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(exec2) {
10415
+ async function defaultReadRateLimit(exec3) {
10414
10416
  try {
10415
- const { stdout } = await exec2("gh", ["api", "rate_limit"], { timeout: 15e3 });
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 exec2 = deps.exec ?? execFileP;
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(exec2, input));
10593
- const restCreateIssue = deps.restCreateIssue ?? ((input) => defaultRestCreateIssue(exec2, input));
10594
- const findOpenPr = deps.findOpenPr ?? ((input) => defaultFindOpenPr(exec2, input));
10595
- const readRateLimit = deps.readRateLimit ?? (() => defaultReadRateLimit(exec2));
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: exec2,
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: exec2,
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 exec2("gh", swapped.args, { timeout: GH_MUTATION_TIMEOUT_MS });
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: exec2,
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, exec2 = import_node_child_process6.execFileSync) {
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 = exec2("git", ["check-ignore", "--stdin"], {
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, exec2 = import_node_child_process6.execFileSync) {
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 = exec2("git", ["ls-files", "-z", "--", ...firstSegments.map((s) => `:(literal)${s}`)], {
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) tracked.add(path2.split("/")[0]);
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: refResult.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.122",
18097
- tag: "v4.5.122",
18098
- commit: "6eb49646ae34",
18099
- npm: "@mutmutco/cli@4.5.122"
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.122"
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.122 and redeploy the Hub Lambda from tag v4.5.122 (6eb49646ae34); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18123
- v3Target: "v4.5.122 (@mutmutco/cli@4.5.122, tag commit 6eb49646ae34 \u2014 last known-good release carrying the repo-index v4-only contract)"
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 partialRelease = await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo);
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: join48 } = await import("node:path");
38052
- const dir = await mkdtemp2(join48(tmpdir12(), "mmi-hotfix-preflight-"));
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) \u2014 stdout and exit codes pass gh through verbatim");
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+/jobs$`);
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 ?? (async (args, timeoutMs, env, binary) => (await execFileP("gh", args, { timeout: timeoutMs, ...binary ? { encoding: "latin1", maxBuffer: ZIP_MAX_BUFFER } : {}, ...env ? { env } : {} })).stdout);
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 import_node_path53 = require("node:path");
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 import_node_path51 = require("node:path");
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, import_node_path51.join)(dir, name), "utf8") });
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 import_node_child_process20 = require("node:child_process");
54829
+ var import_node_child_process21 = require("node:child_process");
54484
54830
  var import_node_fs55 = require("node:fs");
54485
- var import_node_path52 = require("node:path");
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, import_node_child_process20.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
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, import_node_path52.join)(root, file), "utf8");
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, import_node_path53.join)(process.cwd(), ".gitignore");
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 commandPaths = new Set(
54745
- buildCommandManifest(program3).index.map((entry) => entry.path)
54746
- );
54747
- const result = runDocRefs(root, { commandPaths });
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, import_node_path54.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
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, import_node_path54.join)(root, DOCS_INDEX_PATH))) return void 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, import_node_path54.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 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, import_node_path54.join)(root, path2);
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, import_node_path54.join)(__dirname, "..", "..");
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, import_node_path54.join)(fromPkg, marker))) return fromPkg;
56164
- if ((0, import_node_fs57.existsSync)((0, import_node_path54.join)(process.cwd(), marker))) return process.cwd();
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, import_node_path54.join)(path2, entry.name);
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, import_node_path54.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs57.statSync)(p).mtimeMs) };
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, import_node_path54.join)(stagingRoot, name);
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);