@mutmutco/codex-plugin 4.6.22 → 4.6.24

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/cli/dist/main.cjs CHANGED
@@ -710,6 +710,10 @@ function createGitHubClient(options = {}) {
710
710
  }
711
711
  return read();
712
712
  },
713
+ async restWithStatus(method, path2, init) {
714
+ const res = await request(method, joinUrl(baseUrl, path2), init);
715
+ return { status: res.status, body: await parseJson(res) };
716
+ },
713
717
  async restPaginate(path2, init) {
714
718
  const items = [];
715
719
  let url = withPerPage(joinUrl(baseUrl, path2));
@@ -1237,6 +1241,10 @@ var init_house_map = __esm({
1237
1241
  // allowlisted GitHub REST GET reads for automation (pull files, compare, contents)
1238
1242
  run: "devops",
1239
1243
  // #9479: one named workflow run — the bounded `run cancel <id>` and `run rerun <id>` writes
1244
+ vars: "devops",
1245
+ // #9780: repository Actions variables — typed get/list/create/update/delete receipts
1246
+ repo: "devops",
1247
+ // #9779: canonical repository typed reads (archive status)
1240
1248
  ci: "devops",
1241
1249
  // the CI/gate audit
1242
1250
  jev: "devops",
@@ -1616,10 +1624,10 @@ var init_cli_shared = __esm({
1616
1624
  );
1617
1625
  const appEnv = options.env ? void 0 : await ghReadAppEnv(file, args) ?? await ghFilingAppEnv(file, args);
1618
1626
  const reportsCredential = !options.env && /(^|[\\/])gh(\.exe)?$/i.test(file) && isAppFilingWrite(args);
1619
- const tag = async (call, credential) => {
1620
- if (!reportsCredential) return call;
1627
+ const tag = async (call3, credential) => {
1628
+ if (!reportsCredential) return call3;
1621
1629
  try {
1622
- return { ...await call, credential };
1630
+ return { ...await call3, credential };
1623
1631
  } catch (e) {
1624
1632
  if (e && typeof e === "object") e.credential = credential;
1625
1633
  throw e;
@@ -13519,7 +13527,7 @@ function useColor() {
13519
13527
  var program = new Command();
13520
13528
 
13521
13529
  // src/command-composition.ts
13522
- var import_node_fs65 = require("node:fs");
13530
+ var import_node_fs66 = require("node:fs");
13523
13531
  init_clean_exit();
13524
13532
  init_cli_shared();
13525
13533
 
@@ -15187,7 +15195,7 @@ function resolveVerbAliasShim(argv) {
15187
15195
 
15188
15196
  // src/command-composition.ts
15189
15197
  var import_node_os28 = require("node:os");
15190
- var import_node_path62 = require("node:path");
15198
+ var import_node_path63 = require("node:path");
15191
15199
 
15192
15200
  // src/board-read.ts
15193
15201
  var import_node_fs18 = require("node:fs");
@@ -15354,12 +15362,19 @@ var APP_POLL_PERMISSIONS = {
15354
15362
  var APP_JWT_BACKDATE_S = 60;
15355
15363
  var APP_JWT_TTL_S = 9 * 60;
15356
15364
  var AppActorError = class extends Error {
15357
- constructor(message2, code = "mint-failed") {
15365
+ constructor(message2, code = "mint-failed", mintRefusal) {
15358
15366
  super(message2);
15359
15367
  this.code = code;
15368
+ this.mintRefusal = mintRefusal;
15360
15369
  }
15361
15370
  code;
15371
+ mintRefusal;
15362
15372
  };
15373
+ function sanitizeMintRefusalReason(raw) {
15374
+ if (typeof raw !== "string") return void 0;
15375
+ const cleaned = raw.replace(/-----BEGIN [^-]+-----[\s\S]*?(?:-----END [^-]+-----|$)/g, "[redacted]").replace(/\bBearer\s+\S+/gi, "Bearer [redacted]").replace(/\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*\.?[A-Za-z0-9_-]*/g, "[redacted]").replace(/\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_]+/g, "[redacted]").replace(/\s+/g, " ").trim();
15376
+ return cleaned === "" ? void 0 : cleaned.slice(0, 200);
15377
+ }
15363
15378
  function commandPathOf(cmd) {
15364
15379
  const parts = [];
15365
15380
  let cursor = cmd;
@@ -15415,6 +15430,18 @@ var APP_WRITE_COMMANDS = /* @__PURE__ */ new Map([
15415
15430
  ["run cancel", { metadata: "read", actions: "write" }],
15416
15431
  // #9587: one named run rerun (all or failed jobs) — same actions:write profile, nothing broader.
15417
15432
  ["run rerun", { metadata: "read", actions: "write" }],
15433
+ // #9780: repository Actions variables — get/list mint only Variables:read; create/update/delete
15434
+ // Variables:write. Repository scope only; no secret store, no rename, no workflow dispatch.
15435
+ // #9790: the token key is `actions_variables` (as ci-reconcile.yml mints); `variables` was refused 422.
15436
+ ["vars get", { metadata: "read", actions_variables: "read" }],
15437
+ ["vars list", { metadata: "read", actions_variables: "read" }],
15438
+ ["vars create", { metadata: "read", actions_variables: "write" }],
15439
+ ["vars update", { metadata: "read", actions_variables: "write" }],
15440
+ ["vars delete", { metadata: "read", actions_variables: "write" }],
15441
+ // #9779: the archive-status read is a metadata:read-only repository read — nothing broader.
15442
+ ["repo archive-status", { metadata: "read" }],
15443
+ // #9788: historical merge-queue lifecycle — one fixed PullRequest.timelineItems GraphQL query; read-only.
15444
+ ["read merge-queue lifecycle", { metadata: "read", pull_requests: "read" }],
15418
15445
  ["issue comment", { metadata: "read", issues: "write", pull_requests: "write" }],
15419
15446
  // #9614: closing an issue is an issue-state write — it rides the org App (mmi-github-app), so the close
15420
15447
  // is attributed to the App instead of the personal account. Nothing broader than issues.
@@ -15557,7 +15584,17 @@ async function mintFromMember(deps, permissions, keys) {
15557
15584
  throw new AppActorError(`app actor: token mint transport failed (${e?.constructor?.name ?? "Error"}) \u2014 automation fails closed, no personal-token fallback`);
15558
15585
  }
15559
15586
  if (!res.ok) {
15560
- throw new AppActorError(`app actor: installation token mint failed (HTTP ${res.status}) \u2014 automation fails closed, no personal-token fallback`);
15587
+ let reason;
15588
+ try {
15589
+ reason = sanitizeMintRefusalReason((await res.json())?.message);
15590
+ } catch {
15591
+ reason = void 0;
15592
+ }
15593
+ throw new AppActorError(
15594
+ `app actor: installation token mint failed (HTTP ${res.status}${reason ? `: ${reason}` : ""}) \u2014 automation fails closed, no personal-token fallback`,
15595
+ "mint-failed",
15596
+ { status: res.status, ...reason ? { reason } : {} }
15597
+ );
15561
15598
  }
15562
15599
  let body;
15563
15600
  try {
@@ -17934,7 +17971,45 @@ function jobReportsPullRequestCheck(job) {
17934
17971
  });
17935
17972
  if (!directIf) return true;
17936
17973
  const expression = directIf.replace(/^\s*if:\s*/, "").trim().replace(/^\$\{\{\s*/, "").replace(/\s*\}\}$/, "").trim();
17937
- return !/^(?:github\.event_name\s*==\s*['"]push['"]|['"]push['"]\s*==\s*github\.event_name)$/.test(expression);
17974
+ if (/pull_request/i.test(expression)) return true;
17975
+ let rest = expression;
17976
+ let valid = true;
17977
+ const take = (pattern) => {
17978
+ const match = rest.match(pattern);
17979
+ if (!match) return void 0;
17980
+ rest = rest.slice(match[0].length).trimStart();
17981
+ return match[0];
17982
+ };
17983
+ const atom = () => {
17984
+ if (take(/^\(/)) {
17985
+ const possible2 = disjunction();
17986
+ if (!take(/^\)/)) valid = false;
17987
+ return possible2;
17988
+ }
17989
+ if (take(/^(?:github\.event_name\s*==\s*'[^']*'|github\.event_name\s*==\s*"[^"]*"|'[^']*'\s*==\s*github\.event_name|"[^"]*"\s*==\s*github\.event_name)/)) return false;
17990
+ if (take(/^always\(\s*\)/)) return true;
17991
+ if (take(/^needs\.[A-Za-z0-9_-]+\.outputs\.[A-Za-z0-9_-]+\s*(?:==|!=)\s*(?:'[^']*'|"[^"]*")/)) return true;
17992
+ valid = false;
17993
+ return true;
17994
+ };
17995
+ const conjunction = () => {
17996
+ let possible2 = atom();
17997
+ while (take(/^&&/)) {
17998
+ const next = atom();
17999
+ possible2 = possible2 && next;
18000
+ }
18001
+ return possible2;
18002
+ };
18003
+ const disjunction = () => {
18004
+ let possible2 = conjunction();
18005
+ while (take(/^\|\|/)) {
18006
+ const next = conjunction();
18007
+ possible2 = possible2 || next;
18008
+ }
18009
+ return possible2;
18010
+ };
18011
+ const possible = disjunction();
18012
+ return !valid || rest.length > 0 || possible;
17938
18013
  }
17939
18014
  function workflowTriggersPullRequest(yaml) {
17940
18015
  const onBlock = extractOnBlock(yaml);
@@ -25428,7 +25503,9 @@ function checkRunbookClaims(root, docs, deps) {
25428
25503
  if (token === "--") break;
25429
25504
  const flag = token.split("=")[0];
25430
25505
  if (/^--[\w-]+$/.test(flag) && flag !== "--help" && !placeholder(flag) && !deps.commandOptions?.get(resolved)?.has(flag)) {
25431
- report("unknown-flag", `mmi-cli ${resolved}: ${flag}`);
25506
+ const known = deps.commandOptions?.get(resolved);
25507
+ const knownFlags = known?.size ? ` (known flags: ${[...known].sort().join(" ")})` : "";
25508
+ report("unknown-flag", `mmi-cli ${resolved}: ${flag}${knownFlags} (source: ${deps.commandSource ?? "unspecified"})`);
25432
25509
  }
25433
25510
  }
25434
25511
  } else if (cmd === "npm") {
@@ -25667,7 +25744,7 @@ function runDocRefs(root, deps = {}) {
25667
25744
  findings.push(...checkCommands(docs, commandPaths).findings);
25668
25745
  }
25669
25746
  const runbooks = Object.fromEntries(Object.entries(docs).filter(([doc, body]) => isRunbook(doc, body)));
25670
- findings.push(...checkRunbookClaims(root, runbooks, { readFile: readFile10, exists, isIgnored, trackedFirstSegments, commandPaths, commandOptions: deps.commandOptions }));
25747
+ findings.push(...checkRunbookClaims(root, runbooks, { readFile: readFile10, exists, isIgnored, trackedFirstSegments, commandPaths, commandOptions: deps.commandOptions, commandSource: deps.commandSource }));
25671
25748
  const warnings = [...refResult.warnings];
25672
25749
  if (deps.runbooksOnly && !Object.keys(runbooks).length) {
25673
25750
  warnings.push({ kind: "runbook-missing", doc: "docs/Guides/operator.md", line: 0, detail: "no operator runbook or <!-- runbook --> document exists" });
@@ -28089,10 +28166,10 @@ var rollout_plan_default = {
28089
28166
  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)."
28090
28167
  },
28091
28168
  baseline: {
28092
- version: "4.6.22",
28093
- tag: "v4.6.22",
28094
- commit: "bc3629b50994",
28095
- npm: "@mutmutco/cli@4.6.22"
28169
+ version: "4.6.24",
28170
+ tag: "v4.6.24",
28171
+ commit: "3632dffdf8a5",
28172
+ npm: "@mutmutco/cli@4.6.24"
28096
28173
  },
28097
28174
  exitCriterion: "fleet-n-of-n",
28098
28175
  hubOnlyShortcut: "forbidden",
@@ -28109,14 +28186,14 @@ var rollout_plan_default = {
28109
28186
  repo: "mutmutco/mmi-hub",
28110
28187
  role: "canary",
28111
28188
  schedule: "train",
28112
- v3Target: "v4.6.22"
28189
+ v3Target: "v4.6.24"
28113
28190
  }
28114
28191
  ],
28115
28192
  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.",
28116
28193
  rollback: {
28117
28194
  independent: true,
28118
- mechanism: "npm dist-tag latest -> 4.6.22 and redeploy the Hub Lambda from tag v4.6.22 (bc3629b50994); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
28119
- v3Target: "v4.6.22 (@mutmutco/cli@4.6.22, tag commit bc3629b50994 \u2014 last known-good release carrying the repo-index v4-only contract)"
28195
+ mechanism: "npm dist-tag latest -> 4.6.24 and redeploy the Hub Lambda from tag v4.6.24 (3632dffdf8a5); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
28196
+ v3Target: "v4.6.24 (@mutmutco/cli@4.6.24, tag commit 3632dffdf8a5 \u2014 last known-good release carrying the repo-index v4-only contract)"
28120
28197
  }
28121
28198
  },
28122
28199
  {
@@ -41185,7 +41262,7 @@ var PRIMARY_GROUPS = [
41185
41262
  ["Setup and support", ["bootstrap", "secrets", "docs", "tests", "spawn", "dist"]],
41186
41263
  ["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
41187
41264
  ];
41188
- var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["oauth", "org", "runtime", "plugin", "packages", "github", "archive-prune", "intercom", "jev"]);
41265
+ var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["oauth", "org", "runtime", "plugin", "packages", "github", "archive-prune", "intercom", "jev", "vars", "repo"]);
41189
41266
  var CAMPAIGN_ORCHESTRATOR_TOP_LEVEL = /* @__PURE__ */ new Set(["pickup", "read", "run"]);
41190
41267
  var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
41191
41268
  var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
@@ -41234,6 +41311,10 @@ var COMMAND_OWNERSHIP = {
41234
41311
  read: { module_owner: "cli/src/app-read-broker.ts", consumer: "agent-workflow" },
41235
41312
  run: { module_owner: "cli/src/merge-group-orphans.ts", consumer: "agent-workflow" },
41236
41313
  // #9479
41314
+ vars: { module_owner: "cli/src/devops-vars.ts", consumer: "agent-workflow" },
41315
+ // #9780 repository Actions variables
41316
+ repo: { module_owner: "cli/src/repo-archive-status.ts", consumer: "agent-workflow" },
41317
+ // #9779 archive-status read
41237
41318
  ci: { module_owner: "cli/src/ci-audit.ts", consumer: "release-operator" },
41238
41319
  rcand: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
41239
41320
  release: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
@@ -44436,6 +44517,1208 @@ function registerRunRerun(run, writeToken, projects) {
44436
44517
  });
44437
44518
  }
44438
44519
 
44520
+ // src/devops-vars.ts
44521
+ var import_node_crypto13 = require("node:crypto");
44522
+ init_github_client();
44523
+ init_cli_shared();
44524
+ var VARS_RECEIPT_SCHEMA = "mmi-cli.devops-vars/1";
44525
+ function parseVarsName(raw) {
44526
+ const name = raw.trim();
44527
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || name.length > 256) {
44528
+ throw new Error(`<name> must match [A-Za-z_][A-Za-z0-9_]* (\u2264256 chars) \u2014 received '${raw.slice(0, 64)}'`);
44529
+ }
44530
+ return name;
44531
+ }
44532
+ function parseVarsValue(raw) {
44533
+ if (typeof raw !== "string") throw new Error(`--value must be a string \u2014 received ${raw === null ? "null" : typeof raw}; values are preserved exactly, never coerced`);
44534
+ if (raw.length > 49152) throw new Error(`--value exceeds GitHub's 48KB variable size limit (${raw.length} chars)`);
44535
+ return raw;
44536
+ }
44537
+ function resolveCanonicalRepo(raw, projects) {
44538
+ if (!Array.isArray(projects)) return { kind: "unavailable" };
44539
+ const input = raw.trim();
44540
+ if (!/^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)?$/.test(input) || input.split("/").some((part) => part === "." || part === "..")) {
44541
+ return { kind: "unregistered" };
44542
+ }
44543
+ const key = registryRepoKey(qualifyRegistryRepo(input));
44544
+ const byKey = /* @__PURE__ */ new Map();
44545
+ const aliasRepos = [];
44546
+ const lower = input.toLowerCase();
44547
+ for (const project2 of projects) {
44548
+ if (!project2 || typeof project2 !== "object" || Array.isArray(project2)) continue;
44549
+ if (!input.includes("/")) {
44550
+ const alias = typeof project2.slug === "string" ? project2.slug : typeof project2.name === "string" ? project2.name : void 0;
44551
+ if (typeof alias === "string" && alias.trim() !== "" && alias.toLowerCase() === lower && Array.isArray(project2.repos)) {
44552
+ for (const r of project2.repos) if (typeof r === "string" && r.trim() !== "") aliasRepos.push(qualifyRegistryRepo(r.trim()));
44553
+ }
44554
+ }
44555
+ if (!Array.isArray(project2.repos)) continue;
44556
+ for (const r of project2.repos) {
44557
+ if (typeof r !== "string" || r.trim() === "") continue;
44558
+ const qualified = qualifyRegistryRepo(r.trim());
44559
+ if (registryRepoKey(qualified) === key) byKey.set(key, byKey.get(key) ?? qualified);
44560
+ }
44561
+ }
44562
+ const distinct = /* @__PURE__ */ new Map();
44563
+ if (byKey.has(key)) distinct.set(key, byKey.get(key));
44564
+ for (const r of aliasRepos) {
44565
+ const rk = registryRepoKey(r);
44566
+ if (!distinct.has(rk)) distinct.set(rk, r);
44567
+ }
44568
+ const hits = [...distinct.entries()];
44569
+ if (hits.length === 1) {
44570
+ const [hitKey, repo] = hits[0];
44571
+ return { kind: "resolved", repo, via: hitKey === key ? "repo-name" : "project-alias" };
44572
+ }
44573
+ if (hits.length > 1) return { kind: "conflict", candidates: hits.map(([, repo]) => repo) };
44574
+ return { kind: "unregistered" };
44575
+ }
44576
+ function classifyUpstreamError(e) {
44577
+ if (e instanceof AppActorError) {
44578
+ const status = e.mintRefusal?.status ?? 0;
44579
+ return { status, reason: "mint-refused", mint: { status, reason: e.mintRefusal?.reason ?? e.message } };
44580
+ }
44581
+ if (e instanceof GitHubApiError) {
44582
+ if (e.status === 404) return { status: 404, reason: "bare-404" };
44583
+ if (e.status === 403 || e.status === 401) return { status: e.status, reason: "denied" };
44584
+ return { status: e.status, reason: "transport-error" };
44585
+ }
44586
+ const name = e?.name ?? "";
44587
+ if (name === "TimeoutError" || name === "AbortError" || /timeout|abort/i.test(String(e?.message ?? ""))) {
44588
+ return { status: 0, reason: "timeout" };
44589
+ }
44590
+ return { status: 0, reason: "transport-error" };
44591
+ }
44592
+ async function call(deps, log, method, path2, body) {
44593
+ try {
44594
+ const res = await deps.transport.restWithStatus(method, path2, body !== void 0 ? { body } : void 0);
44595
+ log.push({ method, path: path2, status: res.status });
44596
+ return res;
44597
+ } catch (e) {
44598
+ if (!(e instanceof AppActorError)) log.push({ method, path: path2, status: classifyUpstreamError(e).status });
44599
+ throw e;
44600
+ }
44601
+ }
44602
+ function beginOp() {
44603
+ return { startedAt: /* @__PURE__ */ new Date(), log: [] };
44604
+ }
44605
+ function receiptBase(deps, operation, repo, ctx, now) {
44606
+ return {
44607
+ schema: VARS_RECEIPT_SCHEMA,
44608
+ operation,
44609
+ repo,
44610
+ window: { startedAt: ctx.startedAt.toISOString(), endedAt: now().toISOString(), atomicSnapshot: false },
44611
+ actor: deps.actor ?? { credential: "app_installation" },
44612
+ correlationId: (deps.correlationId ?? import_node_crypto13.randomUUID)(),
44613
+ upstream: ctx.log
44614
+ };
44615
+ }
44616
+ var VARS_PER_PAGE = 30;
44617
+ var VARS_MAX_PAGES = 100;
44618
+ function parseVariableEntry(raw) {
44619
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("variable entry is not an object");
44620
+ const e = raw;
44621
+ if (typeof e.name !== "string" || e.name === "") throw new Error("variable entry has no name");
44622
+ if (e.value !== void 0 && typeof e.value !== "string") throw new Error(`variable '${e.name}' value is ${typeof e.value}, not a string \u2014 refusing to coerce`);
44623
+ if (e.created_at !== void 0 && typeof e.created_at !== "string") throw new Error(`variable '${e.name}' created_at is not a string`);
44624
+ if (e.updated_at !== void 0 && typeof e.updated_at !== "string") throw new Error(`variable '${e.name}' updated_at is not a string`);
44625
+ return {
44626
+ name: e.name,
44627
+ ...e.value !== void 0 ? { value: e.value } : {},
44628
+ ...e.created_at !== void 0 ? { createdAt: e.created_at } : {},
44629
+ ...e.updated_at !== void 0 ? { updatedAt: e.updated_at } : {}
44630
+ };
44631
+ }
44632
+ async function enumerateRepositoryVariables(deps, repo, log = []) {
44633
+ const entries = [];
44634
+ let total = -1;
44635
+ for (let page = 1; page <= VARS_MAX_PAGES; page++) {
44636
+ const { body } = await call(deps, log, "GET", `repos/${repo}/actions/variables?per_page=${VARS_PER_PAGE}&page=${page}`);
44637
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
44638
+ return { complete: false, reason: "malformed-page", entries, pages: page, detail: `page ${page} body is not a JSON object` };
44639
+ }
44640
+ const envelope = body;
44641
+ if (typeof envelope.total_count !== "number" || !Number.isInteger(envelope.total_count) || envelope.total_count < 0 || !Array.isArray(envelope.variables)) {
44642
+ return { complete: false, reason: "malformed-page", entries, pages: page, detail: `page ${page} envelope has no integer total_count / variables array` };
44643
+ }
44644
+ if (total >= 0 && envelope.total_count !== total) {
44645
+ return { complete: false, reason: "incomplete-pagination", entries, pages: page, detail: `total_count moved between pages (${total} \u2192 ${envelope.total_count}); the enumeration is not a snapshot` };
44646
+ }
44647
+ total = envelope.total_count;
44648
+ try {
44649
+ for (const raw of envelope.variables) entries.push(parseVariableEntry(raw));
44650
+ } catch (e) {
44651
+ return { complete: false, reason: "malformed-page", entries, pages: page, detail: `page ${page}: ${e.message}` };
44652
+ }
44653
+ const expectedPages = Math.max(1, Math.ceil(total / VARS_PER_PAGE));
44654
+ if (expectedPages > VARS_MAX_PAGES) {
44655
+ return { complete: false, reason: "page-bound-exceeded", entries, pages: page, detail: `total_count ${total} needs ${expectedPages} pages; the bounded read stops at ${VARS_MAX_PAGES}` };
44656
+ }
44657
+ if (page >= expectedPages) break;
44658
+ }
44659
+ if (entries.length !== total) {
44660
+ return { complete: false, reason: "incomplete-pagination", entries, pages: Math.max(1, Math.ceil(total / VARS_PER_PAGE)), detail: `accumulated ${entries.length} entries but total_count is ${total}` };
44661
+ }
44662
+ return { complete: true, entries, totalVariables: total, pages: Math.max(1, Math.ceil(total / VARS_PER_PAGE)) };
44663
+ }
44664
+ function mintCapability(c, permission) {
44665
+ return c.mint ? { permission, outcome: "mint-refused", status: c.mint.status, reason: c.mint.reason } : void 0;
44666
+ }
44667
+ function mintDetail(c) {
44668
+ return `App token mint refused${c.mint?.status ? ` (HTTP ${c.mint.status})` : ""}: ${c.mint?.reason ?? "unknown"}`;
44669
+ }
44670
+ function findEntry(entries, name) {
44671
+ const lower = name.toLowerCase();
44672
+ return entries.find((e) => e.name.toLowerCase() === lower);
44673
+ }
44674
+ async function readVariable(deps, repo, name, log) {
44675
+ let body;
44676
+ try {
44677
+ ({ body } = await call(deps, log, "GET", `repos/${repo}/actions/variables/${name}`));
44678
+ } catch (e) {
44679
+ const classified = classifyUpstreamError(e);
44680
+ const { status, reason } = classified;
44681
+ if (reason === "mint-refused") return { presence: "unknown", unknownReason: "mint-refused", detail: mintDetail(classified), capability: mintCapability(classified, deps.mintPermission ?? "variables:read") };
44682
+ if (reason === "denied") return { presence: "unknown", unknownReason: "denied", detail: `HTTP ${status}: the App installation cannot read repository variables`, capability: { permission: "variables:read", outcome: "denied" } };
44683
+ if (reason !== "bare-404") return { presence: "unknown", unknownReason: reason, detail: String(e.message).slice(0, 200) };
44684
+ try {
44685
+ const en = await enumerateRepositoryVariables(deps, repo, log);
44686
+ if (!en.complete) return { presence: "unknown", unknownReason: en.reason, detail: `bare 404 on GET, and the absence proof failed: ${en.detail}` };
44687
+ const hit = findEntry(en.entries, name);
44688
+ if (hit) return { presence: "present", value: hit.value, observed: { ...hit.createdAt ? { createdAt: hit.createdAt } : {}, ...hit.updatedAt ? { updatedAt: hit.updatedAt } : {}, source: "enumeration" } };
44689
+ return { presence: "absent", absenceProof: { provenBy: "complete-enumeration", pages: en.pages, perPage: VARS_PER_PAGE, totalVariables: en.totalVariables, nameExcluded: true } };
44690
+ } catch (proofError) {
44691
+ const proof = classifyUpstreamError(proofError);
44692
+ if (proof.reason === "mint-refused") return { presence: "unknown", unknownReason: "mint-refused", detail: `bare 404 on GET, and the absence proof failed: ${mintDetail(proof)}`, capability: mintCapability(proof, deps.mintPermission ?? "variables:read") };
44693
+ return { presence: "unknown", unknownReason: proof.reason === "bare-404" ? "transport-error" : proof.reason, detail: `bare 404 on GET, and the absence proof failed: ${String(proofError.message).slice(0, 160)}` };
44694
+ }
44695
+ }
44696
+ try {
44697
+ const entry = parseVariableEntry(body);
44698
+ return { presence: "present", value: entry.value, observed: { ...entry.createdAt ? { createdAt: entry.createdAt } : {}, ...entry.updatedAt ? { updatedAt: entry.updatedAt } : {}, source: "get" } };
44699
+ } catch (e) {
44700
+ return { presence: "unknown", unknownReason: "parser-refused", detail: `HTTP 200 but the body could not be parsed as a variable: ${e.message}` };
44701
+ }
44702
+ }
44703
+ async function getVariableReceipt(deps, repo, name) {
44704
+ const ctx = beginOp();
44705
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
44706
+ const read = await readVariable(deps, repo, name, ctx.log);
44707
+ return {
44708
+ ...receiptBase(deps, "get", repo, ctx, now),
44709
+ name,
44710
+ presence: read.presence,
44711
+ ...read.value !== void 0 ? { value: read.value } : {},
44712
+ ...read.observed ? { observed: read.observed } : {},
44713
+ ...read.absenceProof ? { absenceProof: read.absenceProof } : {},
44714
+ ...read.unknownReason ? { unknownReason: read.unknownReason } : {},
44715
+ ...read.capability ? { capability: read.capability } : {}
44716
+ };
44717
+ }
44718
+ async function listVariablesReceipt(deps, repo) {
44719
+ const ctx = beginOp();
44720
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
44721
+ try {
44722
+ const en = await enumerateRepositoryVariables(deps, repo, ctx.log);
44723
+ if (en.complete) {
44724
+ return {
44725
+ ...receiptBase(deps, "list", repo, ctx, now),
44726
+ pagination: { pages: en.pages, perPage: VARS_PER_PAGE, complete: true, totalVariables: en.totalVariables },
44727
+ variables: en.entries.map(({ name, createdAt, updatedAt }) => ({ name, ...createdAt ? { createdAt } : {}, ...updatedAt ? { updatedAt } : {} }))
44728
+ };
44729
+ }
44730
+ return {
44731
+ ...receiptBase(deps, "list", repo, ctx, now),
44732
+ pagination: { pages: en.pages, perPage: VARS_PER_PAGE, complete: false, reason: en.reason, detail: en.detail },
44733
+ variables: en.entries.map(({ name, createdAt, updatedAt }) => ({ name, ...createdAt ? { createdAt } : {}, ...updatedAt ? { updatedAt } : {} })),
44734
+ unknownReason: en.reason
44735
+ };
44736
+ } catch (e) {
44737
+ const classified = classifyUpstreamError(e);
44738
+ const { status, reason } = classified;
44739
+ return {
44740
+ ...receiptBase(deps, "list", repo, ctx, now),
44741
+ pagination: { pages: 0, perPage: VARS_PER_PAGE, complete: false, reason },
44742
+ variables: [],
44743
+ unknownReason: reason,
44744
+ ...reason === "mint-refused" ? { capability: mintCapability(classified, deps.mintPermission ?? "variables:read"), pagination: { pages: 0, perPage: VARS_PER_PAGE, complete: false, reason, detail: mintDetail(classified) } } : {},
44745
+ ...reason === "denied" ? { capability: { permission: "variables:read", outcome: "denied" }, pagination: { pages: 0, perPage: VARS_PER_PAGE, complete: false, reason, detail: `HTTP ${status}: the App installation cannot list repository variables` } } : {}
44746
+ };
44747
+ }
44748
+ }
44749
+ async function readBaseline(deps, repo, name, log) {
44750
+ const read = await readVariable(deps, repo, name, log);
44751
+ if (read.presence === "present") return { presence: "present", value: read.value };
44752
+ if (read.presence === "absent") return { presence: "absent" };
44753
+ return { presence: "unknown", unknownReason: read.unknownReason, ...read.capability ? { capability: read.capability } : {} };
44754
+ }
44755
+ function rejectedWrite(base, baseline, reason, capability) {
44756
+ return {
44757
+ ...base,
44758
+ outcome: "rejected",
44759
+ casLimitation: "no-atomic-compare-and-swap",
44760
+ baseline: { presence: "present", value: baseline.value, source: "get" },
44761
+ reason,
44762
+ ...capability ? { capability } : {}
44763
+ };
44764
+ }
44765
+ function unknownWrite(base, baseline, reason, readback) {
44766
+ return {
44767
+ ...base,
44768
+ outcome: "outcome-unknown",
44769
+ casLimitation: "no-atomic-compare-and-swap",
44770
+ baseline: baseline.presence === "present" ? { presence: "present", value: baseline.value, source: "get" } : baseline,
44771
+ ...readback ? { readback } : {},
44772
+ reason
44773
+ };
44774
+ }
44775
+ async function createVariableReceipt(deps, repo, name, value) {
44776
+ const ctx = beginOp();
44777
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
44778
+ let en;
44779
+ try {
44780
+ en = await enumerateRepositoryVariables(deps, repo, ctx.log);
44781
+ } catch (e) {
44782
+ const classified = classifyUpstreamError(e);
44783
+ const { status, reason } = classified;
44784
+ return {
44785
+ ...receiptBase(deps, "create", repo, ctx, now),
44786
+ operation: "create",
44787
+ name,
44788
+ outcome: "not-dispatched",
44789
+ casLimitation: "no-atomic-compare-and-swap",
44790
+ baseline: { presence: "unknown", source: "complete-enumeration" },
44791
+ reason: `no observed absent baseline: ${reason === "denied" ? `HTTP ${status}: the App installation cannot list repository variables` : reason === "mint-refused" ? mintDetail(classified) : String(e.message).slice(0, 160)}`,
44792
+ ...reason === "denied" ? { capability: { permission: "variables:read", outcome: "denied" } } : {},
44793
+ ...reason === "mint-refused" ? { capability: mintCapability(classified, deps.mintPermission ?? "variables:write") } : {}
44794
+ };
44795
+ }
44796
+ const base = { ...receiptBase(deps, "create", repo, ctx, now), operation: "create", name, casLimitation: "no-atomic-compare-and-swap" };
44797
+ if (!en.complete) {
44798
+ return { ...base, outcome: "not-dispatched", baseline: { presence: "unknown", source: "complete-enumeration" }, reason: `no observed absent baseline: ${en.reason} (${en.detail})` };
44799
+ }
44800
+ const existing = findEntry(en.entries, name);
44801
+ if (existing) {
44802
+ return { ...base, outcome: "not-dispatched", baseline: { presence: "present", ...existing.value !== void 0 ? { value: existing.value } : {}, source: "complete-enumeration" }, reason: "variable already present \u2014 create only dispatches against an observed absent baseline (use update)" };
44803
+ }
44804
+ try {
44805
+ await call(deps, ctx.log, "POST", `repos/${repo}/actions/variables`, { name, value });
44806
+ } catch (e) {
44807
+ const classified = classifyUpstreamError(e);
44808
+ const { status, reason } = classified;
44809
+ const absentBaseline = { presence: "absent", source: "complete-enumeration" };
44810
+ if (reason === "mint-refused") return { ...base, outcome: "not-dispatched", baseline: absentBaseline, reason: mintDetail(classified), capability: mintCapability(classified, deps.mintPermission ?? "variables:write") };
44811
+ if (reason === "denied") return { ...base, outcome: "rejected", baseline: absentBaseline, reason: `HTTP ${status}: the App installation lacks repository Variables write`, capability: { permission: "variables:write", outcome: "denied" } };
44812
+ if (status === 404) return { ...base, outcome: "rejected", baseline: absentBaseline, reason: "HTTP 404: repository missing or not visible to the installation" };
44813
+ if (status === 409 || status === 422) return { ...base, outcome: "rejected", baseline: absentBaseline, reason: `HTTP ${status}: ${String(e.message).slice(0, 160)}` };
44814
+ return unknownWrite({ ...base, schema: VARS_RECEIPT_SCHEMA, operation: "create", repo }, { presence: "absent", source: "complete-enumeration" }, `${reason === "timeout" ? "dispatch timed out" : `HTTP ${status || "no response"}`}; the create MAY have applied \u2014 reconcile read-only, never replay`);
44815
+ }
44816
+ const rb = await readVariable(deps, repo, name, ctx.log);
44817
+ if (rb.presence === "present") {
44818
+ if (rb.value === value) {
44819
+ return { ...base, outcome: "verified", baseline: { presence: "absent", source: "complete-enumeration" }, readback: { presence: "present", value: rb.value } };
44820
+ }
44821
+ return unknownWrite(base, { presence: "absent", source: "complete-enumeration" }, "read-back shows a different value than dispatched \u2014 a concurrent outside write is possible; reconcile read-only", { presence: "present", value: rb.value });
44822
+ }
44823
+ if (rb.presence === "absent") {
44824
+ return unknownWrite(base, { presence: "absent", source: "complete-enumeration" }, "accepted but the read-back proves absence \u2014 reconcile read-only before any retry", { presence: "absent" });
44825
+ }
44826
+ return {
44827
+ ...base,
44828
+ outcome: "accepted-but-unverified",
44829
+ baseline: { presence: "absent", source: "complete-enumeration" },
44830
+ readback: { presence: "unknown" },
44831
+ reason: `create accepted but the read-back could not verify: ${rb.unknownReason ?? "unknown"}`
44832
+ };
44833
+ }
44834
+ async function updateVariableReceipt(deps, repo, name, value) {
44835
+ const ctx = beginOp();
44836
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
44837
+ const base = { ...receiptBase(deps, "update", repo, ctx, now), operation: "update", name, casLimitation: "no-atomic-compare-and-swap" };
44838
+ const baseline = await readBaseline(deps, repo, name, ctx.log);
44839
+ if (baseline.presence !== "present") {
44840
+ return {
44841
+ ...base,
44842
+ outcome: "not-dispatched",
44843
+ baseline: { presence: baseline.presence, ...baseline.value !== void 0 ? { value: baseline.value } : {}, source: "get" },
44844
+ reason: baseline.presence === "absent" ? "no present baseline \u2014 the enumeration proved absence (use create)" : `no retained present baseline (${baseline.unknownReason ?? "unknown"}); a bare 404 is never a present baseline`,
44845
+ ...baseline.capability ? { capability: baseline.capability } : {}
44846
+ };
44847
+ }
44848
+ const live = await readVariable(deps, repo, name, ctx.log);
44849
+ if (live.presence !== "present" || live.value !== baseline.value) {
44850
+ return {
44851
+ ...base,
44852
+ outcome: "not-dispatched",
44853
+ baseline: { presence: "present", value: baseline.value, source: "get" },
44854
+ ...live.presence === "present" ? { readback: { presence: "present", value: live.value } } : {},
44855
+ reason: live.presence === "present" ? "concurrent change: the live value no longer matches the retained baseline \u2014 stopped for reconciliation" : `baseline unconfirmed before dispatch (${live.unknownReason ?? "unknown"}) \u2014 stopped`
44856
+ };
44857
+ }
44858
+ try {
44859
+ await call(deps, ctx.log, "PATCH", `repos/${repo}/actions/variables/${name}`, { value });
44860
+ } catch (e) {
44861
+ const classified = classifyUpstreamError(e);
44862
+ const { status, reason } = classified;
44863
+ if (reason === "mint-refused") return { ...base, outcome: "not-dispatched", baseline: { presence: "present", value: baseline.value, source: "get" }, reason: mintDetail(classified), capability: mintCapability(classified, deps.mintPermission ?? "variables:write") };
44864
+ if (reason === "denied") return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: the App installation lacks repository Variables write`, { permission: "variables:write", outcome: "denied" });
44865
+ if (status === 404) return rejectedWrite(base, { presence: "present", value: baseline.value }, "HTTP 404 at write: the variable vanished after the baseline check (concurrent deletion suspected) \u2014 reconcile read-only, never create");
44866
+ if (status === 409 || status === 422) return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: ${String(e.message).slice(0, 160)}`);
44867
+ return unknownWrite(base, { presence: "present", value: baseline.value }, `${reason === "timeout" ? "dispatch timed out" : `HTTP ${status || "no response"}`}; the update MAY have applied \u2014 reconcile read-only, never replay`);
44868
+ }
44869
+ const rb = await readVariable(deps, repo, name, ctx.log);
44870
+ if (rb.presence === "present") {
44871
+ if (rb.value === value) return { ...base, outcome: "verified", baseline: { presence: "present", value: baseline.value, source: "get" }, readback: { presence: "present", value: rb.value } };
44872
+ return unknownWrite(base, { presence: "present", value: baseline.value }, "read-back shows a different value than dispatched \u2014 a concurrent outside write is possible; reconcile read-only", { presence: "present", value: rb.value });
44873
+ }
44874
+ if (rb.presence === "absent") {
44875
+ return unknownWrite(base, { presence: "present", value: baseline.value }, "accepted but the read-back proves absence \u2014 reconcile read-only before any retry", { presence: "absent" });
44876
+ }
44877
+ return {
44878
+ ...base,
44879
+ outcome: "accepted-but-unverified",
44880
+ baseline: { presence: "present", value: baseline.value, source: "get" },
44881
+ readback: { presence: "unknown" },
44882
+ reason: `update accepted but the read-back could not verify: ${rb.unknownReason ?? "unknown"}`
44883
+ };
44884
+ }
44885
+ async function deleteVariableReceipt(deps, repo, name) {
44886
+ const ctx = beginOp();
44887
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
44888
+ const base = { ...receiptBase(deps, "delete", repo, ctx, now), operation: "delete", name, casLimitation: "no-atomic-compare-and-swap" };
44889
+ const baseline = await readBaseline(deps, repo, name, ctx.log);
44890
+ if (baseline.presence !== "present") {
44891
+ return {
44892
+ ...base,
44893
+ outcome: "not-dispatched",
44894
+ baseline: { presence: baseline.presence, ...baseline.value !== void 0 ? { value: baseline.value } : {}, source: "get" },
44895
+ reason: baseline.presence === "absent" ? "no present baseline \u2014 the enumeration already proves absence; nothing to delete" : `no retained present baseline (${baseline.unknownReason ?? "unknown"}); a bare 404 never authorizes a delete`,
44896
+ ...baseline.capability ? { capability: baseline.capability } : {}
44897
+ };
44898
+ }
44899
+ const live = await readVariable(deps, repo, name, ctx.log);
44900
+ if (live.presence !== "present" || live.value !== baseline.value) {
44901
+ return {
44902
+ ...base,
44903
+ outcome: "not-dispatched",
44904
+ baseline: { presence: "present", value: baseline.value, source: "get" },
44905
+ ...live.presence === "present" ? { readback: { presence: "present", value: live.value } } : {},
44906
+ reason: live.presence === "present" ? "concurrent change: the live value no longer matches the retained baseline \u2014 stopped for reconciliation" : `baseline unconfirmed before dispatch (${live.unknownReason ?? "unknown"}) \u2014 stopped`
44907
+ };
44908
+ }
44909
+ try {
44910
+ await call(deps, ctx.log, "DELETE", `repos/${repo}/actions/variables/${name}`);
44911
+ } catch (e) {
44912
+ const classified = classifyUpstreamError(e);
44913
+ const { status, reason } = classified;
44914
+ if (reason === "mint-refused") return { ...base, outcome: "not-dispatched", baseline: { presence: "present", value: baseline.value, source: "get" }, reason: mintDetail(classified), capability: mintCapability(classified, deps.mintPermission ?? "variables:write") };
44915
+ if (reason === "denied") return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: the App installation lacks repository Variables write`, { permission: "variables:write", outcome: "denied" });
44916
+ if (status === 404) return rejectedWrite(base, { presence: "present", value: baseline.value }, "HTTP 404 at write: the variable vanished after the baseline check (concurrent deletion suspected) \u2014 reconcile read-only");
44917
+ if (status === 409 || status === 422) return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: ${String(e.message).slice(0, 160)}`);
44918
+ return unknownWrite(base, { presence: "present", value: baseline.value }, `${reason === "timeout" ? "dispatch timed out" : `HTTP ${status || "no response"}`}; the delete MAY have applied \u2014 reconcile read-only, never replay`);
44919
+ }
44920
+ try {
44921
+ const en = await enumerateRepositoryVariables(deps, repo);
44922
+ if (en.complete && !findEntry(en.entries, name)) {
44923
+ return { ...base, outcome: "verified", baseline: { presence: "present", value: baseline.value, source: "get" }, readback: { presence: "absent" }, reason: `absence proven by complete enumeration (${en.pages} page(s), ${en.totalVariables} variable(s), name excluded)` };
44924
+ }
44925
+ if (en.complete) {
44926
+ const still = findEntry(en.entries, name);
44927
+ return unknownWrite(base, { presence: "present", value: baseline.value }, "delete accepted but the variable is still enumerated \u2014 reconcile read-only, never replay", { presence: "present", ...still.value !== void 0 ? { value: still.value } : {} });
44928
+ }
44929
+ return {
44930
+ ...base,
44931
+ outcome: "accepted-but-unverified",
44932
+ baseline: { presence: "present", value: baseline.value, source: "get" },
44933
+ reason: `delete accepted but absence is unproven: ${en.reason} (${en.detail})`
44934
+ };
44935
+ } catch (e) {
44936
+ return {
44937
+ ...base,
44938
+ outcome: "accepted-but-unverified",
44939
+ baseline: { presence: "present", value: baseline.value, source: "get" },
44940
+ reason: `delete accepted but absence is unproven (${classifyUpstreamError(e).reason}): ${String(e.message).slice(0, 160)}`
44941
+ };
44942
+ }
44943
+ }
44944
+ var VARS_HELP_AFTER = [
44945
+ "",
44946
+ "Presence semantics (#9780): present | absent | unknown. A bare 404 is UNKNOWN, never absence \u2014",
44947
+ "absence is claimed only from a complete bounded enumeration excluding the exact name, with",
44948
+ "pagination evidence. Truncated pagination, malformed pages, denial, parser refusal or timeout is",
44949
+ "unknown. Values are preserved exactly (including `off`) and never coerced.",
44950
+ "Mutations verify by read-back (delete: complete enumeration) \u2014 an HTTP success alone is never",
44951
+ "verified; an ambiguous result is reported unknown and reconciled READ-ONLY, never replayed.",
44952
+ "The pre-write check is NOT atomic compare-and-swap: a concurrent outside write between the check",
44953
+ "and the dispatch is not prevented. Repository scope only \u2014 no organization/environment scope, no",
44954
+ "secret store, no rename, no workflow dispatch, no credential fallback."
44955
+ ].join("\n");
44956
+ function resolveRepoOrFail(raw, projects, verb) {
44957
+ const resolution = resolveCanonicalRepo(raw, projects);
44958
+ if (resolution.kind === "resolved") return resolution.repo;
44959
+ if (resolution.kind === "conflict") return fail(`vars ${verb}: '${raw}' resolves to MULTIPLE registered repositories (${resolution.candidates.join(", ")}) \u2014 pass the exact owner/repo; ambiguous aliases are refused (#9780)`);
44960
+ if (resolution.kind === "unavailable") return fail(`vars ${verb}: the Hub project registry is unreadable; refusing to address a repo this command cannot prove registered`);
44961
+ return fail(`vars ${verb}: '${raw}' is not a registered repository in the Hub registry; App variables access is limited to registered repos \u2014 pass the exact owner/repo`);
44962
+ }
44963
+ function registerDevopsVarsCommands(program3, deps) {
44964
+ const vars = program3.command("vars").description("repository Actions variables \u2014 typed get/list/create/update/delete on the org App (Variables read/write), Hub-registered repos only (#9780)");
44965
+ const runRead = async (commandPath4, verb, repoArg, name, o) => {
44966
+ const projects = await deps.projects();
44967
+ const repo = resolveRepoOrFail(repoArg, projects, verb);
44968
+ const ops = { transport: await deps.appClient(commandPath4), ...deps.actor ? { actor: deps.actor() } : {} };
44969
+ const receipt = verb === "get" && name !== void 0 ? await getVariableReceipt(ops, repo, name) : await listVariablesReceipt(ops, repo);
44970
+ if (o.json) console.log(JSON.stringify(receipt, null, 2));
44971
+ else printReadText(receipt);
44972
+ const failed2 = receipt.operation === "get" ? receipt.presence === "unknown" : !receipt.pagination.complete;
44973
+ if (failed2) process.exitCode = 1;
44974
+ };
44975
+ const runMutation = async (commandPath4, verb, nameArg, value, repoArg, o) => {
44976
+ let name;
44977
+ let exactValue;
44978
+ try {
44979
+ name = parseVarsName(nameArg);
44980
+ if (verb !== "delete") exactValue = parseVarsValue(value);
44981
+ } catch (e) {
44982
+ return fail(`vars ${verb}: ${e.message}`);
44983
+ }
44984
+ const projects = await deps.projects();
44985
+ const repo = resolveRepoOrFail(repoArg, projects, verb);
44986
+ const ops = { transport: await deps.appClient(commandPath4), mintPermission: "variables:write", ...deps.actor ? { actor: deps.actor() } : {} };
44987
+ const receipt = verb === "create" ? await createVariableReceipt(ops, repo, name, exactValue) : verb === "update" ? await updateVariableReceipt(ops, repo, name, exactValue) : await deleteVariableReceipt(ops, repo, name);
44988
+ if (o.json) console.log(JSON.stringify(receipt, null, 2));
44989
+ else printMutationText(receipt);
44990
+ if (receipt.outcome !== "verified") process.exitCode = 1;
44991
+ };
44992
+ vars.command("get <name>").description("exact variable baseline or typed observed absence/unknown \u2014 a bare 404 is unknown; absence needs a complete enumeration (#9780)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runRead("vars get", "get", o.repo ?? "", name, o));
44993
+ vars.command("list").description("exhaustive bounded enumeration: names + provider timestamps only (never values), with pagination/completeness evidence (#9780)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (o) => runRead("vars list", "list", o.repo ?? "", void 0, o));
44994
+ vars.command("create <name>").description("create ONLY against an observed absent baseline (complete enumeration); value preserved exactly; verified by read-back (#9780)").requiredOption("--value <value>", "exact string value (never coerced; e.g. the literal `off`)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runMutation("vars create", "create", name, o.value, o.repo ?? "", o));
44995
+ vars.command("update <name>").description("replace ONLY against a retained present baseline, re-checked just before dispatch; concurrent changes stop the write (#9780)").requiredOption("--value <value>", "exact string value (never coerced)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runMutation("vars update", "update", name, o.value, o.repo ?? "", o));
44996
+ vars.command("delete <name>").description("delete ONLY against a retained present baseline; absence proven afterwards by a complete enumeration (#9780)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runMutation("vars delete", "delete", name, void 0, o.repo ?? "", o));
44997
+ }
44998
+ function printReadText(receipt) {
44999
+ if (receipt.operation === "get") {
45000
+ if (receipt.presence === "present") {
45001
+ console.log(`vars get: ${receipt.repo} ${receipt.name} = ${JSON.stringify(receipt.value ?? "")} (presence: present, source: ${receipt.observed?.source ?? "get"})`);
45002
+ } else if (receipt.presence === "absent") {
45003
+ const p = receipt.absenceProof;
45004
+ console.log(`vars get: ${receipt.repo} ${receipt.name} absent \u2014 proven by complete enumeration (${p.pages} page(s), ${p.totalVariables} variable(s), name excluded)`);
45005
+ } else {
45006
+ console.log(`vars get: ${receipt.repo} ${receipt.name} presence UNKNOWN (${receipt.unknownReason}${receipt.capability ? `, capability ${receipt.capability.outcome}` : ""}) \u2014 absence is never claimed from this read`);
45007
+ }
45008
+ return;
45009
+ }
45010
+ if (receipt.pagination.complete) {
45011
+ console.log(`vars list: ${receipt.repo} \u2014 ${receipt.pagination.totalVariables} variable(s), complete (${receipt.pagination.pages} page(s)): ${receipt.variables.map((v) => v.name).join(", ") || "(none)"}`);
45012
+ } else {
45013
+ console.log(`vars list: ${receipt.repo} \u2014 INCOMPLETE enumeration (${receipt.pagination.reason ?? "unknown"}${receipt.pagination.detail ? `: ${receipt.pagination.detail}` : ""}); the names below are NOT the full set: ${receipt.variables.map((v) => v.name).join(", ") || "(none)"}`);
45014
+ }
45015
+ }
45016
+ function printMutationText(receipt) {
45017
+ const baseline = receipt.baseline.presence === "present" ? `baseline ${JSON.stringify(receipt.baseline.value ?? "")}` : `baseline ${receipt.baseline.presence}`;
45018
+ const readback = receipt.readback ? `, read-back ${receipt.readback.presence}${receipt.readback.value !== void 0 ? ` ${JSON.stringify(receipt.readback.value)}` : ""}` : "";
45019
+ console.log(`vars ${receipt.operation}: ${receipt.repo} ${receipt.name} \u2014 ${receipt.outcome} (${baseline}${readback})${receipt.reason ? ` \u2014 ${receipt.reason}` : ""} [no atomic compare-and-swap; writes are never replayed]`);
45020
+ }
45021
+
45022
+ // src/repo-archive-status.ts
45023
+ var import_node_crypto14 = require("node:crypto");
45024
+ init_github_client();
45025
+ init_cli_shared();
45026
+ var ARCHIVE_STATUS_RECEIPT_SCHEMA = "mmi-cli.repo-archive-status/1";
45027
+ var ARCHIVE_SOURCE_TIME_NOTE = "GitHub exposes no archived_at; observedAt is this read's window end and the provider timestamps are the repository's own updated_at/pushed_at from the same response that carried the archived flag";
45028
+ async function call2(deps, log, method, path2) {
45029
+ try {
45030
+ const res = await deps.transport.restWithStatus(method, path2);
45031
+ log.push({ method, path: path2, status: res.status });
45032
+ return res;
45033
+ } catch (e) {
45034
+ if (e instanceof AppActorError) throw e;
45035
+ const status = e instanceof GitHubApiError ? e.status : 0;
45036
+ log.push({ method, path: path2, status });
45037
+ throw e;
45038
+ }
45039
+ }
45040
+ function classify2(e) {
45041
+ if (e instanceof AppActorError) {
45042
+ const status = e.mintRefusal?.status ?? 0;
45043
+ return { status, reason: "mint-refused", mint: { status, reason: e.mintRefusal?.reason ?? e.message } };
45044
+ }
45045
+ if (e instanceof GitHubApiError) {
45046
+ if (e.status === 404) return { status: 404, reason: "missing-or-invisible" };
45047
+ if (e.status === 403 || e.status === 401) return { status: e.status, reason: "denied" };
45048
+ return { status: e.status, reason: "transport-error" };
45049
+ }
45050
+ const name = e?.name ?? "";
45051
+ if (name === "TimeoutError" || name === "AbortError" || /timeout|abort/i.test(String(e?.message ?? ""))) return { status: 0, reason: "timeout" };
45052
+ return { status: 0, reason: "transport-error" };
45053
+ }
45054
+ async function readArchiveStatusReceipt(deps, repo) {
45055
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
45056
+ const startedAt = now();
45057
+ const log = [];
45058
+ const base = {
45059
+ schema: ARCHIVE_STATUS_RECEIPT_SCHEMA,
45060
+ repo,
45061
+ sourceTime: { observedAt: "", note: ARCHIVE_SOURCE_TIME_NOTE },
45062
+ window: { startedAt: startedAt.toISOString(), endedAt: "", atomicSnapshot: false },
45063
+ actor: deps.actor ?? { credential: "app_installation" },
45064
+ correlationId: (deps.correlationId ?? import_node_crypto14.randomUUID)(),
45065
+ upstream: log
45066
+ };
45067
+ try {
45068
+ const { body } = await call2(deps, log, "GET", `repos/${repo}`);
45069
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
45070
+ return { ...base, archived: "unknown", unknownReason: "parser-refused", sourceTime: { ...base.sourceTime, observedAt: now().toISOString() }, window: { ...base.window, endedAt: now().toISOString() } };
45071
+ }
45072
+ const payload = body;
45073
+ if (typeof payload.archived !== "boolean") {
45074
+ return { ...base, archived: "unknown", unknownReason: "parser-refused", sourceTime: { ...base.sourceTime, observedAt: now().toISOString() }, window: { ...base.window, endedAt: now().toISOString() } };
45075
+ }
45076
+ if (payload.updated_at !== void 0 && typeof payload.updated_at !== "string") payload.updated_at = void 0;
45077
+ if (payload.pushed_at !== void 0 && typeof payload.pushed_at !== "string") payload.pushed_at = void 0;
45078
+ return {
45079
+ ...base,
45080
+ archived: payload.archived,
45081
+ sourceTime: {
45082
+ observedAt: now().toISOString(),
45083
+ ...typeof payload.updated_at === "string" ? { providerUpdatedAt: payload.updated_at } : {},
45084
+ ...typeof payload.pushed_at === "string" ? { providerPushedAt: payload.pushed_at } : {},
45085
+ note: ARCHIVE_SOURCE_TIME_NOTE
45086
+ },
45087
+ window: { ...base.window, endedAt: now().toISOString() }
45088
+ };
45089
+ } catch (e) {
45090
+ const { reason, mint } = classify2(e);
45091
+ return {
45092
+ ...base,
45093
+ archived: "unknown",
45094
+ unknownReason: reason,
45095
+ ...reason === "denied" ? { capability: { permission: "metadata:read", outcome: "denied" } } : {},
45096
+ ...mint ? { capability: { permission: "metadata:read", outcome: "mint-refused", status: mint.status, reason: mint.reason } } : {},
45097
+ ...reason === "missing-or-invisible" ? { sourceTime: { ...base.sourceTime, observedAt: now().toISOString(), note: `${ARCHIVE_SOURCE_TIME_NOTE} \u2014 HTTP 404: the repository is missing or not visible to the installation, which is NOT archived:false` } } : { sourceTime: { ...base.sourceTime, observedAt: now().toISOString() } },
45098
+ window: { ...base.window, endedAt: now().toISOString() }
45099
+ };
45100
+ }
45101
+ }
45102
+ function registerRepoArchiveStatusCommand(program3, deps) {
45103
+ const repoRoot2 = program3.command("repo").description("canonical repository typed reads \u2014 archive status (#9779)");
45104
+ repoRoot2.command("archive-status").description("typed archived-state read for one canonical repository: archived + source time, App metadata:read; a 404 is missing-or-invisible, never archived:false (#9779)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").action(async (o) => {
45105
+ const projects = await deps.projects();
45106
+ const resolution = resolveCanonicalRepo(o.repo ?? "", projects);
45107
+ const repo = resolution.kind === "resolved" ? resolution.repo : resolution.kind === "conflict" ? fail(`repo archive-status: '${o.repo ?? ""}' resolves to MULTIPLE registered repositories (${resolution.candidates.join(", ")}) \u2014 pass the exact owner/repo (#9779)`) : resolution.kind === "unavailable" ? fail("repo archive-status: the Hub project registry is unreadable; refusing to address a repo this command cannot prove registered") : fail(`repo archive-status: '${o.repo ?? ""}' is not a registered repository in the Hub registry \u2014 pass the exact owner/repo`);
45108
+ const receipt = await readArchiveStatusReceipt({ transport: await deps.appClient("repo archive-status"), ...deps.actor ? { actor: deps.actor() } : {} }, repo);
45109
+ if (o.json) console.log(JSON.stringify(receipt, null, 2));
45110
+ else if (receipt.archived === "unknown") console.log(`repo archive-status: ${receipt.repo} \u2014 archived state UNKNOWN (${receipt.unknownReason}${receipt.capability?.outcome === "mint-refused" ? `: App token mint refused${receipt.capability.status ? ` (HTTP ${receipt.capability.status})` : ""}: ${receipt.capability.reason}` : ""}) \u2014 never reads as not-archived`);
45111
+ else console.log(`repo archive-status: ${receipt.repo} \u2014 archived: ${receipt.archived} (observed ${receipt.sourceTime.observedAt}${receipt.sourceTime.providerUpdatedAt ? `, provider updated_at ${receipt.sourceTime.providerUpdatedAt}` : ""})`);
45112
+ if (receipt.archived === "unknown") process.exitCode = 1;
45113
+ });
45114
+ }
45115
+
45116
+ // src/merge-queue-lifecycle.ts
45117
+ var import_node_crypto15 = require("node:crypto");
45118
+ var import_node_fs35 = require("node:fs");
45119
+ var import_node_path32 = require("node:path");
45120
+ init_github_client();
45121
+ init_cli_shared();
45122
+ var LIFECYCLE_SCHEMA = "mmi-cli.merge-queue-lifecycle/1";
45123
+ var COHORT_SCHEMA = "mmi.merge-queue-cohort/1";
45124
+ var LIFECYCLE_SOURCE = {
45125
+ provider: "github-graphql",
45126
+ operation: "PullRequest.timelineItems",
45127
+ itemTypes: ["ADDED_TO_MERGE_QUEUE_EVENT", "REMOVED_FROM_MERGE_QUEUE_EVENT", "MERGED_EVENT", "HEAD_REF_FORCE_PUSHED_EVENT", "BASE_REF_CHANGED_EVENT"],
45128
+ schemaRef: "https://docs.github.com/public/fpt/schema.docs.graphql",
45129
+ version: "merge-queue-lifecycle-source/1",
45130
+ permission: "pull_requests:read"
45131
+ };
45132
+ var LIFECYCLE_QUERY = "query($owner:String!,$name:String!,$number:Int!,$after:String){repository(owner:$owner,name:$name){pullRequest(number:$number){number baseRefName mergedAt mergeCommit{oid} timelineItems(first:100,after:$after,itemTypes:[ADDED_TO_MERGE_QUEUE_EVENT,REMOVED_FROM_MERGE_QUEUE_EVENT,MERGED_EVENT,HEAD_REF_FORCE_PUSHED_EVENT,BASE_REF_CHANGED_EVENT]){pageInfo{hasNextPage endCursor} nodes{__typename ... on AddedToMergeQueueEvent{id createdAt} ... on RemovedFromMergeQueueEvent{id createdAt reason beforeCommit{oid}} ... on MergedEvent{id createdAt commit{oid}} ... on HeadRefForcePushedEvent{id createdAt beforeCommit{oid} afterCommit{oid}} ... on BaseRefChangedEvent{id createdAt previousRefName currentRefName}}}}}}";
45133
+ var UTC_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/;
45134
+ var SHA_RE2 = /^[0-9a-f]{40}$/;
45135
+ function parseUtc(raw, flag) {
45136
+ if (!UTC_RE.test(raw) || Number.isNaN(Date.parse(raw))) throw new Error(`${flag} must be a UTC timestamp like 2026-11-02T00:00:00Z \u2014 received '${raw.slice(0, 40)}'`);
45137
+ return raw;
45138
+ }
45139
+ function parseBound(raw, flag, max) {
45140
+ if (!/^\d+$/.test(raw) || Number(raw) < 1 || Number(raw) > max) throw new Error(`${flag} must be an integer 1-${max} \u2014 received '${raw.slice(0, 20)}'`);
45141
+ return Number(raw);
45142
+ }
45143
+ function optionalUtc(v, where) {
45144
+ if (v === void 0) return void 0;
45145
+ if (typeof v !== "string") throw new Error(`${where} must be a UTC timestamp string`);
45146
+ return parseUtc(v, where);
45147
+ }
45148
+ function parseCohortManifest(text) {
45149
+ let raw;
45150
+ try {
45151
+ raw = JSON.parse(text);
45152
+ } catch {
45153
+ throw new Error("cohort manifest is not valid JSON");
45154
+ }
45155
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("cohort manifest must be a JSON object");
45156
+ const m = raw;
45157
+ if (m.schema !== COHORT_SCHEMA) throw new Error(`cohort manifest schema must be '${COHORT_SCHEMA}'`);
45158
+ if (typeof m.repo !== "string" || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(m.repo)) throw new Error("cohort manifest repo must be owner/repo");
45159
+ if (typeof m.base_branch !== "string" || m.base_branch.trim() === "") throw new Error("cohort manifest base_branch is required");
45160
+ const receipt = m.measurement_receipt;
45161
+ if (!receipt || typeof receipt.id !== "string" || receipt.id.trim() === "" || typeof receipt.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(receipt.sha256)) {
45162
+ throw new Error("cohort manifest measurement_receipt needs an id and a 64-hex sha256 binding the existing measurement");
45163
+ }
45164
+ const since = parseUtc(String(m.since ?? ""), "manifest since");
45165
+ const until = parseUtc(String(m.until ?? ""), "manifest until");
45166
+ if (!Array.isArray(m.prs) || m.prs.length === 0) throw new Error("cohort manifest prs must list explicit PR identities");
45167
+ const seen = /* @__PURE__ */ new Set();
45168
+ const prs = m.prs.map((p, i) => {
45169
+ const row = p;
45170
+ if (!row || !Number.isInteger(row.number) || row.number < 1) throw new Error(`cohort manifest prs[${i}].number must be a positive integer`);
45171
+ const number = row.number;
45172
+ if (seen.has(number)) throw new Error(`cohort manifest lists PR #${number} twice`);
45173
+ seen.add(number);
45174
+ if (row.excluded !== void 0 && (typeof row.excluded !== "string" || row.excluded.trim() === "")) throw new Error(`cohort manifest prs[${i}].excluded must be a reason string`);
45175
+ return { number, ...typeof row.excluded === "string" ? { excluded: row.excluded } : {} };
45176
+ });
45177
+ const runs2 = m.runs === void 0 ? [] : m.runs;
45178
+ if (!Array.isArray(runs2)) throw new Error("cohort manifest runs must be an array");
45179
+ const parsedRuns = runs2.map((r, i) => {
45180
+ const row = r;
45181
+ const where = `cohort manifest runs[${i}]`;
45182
+ if (!row || !Number.isInteger(row.run_id) || !Number.isInteger(row.run_attempt) || row.run_attempt < 1) throw new Error(`${where} needs integer run_id and run_attempt >= 1`);
45183
+ if (typeof row.head_sha !== "string" || !SHA_RE2.test(row.head_sha)) throw new Error(`${where}.head_sha must be a full 40-hex SHA`);
45184
+ if (!Array.isArray(row.members)) throw new Error(`${where}.members must list explicit PR memberships`);
45185
+ const members = row.members.map((mem, j) => {
45186
+ const mm = mem;
45187
+ if (!mm || !Number.isInteger(mm.pr)) throw new Error(`${where}.members[${j}].pr must be an integer`);
45188
+ if (mm.admission_event_id !== void 0 && (typeof mm.admission_event_id !== "string" || mm.admission_event_id === "")) throw new Error(`${where}.members[${j}].admission_event_id must be a provider event id`);
45189
+ return { pr: mm.pr, ...typeof mm.admission_event_id === "string" ? { admission_event_id: mm.admission_event_id } : {} };
45190
+ });
45191
+ const started = optionalUtc(row.started_at, `${where}.started_at`);
45192
+ const failureResult = optionalUtc(row.failure_result_at, `${where}.failure_result_at`);
45193
+ const failureObserved = optionalUtc(row.failure_observed_at, `${where}.failure_observed_at`);
45194
+ return {
45195
+ run_id: row.run_id,
45196
+ run_attempt: row.run_attempt,
45197
+ head_sha: row.head_sha,
45198
+ members,
45199
+ ...started ? { started_at: started } : {},
45200
+ ...failureResult ? { failure_result_at: failureResult } : {},
45201
+ ...failureObserved ? { failure_observed_at: failureObserved } : {}
45202
+ };
45203
+ });
45204
+ return { schema: COHORT_SCHEMA, repo: m.repo, base_branch: m.base_branch, measurement_receipt: { id: receipt.id, sha256: receipt.sha256 }, since, until, prs, runs: parsedRuns };
45205
+ }
45206
+ var oidOf = (v) => v && typeof v === "object" && typeof v.oid === "string" ? v.oid : null;
45207
+ function normalizeNodes(nodes, malformed) {
45208
+ const out = [];
45209
+ for (const node of nodes) {
45210
+ const n = node;
45211
+ if (!n || typeof n.id !== "string" || typeof n.createdAt !== "string") {
45212
+ malformed.push("event-missing-id-or-createdAt");
45213
+ continue;
45214
+ }
45215
+ const base = { id: n.id, at: n.createdAt };
45216
+ switch (n.__typename) {
45217
+ case "AddedToMergeQueueEvent":
45218
+ out.push({ kind: "enqueued", ...base });
45219
+ break;
45220
+ case "RemovedFromMergeQueueEvent":
45221
+ out.push({ kind: "removed", ...base, reason: typeof n.reason === "string" && n.reason !== "" ? n.reason : null, before_head: oidOf(n.beforeCommit) });
45222
+ break;
45223
+ case "MergedEvent":
45224
+ out.push({ kind: "merged", ...base, merge_sha: oidOf(n.commit) });
45225
+ break;
45226
+ case "HeadRefForcePushedEvent":
45227
+ out.push({ kind: "head_changed", ...base, before: oidOf(n.beforeCommit), after: oidOf(n.afterCommit) });
45228
+ break;
45229
+ case "BaseRefChangedEvent":
45230
+ if (typeof n.previousRefName !== "string" || typeof n.currentRefName !== "string") {
45231
+ malformed.push("base-change-missing-refs");
45232
+ break;
45233
+ }
45234
+ out.push({ kind: "base_changed", ...base, from: n.previousRefName, to: n.currentRefName });
45235
+ break;
45236
+ default:
45237
+ malformed.push(`unexpected-typename:${String(n.__typename).slice(0, 40)}`);
45238
+ }
45239
+ }
45240
+ return out;
45241
+ }
45242
+ function classifyError(e) {
45243
+ if (e instanceof GitHubApiError) {
45244
+ if (e.rateLimited) return { code: "rate-limited", status: e.status };
45245
+ const types = (e.graphqlErrors ?? []).map((g) => g.type ?? "");
45246
+ if (e.status === 401 || e.status === 403 || types.includes("FORBIDDEN")) return { code: "permission-denied", status: e.status };
45247
+ if (e.status === 404 || types.includes("NOT_FOUND")) return { code: "not-found-or-masked", status: e.status };
45248
+ if (types.some((t) => t !== "")) return { code: "unsupported-source", status: e.status };
45249
+ if (/did not include data/i.test(e.message)) return { code: "malformed-response", status: e.status };
45250
+ return { code: "transport-error", status: e.status };
45251
+ }
45252
+ const name = e?.name ?? "";
45253
+ if (name === "TimeoutError" || name === "AbortError" || /timeout|timed out|abort/i.test(String(e?.message ?? ""))) return { code: "timeout", status: 0 };
45254
+ return { code: "transport-error", status: 0 };
45255
+ }
45256
+ function classifyRemovalReason(reason, headChangedBeforeReentry) {
45257
+ if (headChangedBeforeReentry) return "head-change";
45258
+ if (reason === null) return "unknown";
45259
+ if (/manual|dequeued by|removed by|user/i.test(reason)) return "manual";
45260
+ if (/fail|timed? ?out|timeout|check|status/i.test(reason)) return "failure-linked";
45261
+ if (/conflict|protection|invalid|base|push|commit|head/i.test(reason)) return "invalidation";
45262
+ return "unknown";
45263
+ }
45264
+ var secondsBetween = (from, to) => {
45265
+ const d = (Date.parse(to) - Date.parse(from)) / 1e3;
45266
+ if (!Number.isFinite(d)) return { value: null, reason: "unparseable-timestamp" };
45267
+ if (d < 0) return { value: null, reason: "negative-duration" };
45268
+ return { value: d };
45269
+ };
45270
+ var ZERO_BOUNCES = () => ({ manual: 0, "failure-linked": 0, "head-change": 0, invalidation: 0, unknown: 0 });
45271
+ function buildPrLifecycle(state, manifest, joins) {
45272
+ const entry = manifest.prs.find((p) => p.number === state.number);
45273
+ const anomalies = [...state.malformed ?? []];
45274
+ const events = state.events.map((e, i) => ({ e, i })).filter(({ e }) => {
45275
+ if (UTC_RE.test(e.at) && !Number.isNaN(Date.parse(e.at))) return true;
45276
+ anomalies.push(`malformed-timestamp:${e.id}`);
45277
+ return false;
45278
+ });
45279
+ for (let k = 1; k < events.length; k++) if (Date.parse(events[k].e.at) < Date.parse(events[k - 1].e.at)) anomalies.push(`out-of-order:${events[k].e.id}`);
45280
+ const ordered = events.sort((a, b) => Date.parse(a.e.at) - Date.parse(b.e.at) || a.i - b.i).map(({ e }) => e);
45281
+ const prBase = state.pr?.base_ref ?? null;
45282
+ const baseChanges = ordered.filter((e) => e.kind === "base_changed");
45283
+ const baseAt = (at) => {
45284
+ const next = baseChanges.find((c) => Date.parse(c.at) > Date.parse(at));
45285
+ if (next) return { value: next.from, reason: "derived-from-base-ref-changed-event" };
45286
+ return prBase === null ? { value: null, reason: "base-unavailable" } : { value: prBase, reason: baseChanges.length ? "derived-from-base-ref-changed-event" : "current-base-no-recorded-change" };
45287
+ };
45288
+ const episodes = [];
45289
+ let open2 = null;
45290
+ let leftCensored = false;
45291
+ let sawQueueEvent = false;
45292
+ let mergedEvent = null;
45293
+ for (const e of ordered) {
45294
+ if (e.kind === "enqueued") {
45295
+ if (open2) {
45296
+ anomalies.push(`admission-without-removal:${e.id}`);
45297
+ episodes.push(open2);
45298
+ }
45299
+ const prev = open2 ?? episodes[episodes.length - 1] ?? null;
45300
+ const headChanged = prev ? ordered.some((h) => h.kind === "head_changed" && Date.parse(h.at) >= Date.parse(prev.terminal.at ?? prev.enqueued_at) && Date.parse(h.at) <= Date.parse(e.at)) : null;
45301
+ open2 = {
45302
+ episode_id: e.id,
45303
+ enqueued_at: e.at,
45304
+ head_sha: { value: null, reason: "source-omits-admission-head (AddedToMergeQueueEvent has no commit field)" },
45305
+ base: baseAt(e.at),
45306
+ terminal: { type: null, at: null, event_id: null, reason: null, reason_class: null, head_at_removal: null },
45307
+ reentry_of: prev && prev.terminal.type === "removed" ? prev.episode_id : null,
45308
+ head_changed_since_previous_episode: headChanged,
45309
+ runs: [],
45310
+ queue_wait_to_execution_seconds: { value: null, reason: "no-explicit-run-join" }
45311
+ };
45312
+ sawQueueEvent = true;
45313
+ } else if (e.kind === "removed") {
45314
+ if (!open2) {
45315
+ if (!sawQueueEvent) leftCensored = true;
45316
+ anomalies.push(`removal-without-admission:${e.id}`);
45317
+ sawQueueEvent = true;
45318
+ continue;
45319
+ }
45320
+ open2.terminal = { type: "removed", at: e.at, event_id: e.id, reason: e.reason, reason_class: null, head_at_removal: e.before_head };
45321
+ episodes.push(open2);
45322
+ open2 = null;
45323
+ } else if (e.kind === "merged") {
45324
+ mergedEvent = e;
45325
+ if (open2) {
45326
+ open2.terminal = { type: "merged", at: e.at, event_id: e.id, reason: null, reason_class: null, head_at_removal: null };
45327
+ episodes.push(open2);
45328
+ open2 = null;
45329
+ }
45330
+ }
45331
+ }
45332
+ if (open2) episodes.push(open2);
45333
+ let bounceCount = 0;
45334
+ const byReason = ZERO_BOUNCES();
45335
+ episodes.forEach((ep, idx) => {
45336
+ if (ep.terminal.type !== "removed") return;
45337
+ const next = episodes[idx + 1];
45338
+ const headChange = next ? next.head_changed_since_previous_episode === true : false;
45339
+ ep.terminal.reason_class = classifyRemovalReason(ep.terminal.reason, headChange);
45340
+ if (next && next.reentry_of === ep.episode_id) {
45341
+ bounceCount++;
45342
+ byReason[ep.terminal.reason_class]++;
45343
+ }
45344
+ });
45345
+ for (const run of manifest.runs) {
45346
+ for (const member of run.members) {
45347
+ if (member.pr !== state.number) continue;
45348
+ if (!member.admission_event_id) continue;
45349
+ const ep = episodes.find((x) => x.episode_id === member.admission_event_id);
45350
+ const join54 = joins.find((j) => j.run_id === run.run_id && j.run_attempt === run.run_attempt && j.pr === member.pr && j.admission_event_id === member.admission_event_id);
45351
+ if (!join54) continue;
45352
+ if (!ep) {
45353
+ if (state.status === "complete") {
45354
+ join54.status = "unresolved";
45355
+ join54.reason = "admission-event-not-in-history";
45356
+ } else {
45357
+ join54.status = "unresolved";
45358
+ join54.reason = "history-incomplete";
45359
+ }
45360
+ continue;
45361
+ }
45362
+ join54.status = "joined";
45363
+ delete join54.reason;
45364
+ ep.runs.push({ run_id: run.run_id, run_attempt: run.run_attempt, head_sha: run.head_sha });
45365
+ }
45366
+ }
45367
+ const runOf = (ep) => manifest.runs.filter((r) => ep.runs.some((x) => x.run_id === r.run_id && x.run_attempt === r.run_attempt));
45368
+ const earliest = (ep, field) => {
45369
+ const vals = runOf(ep).map((r) => r[field]).filter((v) => typeof v === "string").sort((a, b) => Date.parse(a) - Date.parse(b));
45370
+ return vals[0] ?? null;
45371
+ };
45372
+ for (const ep of episodes) {
45373
+ if (!ep.runs.length) continue;
45374
+ const started = earliest(ep, "started_at");
45375
+ ep.queue_wait_to_execution_seconds = started ? secondsBetween(ep.enqueued_at, started) : { value: null, reason: "joined-run-has-no-execution-start" };
45376
+ }
45377
+ const mergedAt = state.pr?.merged_at ?? null;
45378
+ let cohort;
45379
+ let cohortReason;
45380
+ if (entry.excluded || state.status === "excluded") {
45381
+ cohort = "excluded";
45382
+ cohortReason = `manifest-exclusion: ${entry.excluded ?? "excluded"}`;
45383
+ } else if (state.status !== "complete") {
45384
+ cohort = "unknown";
45385
+ cohortReason = state.status === "failed" || state.status === "unsupported" ? `lookup-${state.status}:${state.error?.code ?? "unknown"}` : "history-incomplete";
45386
+ } else if (mergedAt === null) {
45387
+ cohort = "right-censored";
45388
+ cohortReason = "not-merged-pending-or-closed";
45389
+ } else if (Date.parse(mergedAt) >= Date.parse(manifest.until)) {
45390
+ cohort = "right-censored";
45391
+ cohortReason = "merged-at-or-after-until";
45392
+ } else if (Date.parse(mergedAt) < Date.parse(manifest.since)) {
45393
+ cohort = "outside-window";
45394
+ cohortReason = "merged-before-since";
45395
+ } else cohort = "eligible";
45396
+ const blocked = () => {
45397
+ if (state.status === "excluded") return "excluded-by-manifest";
45398
+ if (state.status !== "complete") return state.status === "incomplete" || state.status === "pending" ? "history-incomplete" : `lookup-${state.status}`;
45399
+ if (mergedAt === null) return "not-merged";
45400
+ if (anomalies.some((a) => a.startsWith("malformed") || a.startsWith("event-missing") || a.startsWith("unexpected-typename") || a.startsWith("base-change-missing"))) return "malformed-history";
45401
+ if (anomalies.some((a) => a.startsWith("out-of-order") || a.startsWith("admission-without-removal"))) return "contradictory-ordering";
45402
+ if (mergedEvent && mergedEvent.at !== mergedAt && Date.parse(mergedEvent.at) !== Date.parse(mergedAt)) return "conflicting-merge-time";
45403
+ if (mergedEvent && state.pr?.merge_sha && mergedEvent.merge_sha && mergedEvent.merge_sha !== state.pr.merge_sha) return "conflicting-merge-sha";
45404
+ return null;
45405
+ };
45406
+ const block = blocked();
45407
+ const nul = (reason) => ({ value: null, reason });
45408
+ let first;
45409
+ if (block) first = nul(block);
45410
+ else if (leftCensored) first = nul("left-censored: removal precedes any recorded admission");
45411
+ else if (!episodes.length) first = nul("no-verified-admission-in-history (not proof of queue bypass)");
45412
+ else first = secondsBetween(episodes[0].enqueued_at, mergedAt);
45413
+ const successful = episodes.filter((ep) => ep.terminal.type === "merged");
45414
+ let success;
45415
+ let successEp = null;
45416
+ if (block) success = nul(block);
45417
+ else if (successful.length !== 1) success = nul(successful.length ? "ambiguous-successful-episode" : "no-episode-terminated-by-merge");
45418
+ else {
45419
+ successEp = successful[0];
45420
+ const headMoved = ordered.some((h) => h.kind === "head_changed" && Date.parse(h.at) > Date.parse(successEp.enqueued_at) && Date.parse(h.at) <= Date.parse(mergedAt));
45421
+ success = headMoved ? nul("head-changed-within-successful-episode") : secondsBetween(successEp.enqueued_at, mergedAt);
45422
+ }
45423
+ const wait = block ? nul(block) : successEp ? successEp.queue_wait_to_execution_seconds : nul("no-successful-episode");
45424
+ const failureEp = episodes.find((ep) => earliest(ep, "failure_result_at") !== null);
45425
+ const observedEp = episodes.find((ep) => earliest(ep, "failure_observed_at") !== null);
45426
+ const failureResult = failureEp ? secondsBetween(failureEp.enqueued_at, earliest(failureEp, "failure_result_at")) : nul("no-joined-failure-result-evidence");
45427
+ const failureObserved = observedEp ? secondsBetween(observedEp.enqueued_at, earliest(observedEp, "failure_observed_at")) : nul("no-joined-failure-observation-evidence");
45428
+ const historyUsable = state.status === "complete" && !leftCensored && !(block && block !== "not-merged");
45429
+ const failureBlock = block === "not-merged" ? null : block;
45430
+ return {
45431
+ number: state.number,
45432
+ lookup: state.status,
45433
+ ...state.error ? { error: state.error } : {},
45434
+ cohort,
45435
+ ...cohortReason ? { cohort_reason: cohortReason } : {},
45436
+ merged_at: mergedAt,
45437
+ merge_sha: state.pr?.merge_sha ?? null,
45438
+ base_ref: prBase,
45439
+ head_transitions: ordered.filter((e) => e.kind === "head_changed").map((e) => ({ event_id: e.id, at: e.at, before: e.before, after: e.after })),
45440
+ base_transitions: baseChanges.map((e) => ({ event_id: e.id, at: e.at, from: e.from, to: e.to })),
45441
+ episodes,
45442
+ events: ordered,
45443
+ anomalies,
45444
+ metrics: {
45445
+ first_enqueue_to_merge_seconds: first,
45446
+ successful_episode_enqueue_to_merge_seconds: success,
45447
+ queue_wait_to_execution_seconds: wait,
45448
+ enqueue_to_failure_result_seconds: { ...failureBlock ? nul(failureBlock) : failureResult, semantics: "provider-completion-time" },
45449
+ enqueue_to_first_observed_failure_seconds: { ...failureBlock ? nul(failureBlock) : failureObserved, semantics: "measurement-poll-observation" },
45450
+ bounce_count: historyUsable ? bounceCount : null,
45451
+ bounce_by_reason: historyUsable ? byReason : null
45452
+ }
45453
+ };
45454
+ }
45455
+ function median(values) {
45456
+ if (!values.length) return null;
45457
+ const s = [...values].sort((a, b) => a - b);
45458
+ const mid = Math.floor(s.length / 2);
45459
+ return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
45460
+ }
45461
+ function summarizeMetric(prs, key, definition, complete) {
45462
+ const values = [];
45463
+ const missing = {};
45464
+ for (const pr of prs) {
45465
+ if (pr.cohort !== "eligible") continue;
45466
+ const m = pr.metrics[key];
45467
+ if (m.value === null) missing[m.reason ?? "unknown"] = (missing[m.reason ?? "unknown"] ?? 0) + 1;
45468
+ else values.push(m.value);
45469
+ }
45470
+ const count = (c) => prs.filter((p) => p.cohort === c).length;
45471
+ const missingTotal = Object.values(missing).reduce((a, b) => a + b, 0);
45472
+ return {
45473
+ definition,
45474
+ label: complete && missingTotal === 0 && count("unknown") === 0 ? "completed-cohort-median" : "available-case-median",
45475
+ median_seconds: median(values),
45476
+ cohort_denominator: prs.length,
45477
+ eligible: values.length,
45478
+ missing_by_reason: missing,
45479
+ right_censored: count("right-censored"),
45480
+ excluded: count("excluded"),
45481
+ outside_window: count("outside-window"),
45482
+ unknown: count("unknown")
45483
+ };
45484
+ }
45485
+ async function readMergeQueueLifecycle(deps, req) {
45486
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
45487
+ const started = now();
45488
+ const deadlineMs = started.getTime() + req.deadlineSeconds * 1e3;
45489
+ const [owner, name] = req.repo.split("/");
45490
+ const prior = new Map((req.resume?.prs ?? []).map((p) => [p.number, p]));
45491
+ const states = req.manifest.prs.map((p) => {
45492
+ const was = prior.get(p.number);
45493
+ if (was && was.status !== "incomplete" && was.status !== "pending") return was;
45494
+ if (was) return { ...was, status: "pending" };
45495
+ return { number: p.number, status: "pending", pages: 0, cursors: [], next_cursor: null, events: [], duplicates_dropped: 0 };
45496
+ });
45497
+ let pages = 0;
45498
+ let eventCount = 0;
45499
+ const exhausted = /* @__PURE__ */ new Set();
45500
+ for (const st of states) {
45501
+ if (st.status !== "pending") continue;
45502
+ if (req.manifest.prs.find((p) => p.number === st.number)?.excluded) {
45503
+ st.status = "excluded";
45504
+ continue;
45505
+ }
45506
+ const ids = new Set(st.events.map((e) => e.id));
45507
+ for (; ; ) {
45508
+ if (exhausted.size) break;
45509
+ if (pages >= req.maxPages) {
45510
+ exhausted.add("max-pages");
45511
+ break;
45512
+ }
45513
+ if (eventCount >= req.maxEvents) {
45514
+ exhausted.add("max-events");
45515
+ break;
45516
+ }
45517
+ const remainingMs = deadlineMs - now().getTime();
45518
+ if (remainingMs <= 0) {
45519
+ exhausted.add("deadline");
45520
+ break;
45521
+ }
45522
+ const after = st.next_cursor;
45523
+ let page;
45524
+ try {
45525
+ page = await deps.transport.graphql(LIFECYCLE_QUERY, { owner, name, number: st.number, after }, { timeoutMs: Math.min(6e4, remainingMs) });
45526
+ } catch (e) {
45527
+ const err = classifyError(e);
45528
+ if (err.code === "rate-limited") {
45529
+ exhausted.add("rate-limited");
45530
+ st.error = err;
45531
+ break;
45532
+ }
45533
+ st.status = err.code === "unsupported-source" ? "unsupported" : "failed";
45534
+ st.error = err;
45535
+ break;
45536
+ }
45537
+ pages++;
45538
+ st.pages++;
45539
+ st.cursors.push(after);
45540
+ const pr = page?.repository?.pullRequest;
45541
+ const items = pr?.timelineItems;
45542
+ const hasNext = items?.pageInfo?.hasNextPage;
45543
+ const endCursor = items?.pageInfo?.endCursor;
45544
+ if (!pr || typeof pr.baseRefName !== "string" || !items || !Array.isArray(items.nodes) || typeof hasNext !== "boolean" || hasNext && typeof endCursor !== "string") {
45545
+ st.status = "failed";
45546
+ st.error = { code: pr === null ? "not-found-or-masked" : "malformed-response", status: 200 };
45547
+ break;
45548
+ }
45549
+ st.pr = { base_ref: pr.baseRefName, merged_at: typeof pr.mergedAt === "string" ? pr.mergedAt : null, merge_sha: oidOf(pr.mergeCommit) };
45550
+ const malformed = st.malformed ?? [];
45551
+ for (const ev of normalizeNodes(items.nodes, malformed)) {
45552
+ if (ids.has(ev.id)) {
45553
+ st.duplicates_dropped++;
45554
+ continue;
45555
+ }
45556
+ ids.add(ev.id);
45557
+ st.events.push(ev);
45558
+ eventCount++;
45559
+ }
45560
+ if (malformed.length) st.malformed = malformed;
45561
+ if (!hasNext) {
45562
+ st.status = "complete";
45563
+ st.next_cursor = null;
45564
+ break;
45565
+ }
45566
+ if (endCursor === after) {
45567
+ st.status = "failed";
45568
+ st.error = { code: "malformed-response", status: 200 };
45569
+ break;
45570
+ }
45571
+ st.next_cursor = endCursor;
45572
+ }
45573
+ if (st.status === "pending") st.status = "incomplete";
45574
+ }
45575
+ const joins = [];
45576
+ const cohortNumbers = new Set(req.manifest.prs.map((p) => p.number));
45577
+ for (const run of req.manifest.runs) {
45578
+ if (!run.members.length) {
45579
+ joins.push({ run_id: run.run_id, run_attempt: run.run_attempt, pr: null, admission_event_id: null, status: "unresolved", reason: "no-explicit-pr-membership" });
45580
+ continue;
45581
+ }
45582
+ const prsInRun = run.members.map((m) => m.pr);
45583
+ for (const member of run.members) {
45584
+ const base = { run_id: run.run_id, run_attempt: run.run_attempt, pr: member.pr, admission_event_id: member.admission_event_id ?? null };
45585
+ if (!cohortNumbers.has(member.pr)) joins.push({ ...base, status: "unresolved", reason: "pr-not-in-cohort" });
45586
+ else if (prsInRun.filter((n) => n === member.pr).length > 1) joins.push({ ...base, status: "unresolved", reason: "conflicting-membership" });
45587
+ else if (!member.admission_event_id) joins.push({ ...base, status: "unresolved", reason: "no-explicit-episode-identity" });
45588
+ else joins.push({ ...base, status: "unresolved", reason: "pending-history" });
45589
+ }
45590
+ }
45591
+ const joinable = {
45592
+ ...req.manifest,
45593
+ runs: req.manifest.runs.map((r) => ({ ...r, members: r.members.filter((m) => joins.some((j) => j.run_id === r.run_id && j.run_attempt === r.run_attempt && j.pr === m.pr && j.reason === "pending-history")) }))
45594
+ };
45595
+ const prs = states.map((st) => buildPrLifecycle(st, joinable, joins));
45596
+ for (const j of joins) if (j.reason === "pending-history") j.reason = "history-incomplete";
45597
+ const complete = !exhausted.size && states.every((s) => s.status === "complete" || s.status === "excluded");
45598
+ const bounceTotals = ZERO_BOUNCES();
45599
+ let bounceTotal = 0;
45600
+ let bouncePrs = 0;
45601
+ for (const pr of prs) {
45602
+ if (pr.metrics.bounce_count === null || pr.cohort === "excluded") continue;
45603
+ bouncePrs++;
45604
+ bounceTotal += pr.metrics.bounce_count;
45605
+ for (const [k, v] of Object.entries(pr.metrics.bounce_by_reason)) bounceTotals[k] += v;
45606
+ }
45607
+ return {
45608
+ schema: LIFECYCLE_SCHEMA,
45609
+ repo: req.repo,
45610
+ provider: "github.com",
45611
+ base_branch: req.manifest.base_branch,
45612
+ cohort: { manifest_sha256: req.manifestSha256, measurement_receipt: req.manifest.measurement_receipt, pr_count: req.manifest.prs.length, run_count: req.manifest.runs.length },
45613
+ window: { since: req.since, until: req.until, semantics: "half-open [since, until) over merged_at; queue history is read from each PR timeline start, not truncated at since" },
45614
+ observation: { started_at: started.toISOString(), ended_at: now().toISOString() },
45615
+ source: LIFECYCLE_SOURCE,
45616
+ actor: deps.actor ?? { credential: "app_installation" },
45617
+ correlation_id: (deps.correlationId ?? import_node_crypto15.randomUUID)(),
45618
+ bounds: { max_pages: req.maxPages, max_events: req.maxEvents, deadline_seconds: req.deadlineSeconds },
45619
+ counts: { pages, events: eventCount, duplicates_dropped: states.reduce((a, s) => a + s.duplicates_dropped, 0) },
45620
+ complete,
45621
+ exhausted: [...exhausted],
45622
+ continuation: complete ? null : {
45623
+ repo: req.repo,
45624
+ manifest_sha256: req.manifestSha256,
45625
+ since: req.since,
45626
+ until: req.until,
45627
+ source_version: LIFECYCLE_SOURCE.version,
45628
+ remaining: states.filter((s) => s.status === "incomplete" || s.status === "pending").map((s) => ({ pr: s.number, after: s.next_cursor }))
45629
+ },
45630
+ resumed_from: req.resume?.receipt_sha256 ?? null,
45631
+ prs,
45632
+ unresolved_joins: joins.filter((j) => j.status === "unresolved"),
45633
+ joins,
45634
+ medians: {
45635
+ first_enqueue_to_merge_seconds: summarizeMetric(prs, "first_enqueue_to_merge_seconds", "merged_at minus the first verified admission of the complete PR queue journey (re-entries included)", complete),
45636
+ successful_episode_enqueue_to_merge_seconds: summarizeMetric(prs, "successful_episode_enqueue_to_merge_seconds", "merged_at minus the admission of the unique episode terminated by the merge", complete),
45637
+ queue_wait_to_execution_seconds: summarizeMetric(prs, "queue_wait_to_execution_seconds", "successful-episode admission to the earliest execution start of an explicitly joined run", complete)
45638
+ },
45639
+ bounces: { total: bounceTotal, by_reason: bounceTotals, prs_counted: bouncePrs },
45640
+ checkpoint: { prs: states }
45641
+ };
45642
+ }
45643
+ var sha256 = (text) => (0, import_node_crypto15.createHash)("sha256").update(text).digest("hex");
45644
+ function writeLifecycleReceipt(path2, receipt) {
45645
+ const text = `${JSON.stringify(receipt, null, 2)}
45646
+ `;
45647
+ (0, import_node_fs35.mkdirSync)((0, import_node_path32.dirname)(path2), { recursive: true });
45648
+ (0, import_node_fs35.writeFileSync)(path2, text, { encoding: "utf8", flag: "wx" });
45649
+ const back = (0, import_node_fs35.readFileSync)(path2);
45650
+ if (back.toString("utf8") !== text) throw new Error(`receipt read-back mismatch at ${path2}`);
45651
+ return { path: path2, sha256: sha256(back), bytes: back.length };
45652
+ }
45653
+ function loadCheckpoint(text, scope) {
45654
+ let parsed;
45655
+ try {
45656
+ parsed = JSON.parse(text);
45657
+ } catch {
45658
+ throw new Error("checkpoint receipt is not valid JSON");
45659
+ }
45660
+ const r = parsed.result;
45661
+ if (!r || r.schema !== LIFECYCLE_SCHEMA || !r.checkpoint || !Array.isArray(r.checkpoint.prs)) throw new Error("checkpoint is not a merge-queue lifecycle receipt");
45662
+ const mismatch = [];
45663
+ if (r.repo.toLowerCase() !== scope.repo.toLowerCase()) mismatch.push("repo");
45664
+ if (r.cohort?.manifest_sha256 !== scope.manifestSha256) mismatch.push("cohort manifest");
45665
+ if (r.window?.since !== scope.since || r.window?.until !== scope.until) mismatch.push("window");
45666
+ if (r.source?.version !== LIFECYCLE_SOURCE.version) mismatch.push("source version");
45667
+ if (mismatch.length) throw new Error(`checkpoint scope mismatch (${mismatch.join(", ")}) \u2014 a continuation is bound to the same repository, cohort, window and source; it never widens scope`);
45668
+ return { receipt_sha256: sha256(text), prs: r.checkpoint.prs };
45669
+ }
45670
+ var LIFECYCLE_COMMAND_PATH = "read merge-queue lifecycle";
45671
+ function registerMergeQueueLifecycleCommand(readRoot, deps) {
45672
+ readRoot.command("merge-queue").description("historical merge-queue reads (#9788)").command("lifecycle").description("typed per-PR merge-queue history for a retained cohort: one fixed GraphQL timeline query on the App pull_requests:read profile, bounded, receipt-bound; unknown values are null with a reason, never zero (#9788)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").requiredOption("--input <manifest>", `retained cohort manifest JSON (${COHORT_SCHEMA})`).requiredOption("--since <utc>", "window start, inclusive (UTC, e.g. 2026-11-02T00:00:00Z)").requiredOption("--until <utc>", "window end, exclusive (UTC)").requiredOption("--max-pages <n>", "page bound across the cohort (1-1000)").requiredOption("--max-events <n>", "event bound across the cohort (1-100000)").requiredOption("--deadline-seconds <n>", "wall-clock bound (1-3600)").requiredOption("--receipt <new-path>", "new append-only receipt file (refused if it exists)").option("--resume <prior-receipt>", "continue an incomplete read from its receipt (same repo, cohort, window and source only)").option("--json", "machine-readable result").action(async (o) => {
45673
+ const label = "read merge-queue lifecycle";
45674
+ let since, until, maxPages, maxEvents, deadlineSeconds, manifest, manifestText;
45675
+ try {
45676
+ since = parseUtc(o.since, "--since");
45677
+ until = parseUtc(o.until, "--until");
45678
+ if (Date.parse(since) >= Date.parse(until)) throw new Error("--since must be earlier than --until (half-open window)");
45679
+ maxPages = parseBound(o.maxPages, "--max-pages", 1e3);
45680
+ maxEvents = parseBound(o.maxEvents, "--max-events", 1e5);
45681
+ deadlineSeconds = parseBound(o.deadlineSeconds, "--deadline-seconds", 3600);
45682
+ manifestText = (0, import_node_fs35.readFileSync)(o.input, "utf8");
45683
+ manifest = parseCohortManifest(manifestText);
45684
+ if (manifest.since !== since || manifest.until !== until) throw new Error("--since/--until must equal the manifest window exactly; the authorized window is never widened or narrowed silently");
45685
+ } catch (e) {
45686
+ return fail(`${label}: ${e.message}`);
45687
+ }
45688
+ const resolution = resolveCanonicalRepo(o.repo, await deps.projects());
45689
+ const repo = resolution.kind === "resolved" ? resolution.repo : resolution.kind === "conflict" ? fail(`${label}: '${o.repo}' resolves to MULTIPLE registered repositories (${resolution.candidates.join(", ")}) \u2014 pass the exact owner/repo`) : resolution.kind === "unavailable" ? fail(`${label}: the Hub project registry is unreadable; refusing to address a repo this command cannot prove registered`) : fail(`${label}: '${o.repo}' is not a registered repository in the Hub registry \u2014 pass the exact owner/repo`);
45690
+ if (manifest.repo.toLowerCase() !== repo.toLowerCase()) return fail(`${label}: manifest repo ${manifest.repo} does not match --repo ${repo}; refusing before dispatch`);
45691
+ const manifestSha256 = sha256(manifestText);
45692
+ let resume;
45693
+ try {
45694
+ if (o.resume) resume = loadCheckpoint((0, import_node_fs35.readFileSync)(o.resume, "utf8"), { repo, manifestSha256, since, until });
45695
+ } catch (e) {
45696
+ return fail(`${label}: ${e.message}`);
45697
+ }
45698
+ const request = { repo, input: o.input, since, until, max_pages: maxPages, max_events: maxEvents, deadline_seconds: deadlineSeconds, resume: o.resume ?? null };
45699
+ const result = await readMergeQueueLifecycle(
45700
+ { transport: await deps.appClient(LIFECYCLE_COMMAND_PATH), ...deps.actor ? { actor: deps.actor() } : {} },
45701
+ { repo, since, until, maxPages, maxEvents, deadlineSeconds, manifestSha256, manifest, ...resume ? { resume } : {} }
45702
+ );
45703
+ let receipt;
45704
+ try {
45705
+ receipt = writeLifecycleReceipt(o.receipt, { request, result });
45706
+ } catch (e) {
45707
+ const code = e.code;
45708
+ return fail(`${label}: receipt not written (${code === "EEXIST" ? "path exists \u2014 receipts are append-only, pass a new path" : code ?? "write failed"})`);
45709
+ }
45710
+ const output = { ...result, receipt };
45711
+ if (o.json) console.log(JSON.stringify(output, null, 2));
45712
+ else {
45713
+ const m = result.medians;
45714
+ console.log(`${label}: ${repo} ${since}..${until} \u2014 ${result.complete ? "complete" : `INCOMPLETE (${result.exhausted.join(", ") || "lookup failures"})`}; pages ${result.counts.pages}, events ${result.counts.events}`);
45715
+ for (const [k, s] of Object.entries(m)) console.log(` ${k}: ${s.median_seconds ?? "UNKNOWN"} (${s.label}; eligible ${s.eligible}/${s.cohort_denominator}, right-censored ${s.right_censored}, unknown ${s.unknown})`);
45716
+ console.log(` bounces: ${result.bounces.total}; unresolved joins: ${result.unresolved_joins.length}; receipt ${receipt.path} sha256 ${receipt.sha256}`);
45717
+ }
45718
+ if (!result.complete) process.exitCode = 1;
45719
+ });
45720
+ }
45721
+
44439
45722
  // src/board-commands.ts
44440
45723
  init_cli_shared();
44441
45724
 
@@ -45063,9 +46346,9 @@ function registerBoardCommands(program3) {
45063
46346
  }
45064
46347
 
45065
46348
  // src/bootstrap-commands.ts
45066
- var import_node_fs38 = require("node:fs");
46349
+ var import_node_fs39 = require("node:fs");
45067
46350
  var import_node_os21 = require("node:os");
45068
- var import_node_path35 = require("node:path");
46351
+ var import_node_path36 = require("node:path");
45069
46352
  init_cli_shared();
45070
46353
  init_clean_exit();
45071
46354
 
@@ -45250,8 +46533,8 @@ init_github_client();
45250
46533
  init_cli_shared();
45251
46534
 
45252
46535
  // src/port-registry.ts
45253
- var import_node_fs35 = require("node:fs");
45254
- var import_node_path32 = require("node:path");
46536
+ var import_node_fs36 = require("node:fs");
46537
+ var import_node_path33 = require("node:path");
45255
46538
 
45256
46539
  // ../infra/port-geometry.mjs
45257
46540
  var PORT_BLOCK = 100;
@@ -45265,8 +46548,8 @@ function nextPortBlock(registry2) {
45265
46548
  return [base, base + PORT_SPAN];
45266
46549
  }
45267
46550
  function loadPortRegistry(path2) {
45268
- if (!(0, import_node_fs35.existsSync)(path2)) return {};
45269
- const raw = JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8"));
46551
+ if (!(0, import_node_fs36.existsSync)(path2)) return {};
46552
+ const raw = JSON.parse((0, import_node_fs36.readFileSync)(path2, "utf8"));
45270
46553
  const out = {};
45271
46554
  for (const [key, value] of Object.entries(raw)) {
45272
46555
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -45294,22 +46577,22 @@ function existingPortRange(repo, registry2) {
45294
46577
  return registry2[repo] ?? null;
45295
46578
  }
45296
46579
  function portRangeInfraAt(root, source) {
45297
- const registryPath = (0, import_node_path32.join)(root, "infra", "port-ranges.json");
45298
- const ddbScriptPath = (0, import_node_path32.join)(root, "infra", "port-ddb.mjs");
45299
- if (!(0, import_node_fs35.existsSync)(registryPath) || !(0, import_node_fs35.existsSync)(ddbScriptPath)) return null;
46580
+ const registryPath = (0, import_node_path33.join)(root, "infra", "port-ranges.json");
46581
+ const ddbScriptPath = (0, import_node_path33.join)(root, "infra", "port-ddb.mjs");
46582
+ if (!(0, import_node_fs36.existsSync)(registryPath) || !(0, import_node_fs36.existsSync)(ddbScriptPath)) return null;
45300
46583
  return { root, source, registryPath, ddbScriptPath };
45301
46584
  }
45302
46585
  function resolvePortRangeInfra(cwd, packageDir) {
45303
46586
  const direct = portRangeInfraAt(cwd, "cwd");
45304
46587
  if (direct) return direct;
45305
- for (let dir = cwd; ; dir = (0, import_node_path32.dirname)(dir)) {
45306
- const sibling = portRangeInfraAt((0, import_node_path32.join)(dir, "MMI-Hub"), "sibling-hub");
46588
+ for (let dir = cwd; ; dir = (0, import_node_path33.dirname)(dir)) {
46589
+ const sibling = portRangeInfraAt((0, import_node_path33.join)(dir, "MMI-Hub"), "sibling-hub");
45307
46590
  if (sibling) return sibling;
45308
- const parent = (0, import_node_path32.dirname)(dir);
46591
+ const parent = (0, import_node_path33.dirname)(dir);
45309
46592
  if (parent === dir) break;
45310
46593
  }
45311
46594
  if (packageDir) {
45312
- const pkgRoot = (0, import_node_path32.join)(packageDir, "..", "..");
46595
+ const pkgRoot = (0, import_node_path33.join)(packageDir, "..", "..");
45313
46596
  const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
45314
46597
  if (pkgFrom) return pkgFrom;
45315
46598
  }
@@ -45326,7 +46609,7 @@ async function ensurePortRangeAtomic(repo, path2, allocate) {
45326
46609
  } catch (e) {
45327
46610
  const why = e.message;
45328
46611
  const missing = /@aws-sdk\/[\w-]+/.exec(why)?.[0];
45329
- throw new Error(`DDB port allocator failed for ${repo} \u2014 refusing to fall back to the frozen ${path2} (it would hand out a block the cursor already gave away). ` + (missing && /cannot find|not found|ERR_MODULE_NOT_FOUND/i.test(why) ? `Missing dependency ${missing}: run \`npm ci\` in ${(0, import_node_path32.dirname)(path2)}, then retry.` : "Check AWS credentials and that the registry table is reachable, then retry.") + ` Cause: ${why}`);
46612
+ throw new Error(`DDB port allocator failed for ${repo} \u2014 refusing to fall back to the frozen ${path2} (it would hand out a block the cursor already gave away). ` + (missing && /cannot find|not found|ERR_MODULE_NOT_FOUND/i.test(why) ? `Missing dependency ${missing}: run \`npm ci\` in ${(0, import_node_path33.dirname)(path2)}, then retry.` : "Check AWS credentials and that the registry table is reachable, then retry.") + ` Cause: ${why}`);
45330
46613
  }
45331
46614
  }
45332
46615
 
@@ -45378,13 +46661,13 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
45378
46661
  }
45379
46662
 
45380
46663
  // src/box-commands.ts
45381
- var import_node_fs37 = require("node:fs");
46664
+ var import_node_fs38 = require("node:fs");
45382
46665
  var import_node_child_process12 = require("node:child_process");
45383
46666
  var import_node_os20 = require("node:os");
45384
- var import_node_path34 = require("node:path");
46667
+ var import_node_path35 = require("node:path");
45385
46668
 
45386
46669
  // src/box-control.ts
45387
- var import_node_crypto13 = require("node:crypto");
46670
+ var import_node_crypto16 = require("node:crypto");
45388
46671
  var MANIFEST_PATH = "infra/box-control/manifest.json";
45389
46672
  var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
45390
46673
  var sshdPost = (dest) => `if sshd -t; then systemctl reload ssh; else rm -f ${dest}; echo 'sshd -t rejected ${dest}: removed, not reloaded' >&2; exit 1; fi`;
@@ -45488,7 +46771,7 @@ function laneCoverageAction(files, box, op) {
45488
46771
  return { notice: `LANE_COVERAGE: ${mismatch}` };
45489
46772
  }
45490
46773
  function md5(content) {
45491
- return (0, import_node_crypto13.createHash)("md5").update(content).digest("hex");
46774
+ return (0, import_node_crypto16.createHash)("md5").update(content).digest("hex");
45492
46775
  }
45493
46776
  function planRemoteScript(dests) {
45494
46777
  const lines2 = ["set -u"];
@@ -45606,8 +46889,8 @@ init_clean_exit();
45606
46889
  init_cli_shared();
45607
46890
 
45608
46891
  // src/secrets-commands.ts
45609
- var import_node_fs36 = require("node:fs");
45610
- var import_node_path33 = require("node:path");
46892
+ var import_node_fs37 = require("node:fs");
46893
+ var import_node_path34 = require("node:path");
45611
46894
  var import_node_os19 = require("node:os");
45612
46895
  init_cli_shared();
45613
46896
  init_hub_auth();
@@ -46804,18 +48087,18 @@ function collectMap(value, previous = []) {
46804
48087
  return [...previous, value];
46805
48088
  }
46806
48089
  async function decryptRailsCredentials(input) {
46807
- const appDir = (0, import_node_path33.resolve)(input.appDir ?? process.cwd());
48090
+ const appDir = (0, import_node_path34.resolve)(input.appDir ?? process.cwd());
46808
48091
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
46809
48092
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
46810
- const credentialsPath = (0, import_node_path33.resolve)(appDir, credentialsFile);
46811
- const masterKeyPath = (0, import_node_path33.resolve)(appDir, masterKeyFile);
48093
+ const credentialsPath = (0, import_node_path34.resolve)(appDir, credentialsFile);
48094
+ const masterKeyPath = (0, import_node_path34.resolve)(appDir, masterKeyFile);
46812
48095
  const env = {
46813
48096
  ...process.env,
46814
48097
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
46815
48098
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
46816
48099
  };
46817
- if ((0, import_node_fs36.existsSync)(masterKeyPath)) {
46818
- env.RAILS_MASTER_KEY = (0, import_node_fs36.readFileSync)(masterKeyPath, "utf8").trim();
48100
+ if ((0, import_node_fs37.existsSync)(masterKeyPath)) {
48101
+ env.RAILS_MASTER_KEY = (0, import_node_fs37.readFileSync)(masterKeyPath, "utf8").trim();
46819
48102
  }
46820
48103
  const script = [
46821
48104
  'require "json"',
@@ -46825,9 +48108,9 @@ async function decryptRailsCredentials(input) {
46825
48108
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
46826
48109
  "puts JSON.generate(config.config)"
46827
48110
  ].join("\n");
46828
- const scriptDir = (0, import_node_fs36.mkdtempSync)((0, import_node_path33.join)((0, import_node_os19.tmpdir)(), "mmi-rails-decrypt-"));
46829
- const scriptPath = (0, import_node_path33.join)(scriptDir, "decrypt.rb");
46830
- (0, import_node_fs36.writeFileSync)(scriptPath, script, "utf8");
48111
+ const scriptDir = (0, import_node_fs37.mkdtempSync)((0, import_node_path34.join)((0, import_node_os19.tmpdir)(), "mmi-rails-decrypt-"));
48112
+ const scriptPath = (0, import_node_path34.join)(scriptDir, "decrypt.rb");
48113
+ (0, import_node_fs37.writeFileSync)(scriptPath, script, "utf8");
46831
48114
  try {
46832
48115
  const args = ["exec", "ruby", scriptPath];
46833
48116
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -46839,7 +48122,7 @@ async function decryptRailsCredentials(input) {
46839
48122
  });
46840
48123
  return JSON.parse(stdout);
46841
48124
  } finally {
46842
- (0, import_node_fs36.rmSync)(scriptDir, { recursive: true, force: true });
48125
+ (0, import_node_fs37.rmSync)(scriptDir, { recursive: true, force: true });
46843
48126
  }
46844
48127
  }
46845
48128
  async function readSecretStdin() {
@@ -46929,7 +48212,7 @@ function registerSecretsCommands(program3) {
46929
48212
  let body;
46930
48213
  if (o.file) {
46931
48214
  try {
46932
- body = (0, import_node_fs36.readFileSync)((0, import_node_path33.resolve)(o.file), "utf8");
48215
+ body = (0, import_node_fs37.readFileSync)((0, import_node_path34.resolve)(o.file), "utf8");
46933
48216
  } catch (e) {
46934
48217
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
46935
48218
  }
@@ -47065,7 +48348,7 @@ function registerSecretsCommands(program3) {
47065
48348
  {
47066
48349
  ...d,
47067
48350
  decryptRailsCredentials,
47068
- removeFile: (path2) => (0, import_node_fs36.unlinkSync)((0, import_node_path33.resolve)(o.appDir ?? process.cwd(), path2))
48351
+ removeFile: (path2) => (0, import_node_fs37.unlinkSync)((0, import_node_path34.resolve)(o.appDir ?? process.cwd(), path2))
47069
48352
  },
47070
48353
  {
47071
48354
  repo: o.repo,
@@ -47381,7 +48664,7 @@ function registerBoxCommands(program3) {
47381
48664
  const stdinSource = o.body ? `"${o.body}"` : "<body.sh>";
47382
48665
  const wroteScript = o.ssh && o.script ? o.script : null;
47383
48666
  const runLine = wroteScript ? useStdin ? `bash "${wroteScript}" < ${stdinSource}` : `bash "${wroteScript}"` : void 0;
47384
- if (wroteScript) (0, import_node_fs37.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
48667
+ if (wroteScript) (0, import_node_fs38.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
47385
48668
  if (o.json) {
47386
48669
  console.log(JSON.stringify({
47387
48670
  box: found,
@@ -47508,17 +48791,17 @@ function hubGitShow(ref) {
47508
48791
  return (path2) => git3(["show", `${ref}:${path2}`]);
47509
48792
  }
47510
48793
  function runOnBox(found, script) {
47511
- const dir = (0, import_node_fs37.mkdtempSync)((0, import_node_path34.join)((0, import_node_os20.tmpdir)(), "mmi-box-control-"));
48794
+ const dir = (0, import_node_fs38.mkdtempSync)((0, import_node_path35.join)((0, import_node_os20.tmpdir)(), "mmi-box-control-"));
47512
48795
  try {
47513
- const recipe = (0, import_node_path34.join)(dir, "recipe.sh");
47514
- (0, import_node_fs37.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
48796
+ const recipe = (0, import_node_path35.join)(dir, "recipe.sh");
48797
+ (0, import_node_fs38.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
47515
48798
  const r = (0, import_node_child_process12.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
47516
48799
  if (r.status !== 0) {
47517
48800
  throw new UnreadableError(`box control: the ssh session to ${found.name} failed (exit ${r.status ?? r.signal}): ${(r.stderr || "").trim().split("\n").slice(-3).join(" | ")}`);
47518
48801
  }
47519
48802
  return r.stdout;
47520
48803
  } finally {
47521
- (0, import_node_fs37.rmSync)(dir, { recursive: true, force: true });
48804
+ (0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
47522
48805
  }
47523
48806
  }
47524
48807
  function formatControl(mode, box, rows) {
@@ -47646,7 +48929,7 @@ function renderDeployPortDoctor(report) {
47646
48929
  }
47647
48930
 
47648
48931
  // src/bootstrap-drift.ts
47649
- var import_node_crypto14 = require("node:crypto");
48932
+ var import_node_crypto17 = require("node:crypto");
47650
48933
  function byteComparableSeeds(manifest, cls) {
47651
48934
  return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
47652
48935
  }
@@ -47666,7 +48949,7 @@ function compareSeedBytes(hubContent, repoContent) {
47666
48949
  return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
47667
48950
  }
47668
48951
  function seedContentHash(content) {
47669
- return (0, import_node_crypto14.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
48952
+ return (0, import_node_crypto17.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
47670
48953
  }
47671
48954
  function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
47672
48955
  const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
@@ -48916,7 +50199,7 @@ function renderBootstrapVerifyReport(report) {
48916
50199
  }
48917
50200
 
48918
50201
  // src/bootstrap-seed-delivery.ts
48919
- var import_node_crypto15 = require("node:crypto");
50202
+ var import_node_crypto18 = require("node:crypto");
48920
50203
  init_github_client();
48921
50204
  var SHA = /^[a-f0-9]{40}$/;
48922
50205
  async function readSeedFile(repo, target, ref, client) {
@@ -48949,7 +50232,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
48949
50232
  }
48950
50233
  const baseSha = base?.object?.sha;
48951
50234
  if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
48952
- const identity = (0, import_node_crypto15.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
50235
+ const identity = (0, import_node_crypto18.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
48953
50236
  const delivery = planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`);
48954
50237
  const scoped = forcePr && delivery.mode === "direct" ? { mode: "pr", ref: `bootstrap-seed-${identity}-${slug}`, branch: `bootstrap-seed-${identity}-${slug}`, reason: "registry gate render requires review" } : delivery;
48955
50238
  return { ...scoped, baseSha, baseBranch };
@@ -49019,12 +50302,12 @@ var declaredProjectAdmins = (meta) => {
49019
50302
  };
49020
50303
  var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
49021
50304
  async function gateRenderDiff(current, rendered) {
49022
- const dir = (0, import_node_fs38.mkdtempSync)((0, import_node_path35.join)((0, import_node_os21.tmpdir)(), "mmi-gate-diff-"));
49023
- const before = (0, import_node_path35.join)(dir, "committed-gate.yml");
49024
- const after = (0, import_node_path35.join)(dir, "registry-render.yml");
50305
+ const dir = (0, import_node_fs39.mkdtempSync)((0, import_node_path36.join)((0, import_node_os21.tmpdir)(), "mmi-gate-diff-"));
50306
+ const before = (0, import_node_path36.join)(dir, "committed-gate.yml");
50307
+ const after = (0, import_node_path36.join)(dir, "registry-render.yml");
49025
50308
  try {
49026
- (0, import_node_fs38.writeFileSync)(before, current);
49027
- (0, import_node_fs38.writeFileSync)(after, rendered);
50309
+ (0, import_node_fs39.writeFileSync)(before, current);
50310
+ (0, import_node_fs39.writeFileSync)(after, rendered);
49028
50311
  try {
49029
50312
  return (await execFileP("git", ["diff", "--no-index", "--", before, after], { timeout: GIT_TIMEOUT_MS })).stdout;
49030
50313
  } catch (e) {
@@ -49033,7 +50316,7 @@ async function gateRenderDiff(current, rendered) {
49033
50316
  throw e;
49034
50317
  }
49035
50318
  } finally {
49036
- (0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
50319
+ (0, import_node_fs39.rmSync)(dir, { recursive: true, force: true });
49037
50320
  }
49038
50321
  }
49039
50322
  function bootstrapTrainDoctorDeps() {
@@ -49054,9 +50337,9 @@ async function propagationScope(classes, what) {
49054
50337
  }
49055
50338
  const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
49056
50339
  let independentCount = rosterRepos.length;
49057
- if ((0, import_node_fs38.existsSync)("projects.json")) {
50340
+ if ((0, import_node_fs39.existsSync)("projects.json")) {
49058
50341
  try {
49059
- const local = JSON.parse((0, import_node_fs38.readFileSync)("projects.json", "utf8"));
50342
+ const local = JSON.parse((0, import_node_fs39.readFileSync)("projects.json", "utf8"));
49060
50343
  const localRepos = /* @__PURE__ */ new Set();
49061
50344
  for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
49062
50345
  const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
@@ -49181,13 +50464,13 @@ function registerBootstrapCommands(program3) {
49181
50464
  })),
49182
50465
  pushAllowlistOwners: pushAllowlistAuthority(verifyOwners, declaredProjectAdmins(meta)),
49183
50466
  pushAllowlistOwnersRead: verifyOwnersRead,
49184
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null,
50467
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs39.existsSync)(path2) ? (0, import_node_fs39.readFileSync)(path2, "utf8") : null,
49185
50468
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
49186
50469
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
49187
50470
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
49188
50471
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
49189
50472
  // sanction, which is the pre-#3664 behaviour.
49190
- sanctionedAdmins: (0, import_node_fs38.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs38.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
50473
+ sanctionedAdmins: (0, import_node_fs39.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs39.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
49191
50474
  requiredGcpApis: (() => {
49192
50475
  const v = meta?.requiredGcpApis;
49193
50476
  if (Array.isArray(v)) return v;
@@ -49265,14 +50548,14 @@ function registerBootstrapCommands(program3) {
49265
50548
  bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
49266
50549
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
49267
50550
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
49268
- if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
50551
+ if (!(0, import_node_fs39.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
49269
50552
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
49270
50553
  if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
49271
- const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
50554
+ const manifest = loadBootstrapSeeds((0, import_node_fs39.readFileSync)(manifestPath, "utf8"));
49272
50555
  const hubContents = /* @__PURE__ */ new Map();
49273
50556
  for (const s of manifest.seeds) {
49274
50557
  if (s.ownership !== "org" || s.source !== "self") continue;
49275
- hubContents.set(s.target, (0, import_node_fs38.existsSync)(s.target) ? (0, import_node_fs38.readFileSync)(s.target, "utf8") : null);
50558
+ hubContents.set(s.target, (0, import_node_fs39.existsSync)(s.target) ? (0, import_node_fs39.readFileSync)(s.target, "utf8") : null);
49276
50559
  }
49277
50560
  let targets;
49278
50561
  let classOf = (_repo) => "deployable";
@@ -49528,24 +50811,24 @@ function registerBootstrapCommands(program3) {
49528
50811
  return;
49529
50812
  }
49530
50813
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
49531
- if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
50814
+ if (!(0, import_node_fs39.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
49532
50815
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
49533
50816
  if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
49534
- const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
50817
+ const manifest = loadBootstrapSeeds((0, import_node_fs39.readFileSync)(manifestPath, "utf8"));
49535
50818
  const baseBranch = o.class === "content" ? "main" : "development";
49536
50819
  const slug = parsedRepo.slug;
49537
50820
  const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
49538
50821
  const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
49539
- const readFile10 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
50822
+ const readFile10 = (p) => (0, import_node_fs39.existsSync)(p) ? (0, import_node_fs39.readFileSync)(p, "utf8") : null;
49540
50823
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
49541
50824
  const putSeed = async (target, content, ref, sha) => {
49542
- const tmp = (0, import_node_path35.join)((0, import_node_os21.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
49543
- (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
50825
+ const tmp = (0, import_node_path36.join)((0, import_node_os21.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
50826
+ (0, import_node_fs39.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
49544
50827
  try {
49545
50828
  await gh2(contentPutInputArgs(repo, target, tmp));
49546
50829
  } finally {
49547
50830
  try {
49548
- (0, import_node_fs38.unlinkSync)(tmp);
50831
+ (0, import_node_fs39.unlinkSync)(tmp);
49549
50832
  } catch {
49550
50833
  }
49551
50834
  }
@@ -50183,10 +51466,10 @@ LIVE apply to ${repo}:
50183
51466
  if (rawFlag("--pins")) return propagatePins(rawFlag("--apply"), rawFlag("--json"));
50184
51467
  const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
50185
51468
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
50186
- if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
51469
+ if (!(0, import_node_fs39.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
50187
51470
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
50188
51471
  if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
50189
- const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
51472
+ const manifest = loadBootstrapSeeds((0, import_node_fs39.readFileSync)(manifestPath, "utf8"));
50190
51473
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
50191
51474
  if (!o.target) {
50192
51475
  return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
@@ -50195,9 +51478,9 @@ LIVE apply to ${repo}:
50195
51478
  const seed = propagatable.find((s) => s.target === o.target);
50196
51479
  if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
50197
51480
  ${propagatable.map((s) => s.target).join("\n ")}`);
50198
- if (!seed.managedBlock && !(0, import_node_fs38.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
50199
- const hubContent = seed.managedBlock ? null : (0, import_node_fs38.readFileSync)(seed.target, "utf8");
50200
- const readSeedFile2 = (path2) => (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null;
51481
+ if (!seed.managedBlock && !(0, import_node_fs39.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
51482
+ const hubContent = seed.managedBlock ? null : (0, import_node_fs39.readFileSync)(seed.target, "utf8");
51483
+ const readSeedFile2 = (path2) => (0, import_node_fs39.existsSync)(path2) ? (0, import_node_fs39.readFileSync)(path2, "utf8") : null;
50201
51484
  const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
50202
51485
  const { projects, classOf, inClassRepos, canarySlug, sourcePilot } = await propagationScope(seed.classes, `target '${seed.target}'`);
50203
51486
  const repos = inClassRepos.map((repo) => {
@@ -50316,7 +51599,7 @@ LIVE apply to ${repo}:
50316
51599
  if (plan.refusedNoCanary) return fail("bootstrap propagate --execute: no canary declared for this target \u2014 refusing to write (set seedCanary:true on exactly one registry repo)");
50317
51600
  const headSha = seedSource.sha;
50318
51601
  const sourceBlob = (await execGitForSeedSource(["rev-parse", `${headSha}:${seedSourcePath(seed)}`])).trim();
50319
- const runbookUnaffected = (0, import_node_fs38.existsSync)(RUNBOOK_UNAFFECTED_PATH) ? runbookUnaffectedReason(JSON.parse((0, import_node_fs38.readFileSync)(RUNBOOK_UNAFFECTED_PATH, "utf8")), seed.target, sourceBlob) : void 0;
51602
+ const runbookUnaffected = (0, import_node_fs39.existsSync)(RUNBOOK_UNAFFECTED_PATH) ? runbookUnaffectedReason(JSON.parse((0, import_node_fs39.readFileSync)(RUNBOOK_UNAFFECTED_PATH, "utf8")), seed.target, sourceBlob) : void 0;
50320
51603
  for (const rec of plan.records) {
50321
51604
  if (rec.action !== "open-pr") continue;
50322
51605
  const baseBranch = classOf(rec.repo) === "content" ? "main" : "development";
@@ -50338,15 +51621,15 @@ LIVE apply to ${repo}:
50338
51621
  } catch {
50339
51622
  existingSha = void 0;
50340
51623
  }
50341
- const tmp = (0, import_node_path35.join)((0, import_node_os21.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
51624
+ const tmp = (0, import_node_path36.join)((0, import_node_os21.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
50342
51625
  const desiredContent = desiredByRepo.get(rec.repo);
50343
51626
  if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
50344
- (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha, runbookUnaffected)), "utf8");
51627
+ (0, import_node_fs39.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha, runbookUnaffected)), "utf8");
50345
51628
  try {
50346
51629
  await gh2(contentPutInputArgs(rec.repo, seed.target, tmp));
50347
51630
  } finally {
50348
51631
  try {
50349
- (0, import_node_fs38.unlinkSync)(tmp);
51632
+ (0, import_node_fs39.unlinkSync)(tmp);
50350
51633
  } catch {
50351
51634
  }
50352
51635
  }
@@ -50415,10 +51698,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
50415
51698
  return fail(`bootstrap rollback: ${e.message}`);
50416
51699
  }
50417
51700
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
50418
- if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
51701
+ if (!(0, import_node_fs39.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
50419
51702
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
50420
51703
  if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
50421
- const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
51704
+ const manifest = loadBootstrapSeeds((0, import_node_fs39.readFileSync)(manifestPath, "utf8"));
50422
51705
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
50423
51706
  if (!o.target) {
50424
51707
  return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
@@ -50435,10 +51718,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
50435
51718
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
50436
51719
  let candidates;
50437
51720
  if (o.record) {
50438
- if (!(0, import_node_fs38.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
51721
+ if (!(0, import_node_fs39.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
50439
51722
  let parsed;
50440
51723
  try {
50441
- parsed = JSON.parse((0, import_node_fs38.readFileSync)(o.record, "utf8"));
51724
+ parsed = JSON.parse((0, import_node_fs39.readFileSync)(o.record, "utf8"));
50442
51725
  } catch (e) {
50443
51726
  return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
50444
51727
  }
@@ -50515,13 +51798,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
50515
51798
  } catch {
50516
51799
  existingSha = void 0;
50517
51800
  }
50518
- const tmp = (0, import_node_path35.join)((0, import_node_os21.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
50519
- (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
51801
+ const tmp = (0, import_node_path36.join)((0, import_node_os21.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
51802
+ (0, import_node_fs39.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
50520
51803
  try {
50521
51804
  await gh2(contentPutInputArgs(repo, seed.target, tmp));
50522
51805
  } finally {
50523
51806
  try {
50524
- (0, import_node_fs38.unlinkSync)(tmp);
51807
+ (0, import_node_fs39.unlinkSync)(tmp);
50525
51808
  } catch {
50526
51809
  }
50527
51810
  }
@@ -50834,7 +52117,7 @@ init_github_client();
50834
52117
  init_cli_shared();
50835
52118
 
50836
52119
  // src/suite-status.ts
50837
- var import_node_crypto16 = require("node:crypto");
52120
+ var import_node_crypto19 = require("node:crypto");
50838
52121
  var SUITE_REPO = "mutmutco/MM-Work";
50839
52122
  var REPOS = {
50840
52123
  "mmi-scout": "mutmutco/MMI-Scout",
@@ -50862,7 +52145,7 @@ function parseSuiteMapping(body) {
50862
52145
  return void 0;
50863
52146
  }
50864
52147
  }
50865
- var sha256 = (b) => (0, import_node_crypto16.createHash)("sha256").update(b).digest("hex");
52148
+ var sha2562 = (b) => (0, import_node_crypto19.createHash)("sha256").update(b).digest("hex");
50866
52149
  async function proveBundle(url, tag, fetchUrl) {
50867
52150
  const get = async (u) => {
50868
52151
  try {
@@ -50874,7 +52157,7 @@ async function proveBundle(url, tag, fetchUrl) {
50874
52157
  const bundle = await get(url);
50875
52158
  if (typeof bundle === "string") return { state: "unprovable", detail: `bundle ${bundle}` };
50876
52159
  if (bundle.status !== 200) return { state: "unprovable", detail: `bundle HTTP ${bundle.status}` };
50877
- const served = sha256(bundle.body);
52160
+ const served = sha2562(bundle.body);
50878
52161
  const recordUrl = new URL(url);
50879
52162
  recordUrl.search = "";
50880
52163
  recordUrl.pathname = recordUrl.pathname.replace(/[^/]*$/, "module.json");
@@ -51117,9 +52400,9 @@ async function waitSuiteDev(deps, opts) {
51117
52400
  }
51118
52401
 
51119
52402
  // src/suite-train.ts
51120
- var import_node_fs39 = require("node:fs");
51121
- var import_node_path36 = require("node:path");
51122
- var import_node_crypto17 = require("node:crypto");
52403
+ var import_node_fs40 = require("node:fs");
52404
+ var import_node_path37 = require("node:path");
52405
+ var import_node_crypto20 = require("node:crypto");
51123
52406
 
51124
52407
  // src/kit-version.ts
51125
52408
  var parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(typeof v === "string" ? v.trim() : "");
@@ -51204,7 +52487,7 @@ function suiteReleaseTag(rc) {
51204
52487
  function readSuiteCut(path2) {
51205
52488
  let raw;
51206
52489
  try {
51207
- raw = (0, import_node_fs39.readFileSync)(path2, "utf8");
52490
+ raw = (0, import_node_fs40.readFileSync)(path2, "utf8");
51208
52491
  } catch (e) {
51209
52492
  if (e.code === "ENOENT") return void 0;
51210
52493
  throw e;
@@ -51220,11 +52503,11 @@ function readSuiteCut(path2) {
51220
52503
  return record2;
51221
52504
  }
51222
52505
  function writeSuiteCut(path2, record2) {
51223
- (0, import_node_fs39.mkdirSync)((0, import_node_path36.dirname)(path2), { recursive: true });
51224
- const temp = `${path2}.${(0, import_node_crypto17.randomBytes)(8).toString("hex")}.tmp`;
52506
+ (0, import_node_fs40.mkdirSync)((0, import_node_path37.dirname)(path2), { recursive: true });
52507
+ const temp = `${path2}.${(0, import_node_crypto20.randomBytes)(8).toString("hex")}.tmp`;
51225
52508
  try {
51226
- (0, import_node_fs39.writeFileSync)(temp, JSON.stringify(record2, null, 2), { flag: "wx" });
51227
- (0, import_node_fs39.renameSync)(temp, path2);
52509
+ (0, import_node_fs40.writeFileSync)(temp, JSON.stringify(record2, null, 2), { flag: "wx" });
52510
+ (0, import_node_fs40.renameSync)(temp, path2);
51228
52511
  } catch (e) {
51229
52512
  throw new Error(`suite cut record write failed at ${path2}: ${e.message}; stop, verify the file before retrying`);
51230
52513
  }
@@ -51273,25 +52556,25 @@ async function suiteMarkAbandoned(deps, ownerGo = false) {
51273
52556
  return tags.map((t) => `${t.repo}@${t.tag}`);
51274
52557
  }
51275
52558
  async function retireFailedLeadRcLedger(deps, ownerGo) {
51276
- if (!(0, import_node_fs39.existsSync)(deps.ledgerPath)) return void 0;
52559
+ if (!(0, import_node_fs40.existsSync)(deps.ledgerPath)) return void 0;
51277
52560
  return withLedgerLock(deps.ledgerPath, async (fence) => {
51278
- const ledger = parseReleaseLedger((0, import_node_fs39.readFileSync)(deps.ledgerPath, "utf8"));
52561
+ const ledger = parseReleaseLedger((0, import_node_fs40.readFileSync)(deps.ledgerPath, "utf8"));
51279
52562
  const states = Object.values(ledger.phases).map((p) => p.state);
51280
52563
  if (ledger.repo !== SUITE_REPO || ledger.lane !== "rcand" || ledger.phases.promotion.state === "complete" || !states.includes("failed")) return void 0;
51281
52564
  if (!ownerGo) throw new Error(`the lead's failed rcand ledger for ${ledger.tag} blocks every train; retire it with suite abandon --owner-go`);
51282
52565
  if ((await deps.rcHead())?.toLowerCase() === ledger.tagSha.toLowerCase()) throw new Error(`origin rc already points at ${ledger.tag}; it was promoted, so its ledger is not retired`);
51283
- const archive = (0, import_node_path36.join)((0, import_node_path36.dirname)(deps.ledgerPath), `phases.archive-${ledger.tag}.json`);
51284
- if ((0, import_node_fs39.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
52566
+ const archive = (0, import_node_path37.join)((0, import_node_path37.dirname)(deps.ledgerPath), `phases.archive-${ledger.tag}.json`);
52567
+ if ((0, import_node_fs40.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
51285
52568
  await deps.markAbandoned(SUITE_REPO, ledger.tag, ledger.tagSha);
51286
- await fence(() => (0, import_node_fs39.renameSync)(deps.ledgerPath, archive));
52569
+ await fence(() => (0, import_node_fs40.renameSync)(deps.ledgerPath, archive));
51287
52570
  return `${ledger.tag} (ledger archived to ${archive})`;
51288
52571
  });
51289
52572
  }
51290
52573
  function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Date()) {
51291
52574
  const record2 = abandonableCut(recordPath, ownerGo);
51292
52575
  const archive = `${recordPath}.abandoned-${record2.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
51293
- if ((0, import_node_fs39.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
51294
- (0, import_node_fs39.renameSync)(recordPath, archive);
52576
+ if ((0, import_node_fs40.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
52577
+ (0, import_node_fs40.renameSync)(recordPath, archive);
51295
52578
  return { archive, orphanTags: record2.members.filter((m) => m.tag).map((m) => `${m.repo}@${m.tag}`) };
51296
52579
  }
51297
52580
  async function suiteRestoreMainPins(deps) {
@@ -51378,12 +52661,12 @@ async function suiteRcandRun(deps, opts) {
51378
52661
  }
51379
52662
  if ((record2?.announcement === "sent" || record2?.announcement === "skipped") && record2.leadReleased) {
51380
52663
  const archive = `${deps.recordPath}.${record2.leadRcTag}.json`;
51381
- if ((0, import_node_fs39.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
51382
- (0, import_node_fs39.renameSync)(deps.recordPath, archive);
52664
+ if ((0, import_node_fs40.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
52665
+ (0, import_node_fs40.renameSync)(deps.recordPath, archive);
51383
52666
  record2 = void 0;
51384
52667
  }
51385
52668
  if (record2 && !record2.pinPr && !record2.leadRcTag && record2.members.every((m) => !m.tag)) {
51386
- (0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
52669
+ (0, import_node_fs40.rmSync)(deps.recordPath, { force: true });
51387
52670
  record2 = void 0;
51388
52671
  }
51389
52672
  if (!record2) {
@@ -51437,7 +52720,7 @@ async function suiteRcandRun(deps, opts) {
51437
52720
  quiet.push(member);
51438
52721
  }
51439
52722
  if (record2.members.every((m) => m.carried || quiet.includes(m)) && await deps.contains(SUITE_REPO, record2.leadDevelopment, record2.leadPreviousRc)) {
51440
- (0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
52723
+ (0, import_node_fs40.rmSync)(deps.recordPath, { force: true });
51441
52724
  throw new Error("nothing to promote: neither MM-Work nor any module has new development commits since its rc; no suite cut started");
51442
52725
  }
51443
52726
  for (const member of quiet) {
@@ -51455,7 +52738,7 @@ async function suiteRcandRun(deps, opts) {
51455
52738
  }
51456
52739
  if (refusals.length) {
51457
52740
  const untouched = record2.members.every((m) => !m.tag) && !record2.pinPr;
51458
- if (untouched) (0, import_node_fs39.rmSync)(deps.recordPath, { force: true });
52741
+ if (untouched) (0, import_node_fs40.rmSync)(deps.recordPath, { force: true });
51459
52742
  throw new Error(`module rc serving is not ready; ${untouched ? "no suite cut started" : "no new tag was cut"}:
51460
52743
  ${refusals.join("\n")}
51461
52744
  Each module must ship a live release that bind-mounts <deployPath>/data read-only and serves <data>/rc/module.js at /rc/module.js (docs/Guides/suite-trains.md).`);
@@ -51647,11 +52930,11 @@ Each member's live release must serve <data>/main/module.js at its main pin path
51647
52930
  }
51648
52931
  return record2;
51649
52932
  }
51650
- var hotfixRecordPath = (deps) => (0, import_node_path36.join)((0, import_node_path36.dirname)(deps.recordPath), "hotfix.json");
52933
+ var hotfixRecordPath = (deps) => (0, import_node_path37.join)((0, import_node_path37.dirname)(deps.recordPath), "hotfix.json");
51651
52934
  function readSuiteHotfix(path2) {
51652
52935
  let raw;
51653
52936
  try {
51654
- raw = (0, import_node_fs39.readFileSync)(path2, "utf8");
52937
+ raw = (0, import_node_fs40.readFileSync)(path2, "utf8");
51655
52938
  } catch (e) {
51656
52939
  if (e.code === "ENOENT") return void 0;
51657
52940
  throw e;
@@ -51694,7 +52977,7 @@ async function suiteHotfix(deps, opts) {
51694
52977
  if (completed) record2 = void 0;
51695
52978
  if (record2 && record2.repo !== found.repo) throw new Error(`an unfinished suite hotfix for ${record2.repo} is recorded; finish it (rerun with that member) first`);
51696
52979
  if (!record2?.tag) await deps.validateHotfixSummary();
51697
- if (completed) (0, import_node_fs39.renameSync)(path2, `${path2}.${completed.leadTag}.json`);
52980
+ if (completed) (0, import_node_fs40.renameSync)(path2, `${path2}.${completed.leadTag}.json`);
51698
52981
  const rec = record2 ?? { schema: "mmi-suite-hotfix/v1", member: found.id, repo: found.repo, from: opts.from, ...opts.ack?.length ? { ack: opts.ack } : {} };
51699
52982
  const save = () => writeSuiteHotfix(path2, rec);
51700
52983
  save();
@@ -51962,9 +53245,9 @@ async function runRcDataSwitch(deps, opts) {
51962
53245
 
51963
53246
  // src/suite-train-io.ts
51964
53247
  var import_node_child_process19 = require("node:child_process");
51965
- var import_node_path49 = require("node:path");
51966
- var import_node_fs53 = require("node:fs");
51967
53248
  var import_node_path50 = require("node:path");
53249
+ var import_node_fs54 = require("node:fs");
53250
+ var import_node_path51 = require("node:path");
51968
53251
  init_cli_shared();
51969
53252
 
51970
53253
  // src/command-register-train-operations.ts
@@ -53656,8 +54939,8 @@ function checkHotfixCarries(options) {
53656
54939
  }
53657
54940
 
53658
54941
  // src/merge-cleanup.ts
53659
- var import_node_fs40 = require("node:fs");
53660
- var import_node_path37 = require("node:path");
54942
+ var import_node_fs41 = require("node:fs");
54943
+ var import_node_path38 = require("node:path");
53661
54944
  var import_node_os22 = require("node:os");
53662
54945
  init_cli_shared();
53663
54946
  init_github_client();
@@ -53702,16 +54985,16 @@ async function ghWithAppOn401(label, args, options) {
53702
54985
  }
53703
54986
  function withAppOn401(client, label = "post-merge") {
53704
54987
  let app;
53705
- const run = async (call) => {
54988
+ const run = async (call3) => {
53706
54989
  try {
53707
- return await call(app ?? client);
54990
+ return await call3(app ?? client);
53708
54991
  } catch (e) {
53709
54992
  if (!isUnauthorized(e)) throw e;
53710
54993
  if (app) throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
53711
54994
  const token = await appTokenFor(label, e);
53712
54995
  app = createGitHubClient({ token: async () => token });
53713
54996
  try {
53714
- return await call(app);
54997
+ return await call3(app);
53715
54998
  } catch (e2) {
53716
54999
  if (isUnauthorized(e2)) throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
53717
55000
  throw e2;
@@ -54065,13 +55348,13 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
54065
55348
  return rewritten === base ? null : rewritten;
54066
55349
  }
54067
55350
  function writeSquashBodyFile(body) {
54068
- const dir = (0, import_node_fs40.mkdtempSync)((0, import_node_path37.join)((0, import_node_os22.tmpdir)(), "mmi-squash-body-"));
54069
- const path2 = (0, import_node_path37.join)(dir, "body.txt");
54070
- (0, import_node_fs40.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
55351
+ const dir = (0, import_node_fs41.mkdtempSync)((0, import_node_path38.join)((0, import_node_os22.tmpdir)(), "mmi-squash-body-"));
55352
+ const path2 = (0, import_node_path38.join)(dir, "body.txt");
55353
+ (0, import_node_fs41.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
54071
55354
  `, "utf8");
54072
55355
  return { path: path2, cleanup: () => {
54073
55356
  try {
54074
- (0, import_node_fs40.rmSync)(dir, { recursive: true, force: true });
55357
+ (0, import_node_fs41.rmSync)(dir, { recursive: true, force: true });
54075
55358
  } catch {
54076
55359
  }
54077
55360
  } };
@@ -54322,9 +55605,9 @@ async function deleteMergedRemoteBranch(options) {
54322
55605
  init_github_client();
54323
55606
 
54324
55607
  // src/release-custody.ts
54325
- var import_node_crypto18 = require("node:crypto");
54326
- var import_node_fs41 = require("node:fs");
54327
- var import_node_path38 = require("node:path");
55608
+ var import_node_crypto21 = require("node:crypto");
55609
+ var import_node_fs42 = require("node:fs");
55610
+ var import_node_path39 = require("node:path");
54328
55611
  init_github_client();
54329
55612
 
54330
55613
  // src/release-custody-bootstrap.ts
@@ -54545,7 +55828,7 @@ var ReleaseCustodyError = class extends Error {
54545
55828
  this.name = "ReleaseCustodyError";
54546
55829
  }
54547
55830
  };
54548
- var custodyDigest = (value) => (0, import_node_crypto18.createHash)("sha256").update(value).digest("hex");
55831
+ var custodyDigest = (value) => (0, import_node_crypto21.createHash)("sha256").update(value).digest("hex");
54549
55832
  function parseCustody(raw, repo) {
54550
55833
  const s = JSON.parse(raw);
54551
55834
  if (s.schema !== "mmi-release-custody/v1" || s.repo !== repo || !s.precutId || !Number.isSafeInteger(s.revision) || s.revision < 1 || !s.controller || !Number.isSafeInteger(s.controller.runId) || s.controller.runId <= 0 || !Number.isSafeInteger(s.controller.runAttempt) || s.controller.runAttempt <= 0 || !s.controller.token || !(s.ledger === null || typeof s.ledger === "string") || !s.archives || !s.effects) {
@@ -54633,7 +55916,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
54633
55916
  throw new ReleaseCustodyError(`previous controller run ${found.state.controller.runId} attempt ${found.state.controller.runAttempt} is not proven terminal`);
54634
55917
  }
54635
55918
  let state = {
54636
- ...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0, import_node_crypto18.randomUUID)(), effects: {}, ...await initialize() },
55919
+ ...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0, import_node_crypto21.randomUUID)(), effects: {}, ...await initialize() },
54637
55920
  revision: (found.state?.revision ?? 0) + 1,
54638
55921
  controller
54639
55922
  };
@@ -54694,7 +55977,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
54694
55977
  proof: { kind: "ledger-lifecycle", controller: state.controller }
54695
55978
  };
54696
55979
  }
54697
- return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0, import_node_crypto18.randomUUID)() : state.precutId };
55980
+ return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0, import_node_crypto21.randomUUID)() : state.precutId };
54698
55981
  });
54699
55982
  }
54700
55983
  async preserveLocal(raw) {
@@ -54794,7 +56077,7 @@ function reclassifySupersededArchives(state) {
54794
56077
  }
54795
56078
  function localRaw(path2) {
54796
56079
  try {
54797
- return (0, import_node_fs41.readFileSync)(path2, "utf8");
56080
+ return (0, import_node_fs42.readFileSync)(path2, "utf8");
54798
56081
  } catch (e) {
54799
56082
  if (e.code === "ENOENT") return null;
54800
56083
  throw e;
@@ -54803,14 +56086,14 @@ function localRaw(path2) {
54803
56086
  function hydrateLocal(path2, raw) {
54804
56087
  const previous = localRaw(path2);
54805
56088
  if (previous === raw) return;
54806
- (0, import_node_fs41.mkdirSync)((0, import_node_path38.dirname)(path2), { recursive: true });
56089
+ (0, import_node_fs42.mkdirSync)((0, import_node_path39.dirname)(path2), { recursive: true });
54807
56090
  if (previous !== null) {
54808
56091
  const ledger = parseReleaseLedger(previous);
54809
- const archive = (0, import_node_path38.join)((0, import_node_path38.dirname)(path2), `phases.local-${ledger.tagSha}-${custodyDigest(previous)}.json`);
54810
- if (!(0, import_node_fs41.existsSync)(archive)) (0, import_node_fs41.writeFileSync)(archive, previous, { flag: "wx" });
54811
- if (raw === null) (0, import_node_fs41.renameSync)(path2, `${path2}.retired-${custodyDigest(previous)}`);
56092
+ const archive = (0, import_node_path39.join)((0, import_node_path39.dirname)(path2), `phases.local-${ledger.tagSha}-${custodyDigest(previous)}.json`);
56093
+ if (!(0, import_node_fs42.existsSync)(archive)) (0, import_node_fs42.writeFileSync)(archive, previous, { flag: "wx" });
56094
+ if (raw === null) (0, import_node_fs42.renameSync)(path2, `${path2}.retired-${custodyDigest(previous)}`);
54812
56095
  }
54813
- if (raw !== null) (0, import_node_fs41.writeFileSync)(path2, raw, "utf8");
56096
+ if (raw !== null) (0, import_node_fs42.writeFileSync)(path2, raw, "utf8");
54814
56097
  }
54815
56098
  async function commandIdentity(file, args, run, repo) {
54816
56099
  if (file === "git" && args[0] === "push") {
@@ -54920,7 +56203,7 @@ function withHubReleaseCustody(deps) {
54920
56203
  if (!authority.ok || !authority.train || authority.authenticatedDelegation !== true) {
54921
56204
  throw new ReleaseCustodyError("repository custody requires the server-verified owner-triggered train session");
54922
56205
  }
54923
- const controller = { ...runIdentity, token: (0, import_node_crypto18.randomUUID)() };
56206
+ const controller = { ...runIdentity, token: (0, import_node_crypto21.randomUUID)() };
54924
56207
  const raw = localRaw(path2);
54925
56208
  const requiredContexts = () => discoverRequiredCheckContexts(
54926
56209
  deps,
@@ -55000,8 +56283,8 @@ function withHubReleaseCustody(deps) {
55000
56283
  }
55001
56284
 
55002
56285
  // src/project-info-sync.ts
55003
- var import_node_fs42 = require("node:fs");
55004
- var import_node_path39 = require("node:path");
56286
+ var import_node_fs43 = require("node:fs");
56287
+ var import_node_path40 = require("node:path");
55005
56288
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
55006
56289
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
55007
56290
  projectV2 { id }
@@ -55048,14 +56331,14 @@ function sharedName(entries, fallback) {
55048
56331
  }
55049
56332
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
55050
56333
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
55051
- const readmePath = (0, import_node_path39.join)(repoRoot2, "README.md");
55052
- if (!(0, import_node_fs42.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
56334
+ const readmePath = (0, import_node_path40.join)(repoRoot2, "README.md");
56335
+ if (!(0, import_node_fs43.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
55053
56336
  const entries = entriesFor(project2, projects);
55054
56337
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
55055
56338
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
55056
56339
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
55057
56340
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
55058
- const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs42.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
56341
+ const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs43.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
55059
56342
  const lines2 = [
55060
56343
  `# ${projectName}`,
55061
56344
  "",
@@ -55074,8 +56357,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
55074
56357
  const targetBase = `https://github.com/${targetRepo2}`;
55075
56358
  const targetBranch = branchFor(targetRepo2, projects);
55076
56359
  const orgDocs = [
55077
- (0, import_node_fs42.existsSync)((0, import_node_path39.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
55078
- (0, import_node_fs42.existsSync)((0, import_node_path39.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
56360
+ (0, import_node_fs43.existsSync)((0, import_node_path40.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
56361
+ (0, import_node_fs43.existsSync)((0, import_node_path40.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
55079
56362
  ].filter(Boolean);
55080
56363
  if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
55081
56364
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
@@ -55097,8 +56380,8 @@ async function syncProjectInfo(plan, client, apply) {
55097
56380
  }
55098
56381
 
55099
56382
  // src/train-commands.ts
55100
- var import_node_fs43 = require("node:fs");
55101
- var import_node_path40 = require("node:path");
56383
+ var import_node_fs44 = require("node:fs");
56384
+ var import_node_path41 = require("node:path");
55102
56385
  init_cli_shared();
55103
56386
  init_clean_exit();
55104
56387
  init_client_version();
@@ -55113,7 +56396,7 @@ function resolveReleaseBumpIntent(raw) {
55113
56396
  }
55114
56397
  function readRepoVersion() {
55115
56398
  try {
55116
- return JSON.parse((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
56399
+ return JSON.parse((0, import_node_fs44.readFileSync)((0, import_node_path41.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
55117
56400
  } catch {
55118
56401
  return void 0;
55119
56402
  }
@@ -55224,7 +56507,7 @@ function releaseProgressVerdict(status, subject = "Release") {
55224
56507
 
55225
56508
  // src/suite-pin.ts
55226
56509
  var import_promises6 = require("node:fs/promises");
55227
- var import_node_path41 = require("node:path");
56510
+ var import_node_path42 = require("node:path");
55228
56511
  var SuitePinError = class _SuitePinError extends Error {
55229
56512
  diagnostic;
55230
56513
  constructor(repo, tag, error) {
@@ -55292,8 +56575,8 @@ async function openSuiteCutPinPr(deps, input) {
55292
56575
  if (input.carriedOnly) return { status: "unchanged" };
55293
56576
  throw new Error("pins already match without a merged cut PR \u2014 provenance unverified");
55294
56577
  }
55295
- await (0, import_promises6.mkdir)((0, import_node_path41.join)(deps.root, "tmp"), { recursive: true });
55296
- scratch = await (0, import_promises6.mkdtemp)((0, import_node_path41.join)(deps.root, "tmp", "suite-pin-"));
56578
+ await (0, import_promises6.mkdir)((0, import_node_path42.join)(deps.root, "tmp"), { recursive: true });
56579
+ scratch = await (0, import_promises6.mkdtemp)((0, import_node_path42.join)(deps.root, "tmp", "suite-pin-"));
55297
56580
  const run = (file, args) => deps.run(file, args, scratch);
55298
56581
  await run("git", ["clone", "--quiet", "--branch", prs.length ? branch : "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
55299
56582
  if (prs.length) {
@@ -55305,7 +56588,7 @@ async function openSuiteCutPinPr(deps, input) {
55305
56588
  await run("git", ["switch", "-c", branch]);
55306
56589
  }
55307
56590
  for (const stage of ["rc", "main"]) {
55308
- const path2 = (0, import_node_path41.join)(scratch, `${stage}-pins.json`);
56591
+ const path2 = (0, import_node_path42.join)(scratch, `${stage}-pins.json`);
55309
56592
  await (0, import_promises6.writeFile)(path2, JSON.stringify(input.pins.map((p) => ({ module: p.module, url: p[stage] }))));
55310
56593
  await run("node", ["scripts/pin-modules.mjs", "--stage", stage, "--pins", path2]);
55311
56594
  }
@@ -55358,12 +56641,12 @@ async function openSuiteRestorePinPr(deps, input) {
55358
56641
  }
55359
56642
  });
55360
56643
  if (!todo.length) return void 0;
55361
- await (0, import_promises6.mkdir)((0, import_node_path41.join)(deps.root, "tmp"), { recursive: true });
55362
- scratch = await (0, import_promises6.mkdtemp)((0, import_node_path41.join)(deps.root, "tmp", "suite-pin-"));
56644
+ await (0, import_promises6.mkdir)((0, import_node_path42.join)(deps.root, "tmp"), { recursive: true });
56645
+ scratch = await (0, import_promises6.mkdtemp)((0, import_node_path42.join)(deps.root, "tmp", "suite-pin-"));
55363
56646
  const run = (file, args) => deps.run(file, args, scratch);
55364
56647
  await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
55365
56648
  await run("git", ["switch", "-c", branch]);
55366
- const path2 = (0, import_node_path41.join)(scratch, "main-pins.json");
56649
+ const path2 = (0, import_node_path42.join)(scratch, "main-pins.json");
55367
56650
  await (0, import_promises6.writeFile)(path2, JSON.stringify(todo.map((p) => ({ module: p.module, url: p.prior }))));
55368
56651
  await run("node", ["scripts/pin-modules.mjs", "--stage", "main", "--pins", path2]);
55369
56652
  await run("git", ["add", "seed/modules.json"]);
@@ -55437,8 +56720,8 @@ async function openSuitePinPr(deps, input) {
55437
56720
  if (newer.length) return { status: "superseded", pr: newer[0].url, url: pin };
55438
56721
  const open2 = onBranch.find((p) => p.state === "OPEN");
55439
56722
  if (open2 && tagOf(open2.title ?? "") === tag) return { status: "pending", pr: open2.url, url: pin };
55440
- await (0, import_promises6.mkdir)((0, import_node_path41.join)(deps.root, "tmp"), { recursive: true });
55441
- scratch = await (0, import_promises6.mkdtemp)((0, import_node_path41.join)(deps.root, "tmp", "suite-pin-"));
56723
+ await (0, import_promises6.mkdir)((0, import_node_path42.join)(deps.root, "tmp"), { recursive: true });
56724
+ scratch = await (0, import_promises6.mkdtemp)((0, import_node_path42.join)(deps.root, "tmp", "suite-pin-"));
55442
56725
  const run = (file, args) => deps.run(file, args, scratch);
55443
56726
  await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
55444
56727
  await run("git", ["switch", "-c", branch]);
@@ -55490,7 +56773,7 @@ function prTitleRefusal(title) {
55490
56773
 
55491
56774
  // src/command-register-collaboration.ts
55492
56775
  var import_node_child_process18 = require("node:child_process");
55493
- var import_node_fs52 = require("node:fs");
56776
+ var import_node_fs53 = require("node:fs");
55494
56777
  var import_promises8 = require("node:fs/promises");
55495
56778
  init_clean_exit();
55496
56779
  init_cli_shared();
@@ -55514,7 +56797,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
55514
56797
  }
55515
56798
 
55516
56799
  // src/command-register-collaboration.ts
55517
- var import_node_path48 = require("node:path");
56800
+ var import_node_path49 = require("node:path");
55518
56801
  init_house_map();
55519
56802
 
55520
56803
  // src/issue-body.ts
@@ -55631,8 +56914,8 @@ init_github_client();
55631
56914
  init_github_quota();
55632
56915
 
55633
56916
  // src/issue-commands.ts
55634
- var import_node_fs44 = require("node:fs");
55635
- var import_node_crypto19 = require("node:crypto");
56917
+ var import_node_fs45 = require("node:fs");
56918
+ var import_node_crypto22 = require("node:crypto");
55636
56919
  init_cli_shared();
55637
56920
  init_clean_exit();
55638
56921
  init_error_codes();
@@ -55824,7 +57107,7 @@ async function editIssue(client, options, deps = {}) {
55824
57107
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
55825
57108
  const patch = {};
55826
57109
  let bodyChanged = false;
55827
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs44.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
57110
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs45.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
55828
57111
  if (options.titleFile !== void 0) {
55829
57112
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
55830
57113
  } else if (options.title !== void 0) {
@@ -56109,7 +57392,7 @@ function rowIdempotencyKey(batchKey, spec) {
56109
57392
  const identity = `${spec.type}
56110
57393
  ${spec.title.trim()}
56111
57394
  ${spec.body ?? ""}`;
56112
- const hash = (0, import_node_crypto19.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
57395
+ const hash = (0, import_node_crypto22.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
56113
57396
  return `${batchKey}:${hash}`;
56114
57397
  }
56115
57398
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -56491,7 +57774,7 @@ function extendCreateCommand(issue, batchAttach) {
56491
57774
  if (opts.batch) {
56492
57775
  let specs;
56493
57776
  try {
56494
- const raw = (0, import_node_fs44.readFileSync)(opts.batch, "utf8");
57777
+ const raw = (0, import_node_fs45.readFileSync)(opts.batch, "utf8");
56495
57778
  specs = JSON.parse(raw);
56496
57779
  if (!Array.isArray(specs)) {
56497
57780
  const top = specs === null ? "null" : typeof specs === "object" ? `an object with keys: ${Object.keys(specs).join(", ") || "(none)"}` : `a ${typeof specs}`;
@@ -56568,8 +57851,8 @@ ${lines2}`, {
56568
57851
  }
56569
57852
 
56570
57853
  // src/post-merge-recon.ts
56571
- var import_node_fs45 = require("node:fs");
56572
- var import_node_path42 = require("node:path");
57854
+ var import_node_fs46 = require("node:fs");
57855
+ var import_node_path43 = require("node:path");
56573
57856
 
56574
57857
  // src/cross-repo-filing-issue.ts
56575
57858
  init_github_client();
@@ -56739,16 +58022,16 @@ function buildPostMergeReconRecovery(input) {
56739
58022
  }
56740
58023
  function writePostMergeReconRecovery(cwd, recovery) {
56741
58024
  const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
56742
- (0, import_node_fs45.mkdirSync)((0, import_node_path42.dirname)(path2), { recursive: true });
56743
- (0, import_node_fs45.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
58025
+ (0, import_node_fs46.mkdirSync)((0, import_node_path43.dirname)(path2), { recursive: true });
58026
+ (0, import_node_fs46.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
56744
58027
  `, "utf8");
56745
58028
  return path2;
56746
58029
  }
56747
58030
  function clearPostMergeReconRecovery(cwd, repo, pr) {
56748
58031
  const path2 = postMergeReconStatePath(cwd, repo, pr);
56749
- if (!(0, import_node_fs45.existsSync)(path2)) return;
58032
+ if (!(0, import_node_fs46.existsSync)(path2)) return;
56750
58033
  try {
56751
- (0, import_node_fs45.unlinkSync)(path2);
58034
+ (0, import_node_fs46.unlinkSync)(path2);
56752
58035
  } catch {
56753
58036
  }
56754
58037
  }
@@ -56783,7 +58066,7 @@ var import_promises7 = require("node:fs/promises");
56783
58066
  init_cli_shared();
56784
58067
 
56785
58068
  // src/api-read.ts
56786
- var import_node_fs46 = require("node:fs");
58069
+ var import_node_fs47 = require("node:fs");
56787
58070
  init_cli_shared();
56788
58071
  var API_ACCEPT_HEADERS = {
56789
58072
  json: "application/vnd.github+json",
@@ -57048,7 +58331,7 @@ function registerApiReadCommands(program3, overrides) {
57048
58331
  if (req.workflowRuns) body = sliceWorkflowRunsPage(body, req.workflowRuns);
57049
58332
  if (o.jq !== void 0) body = await jqRun(o.jq, body, localJqEnv(), ...bindings.length ? [bindings] : []);
57050
58333
  if (zip) {
57051
- (0, import_node_fs46.writeFileSync)(o.out, body, "latin1");
58334
+ (0, import_node_fs47.writeFileSync)(o.out, body, "latin1");
57052
58335
  return;
57053
58336
  }
57054
58337
  } catch (e) {
@@ -57147,6 +58430,8 @@ function registerApiReadCommands(program3, overrides) {
57147
58430
  // src/run-log-read.ts
57148
58431
  init_cli_shared();
57149
58432
  var FAILED2 = /* @__PURE__ */ new Set(["failure", "cancelled", "timed_out"]);
58433
+ var RUN_LOG_RANGE_LINE_CAP = 2e3;
58434
+ var RUN_LOG_RANGE_CHAR_CAP = 32 * 1024;
57150
58435
  function logRepo(raw) {
57151
58436
  const repo = parseBrokerRepo(raw, "--repo");
57152
58437
  if (repo.split("/").some((s) => s === "." || s === "..")) throw new Error("read run log: --repo refuses dot segments");
@@ -57165,6 +58450,78 @@ function sliceRunLog(text, tail) {
57165
58450
  });
57166
58451
  return { errorLines: lines2.filter((_, i) => context.has(i)), tail: lines2.slice(-tail) };
57167
58452
  }
58453
+ function parseLineRange(raw) {
58454
+ const match = /^(\d+):(\d+)$/.exec(raw.trim());
58455
+ if (!match) throw new Error(`read run log: --lines must be <start:end> line numbers, e.g. --lines 1:2000 (received '${raw}')`);
58456
+ const start = Number(match[1]);
58457
+ const end = Number(match[2]);
58458
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 1 || end < start) {
58459
+ throw new Error(`read run log: --lines must be a 1-based start:end range with start <= end and safe-integer bounds (received '${raw}')`);
58460
+ }
58461
+ return { start, end };
58462
+ }
58463
+ async function readRunLogRange(id, o, runner, env = brokerChildEnv()) {
58464
+ const { start, end: requestedEnd } = parseLineRange(o.lines);
58465
+ if (o.tail !== void 0) throw new Error("read run log: --lines and --tail are mutually exclusive (the range already bounds the returned lines)");
58466
+ if (o.failed) throw new Error("read run log: --lines and --failed are mutually exclusive (--lines reads exactly one named job)");
58467
+ if (o.attempt === void 0) throw new Error("read run log: --lines requires an explicit --attempt <n> (1-1000) \u2014 Actions job ids are minted per attempt, so the id must name one attempt");
58468
+ if (o.job === void 0 || !/^\d+$/.test(o.job.trim())) throw new Error("read run log: --lines requires a numeric --job <id> \u2014 job names are ambiguous across attempts; take the id from the attempt-pinned jobs read");
58469
+ const runId = parseBrokerId(id, "run id");
58470
+ const repo = logRepo(o.repo);
58471
+ const attempt = parseBrokerInt(o.attempt, "--attempt", 1, 1e3);
58472
+ const jobId = Number(parseBrokerId(o.job, "job id"));
58473
+ if (!Number.isSafeInteger(jobId) || jobId < 1) throw new Error(`read run log: --lines requires a numeric --job id (received '${o.job}')`);
58474
+ const pages = JSON.parse(await readAppApi(`repos/${repo}/actions/runs/${runId}/attempts/${attempt}/jobs`, runner));
58475
+ const listed = pages.flatMap((page) => page.jobs);
58476
+ const job = listed.find((candidate) => candidate.id === jobId);
58477
+ if (!job) {
58478
+ const shown = listed.slice(0, 30).map((j) => `${j.name} (${j.id})`).join(", ");
58479
+ return {
58480
+ result: { runId, repo, jobs: [] },
58481
+ exitCode: 1,
58482
+ message: `read run log: job ${jobId} is not in run ${runId} attempt ${attempt} \u2014 Actions job ids are minted per workflow run and per re-run attempt, so only an id from this attempt's listing matches; jobs in run ${runId} attempt ${attempt}: ${shown || "none"}${listed.length > 30 ? `, \u2026+${listed.length - 30} more` : ""}`
58483
+ };
58484
+ }
58485
+ const text = await readAppApi(`repos/${repo}/actions/jobs/${job.id}/logs`, runner);
58486
+ const redacted = redactPrivateCredential(text, env);
58487
+ const all = redacted.replace(/\r\n/g, "\n").split("\n");
58488
+ if (all.at(-1) === "") all.pop();
58489
+ const total = all.length;
58490
+ if (start > total) {
58491
+ return {
58492
+ result: { runId, repo, jobs: [] },
58493
+ exitCode: 1,
58494
+ message: `read run log: --lines ${start}:${requestedEnd} starts beyond the end of the log \u2014 job ${job.name} (${job.id}) attempt ${attempt} retains ${total} lines; nothing to return`
58495
+ };
58496
+ }
58497
+ const requestedCount = requestedEnd - start + 1;
58498
+ const selected = all.slice(start - 1, Math.min(requestedEnd, start - 1 + RUN_LOG_RANGE_LINE_CAP));
58499
+ let truncated = requestedCount > selected.length;
58500
+ let section = selected.join("\n");
58501
+ let charCut = false;
58502
+ if (section.length > RUN_LOG_RANGE_CHAR_CAP) {
58503
+ section = section.slice(0, RUN_LOG_RANGE_CHAR_CAP);
58504
+ charCut = true;
58505
+ truncated = true;
58506
+ }
58507
+ const lines2 = section.split("\n");
58508
+ if (charCut && lines2.at(-1) === "") lines2.pop();
58509
+ return {
58510
+ result: {
58511
+ runId,
58512
+ repo,
58513
+ attempt,
58514
+ job: { id: job.id, name: job.name, conclusion: job.conclusion },
58515
+ lines: lines2,
58516
+ start,
58517
+ end: start + lines2.length - 1,
58518
+ requestedEnd,
58519
+ total,
58520
+ truncated
58521
+ },
58522
+ exitCode: 0
58523
+ };
58524
+ }
57168
58525
  async function readRunLog(id, o, runner) {
57169
58526
  const runId = parseBrokerId(id, "run id");
57170
58527
  const repo = logRepo(o.repo);
@@ -57190,14 +58547,17 @@ async function readRunLog(id, o, runner) {
57190
58547
  return { result, exitCode: 0 };
57191
58548
  }
57192
58549
  function registerRunLogRead(run, runner) {
57193
- run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name or numeric job id").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3)), "200").option("--attempt <n>", "run attempt (1-1000; default latest)", (v) => String(parseBrokerInt(v, "--attempt", 1, 1e3))).option("--json", "emit runId, repo and jobs with failedStep/errorLines/tail").argument("<runId>", "workflow run number", (v) => parseBrokerId(v, "run id")).action(async (id, o) => {
58550
+ run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136), or a bounded --lines range from one attempt-pinned job (#9773); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name or numeric job id").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3))).option("--attempt <n>", "run attempt (1-1000; default latest)", (v) => String(parseBrokerInt(v, "--attempt", 1, 1e3))).addOption(new Option("--lines <start:end>", "read lines start:end of one job (needs --attempt and a numeric --job id; capped at 2000 lines and 32KiB characters; conflicts with --tail/--failed)").conflicts("failed")).option("--json", "emit the read as JSON (jobs with failedStep/errorLines/tail, or the --lines selection)").argument("<runId>", "workflow run number", (v) => parseBrokerId(v, "run id")).action(async (id, o) => {
57194
58551
  requireBrokerApp("read run log");
57195
58552
  const env = brokerChildEnv();
57196
58553
  try {
57197
- const { result, message: message2, exitCode } = await readRunLog(id, o, runner);
57198
- if (message2 && (!o.json || exitCode !== 0)) printLine(message2);
58554
+ const { result, message: message2, exitCode } = o.lines === void 0 ? await readRunLog(id, o, runner) : await readRunLogRange(id, o, runner, env);
58555
+ if (message2 && (!o.json || exitCode !== 0)) printLine(redactPrivateCredential(message2, env));
57199
58556
  else if (o.json) printLine(redactPrivateCredential(JSON.stringify(result), env));
57200
- else for (const job of result.jobs) {
58557
+ else if ("lines" in result) {
58558
+ printLine(redactPrivateCredential(`== ${result.job.name} (${result.job.id}) ${result.job.conclusion}: lines ${result.start}-${result.end} of ${result.total} (attempt ${result.attempt}${result.truncated ? ", truncated" : ""})`, env));
58559
+ for (const line of result.lines) printLine(redactPrivateCredential(line, env));
58560
+ } else for (const job of result.jobs) {
57201
58561
  printLine(redactPrivateCredential(`== ${job.name} (${job.id}) ${job.conclusion}: failing step "${job.failedStep ?? "none"}"`, env));
57202
58562
  for (const line of [...job.errorLines, ...job.tail]) printLine(redactPrivateCredential(line, env));
57203
58563
  }
@@ -57383,7 +58743,7 @@ function parseBrokerPrState(raw) {
57383
58743
  function buildPrListSpec(o) {
57384
58744
  const repo = parseBrokerRepo(o.repo, "--repo");
57385
58745
  const fields = rejectPrViewQueueFields(parseBrokerJsonFields(o.json), "<number>", repo, "list");
57386
- const args = withJson(withRepo(["pr", "list"], repo), fields, o.json);
58746
+ const args = withJson(withRepo(["pr", "list"], repo), fields, o.json, PR_LIST_JSON_FIELDS);
57387
58747
  const state = parseBrokerPrState(o.state);
57388
58748
  if (state) args.push("--state", state);
57389
58749
  for (const flag of ["base", "head"]) {
@@ -57402,9 +58762,11 @@ function buildPrListSpec(o) {
57402
58762
  function withRepo(args, repo) {
57403
58763
  return repo ? [...args, "--repo", repo] : args;
57404
58764
  }
57405
- function withJson(args, jsonFields, jsonFlag) {
58765
+ function withJson(args, jsonFields, jsonFlag, bareDefault) {
57406
58766
  if (jsonFlag === void 0 || jsonFlag === false) return args;
57407
- return typeof jsonFields === "string" ? [...args, "--json", jsonFields] : [...args, "--json"];
58767
+ const fields = typeof jsonFields === "string" ? jsonFields : bareDefault;
58768
+ if (fields === void 0) throw new Error(`read: --json needs a comma-separated field list for gh ${args.slice(0, 2).join(" ")} (e.g. --json number,title,state)`);
58769
+ return [...args, "--json", fields];
57408
58770
  }
57409
58771
  function buildRunViewSpec(id, o) {
57410
58772
  const args = withJson(withRepo(["run", "view", parseBrokerId(id, "run id"), ...o.attempt !== void 0 ? ["--attempt", String(parseBrokerInt(o.attempt, "--attempt", 1, 1e3))] : []], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
@@ -57422,8 +58784,11 @@ function parseBrokerCommit(v) {
57422
58784
  if (!/^[0-9a-f]{40}$/i.test(v)) throw new Error(`read: --commit must be a full 40-character commit SHA (received '${v}')`);
57423
58785
  return v;
57424
58786
  }
58787
+ var PR_LIST_JSON_FIELDS = "number,title,state,isDraft,headRefName,baseRefName,author,createdAt,updatedAt,url";
58788
+ var ISSUE_LIST_JSON_FIELDS = "number,title,state,labels,assignees,createdAt,updatedAt,url";
58789
+ var RUN_LIST_JSON_FIELDS = "attempt,conclusion,createdAt,databaseId,displayTitle,event,headBranch,headSha,name,number,startedAt,status,updatedAt,url,workflowDatabaseId,workflowName";
57425
58790
  function buildRunListSpec(o) {
57426
- const args = withJson(withRepo(["run", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
58791
+ const args = withJson(withRepo(["run", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json, RUN_LIST_JSON_FIELDS);
57427
58792
  const workflow = parseBrokerWorkflow(o.workflow);
57428
58793
  if (workflow) args.push("--workflow", workflow);
57429
58794
  const branch = parseBrokerBranch(o.branch);
@@ -57506,7 +58871,7 @@ function parseBrokerLabel(raw) {
57506
58871
  return raw;
57507
58872
  }
57508
58873
  function buildIssueListSpec(o) {
57509
- const args = withJson(withRepo(["issue", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
58874
+ const args = withJson(withRepo(["issue", "list"], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json, ISSUE_LIST_JSON_FIELDS);
57510
58875
  const search = parseBrokerSearch(o.search);
57511
58876
  if (search !== void 0) args.push("--search", search);
57512
58877
  const assignee = parseBrokerAssignee(o.assignee);
@@ -58507,9 +59872,9 @@ function registerPrLifecycleCommands(program3) {
58507
59872
 
58508
59873
  // src/review-verdict.ts
58509
59874
  var import_node_child_process15 = require("node:child_process");
58510
- var import_node_fs47 = require("node:fs");
59875
+ var import_node_fs48 = require("node:fs");
58511
59876
  var import_node_os24 = require("node:os");
58512
- var import_node_path43 = require("node:path");
59877
+ var import_node_path44 = require("node:path");
58513
59878
  init_cli_shared();
58514
59879
  var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
58515
59880
  var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
@@ -58674,15 +60039,15 @@ async function readPrIssueComments(number, repo) {
58674
60039
  return comments;
58675
60040
  }
58676
60041
  async function postPrCommentFromFile(number, repo, body) {
58677
- const dir = (0, import_node_fs47.mkdtempSync)((0, import_node_path43.join)((0, import_node_os24.tmpdir)(), "mmi-review-verdict-"));
58678
- const path2 = (0, import_node_path43.join)(dir, "body.md");
60042
+ const dir = (0, import_node_fs48.mkdtempSync)((0, import_node_path44.join)((0, import_node_os24.tmpdir)(), "mmi-review-verdict-"));
60043
+ const path2 = (0, import_node_path44.join)(dir, "body.md");
58679
60044
  try {
58680
- (0, import_node_fs47.writeFileSync)(path2, body, "utf8");
60045
+ (0, import_node_fs48.writeFileSync)(path2, body, "utf8");
58681
60046
  const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
58682
60047
  return stdout.trim();
58683
60048
  } finally {
58684
60049
  try {
58685
- (0, import_node_fs47.rmSync)(dir, { recursive: true, force: true });
60050
+ (0, import_node_fs48.rmSync)(dir, { recursive: true, force: true });
58686
60051
  } catch {
58687
60052
  }
58688
60053
  }
@@ -59095,18 +60460,18 @@ function createPreparationDeps(input, io = {}) {
59095
60460
  }
59096
60461
 
59097
60462
  // src/worktree-residue-sweep.ts
59098
- var import_node_fs51 = require("node:fs");
59099
- var import_node_path47 = require("node:path");
60463
+ var import_node_fs52 = require("node:fs");
60464
+ var import_node_path48 = require("node:path");
59100
60465
 
59101
60466
  // src/worktree-merge-cleanup.ts
59102
- var import_node_fs50 = require("node:fs");
59103
- var import_node_path46 = require("node:path");
60467
+ var import_node_fs51 = require("node:fs");
60468
+ var import_node_path47 = require("node:path");
59104
60469
  init_cli_shared();
59105
60470
 
59106
60471
  // src/stage-runner.ts
59107
60472
  var import_node_child_process17 = require("node:child_process");
59108
- var import_node_fs49 = require("node:fs");
59109
- var import_node_path45 = require("node:path");
60473
+ var import_node_fs50 = require("node:fs");
60474
+ var import_node_path46 = require("node:path");
59110
60475
  var import_node_net = require("node:net");
59111
60476
  var import_node_util5 = require("node:util");
59112
60477
 
@@ -59116,8 +60481,8 @@ function normalizeEol2(s) {
59116
60481
  }
59117
60482
 
59118
60483
  // src/env-detection.ts
59119
- var import_node_fs48 = require("node:fs");
59120
- var import_node_path44 = require("node:path");
60484
+ var import_node_fs49 = require("node:fs");
60485
+ var import_node_path45 = require("node:path");
59121
60486
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
59122
60487
  function isEnvBaseName(name) {
59123
60488
  return name.toLowerCase() === ".env";
@@ -59127,7 +60492,7 @@ function findEnvFiles(root) {
59127
60492
  const walk2 = (dir, rel) => {
59128
60493
  let entries;
59129
60494
  try {
59130
- entries = (0, import_node_fs48.readdirSync)(dir, { withFileTypes: true });
60495
+ entries = (0, import_node_fs49.readdirSync)(dir, { withFileTypes: true });
59131
60496
  } catch {
59132
60497
  return;
59133
60498
  }
@@ -59135,13 +60500,13 @@ function findEnvFiles(root) {
59135
60500
  const relPath = rel ? `${rel}/${entry.name}` : entry.name;
59136
60501
  if (entry.isDirectory()) {
59137
60502
  if (SKIPPED_DIRS.has(entry.name)) continue;
59138
- walk2((0, import_node_path44.join)(dir, entry.name), relPath);
60503
+ walk2((0, import_node_path45.join)(dir, entry.name), relPath);
59139
60504
  continue;
59140
60505
  }
59141
60506
  if (isEnvBaseName(entry.name)) {
59142
60507
  let isDir = false;
59143
60508
  try {
59144
- isDir = (0, import_node_fs48.lstatSync)((0, import_node_path44.join)(dir, entry.name)).isDirectory();
60509
+ isDir = (0, import_node_fs49.lstatSync)((0, import_node_path45.join)(dir, entry.name)).isDirectory();
59145
60510
  } catch {
59146
60511
  }
59147
60512
  if (!isDir) found.push(relPath);
@@ -59280,12 +60645,12 @@ var DOCKER_TIMEOUT_MS = 15e3;
59280
60645
  function stageBash() {
59281
60646
  if (process.platform !== "win32") return "bash";
59282
60647
  const configured = process.env.SHELL;
59283
- if (configured && (0, import_node_path45.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs49.existsSync)(configured)) return configured;
60648
+ if (configured && (0, import_node_path46.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs50.existsSync)(configured)) return configured;
59284
60649
  const candidates = [
59285
- (0, import_node_path45.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
59286
- ...process.env.LOCALAPPDATA ? [(0, import_node_path45.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
60650
+ (0, import_node_path46.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
60651
+ ...process.env.LOCALAPPDATA ? [(0, import_node_path46.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
59287
60652
  ];
59288
- const bash = candidates.find((path2) => (0, import_node_fs49.existsSync)(path2));
60653
+ const bash = candidates.find((path2) => (0, import_node_fs50.existsSync)(path2));
59289
60654
  if (!bash) throw new Error("Local stages require Git Bash on Windows. Install Git for Windows or set SHELL to its absolute bash.exe path.");
59290
60655
  return bash;
59291
60656
  }
@@ -59423,7 +60788,7 @@ function appendForceRecreate(up) {
59423
60788
  return `${up.trimEnd()} --force-recreate`;
59424
60789
  }
59425
60790
  function stageStatePath(cwd = process.cwd()) {
59426
- return (0, import_node_path45.join)(cwd, "tmp", "stage", "state.json");
60791
+ return (0, import_node_path46.join)(cwd, "tmp", "stage", "state.json");
59427
60792
  }
59428
60793
  function normPath(path2) {
59429
60794
  return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -59640,8 +61005,8 @@ function stageProcessEnv(stagePort, extraEnv) {
59640
61005
  }
59641
61006
  function composeResolvesPort(cwd) {
59642
61007
  if (process.env.PORT) return true;
59643
- const envFile = (0, import_node_path45.join)(cwd, ".env");
59644
- return (0, import_node_fs49.existsSync)(envFile) && envFileKeys((0, import_node_fs49.readFileSync)(envFile, "utf8")).has("PORT");
61008
+ const envFile = (0, import_node_path46.join)(cwd, ".env");
61009
+ return (0, import_node_fs50.existsSync)(envFile) && envFileKeys((0, import_node_fs50.readFileSync)(envFile, "utf8")).has("PORT");
59645
61010
  }
59646
61011
  function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
59647
61012
  const composeProject = config.env?.COMPOSE_PROJECT_NAME ?? vaultEnvMerge?.COMPOSE_PROJECT_NAME ?? stageComposeProjectName(cwd);
@@ -59656,13 +61021,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
59656
61021
  function stageComposeFiles(cwd) {
59657
61022
  const files = ["docker-compose.yml"];
59658
61023
  for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
59659
- if ((0, import_node_fs49.existsSync)((0, import_node_path45.join)(cwd, conventional))) files.push(conventional);
61024
+ if ((0, import_node_fs50.existsSync)((0, import_node_path46.join)(cwd, conventional))) files.push(conventional);
59660
61025
  }
59661
61026
  return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
59662
61027
  }
59663
61028
  async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
59664
61029
  const keys = Object.keys(vaultEnvMerge ?? {});
59665
- if (!keys.length || !(0, import_node_fs49.existsSync)((0, import_node_path45.join)(cwd, "docker-compose.yml"))) return {};
61030
+ if (!keys.length || !(0, import_node_fs50.existsSync)((0, import_node_path46.join)(cwd, "docker-compose.yml"))) return {};
59666
61031
  let configJson;
59667
61032
  try {
59668
61033
  const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
@@ -59678,21 +61043,21 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
59678
61043
  }
59679
61044
  const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson), runtimeEnvSelects(configJson));
59680
61045
  if (!body) return {};
59681
- const overridePath = (0, import_node_path45.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
59682
- (0, import_node_fs49.mkdirSync)((0, import_node_path45.join)(cwd, "tmp", "stage"), { recursive: true });
59683
- (0, import_node_fs49.writeFileSync)(overridePath, body, "utf8");
61046
+ const overridePath = (0, import_node_path46.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
61047
+ (0, import_node_fs50.mkdirSync)((0, import_node_path46.join)(cwd, "tmp", "stage"), { recursive: true });
61048
+ (0, import_node_fs50.writeFileSync)(overridePath, body, "utf8");
59684
61049
  return stageComposeFileEnv(stageComposeFiles(cwd));
59685
61050
  }
59686
61051
  async function ensureStageRuntimeEnv(config, opts, cwd) {
59687
61052
  if (!config.ensureEnv) return;
59688
- const target = (0, import_node_path45.join)(cwd, config.ensureEnv.target);
59689
- const example = (0, import_node_path45.join)(cwd, config.ensureEnv.example);
59690
- if (!(0, import_node_fs49.existsSync)(target) && (0, import_node_fs49.existsSync)(example)) {
59691
- (0, import_node_fs49.copyFileSync)(example, target);
59692
- } else if ((0, import_node_fs49.existsSync)(target) && (0, import_node_fs49.existsSync)(example)) {
59693
- const stale = detectStaleEnvFile((0, import_node_fs49.readFileSync)(example, "utf8"), (0, import_node_fs49.readFileSync)(target, "utf8"), {
59694
- exampleMtimeMs: (0, import_node_fs49.statSync)(example).mtimeMs,
59695
- targetMtimeMs: (0, import_node_fs49.statSync)(target).mtimeMs
61053
+ const target = (0, import_node_path46.join)(cwd, config.ensureEnv.target);
61054
+ const example = (0, import_node_path46.join)(cwd, config.ensureEnv.example);
61055
+ if (!(0, import_node_fs50.existsSync)(target) && (0, import_node_fs50.existsSync)(example)) {
61056
+ (0, import_node_fs50.copyFileSync)(example, target);
61057
+ } else if ((0, import_node_fs50.existsSync)(target) && (0, import_node_fs50.existsSync)(example)) {
61058
+ const stale = detectStaleEnvFile((0, import_node_fs50.readFileSync)(example, "utf8"), (0, import_node_fs50.readFileSync)(target, "utf8"), {
61059
+ exampleMtimeMs: (0, import_node_fs50.statSync)(example).mtimeMs,
61060
+ targetMtimeMs: (0, import_node_fs50.statSync)(target).mtimeMs
59696
61061
  });
59697
61062
  if (stale) {
59698
61063
  const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running mmi-cli stage`;
@@ -59700,8 +61065,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
59700
61065
  console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
59701
61066
  }
59702
61067
  }
59703
- if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs49.existsSync)(target)) {
59704
- (0, import_node_fs49.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs49.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
61068
+ if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs50.existsSync)(target)) {
61069
+ (0, import_node_fs50.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs50.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
59705
61070
  }
59706
61071
  }
59707
61072
  async function gitText(cwd, args) {
@@ -59731,20 +61096,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
59731
61096
  return void 0;
59732
61097
  }
59733
61098
  function readState(path2) {
59734
- if (!(0, import_node_fs49.existsSync)(path2)) return null;
61099
+ if (!(0, import_node_fs50.existsSync)(path2)) return null;
59735
61100
  try {
59736
- return JSON.parse((0, import_node_fs49.readFileSync)(path2, "utf8"));
61101
+ return JSON.parse((0, import_node_fs50.readFileSync)(path2, "utf8"));
59737
61102
  } catch {
59738
61103
  return null;
59739
61104
  }
59740
61105
  }
59741
61106
  function mkdirFor(path2) {
59742
61107
  const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
59743
- (0, import_node_fs49.mkdirSync)(dir, { recursive: true });
61108
+ (0, import_node_fs50.mkdirSync)(dir, { recursive: true });
59744
61109
  }
59745
61110
  function writeState(path2, state) {
59746
61111
  mkdirFor(path2);
59747
- (0, import_node_fs49.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
61112
+ (0, import_node_fs50.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
59748
61113
  }
59749
61114
  function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
59750
61115
  const reservation = {
@@ -59773,7 +61138,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd, currentEn
59773
61138
  );
59774
61139
  }
59775
61140
  for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
59776
- (0, import_node_fs49.rmSync)(path2, { force: true });
61141
+ (0, import_node_fs50.rmSync)(path2, { force: true });
59777
61142
  }
59778
61143
  }
59779
61144
  async function killTree(pid) {
@@ -60020,8 +61385,8 @@ async function runStage(config = {}, opts = {}) {
60020
61385
  });
60021
61386
  }
60022
61387
  } catch (e) {
60023
- (0, import_node_fs49.rmSync)(statePath, { force: true });
60024
- if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs49.rmSync)(globalStatePath, { force: true });
61388
+ (0, import_node_fs50.rmSync)(statePath, { force: true });
61389
+ if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs50.rmSync)(globalStatePath, { force: true });
60025
61390
  throw e;
60026
61391
  }
60027
61392
  const started = await startStage(config, {
@@ -60040,9 +61405,9 @@ function normPath2(p) {
60040
61405
  return p.replace(/\\/g, "/").replace(/\/+$/, "");
60041
61406
  }
60042
61407
  function unlinkNodeModulesJunction(wtPath) {
60043
- const nm = (0, import_node_path46.join)(wtPath, "node_modules");
61408
+ const nm = (0, import_node_path47.join)(wtPath, "node_modules");
60044
61409
  try {
60045
- if ((0, import_node_fs50.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs50.rmdirSync)(nm);
61410
+ if ((0, import_node_fs51.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs51.rmdirSync)(nm);
60046
61411
  return { ok: true };
60047
61412
  } catch (e) {
60048
61413
  if (e.code === "ENOENT") return { ok: true };
@@ -60053,8 +61418,8 @@ function defaultSleep2(ms) {
60053
61418
  return new Promise((resolve7) => setTimeout(resolve7, ms));
60054
61419
  }
60055
61420
  async function removeResidueDirectory(wtPath, options = {}) {
60056
- const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs50.rmdirSync, exists = import_node_fs50.existsSync } = options;
60057
- const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs50.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
61421
+ const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs51.rmdirSync, exists = import_node_fs51.existsSync } = options;
61422
+ const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs51.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
60058
61423
  const junction = unlinkNodeModulesJunction(wtPath);
60059
61424
  if (!junction.ok) return junction;
60060
61425
  try {
@@ -60177,28 +61542,28 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
60177
61542
  }
60178
61543
  async function listNestedIgnoredNodeModules(wtPath, execGit) {
60179
61544
  const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
60180
- return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path46.join)(wtPath, line.slice(0, -1)));
61545
+ return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path47.join)(wtPath, line.slice(0, -1)));
60181
61546
  }
60182
61547
  function safeRemoveTree(path2) {
60183
- const stat4 = (0, import_node_fs50.lstatSync)(path2);
61548
+ const stat4 = (0, import_node_fs51.lstatSync)(path2);
60184
61549
  if (stat4.isSymbolicLink()) {
60185
61550
  try {
60186
- (0, import_node_fs50.rmdirSync)(path2);
61551
+ (0, import_node_fs51.rmdirSync)(path2);
60187
61552
  } catch {
60188
- (0, import_node_fs50.unlinkSync)(path2);
61553
+ (0, import_node_fs51.unlinkSync)(path2);
60189
61554
  }
60190
61555
  return;
60191
61556
  }
60192
61557
  if (stat4.isDirectory()) {
60193
- for (const entry of (0, import_node_fs50.readdirSync)(path2)) safeRemoveTree((0, import_node_path46.join)(path2, entry));
60194
- (0, import_node_fs50.rmdirSync)(path2);
61558
+ for (const entry of (0, import_node_fs51.readdirSync)(path2)) safeRemoveTree((0, import_node_path47.join)(path2, entry));
61559
+ (0, import_node_fs51.rmdirSync)(path2);
60195
61560
  return;
60196
61561
  }
60197
- (0, import_node_fs50.unlinkSync)(path2);
61562
+ (0, import_node_fs51.unlinkSync)(path2);
60198
61563
  }
60199
61564
  async function removeWorktreeNodeModules(wtPath) {
60200
61565
  try {
60201
- safeRemoveTree((0, import_node_path46.join)(wtPath, "node_modules"));
61566
+ safeRemoveTree((0, import_node_path47.join)(wtPath, "node_modules"));
60202
61567
  return { ok: true };
60203
61568
  } catch (e) {
60204
61569
  if (e.code === "ENOENT") return { ok: true };
@@ -60210,7 +61575,7 @@ function errorMessage(e) {
60210
61575
  }
60211
61576
  function resolvedOrRaw(path2) {
60212
61577
  try {
60213
- return normPath2((0, import_node_fs50.realpathSync)(path2));
61578
+ return normPath2((0, import_node_fs51.realpathSync)(path2));
60214
61579
  } catch {
60215
61580
  return normPath2(path2);
60216
61581
  }
@@ -60218,10 +61583,10 @@ function resolvedOrRaw(path2) {
60218
61583
  function unlinkEscapingReparsePoints(root, primaryRoot) {
60219
61584
  let realRoot;
60220
61585
  try {
60221
- if ((0, import_node_fs50.lstatSync)(root).isSymbolicLink()) {
61586
+ if ((0, import_node_fs51.lstatSync)(root).isSymbolicLink()) {
60222
61587
  return { ok: false, error: `delete root ${normPath2(root)} is a reparse point resolving to ${resolvedOrRaw(root)}` };
60223
61588
  }
60224
- realRoot = normPath2((0, import_node_fs50.realpathSync)(root));
61589
+ realRoot = normPath2((0, import_node_fs51.realpathSync)(root));
60225
61590
  } catch (e) {
60226
61591
  if (e.code === "ENOENT") return { ok: true, unlinked: [] };
60227
61592
  return { ok: false, error: `cannot resolve delete root ${normPath2(root)}: ${errorMessage(e)}` };
@@ -60237,17 +61602,17 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
60237
61602
  const dir = stack.pop();
60238
61603
  let entries;
60239
61604
  try {
60240
- entries = (0, import_node_fs50.readdirSync)(dir, { withFileTypes: true });
61605
+ entries = (0, import_node_fs51.readdirSync)(dir, { withFileTypes: true });
60241
61606
  } catch (e) {
60242
61607
  if (e.code === "ENOENT") continue;
60243
61608
  return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
60244
61609
  }
60245
61610
  for (const entry of entries) {
60246
- const child2 = (0, import_node_path46.join)(dir, entry.name);
61611
+ const child2 = (0, import_node_path47.join)(dir, entry.name);
60247
61612
  if (entry.isSymbolicLink()) {
60248
61613
  let target = "";
60249
61614
  try {
60250
- target = normPath2((0, import_node_fs50.realpathSync)(child2));
61615
+ target = normPath2((0, import_node_fs51.realpathSync)(child2));
60251
61616
  } catch {
60252
61617
  target = "";
60253
61618
  }
@@ -60430,7 +61795,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
60430
61795
  return report;
60431
61796
  }
60432
61797
  const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
60433
- const pathExists = options.pathExists ?? import_node_fs50.existsSync;
61798
+ const pathExists = options.pathExists ?? import_node_fs51.existsSync;
60434
61799
  let afterWorktrees = [];
60435
61800
  try {
60436
61801
  afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
@@ -60570,7 +61935,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
60570
61935
  const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
60571
61936
  if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
60572
61937
  unlinkedReparsePoints.push(...preHelperGuard.unlinked);
60573
- if (pathExists((0, import_node_path46.join)(wtPath, "node_modules"))) {
61938
+ if (pathExists((0, import_node_path47.join)(wtPath, "node_modules"))) {
60574
61939
  const nmRemoved = await (options.removeRealNodeModules ?? removeWorktreeNodeModules)(wtPath);
60575
61940
  if (!nmRemoved.ok) {
60576
61941
  report.worktree = {
@@ -60706,18 +62071,18 @@ function renderPrMergeCleanupLines(cleanup) {
60706
62071
  // src/worktree-residue-sweep.ts
60707
62072
  var RESIDUE_LEDGER_FILE = "mmi-worktree-residue.json";
60708
62073
  function canonicalWorktreeRoot(primary) {
60709
- return (0, import_node_path47.join)((0, import_node_path47.dirname)(primary), "mmi-worktrees", (0, import_node_path47.basename)(primary));
62074
+ return (0, import_node_path48.join)((0, import_node_path48.dirname)(primary), "mmi-worktrees", (0, import_node_path48.basename)(primary));
60710
62075
  }
60711
62076
  function norm(p) {
60712
62077
  const s = p.replace(/\\/g, "/").replace(/\/+$/, "");
60713
62078
  return process.platform === "win32" ? s.toLowerCase() : s;
60714
62079
  }
60715
62080
  function residueLedgerPath(gitCommonDir) {
60716
- return (0, import_node_path47.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
62081
+ return (0, import_node_path48.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
60717
62082
  }
60718
62083
  function readPaths(file, deps) {
60719
62084
  try {
60720
- const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs51.readFileSync)(f, "utf8")))(file));
62085
+ const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs52.readFileSync)(f, "utf8")))(file));
60721
62086
  return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
60722
62087
  } catch {
60723
62088
  return [];
@@ -60726,8 +62091,8 @@ function readPaths(file, deps) {
60726
62091
  function writePaths(file, paths, deps) {
60727
62092
  try {
60728
62093
  const write = deps.writeLedger ?? ((f, t) => {
60729
- (0, import_node_fs51.mkdirSync)((0, import_node_path47.dirname)(f), { recursive: true });
60730
- (0, import_node_fs51.writeFileSync)(f, t, "utf8");
62094
+ (0, import_node_fs52.mkdirSync)((0, import_node_path48.dirname)(f), { recursive: true });
62095
+ (0, import_node_fs52.writeFileSync)(f, t, "utf8");
60731
62096
  });
60732
62097
  write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
60733
62098
  `);
@@ -60741,17 +62106,17 @@ function recordResidue(gitCommonDir, path2, deps = {}) {
60741
62106
  writePaths(file, paths, deps);
60742
62107
  }
60743
62108
  async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
60744
- const exists = deps.exists ?? import_node_fs51.existsSync;
60745
- const readDir = deps.readDir ?? ((p) => (0, import_node_fs51.readdirSync)(p));
62109
+ const exists = deps.exists ?? import_node_fs52.existsSync;
62110
+ const readDir = deps.readDir ?? ((p) => (0, import_node_fs52.readdirSync)(p));
60746
62111
  const isDir = deps.isDir ?? ((p) => {
60747
62112
  try {
60748
- return (0, import_node_fs51.statSync)(p).isDirectory();
62113
+ return (0, import_node_fs52.statSync)(p).isDirectory();
60749
62114
  } catch {
60750
62115
  return false;
60751
62116
  }
60752
62117
  });
60753
62118
  const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
60754
- const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs51.rmdirSync)(p));
62119
+ const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs52.rmdirSync)(p));
60755
62120
  const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
60756
62121
  const live = new Set(registered.map(norm));
60757
62122
  const primary = registered[0] ? norm(registered[0]) : void 0;
@@ -60786,7 +62151,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
60786
62151
  } catch {
60787
62152
  }
60788
62153
  for (const name of children) {
60789
- const child2 = (0, import_node_path47.join)(root, name);
62154
+ const child2 = (0, import_node_path48.join)(root, name);
60790
62155
  const key = norm(child2);
60791
62156
  if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
60792
62157
  let entries;
@@ -60829,10 +62194,10 @@ function argvWantsJson2() {
60829
62194
  return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
60830
62195
  }
60831
62196
  function hubRoot() {
60832
- const fromPkg = (0, import_node_path48.join)(__dirname, "..", "..");
62197
+ const fromPkg = (0, import_node_path49.join)(__dirname, "..", "..");
60833
62198
  const marker = "skills/bootstrap/seeds/manifest.json";
60834
- if ((0, import_node_fs52.existsSync)((0, import_node_path48.join)(fromPkg, marker))) return fromPkg;
60835
- if ((0, import_node_fs52.existsSync)((0, import_node_path48.join)(process.cwd(), marker))) return process.cwd();
62199
+ if ((0, import_node_fs53.existsSync)((0, import_node_path49.join)(fromPkg, marker))) return fromPkg;
62200
+ if ((0, import_node_fs53.existsSync)((0, import_node_path49.join)(process.cwd(), marker))) return process.cwd();
60836
62201
  return null;
60837
62202
  }
60838
62203
  function ciAuditDeps() {
@@ -60844,8 +62209,8 @@ function ciAuditDeps() {
60844
62209
  getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
60845
62210
  readSeedFile: (path2) => {
60846
62211
  if (!root) return null;
60847
- const fullPath = (0, import_node_path48.join)(root, path2);
60848
- return (0, import_node_fs52.existsSync)(fullPath) ? (0, import_node_fs52.readFileSync)(fullPath, "utf8") : null;
62212
+ const fullPath = (0, import_node_path49.join)(root, path2);
62213
+ return (0, import_node_fs53.existsSync)(fullPath) ? (0, import_node_fs53.readFileSync)(fullPath, "utf8") : null;
60849
62214
  }
60850
62215
  };
60851
62216
  }
@@ -61712,11 +63077,11 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
61712
63077
  }
61713
63078
  });
61714
63079
  async function listCiWorkflowPaths(cwd = process.cwd()) {
61715
- const wfDir = (0, import_node_path48.join)(cwd, ".github", "workflows");
61716
- if (!(0, import_node_fs52.existsSync)(wfDir)) return [];
61717
- return (0, import_node_fs52.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
63080
+ const wfDir = (0, import_node_path49.join)(cwd, ".github", "workflows");
63081
+ if (!(0, import_node_fs53.existsSync)(wfDir)) return [];
63082
+ return (0, import_node_fs53.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
61718
63083
  try {
61719
- return workflowReportsPrChecks((0, import_node_fs52.readFileSync)((0, import_node_path48.join)(wfDir, name), "utf8"));
63084
+ return workflowReportsPrChecks((0, import_node_fs53.readFileSync)((0, import_node_path49.join)(wfDir, name), "utf8"));
61720
63085
  } catch {
61721
63086
  return true;
61722
63087
  }
@@ -61921,7 +63286,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
61921
63286
  jsonParity(pr.command("review-verdict <number>").description("post an explicit review verdict comment: computes the PR diff patch-id and head sha, renders the v1 contract body, and posts it as a PR comment").requiredOption("--repo <owner/repo>", "target repo").requiredOption("--verdict <verdict>", `one of ${REVIEW_VERDICTS.join("|")}`).requiredOption("--scope <text>", "one line: what was reviewed").requiredOption("--risk <text>", "one line: the residual risk").option("--unverified <text>", "a claim the reviewer could not verify (repeatable)", (v, acc) => [...acc, v], []).requiredOption("--reviewer <id>", "reviewer seat or model id").option("--findings-file <path>", "Markdown findings appended after the JSON block")).action(async (number, o) => {
61922
63287
  if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
61923
63288
  const verdict = o.verdict;
61924
- const findings = o.findingsFile ? (0, import_node_fs52.readFileSync)(o.findingsFile, "utf8") : void 0;
63289
+ const findings = o.findingsFile ? (0, import_node_fs53.readFileSync)(o.findingsFile, "utf8") : void 0;
61925
63290
  let patch;
61926
63291
  let head;
61927
63292
  try {
@@ -62199,7 +63564,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
62199
63564
  const cleanupMerged = async (r) => {
62200
63565
  if (opts.preserveWorktree) return "cleanup skipped (--preserve-worktree)";
62201
63566
  const ran = await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(["pr", "merge", r.pr, "--repo", r.repo])], {
62202
- cwd: (0, import_node_fs52.existsSync)(cwd0) ? cwd0 : primaryRoot0,
63567
+ cwd: (0, import_node_fs53.existsSync)(cwd0) ? cwd0 : primaryRoot0,
62203
63568
  timeout: GH_MUTATION_TIMEOUT_MS * 3
62204
63569
  }).then((x) => ({ stdout: x.stdout, stderr: x.stderr }), (e) => ({ stdout: e.stdout ?? "", stderr: e.stderr ?? "" }));
62205
63570
  const json = ran.stdout.split("\n").reverse().find((l) => l.startsWith("{"));
@@ -62270,7 +63635,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
62270
63635
  const mergeSquashBody = squashBodyTextForMerge(
62271
63636
  closingGuardInput,
62272
63637
  method === "--squash",
62273
- o.squashBodyFile ? (0, import_node_fs52.readFileSync)(o.squashBodyFile, "utf8") : void 0
63638
+ o.squashBodyFile ? (0, import_node_fs53.readFileSync)(o.squashBodyFile, "utf8") : void 0
62274
63639
  );
62275
63640
  if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
62276
63641
  const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
@@ -62302,7 +63667,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
62302
63667
  const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
62303
63668
  let foreignCheckout;
62304
63669
  if (foreignCwd) {
62305
- const sibling = (0, import_node_path48.join)((0, import_node_path48.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
63670
+ const sibling = (0, import_node_path49.join)((0, import_node_path49.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
62306
63671
  const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
62307
63672
  if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
62308
63673
  const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
@@ -62421,7 +63786,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
62421
63786
  }
62422
63787
  if (!repoForPostCleanup) throw e;
62423
63788
  console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
62424
- const commitMessage = bodyFile ? (0, import_node_fs52.readFileSync)(bodyFile, "utf8") : void 0;
63789
+ const commitMessage = bodyFile ? (0, import_node_fs53.readFileSync)(bodyFile, "utf8") : void 0;
62425
63790
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
62426
63791
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
62427
63792
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -62536,7 +63901,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
62536
63901
  let residueSweep;
62537
63902
  if (!(foreignCwd && !foreignCheckout)) {
62538
63903
  try {
62539
- gitCommonDir = (0, import_node_path48.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
63904
+ gitCommonDir = (0, import_node_path49.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
62540
63905
  residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
62541
63906
  } catch {
62542
63907
  }
@@ -62554,7 +63919,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
62554
63919
  preserveWorktree: o.preserveWorktree,
62555
63920
  gcAcknowledged: o.gc,
62556
63921
  expectedHeadOid: headRefOid,
62557
- pathExists: (p) => (0, import_node_fs52.existsSync)(p),
63922
+ pathExists: (p) => (0, import_node_fs53.existsSync)(p),
62558
63923
  // #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
62559
63924
  // standing in is removed mid-cleanup, so a cwd-relative invocation fails with
62560
63925
  // 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
@@ -63820,7 +65185,7 @@ function rcRunCauseFromLog(log) {
63820
65185
  function memberAnnounceSummaryFile(repo, summaryFile, memberSummaryFiles) {
63821
65186
  return repo === SUITE_REPO ? summaryFile : memberSummaryFiles[repo];
63822
65187
  }
63823
- var suiteStateDir = (root) => (0, import_node_path50.join)(resolvePrimaryGitDir(root), "mmi-runtime", "suite");
65188
+ var suiteStateDir = (root) => (0, import_node_path51.join)(resolvePrimaryGitDir(root), "mmi-runtime", "suite");
63824
65189
  async function runSuiteMemberRcand(repo, previousRc, rcWasAbsent) {
63825
65190
  const deps = trainApplyDeps();
63826
65191
  deps.suiteCut = suiteCutContext(repo, SUITE_REPO);
@@ -63867,16 +65232,16 @@ async function rcandInCheckout(repo, deps, previousRc, rcWasAbsent) {
63867
65232
  }
63868
65233
  }
63869
65234
  function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr, memberSummaryFilesTr = {}) {
63870
- const recordPath = (0, import_node_path50.join)(suiteStateDir(root), "cut.json");
63871
- const base = (0, import_node_path50.join)(suiteStateDir(root), "checkouts");
65235
+ const recordPath = (0, import_node_path51.join)(suiteStateDir(root), "cut.json");
65236
+ const base = (0, import_node_path51.join)(suiteStateDir(root), "checkouts");
63872
65237
  const checkout = async (repo) => {
63873
65238
  if (repo === SUITE_REPO) return root;
63874
- const target = (0, import_node_path50.join)(base, repo.split("/")[1]);
63875
- if (!(0, import_node_fs53.existsSync)(target)) {
63876
- (0, import_node_fs53.mkdirSync)(base, { recursive: true });
65239
+ const target = (0, import_node_path51.join)(base, repo.split("/")[1]);
65240
+ if (!(0, import_node_fs54.existsSync)(target)) {
65241
+ (0, import_node_fs54.mkdirSync)(base, { recursive: true });
63877
65242
  await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
63878
65243
  }
63879
- if (!(0, import_node_fs53.existsSync)((0, import_node_path50.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
65244
+ if (!(0, import_node_fs54.existsSync)((0, import_node_path51.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
63880
65245
  const origin = await exec("git", ["remote", "get-url", "origin"], target);
63881
65246
  if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
63882
65247
  throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
@@ -63940,7 +65305,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
63940
65305
  if (repo === SUITE_REPO) return inTrain(repo, (deps) => rcandInCheckout(repo, deps, previousRc, rcWasAbsent));
63941
65306
  const target = await checkout(repo);
63942
65307
  return new Promise((resolveResult, reject2) => {
63943
- const child2 = (0, import_node_child_process19.fork)((0, import_node_path49.resolve)(process.argv[1]), ["devops", "suite", "member-rcand-internal", repo, previousRc ?? "", ...rcWasAbsent ? ["--rc-was-absent"] : []], {
65308
+ const child2 = (0, import_node_child_process19.fork)((0, import_node_path50.resolve)(process.argv[1]), ["devops", "suite", "member-rcand-internal", repo, previousRc ?? "", ...rcWasAbsent ? ["--rc-was-absent"] : []], {
63944
65309
  cwd: target,
63945
65310
  stdio: ["ignore", "inherit", "inherit", "ipc"],
63946
65311
  execArgv: []
@@ -64183,9 +65548,9 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
64183
65548
  const body = await gh(["release", "view", leadTag, "--repo", SUITE_REPO, "--json", "body", "--jq", ".body"]);
64184
65549
  if (parseSuiteMapping(body)) return;
64185
65550
  const dir = suiteStateDir(root);
64186
- (0, import_node_fs53.mkdirSync)(dir, { recursive: true });
64187
- const file = (0, import_node_path50.join)(dir, `notes-${leadTag}.md`);
64188
- (0, import_node_fs53.writeFileSync)(file, `${body}
65551
+ (0, import_node_fs54.mkdirSync)(dir, { recursive: true });
65552
+ const file = (0, import_node_path51.join)(dir, `notes-${leadTag}.md`);
65553
+ (0, import_node_fs54.writeFileSync)(file, `${body}
64189
65554
 
64190
65555
  ${suiteMappingBlock(leadTag, mapping)}`);
64191
65556
  await gh(["release", "edit", leadTag, "--repo", SUITE_REPO, "--notes-file", file]);
@@ -64226,7 +65591,7 @@ ${suiteMappingBlock(leadTag, mapping)}`);
64226
65591
 
64227
65592
  // src/suite-commands.ts
64228
65593
  init_cli_shared();
64229
- var import_node_fs54 = require("node:fs");
65594
+ var import_node_fs55 = require("node:fs");
64230
65595
  function parseMemberSummaryFiles(entries) {
64231
65596
  const memberSummaryFiles = {};
64232
65597
  for (const entry of entries) {
@@ -64320,7 +65685,7 @@ function registerSuiteCommands(program3) {
64320
65685
  try {
64321
65686
  const io = suiteTrainIo(root);
64322
65687
  retired = await retireFailedLeadRcLedger({ ledgerPath: releaseLedgerPath(root), rcHead: () => io.head(SUITE_REPO, "rc").then((h) => h ?? void 0), markAbandoned: io.markAbandoned }, o.ownerGo === true);
64323
- if (retired && !(0, import_node_fs54.existsSync)(io.recordPath)) {
65688
+ if (retired && !(0, import_node_fs55.existsSync)(io.recordPath)) {
64324
65689
  console.log(o.json ? JSON.stringify({ retiredLeadRc: retired }, null, 2) : `suite abandon: retired the lead's failed, never-promoted rc candidate ${retired}; marked abandoned on origin (#7854)`);
64325
65690
  return;
64326
65691
  }
@@ -64560,9 +65925,9 @@ function registerJevCommands(program3) {
64560
65925
 
64561
65926
  // src/packages-adopt-export.ts
64562
65927
  var import_node_child_process20 = require("node:child_process");
64563
- var import_node_fs55 = require("node:fs");
65928
+ var import_node_fs56 = require("node:fs");
64564
65929
  var import_node_os25 = require("node:os");
64565
- var import_node_path51 = require("node:path");
65930
+ var import_node_path52 = require("node:path");
64566
65931
  var import_node_util6 = require("node:util");
64567
65932
  init_cli_shared();
64568
65933
 
@@ -65182,38 +66547,38 @@ function expectedFromRun(run, opts) {
65182
66547
  }
65183
66548
  async function adoptExport(opts, deps) {
65184
66549
  if (!/^sha256:[0-9a-f]{64}$/.test(opts.digest)) throw new Error("--digest must be sha256:<64 hex>, from the export run summary");
65185
- const packagePath = (0, import_node_path51.join)(opts.dir, "package.json");
65186
- const lockPath = (0, import_node_path51.join)(opts.dir, "package-lock.json");
65187
- const packageText = (0, import_node_fs55.readFileSync)(packagePath, "utf8");
65188
- const lockText = (0, import_node_fs55.readFileSync)(lockPath, "utf8");
65189
- const work = (0, import_node_fs55.mkdtempSync)((0, import_node_path51.join)((0, import_node_os25.tmpdir)(), "mmi-adopt-export-"));
66550
+ const packagePath = (0, import_node_path52.join)(opts.dir, "package.json");
66551
+ const lockPath = (0, import_node_path52.join)(opts.dir, "package-lock.json");
66552
+ const packageText = (0, import_node_fs56.readFileSync)(packagePath, "utf8");
66553
+ const lockText = (0, import_node_fs56.readFileSync)(lockPath, "utf8");
66554
+ const work = (0, import_node_fs56.mkdtempSync)((0, import_node_path52.join)((0, import_node_os25.tmpdir)(), "mmi-adopt-export-"));
65190
66555
  try {
65191
66556
  const run = JSON.parse((await deps.ghApi(`repos/${EXPORT_REPO}/actions/runs/${opts.run}`)).toString("utf8"));
65192
- (0, import_node_fs55.writeFileSync)((0, import_node_path51.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
65193
- (0, import_node_fs55.writeFileSync)((0, import_node_path51.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
65194
- (0, import_node_fs55.writeFileSync)((0, import_node_path51.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
65195
- const script = (0, import_node_path51.join)(work, "package_export.py");
65196
- (0, import_node_fs55.writeFileSync)(script, package_export_default);
66557
+ (0, import_node_fs56.writeFileSync)((0, import_node_path52.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
66558
+ (0, import_node_fs56.writeFileSync)((0, import_node_path52.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
66559
+ (0, import_node_fs56.writeFileSync)((0, import_node_path52.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
66560
+ const script = (0, import_node_path52.join)(work, "package_export.py");
66561
+ (0, import_node_fs56.writeFileSync)(script, package_export_default);
65197
66562
  await deps.python(script, ["verify", work, opts.ds, opts.screens]);
65198
- const verified = (0, import_node_path51.join)(work, "verified");
65199
- const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0, import_node_fs55.readFileSync)((0, import_node_path51.join)(verified, `${short3}.metadata.json`), "utf8"))]));
66563
+ const verified = (0, import_node_path52.join)(work, "verified");
66564
+ const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0, import_node_fs56.readFileSync)((0, import_node_path52.join)(verified, `${short3}.metadata.json`), "utf8"))]));
65200
66565
  const plan = planAdoption(packageText, lockText, metadata);
65201
66566
  for (const { name } of plan.result.adopted) {
65202
66567
  const short3 = PACKAGES.find((p) => p.name === name).short;
65203
- await deps.npm(["cache", "add", (0, import_node_path51.join)(verified, `${short3}.tgz`)], opts.dir);
66568
+ await deps.npm(["cache", "add", (0, import_node_path52.join)(verified, `${short3}.tgz`)], opts.dir);
65204
66569
  }
65205
- (0, import_node_fs55.writeFileSync)(packagePath, plan.packageText);
65206
- (0, import_node_fs55.writeFileSync)(lockPath, plan.lockText);
66570
+ (0, import_node_fs56.writeFileSync)(packagePath, plan.packageText);
66571
+ (0, import_node_fs56.writeFileSync)(lockPath, plan.lockText);
65207
66572
  try {
65208
66573
  await deps.npm(["ci", "--offline", "--ignore-scripts", "--no-audit", "--no-fund"], opts.dir);
65209
66574
  } catch (error) {
65210
- (0, import_node_fs55.writeFileSync)(packagePath, packageText);
65211
- (0, import_node_fs55.writeFileSync)(lockPath, lockText);
66575
+ (0, import_node_fs56.writeFileSync)(packagePath, packageText);
66576
+ (0, import_node_fs56.writeFileSync)(lockPath, lockText);
65212
66577
  throw new Error(`npm ci --offline failed, package.json and package-lock.json restored: ${error.message}`);
65213
66578
  }
65214
66579
  return plan.result;
65215
66580
  } finally {
65216
- (0, import_node_fs55.rmSync)(work, { recursive: true, force: true });
66581
+ (0, import_node_fs56.rmSync)(work, { recursive: true, force: true });
65217
66582
  }
65218
66583
  }
65219
66584
  var execFileBuffer = (0, import_node_util6.promisify)(import_node_child_process20.execFile);
@@ -65317,12 +66682,12 @@ function registerPackagesCommands(program3) {
65317
66682
  init_cli_shared();
65318
66683
 
65319
66684
  // src/wave-status.ts
65320
- var import_node_fs56 = require("node:fs");
66685
+ var import_node_fs57 = require("node:fs");
65321
66686
  function observeStage(worktreePath) {
65322
66687
  const statePath = stageStatePath(worktreePath);
65323
66688
  let raw;
65324
66689
  try {
65325
- raw = (0, import_node_fs56.readFileSync)(statePath, "utf8");
66690
+ raw = (0, import_node_fs57.readFileSync)(statePath, "utf8");
65326
66691
  } catch (error) {
65327
66692
  if (error.code === "ENOENT") return { state: "ok", value: { running: false } };
65328
66693
  return { state: "unavailable", reason: "stage state could not be read" };
@@ -65389,9 +66754,9 @@ async function collectWaveStatus(deps) {
65389
66754
  // src/discovery-commands.ts
65390
66755
  init_github_client();
65391
66756
  init_cli_shared();
65392
- var import_node_fs57 = require("node:fs");
66757
+ var import_node_fs58 = require("node:fs");
65393
66758
  var import_node_os26 = require("node:os");
65394
- var import_node_path52 = require("node:path");
66759
+ var import_node_path53 = require("node:path");
65395
66760
  var GC_GH_TIMEOUT_MS3 = 2e4;
65396
66761
  var defaultStatusDeps = {
65397
66762
  repo: resolveRepo,
@@ -65612,8 +66977,8 @@ async function collectOnboardStatus(opts = {}) {
65612
66977
  }
65613
66978
  const home = (0, import_node_os26.homedir)();
65614
66979
  const plugin = onboardPluginGate({
65615
- readKnown: () => readFileSyncSafe((0, import_node_path52.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs57.readFileSync),
65616
- readSettings: () => readFileSyncSafe((0, import_node_path52.join)(home, ".claude", "settings.json"), import_node_fs57.readFileSync)
66980
+ readKnown: () => readFileSyncSafe((0, import_node_path53.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs58.readFileSync),
66981
+ readSettings: () => readFileSyncSafe((0, import_node_path53.join)(home, ".claude", "settings.json"), import_node_fs58.readFileSync)
65617
66982
  });
65618
66983
  return {
65619
66984
  guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
@@ -68744,8 +70109,8 @@ function registerSessionReport(program3) {
68744
70109
  }
68745
70110
 
68746
70111
  // src/stage-commands.ts
68747
- var import_node_fs58 = require("node:fs");
68748
- var import_node_path53 = require("node:path");
70112
+ var import_node_fs59 = require("node:fs");
70113
+ var import_node_path54 = require("node:path");
68749
70114
  init_cli_shared();
68750
70115
  init_clean_exit();
68751
70116
 
@@ -68993,8 +70358,8 @@ function registerStageCommands(program3) {
68993
70358
  const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
68994
70359
  const resolution = decideStage({
68995
70360
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
68996
- hasCompose: (0, import_node_fs58.existsSync)((0, import_node_path53.join)(process.cwd(), "docker-compose.yml")),
68997
- hasEnvExample: (0, import_node_fs58.existsSync)((0, import_node_path53.join)(process.cwd(), ".env.example")),
70361
+ hasCompose: (0, import_node_fs59.existsSync)((0, import_node_path54.join)(process.cwd(), "docker-compose.yml")),
70362
+ hasEnvExample: (0, import_node_fs59.existsSync)((0, import_node_path54.join)(process.cwd(), ".env.example")),
68998
70363
  repo
68999
70364
  });
69000
70365
  if (resolution.source === "derived" && resolution.derived) {
@@ -69286,25 +70651,25 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
69286
70651
  }
69287
70652
 
69288
70653
  // src/tenant-artifact.ts
69289
- var import_node_crypto20 = require("node:crypto");
69290
- var import_node_fs59 = require("node:fs");
70654
+ var import_node_crypto23 = require("node:crypto");
70655
+ var import_node_fs60 = require("node:fs");
69291
70656
  var import_promises11 = require("node:fs/promises");
69292
- var import_node_path54 = require("node:path");
70657
+ var import_node_path55 = require("node:path");
69293
70658
  var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
69294
70659
  var MAX_BYTES2 = 5 * 1024 * 1024 * 1024;
69295
70660
  async function sha256File(path2) {
69296
- const hash = (0, import_node_crypto20.createHash)("sha256");
69297
- for await (const chunk of (0, import_node_fs59.createReadStream)(path2)) hash.update(chunk);
70661
+ const hash = (0, import_node_crypto23.createHash)("sha256");
70662
+ for await (const chunk of (0, import_node_fs60.createReadStream)(path2)) hash.update(chunk);
69298
70663
  return hash.digest("hex");
69299
70664
  }
69300
70665
  async function putTenantArtifact(repo, stage, inputPath, deps) {
69301
70666
  if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
69302
- const path2 = (0, import_node_path54.resolve)(inputPath);
70667
+ const path2 = (0, import_node_path55.resolve)(inputPath);
69303
70668
  const info = await (0, import_promises11.stat)(path2);
69304
70669
  if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
69305
70670
  if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES2) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES2} bytes`);
69306
- const sha2563 = await sha256File(path2);
69307
- const prepared = await tenantArtifactUpload({ repo, stage, size: info.size, sha256: sha2563 }, deps);
70671
+ const sha2564 = await sha256File(path2);
70672
+ const prepared = await tenantArtifactUpload({ repo, stage, size: info.size, sha256: sha2564 }, deps);
69308
70673
  if (!prepared.ok) {
69309
70674
  const detail = prepared.body?.error ?? prepared.error ?? `HTTP ${prepared.status}`;
69310
70675
  throw new Error(`tenant artifact put: ${detail}`);
@@ -69317,7 +70682,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
69317
70682
  return [key, value];
69318
70683
  }));
69319
70684
  headers["content-length"] = String(info.size);
69320
- const stream = (0, import_node_fs59.createReadStream)(path2);
70685
+ const stream = (0, import_node_fs60.createReadStream)(path2);
69321
70686
  let uploaded;
69322
70687
  try {
69323
70688
  uploaded = await fetch(body.uploadUrl, {
@@ -69332,8 +70697,8 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
69332
70697
  throw error;
69333
70698
  }
69334
70699
  if (!uploaded.ok) throw new Error(`tenant artifact put: object upload failed (HTTP ${uploaded.status})`);
69335
- if (body.size !== info.size || body.sha256 !== sha2563 || typeof body.expiresAt !== "string" || !Number.isFinite(Date.parse(body.expiresAt))) throw new Error("tenant artifact put: Hub returned inconsistent artifact metadata");
69336
- return { artifactId: body.artifactId, repo, stage, size: info.size, sha256: sha2563, expiresAt: body.expiresAt };
70700
+ if (body.size !== info.size || body.sha256 !== sha2564 || typeof body.expiresAt !== "string" || !Number.isFinite(Date.parse(body.expiresAt))) throw new Error("tenant artifact put: Hub returned inconsistent artifact metadata");
70701
+ return { artifactId: body.artifactId, repo, stage, size: info.size, sha256: sha2564, expiresAt: body.expiresAt };
69337
70702
  }
69338
70703
 
69339
70704
  // src/tenant-logs.ts
@@ -69671,7 +71036,7 @@ function renderVerifySecrets(body) {
69671
71036
  }
69672
71037
 
69673
71038
  // src/command-register-developer.ts
69674
- var import_node_fs64 = require("node:fs");
71039
+ var import_node_fs65 = require("node:fs");
69675
71040
  init_clean_exit();
69676
71041
  init_cli_shared();
69677
71042
  init_error_codes();
@@ -69704,12 +71069,12 @@ async function resolveWhoami(deps) {
69704
71069
  }
69705
71070
 
69706
71071
  // src/command-register-developer.ts
69707
- var import_node_path61 = require("node:path");
71072
+ var import_node_path62 = require("node:path");
69708
71073
 
69709
71074
  // src/doc-refs-command-source.ts
69710
71075
  var import_node_child_process21 = require("node:child_process");
69711
- var import_node_fs60 = require("node:fs");
69712
- var import_node_path55 = require("node:path");
71076
+ var import_node_fs61 = require("node:fs");
71077
+ var import_node_path56 = require("node:path");
69713
71078
  var BUNDLE_MANIFEST_TIMEOUT_MS = 12e4;
69714
71079
  var BUNDLE_MANIFEST_MAX_BUFFER = 64 * 1024 * 1024;
69715
71080
  var flagsOf = (options) => new Set((options ?? []).flatMap((option) => String(option?.flags ?? "").match(/--[\w-]+/g) ?? []));
@@ -69730,7 +71095,7 @@ function inventoryFromManifest(manifest, source) {
69730
71095
  function readBundleManifest(bundle, exec3 = import_node_child_process21.execFileSync) {
69731
71096
  try {
69732
71097
  const text = exec3(process.execPath, [bundle, "commands", "--all", "--json"], {
69733
- cwd: (0, import_node_path55.dirname)(bundle),
71098
+ cwd: (0, import_node_path56.dirname)(bundle),
69734
71099
  encoding: "utf8",
69735
71100
  windowsHide: true,
69736
71101
  timeout: BUNDLE_MANIFEST_TIMEOUT_MS,
@@ -69746,19 +71111,19 @@ function readBundleManifest(bundle, exec3 = import_node_child_process21.execFile
69746
71111
  function isRunningBundle(bundle, argv1 = process.argv[1], platform2 = process.platform) {
69747
71112
  if (!argv1) return false;
69748
71113
  const norm2 = (path2) => {
69749
- const absolute = (0, import_node_path55.resolve)(path2);
71114
+ const absolute = (0, import_node_path56.resolve)(path2);
69750
71115
  return platform2 === "win32" ? absolute.toLowerCase() : absolute;
69751
71116
  };
69752
71117
  const running = norm2(argv1);
69753
- if (!["main.cjs", "index.cjs"].includes((0, import_node_path55.basename)(running))) return false;
69754
- return (0, import_node_path55.dirname)(running) === (0, import_node_path55.dirname)(norm2(bundle));
71118
+ if (!["main.cjs", "index.cjs"].includes((0, import_node_path56.basename)(running))) return false;
71119
+ return (0, import_node_path56.dirname)(running) === (0, import_node_path56.dirname)(norm2(bundle));
69755
71120
  }
69756
71121
  function resolveCommandInventory(deps) {
69757
- const exists = deps.exists ?? import_node_fs60.existsSync;
71122
+ const exists = deps.exists ?? import_node_fs61.existsSync;
69758
71123
  const read = deps.read ?? ((bundle) => readBundleManifest(bundle));
69759
71124
  const warnings = [];
69760
71125
  if (deps.from) {
69761
- const bundle = (0, import_node_path55.isAbsolute)(deps.from) ? deps.from : (0, import_node_path55.resolve)(deps.root, deps.from);
71126
+ const bundle = (0, import_node_path56.isAbsolute)(deps.from) ? deps.from : (0, import_node_path56.resolve)(deps.root, deps.from);
69762
71127
  if (!exists(bundle)) {
69763
71128
  throw new Error(`docs refs --commands-from: ${bundle} does not exist \u2014 name the CLI bundle to read commands from (usually cli/dist/main.cjs)`);
69764
71129
  }
@@ -69768,7 +71133,7 @@ function resolveCommandInventory(deps) {
69768
71133
  }
69769
71134
  return { inventory: inventory2, warnings };
69770
71135
  }
69771
- const treeBundle = (0, import_node_path55.join)(deps.root, "cli", "dist", "main.cjs");
71136
+ const treeBundle = (0, import_node_path56.join)(deps.root, "cli", "dist", "main.cjs");
69772
71137
  if (!exists(treeBundle) || isRunningBundle(treeBundle, deps.argv1, deps.platform)) {
69773
71138
  return { inventory: deps.inProcess(), warnings };
69774
71139
  }
@@ -69885,28 +71250,28 @@ async function executeWaveLand(plan, deps) {
69885
71250
 
69886
71251
  // src/dist-drift.ts
69887
71252
  var import_node_child_process22 = require("node:child_process");
69888
- var import_node_crypto22 = require("node:crypto");
69889
- var import_node_fs62 = require("node:fs");
71253
+ var import_node_crypto25 = require("node:crypto");
71254
+ var import_node_fs63 = require("node:fs");
69890
71255
  var import_node_os27 = require("node:os");
69891
- var import_node_path57 = require("node:path");
71256
+ var import_node_path58 = require("node:path");
69892
71257
 
69893
71258
  // ../scripts/distribution-digest.mjs
69894
- var import_node_crypto21 = require("node:crypto");
69895
- var import_node_fs61 = require("node:fs");
69896
- var import_node_path56 = require("node:path");
71259
+ var import_node_crypto24 = require("node:crypto");
71260
+ var import_node_fs62 = require("node:fs");
71261
+ var import_node_path57 = require("node:path");
69897
71262
  var slash = (value) => value.replaceAll("\\", "/");
69898
71263
  function repoPath(root, declaredPath, label) {
69899
- const absoluteRoot = (0, import_node_path56.resolve)(root);
69900
- const target = (0, import_node_path56.resolve)(root, declaredPath);
69901
- if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path56.sep}`)) {
71264
+ const absoluteRoot = (0, import_node_path57.resolve)(root);
71265
+ const target = (0, import_node_path57.resolve)(root, declaredPath);
71266
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path57.sep}`)) {
69902
71267
  throw new Error(`${label} ${declaredPath} escapes the repository root`);
69903
71268
  }
69904
71269
  return target;
69905
71270
  }
69906
71271
  function digestFiles(files) {
69907
- const hash = (0, import_node_crypto21.createHash)("sha256");
71272
+ const hash = (0, import_node_crypto24.createHash)("sha256");
69908
71273
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
69909
- const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs61.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs61.readFileSync)(file.absolute);
71274
+ const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs62.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs62.readFileSync)(file.absolute);
69910
71275
  hash.update(file.relative, "utf8");
69911
71276
  hash.update("\0");
69912
71277
  hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
@@ -69921,8 +71286,8 @@ function digestFiles(files) {
69921
71286
  function digestPackedFiles(packageRoot, packedFiles) {
69922
71287
  return digestFiles(packedFiles.map((path2) => {
69923
71288
  const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
69924
- if (!(0, import_node_fs61.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
69925
- return { absolute, relative: slash(path2), stat: (0, import_node_fs61.lstatSync)(absolute) };
71289
+ if (!(0, import_node_fs62.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
71290
+ return { absolute, relative: slash(path2), stat: (0, import_node_fs62.lstatSync)(absolute) };
69926
71291
  }));
69927
71292
  }
69928
71293
 
@@ -69935,10 +71300,10 @@ var DIST_ARTIFACTS = [
69935
71300
  ];
69936
71301
  var BOM_DIST_TREE_ID = "mmi-cli-dist";
69937
71302
  var ABSENT = "absent";
69938
- var sha2562 = (bytes) => `sha256:${(0, import_node_crypto22.createHash)("sha256").update(bytes).digest("hex")}`;
71303
+ var sha2563 = (bytes) => `sha256:${(0, import_node_crypto25.createHash)("sha256").update(bytes).digest("hex")}`;
69939
71304
  function artifactDrift(path2, committedBytes, rebuiltBytes) {
69940
- const committed = committedBytes ? sha2562(committedBytes) : ABSENT;
69941
- const rebuiltExpected = rebuiltBytes ? sha2562(rebuiltBytes) : ABSENT;
71305
+ const committed = committedBytes ? sha2563(committedBytes) : ABSENT;
71306
+ const rebuiltExpected = rebuiltBytes ? sha2563(rebuiltBytes) : ABSENT;
69942
71307
  return { path: path2, status: committed === rebuiltExpected ? "current" : "stale", committed, rebuiltExpected };
69943
71308
  }
69944
71309
  function distTreeIdentity(labels, rebuilt, committed, digest) {
@@ -70007,13 +71372,13 @@ function renderDistDriftReceipt(receipt) {
70007
71372
  return lines2;
70008
71373
  }
70009
71374
  function readOrNull(path2) {
70010
- return (0, import_node_fs62.existsSync)(path2) ? (0, import_node_fs62.readFileSync)(path2) : null;
71375
+ return (0, import_node_fs63.existsSync)(path2) ? (0, import_node_fs63.readFileSync)(path2) : null;
70011
71376
  }
70012
71377
  function walkFiles(root) {
70013
71378
  const files = [];
70014
71379
  const walk2 = (directory) => {
70015
- for (const entry of (0, import_node_fs62.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
70016
- const child2 = (0, import_node_path57.join)(directory, entry.name);
71380
+ for (const entry of (0, import_node_fs63.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
71381
+ const child2 = (0, import_node_path58.join)(directory, entry.name);
70017
71382
  if (entry.isDirectory()) walk2(child2);
70018
71383
  else files.push(child2);
70019
71384
  }
@@ -70023,10 +71388,10 @@ function walkFiles(root) {
70023
71388
  }
70024
71389
  function bomPathFor(root) {
70025
71390
  try {
70026
- const registry2 = JSON.parse((0, import_node_fs62.readFileSync)((0, import_node_path57.join)(root, "surfaces.json"), "utf8"));
70027
- return (0, import_node_path57.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
71391
+ const registry2 = JSON.parse((0, import_node_fs63.readFileSync)((0, import_node_path58.join)(root, "surfaces.json"), "utf8"));
71392
+ return (0, import_node_path58.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
70028
71393
  } catch {
70029
- return (0, import_node_path57.join)(root, "distribution-bom.json");
71394
+ return (0, import_node_path58.join)(root, "distribution-bom.json");
70030
71395
  }
70031
71396
  }
70032
71397
  function rebuildTo(packageRoot, outDir) {
@@ -70039,40 +71404,40 @@ function rebuildTo(packageRoot, outDir) {
70039
71404
  });
70040
71405
  }
70041
71406
  function runDistStatus(root) {
70042
- const stage = (0, import_node_fs62.mkdtempSync)((0, import_node_path57.join)((0, import_node_os27.tmpdir)(), "mmi-dist-drift-"));
71407
+ const stage = (0, import_node_fs63.mkdtempSync)((0, import_node_path58.join)((0, import_node_os27.tmpdir)(), "mmi-dist-drift-"));
70043
71408
  let overlayCount = 0;
70044
71409
  try {
70045
- const cliOut = (0, import_node_path57.join)(stage, "cli-dist");
70046
- const hubOut = (0, import_node_path57.join)(stage, "hub-dist");
70047
- rebuildTo((0, import_node_path57.join)(root, "cli"), cliOut);
70048
- rebuildTo((0, import_node_path57.join)(root, "updater"), hubOut);
71410
+ const cliOut = (0, import_node_path58.join)(stage, "cli-dist");
71411
+ const hubOut = (0, import_node_path58.join)(stage, "hub-dist");
71412
+ rebuildTo((0, import_node_path58.join)(root, "cli"), cliOut);
71413
+ rebuildTo((0, import_node_path58.join)(root, "updater"), hubOut);
70049
71414
  const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
70050
71415
  const rebuilt = (path2) => {
70051
71416
  const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
70052
- return spec ? readOrNull((0, import_node_path57.join)(outDirFor(spec.packageDir), spec.output)) : null;
71417
+ return spec ? readOrNull((0, import_node_path58.join)(outDirFor(spec.packageDir), spec.output)) : null;
70053
71418
  };
70054
- const committed = (path2) => readOrNull((0, import_node_path57.join)(root, path2));
70055
- const tree = (path2) => readOrNull((0, import_node_path57.join)(root, path2));
70056
- const distRoot = (0, import_node_path57.join)(root, "cli", "dist");
70057
- const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path57.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
70058
- const bom = JSON.parse((0, import_node_fs62.readFileSync)(bomPathFor(root), "utf8"));
71419
+ const committed = (path2) => readOrNull((0, import_node_path58.join)(root, path2));
71420
+ const tree = (path2) => readOrNull((0, import_node_path58.join)(root, path2));
71421
+ const distRoot = (0, import_node_path58.join)(root, "cli", "dist");
71422
+ const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path58.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
71423
+ const bom = JSON.parse((0, import_node_fs63.readFileSync)(bomPathFor(root), "utf8"));
70059
71424
  const digest = (entries) => {
70060
- const overlay = (0, import_node_path57.join)(stage, `overlay-${overlayCount++}`);
71425
+ const overlay = (0, import_node_path58.join)(stage, `overlay-${overlayCount++}`);
70061
71426
  for (const entry of entries) {
70062
- const target = (0, import_node_path57.join)(overlay, entry.path);
70063
- (0, import_node_fs62.mkdirSync)((0, import_node_path57.dirname)(target), { recursive: true });
70064
- (0, import_node_fs62.writeFileSync)(target, entry.bytes);
71427
+ const target = (0, import_node_path58.join)(overlay, entry.path);
71428
+ (0, import_node_fs63.mkdirSync)((0, import_node_path58.dirname)(target), { recursive: true });
71429
+ (0, import_node_fs63.writeFileSync)(target, entry.bytes);
70065
71430
  }
70066
71431
  return digestPackedFiles(overlay, entries.map((entry) => entry.path));
70067
71432
  };
70068
71433
  return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
70069
71434
  } finally {
70070
- (0, import_node_fs62.rmSync)(stage, { recursive: true, force: true });
71435
+ (0, import_node_fs63.rmSync)(stage, { recursive: true, force: true });
70071
71436
  }
70072
71437
  }
70073
71438
 
70074
71439
  // src/doc-refs-live.ts
70075
- var import_node_path58 = require("node:path");
71440
+ var import_node_path59 = require("node:path");
70076
71441
  var concrete = (value) => !!value && !/[<>{}$]/.test(value);
70077
71442
  var liveOption = (tokens2, flag) => {
70078
71443
  const index = tokens2.indexOf(flag);
@@ -70145,7 +71510,7 @@ async function runLiveDocRefs(root, deps, live) {
70145
71510
  const offline = runDocRefs(root, { ...deps, runbooksOnly: true });
70146
71511
  const read = deps.readFile ?? readFileOrNull;
70147
71512
  const docs = Object.fromEntries((deps.listDocs ?? defaultListDocs)(root).flatMap((doc) => {
70148
- const body = read((0, import_node_path58.join)(root, doc));
71513
+ const body = read((0, import_node_path59.join)(root, doc));
70149
71514
  return body !== null && isRunbook(doc, body) && !deps.isIgnored?.([doc]).has(doc) ? [[doc, body]] : [];
70150
71515
  }));
70151
71516
  const remote = await checkRunbookLive(docs, live);
@@ -70356,7 +71721,7 @@ async function diagnoseHub(deps) {
70356
71721
 
70357
71722
  // src/schedules-lift-command.ts
70358
71723
  var import_promises12 = require("node:fs/promises");
70359
- var import_node_path59 = require("node:path");
71724
+ var import_node_path60 = require("node:path");
70360
71725
  init_clean_exit();
70361
71726
  init_cli_shared();
70362
71727
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
@@ -70385,7 +71750,7 @@ async function readWorkflowFiles(dir) {
70385
71750
  const files = [];
70386
71751
  for (const name of names.sort()) {
70387
71752
  if (!/\.ya?ml$/.test(name)) continue;
70388
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0, import_node_path59.join)(dir, name), "utf8") });
71753
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0, import_node_path60.join)(dir, name), "utf8") });
70389
71754
  }
70390
71755
  return files;
70391
71756
  }
@@ -70468,8 +71833,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
70468
71833
 
70469
71834
  // src/spawn-policy-core.ts
70470
71835
  var import_node_child_process24 = require("node:child_process");
70471
- var import_node_fs63 = require("node:fs");
70472
- var import_node_path60 = require("node:path");
71836
+ var import_node_fs64 = require("node:fs");
71837
+ var import_node_path61 = require("node:path");
70473
71838
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
70474
71839
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
70475
71840
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -70504,10 +71869,10 @@ function findViolationsInSource(raw) {
70504
71869
  if (imported.size === 0) return [];
70505
71870
  const rawLines = raw.split("\n");
70506
71871
  const src = stripComments(raw);
70507
- const call = new RegExp(CALL_SOURCE, "g");
71872
+ const call3 = new RegExp(CALL_SOURCE, "g");
70508
71873
  const found = [];
70509
71874
  let m;
70510
- while (m = call.exec(src)) {
71875
+ while (m = call3.exec(src)) {
70511
71876
  const callee = m[2];
70512
71877
  if (!imported.has(callee)) continue;
70513
71878
  const open2 = m.index + m[0].length - 1;
@@ -70555,7 +71920,7 @@ function runSpawnPolicy(root) {
70555
71920
  for (const file of files) {
70556
71921
  let raw;
70557
71922
  try {
70558
- raw = (0, import_node_fs63.readFileSync)((0, import_node_path60.join)(root, file), "utf8");
71923
+ raw = (0, import_node_fs64.readFileSync)((0, import_node_path61.join)(root, file), "utf8");
70559
71924
  } catch {
70560
71925
  continue;
70561
71926
  }
@@ -70575,19 +71940,19 @@ function runSpawnPolicy(root) {
70575
71940
  function registerDeveloperCommands(program3) {
70576
71941
  const rules = program3.command("rules").description("org-managed .gitignore delivery");
70577
71942
  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) => {
70578
- const path2 = (0, import_node_path61.join)(process.cwd(), ".gitignore");
70579
- const current = (0, import_node_fs64.existsSync)(path2) ? (0, import_node_fs64.readFileSync)(path2, "utf8") : null;
71943
+ const path2 = (0, import_node_path62.join)(process.cwd(), ".gitignore");
71944
+ const current = (0, import_node_fs65.existsSync)(path2) ? (0, import_node_fs65.readFileSync)(path2, "utf8") : null;
70580
71945
  const plan = planManagedGitignore(current);
70581
71946
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
70582
71947
  if (opts.json) {
70583
- if (opts.write && plan.changed) (0, import_node_fs64.writeFileSync)(path2, plan.content, "utf8");
71948
+ if (opts.write && plan.changed) (0, import_node_fs65.writeFileSync)(path2, plan.content, "utf8");
70584
71949
  console.log(JSON.stringify(plan, null, 2));
70585
71950
  if (!opts.write && plan.changed) process.exitCode = 1;
70586
71951
  return;
70587
71952
  }
70588
71953
  if (opts.write) {
70589
71954
  if (plan.changed) {
70590
- (0, import_node_fs64.writeFileSync)(path2, plan.content, "utf8");
71955
+ (0, import_node_fs65.writeFileSync)(path2, plan.content, "utf8");
70591
71956
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
70592
71957
  } else {
70593
71958
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -70779,8 +72144,8 @@ function registerDeveloperCommands(program3) {
70779
72144
  return { commandPaths: new Set(manifest.index.map((entry) => entry.path)), commandOptions: commandOptions2, source: "in-process" };
70780
72145
  };
70781
72146
  const resolved = resolveCommandInventory({ root, from: o.commandsFrom, inProcess });
70782
- const { commandPaths, commandOptions } = resolved.inventory;
70783
- const deps = { commandPaths, commandOptions, base: o.base, runbooksOnly: o.runbooksOnly };
72147
+ const { commandPaths, commandOptions, source } = resolved.inventory;
72148
+ const deps = { commandPaths, commandOptions, commandSource: source, base: o.base, runbooksOnly: o.runbooksOnly };
70784
72149
  const result = o.live ? await runLiveDocRefs(root, deps, createRunbookLiveDeps(process.argv[1])) : runDocRefs(root, deps);
70785
72150
  result.warnings.push(...resolved.warnings);
70786
72151
  if (o.json) {
@@ -70904,7 +72269,7 @@ ${r.stderr ?? ""}`).catch(() => "");
70904
72269
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
70905
72270
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
70906
72271
  function envHealLockPath(home) {
70907
- return (0, import_node_path62.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
72272
+ return (0, import_node_path63.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
70908
72273
  }
70909
72274
  async function withEnvHealLock(what, run) {
70910
72275
  try {
@@ -71042,7 +72407,7 @@ function mmiDoctorDeps(opts = {}) {
71042
72407
  );
71043
72408
  const result = applyPluginCachePlan(
71044
72409
  plan,
71045
- (p) => (0, import_node_fs65.rmSync)(p, { recursive: true }),
72410
+ (p) => (0, import_node_fs66.rmSync)(p, { recursive: true }),
71046
72411
  stagingApplyFsGuard(configRoot)
71047
72412
  );
71048
72413
  return {
@@ -71077,7 +72442,7 @@ function mmiDoctorDeps(opts = {}) {
71077
72442
  // has no generated routing index would
71078
72443
  // get a permanent — demanding an artifact it never asked for.
71079
72444
  docsIndexState: (root) => {
71080
- if (!(0, import_node_fs65.existsSync)((0, import_node_path62.join)(root, DOCS_INDEX_PATH))) return void 0;
72445
+ if (!(0, import_node_fs66.existsSync)((0, import_node_path63.join)(root, DOCS_INDEX_PATH))) return void 0;
71081
72446
  const real = createDocsIndexDeps(root);
71082
72447
  let docs;
71083
72448
  const listDocs = () => docs ??= real.listDocs();
@@ -71086,7 +72451,7 @@ function mmiDoctorDeps(opts = {}) {
71086
72451
  },
71087
72452
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
71088
72453
  healDocsIndex: (root) => {
71089
- if (!(0, import_node_fs65.existsSync)((0, import_node_path62.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
72454
+ if (!(0, import_node_fs66.existsSync)((0, import_node_path63.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
71090
72455
  const real = createDocsIndexDeps(root);
71091
72456
  let docs;
71092
72457
  const listDocs = () => docs ??= real.listDocs();
@@ -71477,7 +72842,22 @@ registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand
71477
72842
  appActorCommand = commandPath4;
71478
72843
  setAuditCommand(commandPath4);
71479
72844
  if (commandPath4 !== "whoami diagnose-hub") emitCliCallTelemetry(commandPath4);
71480
- if (commandPath4 === "ci cancel-orphans" || commandPath4 === "run cancel" || commandPath4 === "run rerun") return;
72845
+ const LAZY_APP_MINT_COMMANDS = /* @__PURE__ */ new Set([
72846
+ "ci cancel-orphans",
72847
+ "run cancel",
72848
+ "run rerun",
72849
+ // #9779/#9780: the vars verbs and archive-status validate --repo/name/value before the first
72850
+ // dispatch, so their scoped App mint waits for the action handler like the run writes above.
72851
+ "vars get",
72852
+ "vars list",
72853
+ "vars create",
72854
+ "vars update",
72855
+ "vars delete",
72856
+ "repo archive-status",
72857
+ // #9788: the lifecycle read validates its manifest/window/bounds before its scoped mint.
72858
+ "read merge-queue lifecycle"
72859
+ ]);
72860
+ if (LAZY_APP_MINT_COMMANDS.has(commandPath4)) return;
71481
72861
  try {
71482
72862
  await activateAppActor(commandPath4, process.env, () => APP_WRITE_COMMANDS.has(commandPath4) ? mintCommandWriteToken(appActorDeps(), commandPath4) : mintInstallationToken(appActorDeps()));
71483
72863
  } catch (e) {
@@ -71874,7 +73254,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
71874
73254
  if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
71875
73255
  if (o.secretsFile) {
71876
73256
  try {
71877
- vars.push(`secrets=${(0, import_node_fs65.readFileSync)(o.secretsFile, "utf8")}`);
73257
+ vars.push(`secrets=${(0, import_node_fs66.readFileSync)(o.secretsFile, "utf8")}`);
71878
73258
  } catch (e) {
71879
73259
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
71880
73260
  }
@@ -72266,16 +73646,16 @@ function ciAuditDeps2() {
72266
73646
  // gate re-seed step is skipped gracefully rather than failing mid-run.
72267
73647
  readSeedFile: (path2) => {
72268
73648
  if (!root) return null;
72269
- const fullPath = (0, import_node_path62.join)(root, path2);
72270
- return (0, import_node_fs65.existsSync)(fullPath) ? (0, import_node_fs65.readFileSync)(fullPath, "utf8") : null;
73649
+ const fullPath = (0, import_node_path63.join)(root, path2);
73650
+ return (0, import_node_fs66.existsSync)(fullPath) ? (0, import_node_fs66.readFileSync)(fullPath, "utf8") : null;
72271
73651
  }
72272
73652
  };
72273
73653
  }
72274
73654
  function hubRoot2() {
72275
- const fromPkg = (0, import_node_path62.join)(__dirname, "..", "..");
73655
+ const fromPkg = (0, import_node_path63.join)(__dirname, "..", "..");
72276
73656
  const marker = "skills/bootstrap/seeds/manifest.json";
72277
- if ((0, import_node_fs65.existsSync)((0, import_node_path62.join)(fromPkg, marker))) return fromPkg;
72278
- if ((0, import_node_fs65.existsSync)((0, import_node_path62.join)(process.cwd(), marker))) return process.cwd();
73657
+ if ((0, import_node_fs66.existsSync)((0, import_node_path63.join)(fromPkg, marker))) return fromPkg;
73658
+ if ((0, import_node_fs66.existsSync)((0, import_node_path63.join)(process.cwd(), marker))) return process.cwd();
72279
73659
  return null;
72280
73660
  }
72281
73661
  registerQueryCommands(program2);
@@ -72319,6 +73699,20 @@ registerRunRerun(
72319
73699
  async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run rerun")).token,
72320
73700
  async () => fetchProjectsList(registryClientDeps(await loadConfig()))
72321
73701
  );
73702
+ var varsAppClient = (commandPath4) => async () => (
73703
+ // The concrete client always provides restWithStatus (optional on the interface only so hand-built
73704
+ // test stubs of GitHubClient keep compiling).
73705
+ createGitHubClient({ token: async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), commandPath4)).token })
73706
+ );
73707
+ var varsProjects = async () => fetchProjectsList(registryClientDeps(await loadConfig()));
73708
+ var varsActor = () => ({ credential: "app_installation", ...activeAppQuotaScope() ? { quotaScope: activeAppQuotaScope() } : {} });
73709
+ registerDevopsVarsCommands(program2, { appClient: (commandPath4) => varsAppClient(commandPath4)(), projects: varsProjects, actor: varsActor });
73710
+ registerRepoArchiveStatusCommand(program2, { appClient: (commandPath4) => varsAppClient(commandPath4)(), projects: varsProjects, actor: varsActor });
73711
+ registerMergeQueueLifecycleCommand(program2.commands.find((c) => c.name() === "read"), {
73712
+ appClient: async (commandPath4) => createGitHubClient({ token: async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), commandPath4)).token }),
73713
+ projects: varsProjects,
73714
+ actor: varsActor
73715
+ });
72322
73716
  ci.command("reconcile").description("audit + optionally apply merge settings and product ruleset activation (master-admin)").option("--json", "machine-readable output").option("--repo <owner/repo>", "reconcile one repo instead of the full registry").option("--apply", "PATCH merge settings + activate product ruleset when missing (master role required); activation is skipped while the repo's gate has never passed (#3694)").option("--park-ruleset", "stop the product ruleset enforcing without deleting it \u2014 keeps its required contexts (master role required)").action(async (o) => {
72323
73717
  if (o.apply && o.parkRuleset) {
72324
73718
  return fail("ci reconcile: --apply and --park-ruleset ask for opposite things; pass one");
@@ -72418,10 +73812,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
72418
73812
  targets = resolution.targets;
72419
73813
  }
72420
73814
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
72421
- const fileMatrix = (0, import_node_fs65.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs65.readFileSync)("access-matrix.json", "utf8")) : {};
73815
+ const fileMatrix = (0, import_node_fs66.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs66.readFileSync)("access-matrix.json", "utf8")) : {};
72422
73816
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
72423
73817
  const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
72424
- const sanctioned = (0, import_node_fs65.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs65.readFileSync)("access-matrix.json", "utf8")) : {};
73818
+ const sanctioned = (0, import_node_fs66.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs66.readFileSync)("access-matrix.json", "utf8")) : {};
72425
73819
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
72426
73820
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
72427
73821
  if (!report.ok) process.exitCode = 1;
@@ -72458,16 +73852,16 @@ function directoryBytes(path2) {
72458
73852
  let total = 0;
72459
73853
  let entries;
72460
73854
  try {
72461
- entries = (0, import_node_fs65.readdirSync)(path2, { withFileTypes: true });
73855
+ entries = (0, import_node_fs66.readdirSync)(path2, { withFileTypes: true });
72462
73856
  } catch {
72463
73857
  return 0;
72464
73858
  }
72465
73859
  for (const entry of entries) {
72466
- const child2 = (0, import_node_path62.join)(path2, entry.name);
73860
+ const child2 = (0, import_node_path63.join)(path2, entry.name);
72467
73861
  if (entry.isDirectory()) total += directoryBytes(child2);
72468
73862
  else {
72469
73863
  try {
72470
- total += (0, import_node_fs65.statSync)(child2).size;
73864
+ total += (0, import_node_fs66.statSync)(child2).size;
72471
73865
  } catch {
72472
73866
  }
72473
73867
  }
@@ -72475,25 +73869,25 @@ function directoryBytes(path2) {
72475
73869
  return total;
72476
73870
  }
72477
73871
  function listDirEntries(dir) {
72478
- return (0, import_node_fs65.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
73872
+ return (0, import_node_fs66.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
72479
73873
  }
72480
73874
  function readInstalledPluginRefs(configRoot) {
72481
73875
  const p = installedPluginsPathForConfig(configRoot);
72482
- if (!(0, import_node_fs65.existsSync)(p)) return [];
73876
+ if (!(0, import_node_fs66.existsSync)(p)) return [];
72483
73877
  try {
72484
- return installedPluginPaths((0, import_node_fs65.readFileSync)(p, "utf8"));
73878
+ return installedPluginPaths((0, import_node_fs66.readFileSync)(p, "utf8"));
72485
73879
  } catch {
72486
73880
  return null;
72487
73881
  }
72488
73882
  }
72489
73883
  function pluginCacheFsDeps(configRoot, dirBytes) {
72490
73884
  return {
72491
- exists: (p) => (0, import_node_fs65.existsSync)(p),
72492
- listVersionDirs: (root) => (0, import_node_fs65.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
73885
+ exists: (p) => (0, import_node_fs66.existsSync)(p),
73886
+ listVersionDirs: (root) => (0, import_node_fs66.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
72493
73887
  dirBytes,
72494
- listStagingDirs: (root) => (0, import_node_fs65.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
73888
+ listStagingDirs: (root) => (0, import_node_fs66.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
72495
73889
  try {
72496
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path62.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs65.statSync)(p).mtimeMs) };
73890
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path63.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs66.statSync)(p).mtimeMs) };
72497
73891
  } catch {
72498
73892
  return { name: d.name, mtimeMs: Date.now() };
72499
73893
  }
@@ -72507,10 +73901,10 @@ function stagingApplyFsGuard(configRoot) {
72507
73901
  return {
72508
73902
  referencedPaths: () => readInstalledPluginRefs(configRoot),
72509
73903
  mtimeMs: (name) => {
72510
- const p = (0, import_node_path62.join)(stagingRoot, name);
72511
- if (!(0, import_node_fs65.existsSync)(p)) return null;
73904
+ const p = (0, import_node_path63.join)(stagingRoot, name);
73905
+ if (!(0, import_node_fs66.existsSync)(p)) return null;
72512
73906
  try {
72513
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs65.statSync)(q).mtimeMs);
73907
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs66.statSync)(q).mtimeMs);
72514
73908
  } catch {
72515
73909
  return null;
72516
73910
  }
@@ -72536,7 +73930,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
72536
73930
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
72537
73931
  );
72538
73932
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
72539
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs65.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
73933
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs66.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
72540
73934
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
72541
73935
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
72542
73936
  else console.log(renderPluginCachePlan(plan, result));
@@ -72546,7 +73940,7 @@ program2.command("archive-prune").description("inventory worktree evidence archi
72546
73940
  let primary = o.repoRoot;
72547
73941
  if (!primary) {
72548
73942
  const common = await gitOut(["rev-parse", "--path-format=absolute", "--git-common-dir"]);
72549
- primary = common ? (0, import_node_path62.dirname)(common) : "";
73943
+ primary = common ? (0, import_node_path63.dirname)(common) : "";
72550
73944
  }
72551
73945
  if (!primary) {
72552
73946
  const detail = "archive-prune: not inside a git checkout (pass --repo-root <primary checkout>)";