@mutmutco/cli 3.34.0 → 3.35.1

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.
Files changed (2) hide show
  1. package/dist/main.cjs +526 -183
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3408,8 +3408,8 @@ function useColor() {
3408
3408
  var program = new Command();
3409
3409
 
3410
3410
  // src/index.ts
3411
- var import_promises6 = require("node:fs/promises");
3412
- var import_node_fs27 = require("node:fs");
3411
+ var import_promises7 = require("node:fs/promises");
3412
+ var import_node_fs28 = require("node:fs");
3413
3413
  var import_node_child_process12 = require("node:child_process");
3414
3414
 
3415
3415
  // src/cli-shared.ts
@@ -4475,7 +4475,7 @@ function trackedPathStatus(c, realPath, repoAnchor) {
4475
4475
  return null;
4476
4476
  }
4477
4477
  }
4478
- function rootScratchDirSnapshot(root, readdir, stat2) {
4478
+ function rootScratchDirSnapshot(root, readdir2, stat2) {
4479
4479
  try {
4480
4480
  const rootStat = stat2(root);
4481
4481
  let mtimeMs = rootStat.mtimeMs;
@@ -4483,7 +4483,7 @@ function rootScratchDirSnapshot(root, readdir, stat2) {
4483
4483
  const stack = [root];
4484
4484
  while (stack.length) {
4485
4485
  const current = stack.pop();
4486
- for (const ent of readdir(current, { withFileTypes: true })) {
4486
+ for (const ent of readdir2(current, { withFileTypes: true })) {
4487
4487
  const child2 = (0, import_node_path4.join)(current, ent.name);
4488
4488
  if (ent.isSymbolicLink?.()) return null;
4489
4489
  try {
@@ -4503,13 +4503,13 @@ function rootScratchDirSnapshot(root, readdir, stat2) {
4503
4503
  }
4504
4504
  }
4505
4505
  function collectScratchSnapshot(repoRoot2, deps = {}) {
4506
- const readdir = deps.readdir ?? import_node_fs6.readdirSync;
4506
+ const readdir2 = deps.readdir ?? import_node_fs6.readdirSync;
4507
4507
  const stat2 = deps.stat ?? import_node_fs6.statSync;
4508
- const readFile5 = deps.readFile ?? import_node_fs6.readFileSync;
4508
+ const readFile6 = deps.readFile ?? import_node_fs6.readFileSync;
4509
4509
  const plansRoot = (0, import_node_path4.join)(repoRoot2, "plans");
4510
4510
  const rootScratchFiles = [];
4511
4511
  try {
4512
- for (const ent of readdir(repoRoot2, { withFileTypes: true })) {
4512
+ for (const ent of readdir2(repoRoot2, { withFileTypes: true })) {
4513
4513
  const isDir = ent.isDirectory();
4514
4514
  const isFile = ent.isFile();
4515
4515
  if (!(isDir && ROOT_SCRATCH_DIRS.has(ent.name)) && !(isFile && ROOT_SCRATCH_FILE_PREFIXES.some((prefix) => ent.name.startsWith(prefix)))) {
@@ -4518,7 +4518,7 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
4518
4518
  const full = (0, import_node_path4.join)(repoRoot2, ent.name);
4519
4519
  try {
4520
4520
  if (isDir) {
4521
- const snap = rootScratchDirSnapshot(full, readdir, stat2);
4521
+ const snap = rootScratchDirSnapshot(full, readdir2, stat2);
4522
4522
  if (snap) rootScratchFiles.push({ path: full, dir: repoRoot2, name: ent.name, mtimeMs: snap.mtimeMs, bytes: snap.bytes, kind: "dir" });
4523
4523
  } else {
4524
4524
  const st = stat2(full);
@@ -4531,12 +4531,12 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
4531
4531
  }
4532
4532
  const planMdFiles = [];
4533
4533
  try {
4534
- for (const ent of readdir(plansRoot, { withFileTypes: true })) {
4534
+ for (const ent of readdir2(plansRoot, { withFileTypes: true })) {
4535
4535
  if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
4536
4536
  const full = (0, import_node_path4.join)(plansRoot, ent.name);
4537
4537
  try {
4538
4538
  const st = stat2(full);
4539
- const raw = readFile5(full, "utf8");
4539
+ const raw = readFile6(full, "utf8");
4540
4540
  planMdFiles.push({ path: full, dir: plansRoot, name: ent.name, mtimeMs: st.mtimeMs, bytes: st.size, kind: "file", hash: hashContent(normalizeEol(raw)) });
4541
4541
  } catch {
4542
4542
  }
@@ -4545,7 +4545,7 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
4545
4545
  }
4546
4546
  let planMeta = {};
4547
4547
  try {
4548
- planMeta = parseMeta(readFile5((0, import_node_path4.join)(plansRoot, ".plan-meta.json"), "utf8"));
4548
+ planMeta = parseMeta(readFile6((0, import_node_path4.join)(plansRoot, ".plan-meta.json"), "utf8"));
4549
4549
  } catch {
4550
4550
  planMeta = {};
4551
4551
  }
@@ -4553,7 +4553,7 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
4553
4553
  const syncQueueSlugs = (() => {
4554
4554
  let queueRaw;
4555
4555
  try {
4556
- queueRaw = readFile5((0, import_node_path4.join)(plansRoot, ".sync-queue.json"), "utf8");
4556
+ queueRaw = readFile6((0, import_node_path4.join)(plansRoot, ".sync-queue.json"), "utf8");
4557
4557
  } catch (e) {
4558
4558
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
4559
4559
  return code === "ENOENT" || code === "ENOTDIR" ? /* @__PURE__ */ new Set() : null;
@@ -5062,7 +5062,7 @@ async function fetchOrgConfig(deps) {
5062
5062
  }
5063
5063
  }
5064
5064
  async function postJson(pathSuffix, payload, deps, method = "POST", opts = {}) {
5065
- if (!deps.baseUrl) return { ok: false, status: 0, body: null, error: "no Hub API URL (set MMI_HUB_URL or use a current MMI CLI/plugin build)" };
5065
+ if (!deps.baseUrl) return { ok: false, status: 0, body: null, error: "no Hub API URL (set MMI_HUB_URL or use a current MMI CLI/plugin build)", unreachable: "no-base-url" };
5066
5066
  const token = await deps.token();
5067
5067
  if (!token) return { ok: false, status: 0, body: null, error: "no Hub session token (run `gh auth login`)" };
5068
5068
  const timeoutMs = opts.timeoutMs ?? deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS;
@@ -5081,7 +5081,7 @@ async function postJson(pathSuffix, payload, deps, method = "POST", opts = {}) {
5081
5081
  }
5082
5082
  return { ok: res.ok, status: res.status, body };
5083
5083
  } catch (e) {
5084
- return { ok: false, status: 0, body: null, error: e.message };
5084
+ return { ok: false, status: 0, body: null, error: e.message, unreachable: "transport" };
5085
5085
  }
5086
5086
  }
5087
5087
  async function registerProject(payload, deps) {
@@ -5099,6 +5099,11 @@ async function setDeployCoords(slug, payload, deps) {
5099
5099
  async function recordDocsAudit(verdict, deps) {
5100
5100
  return postJson("/docs-audit/record", { ...verdict }, deps);
5101
5101
  }
5102
+ async function postSchedulesLift(repo, schedules, deps) {
5103
+ const res = await postJson("/schedules/lift", { repo, schedules }, deps);
5104
+ if (!res.ok && res.status === 404) return { ...res, unreachable: "route-absent" };
5105
+ return res;
5106
+ }
5102
5107
  async function tenantControl(payload, deps) {
5103
5108
  return postJson("/tenant-control", payload, deps, "POST", { noRetry: true });
5104
5109
  }
@@ -9025,7 +9030,7 @@ function commandLadderHint() {
9025
9030
  }
9026
9031
 
9027
9032
  // src/index.ts
9028
- var import_node_path23 = require("node:path");
9033
+ var import_node_path25 = require("node:path");
9029
9034
 
9030
9035
  // src/merge-ci-policy.ts
9031
9036
  function resolveMergeCiPolicy(input) {
@@ -9315,6 +9320,27 @@ function workflowTriggersPullRequest(yaml) {
9315
9320
  if (!onBlock) return false;
9316
9321
  return /\bpull_request\b/.test(onBlock);
9317
9322
  }
9323
+ function workflowTriggersSchedule(yaml) {
9324
+ const onBlock = extractOnBlock(yaml);
9325
+ if (!onBlock) return false;
9326
+ if (/^(["']?)on\1:/.test(onBlock)) {
9327
+ const value = onBlock.replace(/^(["']?)on\1:\s*/, "");
9328
+ if (/^\s*\[/.test(value)) {
9329
+ return /(^|[,\s\[])["']?schedule["']?([,\s\]]|$)/.test(value);
9330
+ }
9331
+ return /\bschedule["']?\s*:/.test(value);
9332
+ }
9333
+ const content = onBlock.split(/\r?\n/).filter((l) => {
9334
+ const t = l.trim();
9335
+ return t.length > 0 && !t.startsWith("#");
9336
+ });
9337
+ if (!content.length) return false;
9338
+ const minIndent = Math.min(...content.map((l) => l.match(/^(\s*)/)?.[1].length ?? 0));
9339
+ return content.some((line) => {
9340
+ const indent = line.match(/^(\s*)/)?.[1].length ?? 0;
9341
+ return indent === minIndent && /^\s*schedule\s*:/.test(line);
9342
+ });
9343
+ }
9318
9344
  function workflowReportsPrChecks(yaml) {
9319
9345
  const onBlock = extractOnBlock(yaml);
9320
9346
  if (!onBlock) return false;
@@ -9330,6 +9356,9 @@ function extractOnBlock(yaml) {
9330
9356
  if (/^(["']?)on\1:\s*$/.test(line)) {
9331
9357
  inOn = true;
9332
9358
  onIndent = 0;
9359
+ } else if (/^(["']?)on\1:\s+#/.test(line)) {
9360
+ inOn = true;
9361
+ onIndent = 0;
9333
9362
  } else if (/^(["']?)on\1:\s+\S/.test(line)) {
9334
9363
  return line;
9335
9364
  }
@@ -9476,6 +9505,63 @@ function planManagedGitignore(current) {
9476
9505
  return { changed, content, added, removed };
9477
9506
  }
9478
9507
 
9508
+ // src/gate-budget.ts
9509
+ var BLESSED_RUN_WITH_BUDGET_SHA = "b6ec6e4eafcda73b543fc0754a6356551fe762a1";
9510
+ var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
9511
+ var LOCAL_USE = "./.github/actions/run-with-budget";
9512
+ var FULL_SHA = /^[0-9a-f]{40}$/;
9513
+ var MAX_SECONDS_CEILING = 1800;
9514
+ function isGateWorkflowPath(path2) {
9515
+ const base = path2.split(/[/\\]/).pop() ?? path2;
9516
+ return /^(?:.+[-_.])?gate\.ya?ml$/i.test(base);
9517
+ }
9518
+ function checkGateBudget(files, opts = {}) {
9519
+ const failures = [];
9520
+ const notes = [];
9521
+ for (const { path: path2, body } of files) {
9522
+ const uses = [...body.matchAll(/^\s*-?\s*uses:\s*(\S+)/gm)].map((m) => m[1].replace(/^['"]|['"]$/g, "")).filter((ref) => ref.includes("run-with-budget"));
9523
+ if (uses.length === 0) {
9524
+ failures.push(`${path2}: check command does not run via run-with-budget \u2014 the gate has no wall-clock ceiling`);
9525
+ continue;
9526
+ }
9527
+ for (const ref of uses) {
9528
+ if (ref === LOCAL_USE) {
9529
+ if (!opts.hubLocalAllowed) {
9530
+ failures.push(`${path2}: local ${LOCAL_USE} reference \u2014 consume the org-shared action pinned by SHA (mutmutco/MMI-Hub/.github/actions/run-with-budget@<full sha>)`);
9531
+ }
9532
+ continue;
9533
+ }
9534
+ const remote = REMOTE_USE.exec(ref);
9535
+ if (!remote) {
9536
+ failures.push(`${path2}: unrecognized run-with-budget reference '${ref}' \u2014 use mutmutco/MMI-Hub/.github/actions/run-with-budget@<full sha>`);
9537
+ continue;
9538
+ }
9539
+ if (!FULL_SHA.test(remote[1])) {
9540
+ failures.push(`${path2}: run-with-budget pinned to '${remote[1]}' \u2014 pin the full 40-hex commit SHA, not a branch or tag`);
9541
+ continue;
9542
+ }
9543
+ if (remote[1] !== BLESSED_RUN_WITH_BUDGET_SHA) {
9544
+ notes.push(`${path2}: run-with-budget pinned to ${remote[1].slice(0, 12)} (blessed ${BLESSED_RUN_WITH_BUDGET_SHA.slice(0, 12)}) \u2014 valid but outdated`);
9545
+ }
9546
+ }
9547
+ const ceilingLines = [...body.matchAll(/^\s*max-seconds:\s*(.+?)\s*$/gm)].map((m) => m[1].replace(/\s+#.*$/, ""));
9548
+ if (ceilingLines.length === 0) {
9549
+ failures.push(`${path2}: run-with-budget has no max-seconds input \u2014 the budget step guards nothing`);
9550
+ continue;
9551
+ }
9552
+ for (const raw of ceilingLines) {
9553
+ const literal = /^['"]?(\d+)['"]?$/.exec(raw);
9554
+ const seconds = literal ? Number(literal[1]) : NaN;
9555
+ if (!literal || seconds < 1) {
9556
+ failures.push(`${path2}: max-seconds '${raw}' is not a literal positive integer`);
9557
+ } else if (seconds > MAX_SECONDS_CEILING) {
9558
+ failures.push(`${path2}: max-seconds ${seconds} exceeds the org ceiling ${MAX_SECONDS_CEILING} \u2014 a budget that generous guards nothing; measure the gate and set a real ceiling`);
9559
+ }
9560
+ }
9561
+ }
9562
+ return { ok: failures.length === 0, failures, notes };
9563
+ }
9564
+
9479
9565
  // src/bootstrap-apply.ts
9480
9566
  function parseOwnerRepo(repo) {
9481
9567
  const trimmed = repo.trim();
@@ -9489,6 +9575,7 @@ function parseOwnerRepo(repo) {
9489
9575
  return { owner, name, slug: name.toLowerCase(), fullName: `${owner}/${name}` };
9490
9576
  }
9491
9577
  var DEFAULT_GATE_CMD = "npm run check";
9578
+ var DEFAULT_GATE_MAX_SECONDS = "300";
9492
9579
  var DEFAULT_GATE_PY_VERSION = "3.11";
9493
9580
  var GATE_RUNTIME_DEFAULTS = {
9494
9581
  node: { cmd: DEFAULT_GATE_CMD, install: "npm ci" },
@@ -9502,7 +9589,10 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
9502
9589
  GATE_INSTALL_CMD: rt.install,
9503
9590
  GATE_WORKDIR: ".",
9504
9591
  GATE_CACHE_DEP_PATH: "package-lock.json",
9505
- GATE_PY_VERSION: DEFAULT_GATE_PY_VERSION
9592
+ GATE_PY_VERSION: DEFAULT_GATE_PY_VERSION,
9593
+ GATE_MAX_SECONDS: DEFAULT_GATE_MAX_SECONDS,
9594
+ // The pin is the CLI's blessed SHA, not an operator knob — one central place to bump (#3178).
9595
+ GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA
9506
9596
  };
9507
9597
  const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
9508
9598
  if (track === "trunk") {
@@ -9548,6 +9638,8 @@ function gateConfigToVars(gate) {
9548
9638
  if (typeof gate.workdir === "string" && gate.workdir.trim()) out.GATE_WORKDIR = gate.workdir;
9549
9639
  if (typeof gate.cacheDepPath === "string" && gate.cacheDepPath.trim()) out.GATE_CACHE_DEP_PATH = gate.cacheDepPath;
9550
9640
  if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
9641
+ const seconds = typeof gate.maxSeconds === "number" ? String(gate.maxSeconds) : gate.maxSeconds;
9642
+ if (typeof seconds === "string" && /^\d+$/.test(seconds.trim()) && Number(seconds) > 0) out.GATE_MAX_SECONDS = seconds.trim();
9551
9643
  return out;
9552
9644
  }
9553
9645
  function seedMatchesDeployModel(seed, deployModel) {
@@ -9609,10 +9701,10 @@ function labelsToPrune(orgLabelNames) {
9609
9701
  const org = new Set(orgLabelNames);
9610
9702
  return GITHUB_DEFAULT_LABELS.filter((name) => !org.has(name));
9611
9703
  }
9612
- function resolveSeedContent(seed, vars, readFile5) {
9613
- if (seed.source === "self") return readFile5(seed.target);
9704
+ function resolveSeedContent(seed, vars, readFile6) {
9705
+ if (seed.source === "self") return readFile6(seed.target);
9614
9706
  if (seed.source.startsWith("seed:")) {
9615
- const tmpl = readFile5(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
9707
+ const tmpl = readFile6(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
9616
9708
  return tmpl == null ? null : renderSeed(tmpl, vars);
9617
9709
  }
9618
9710
  return null;
@@ -10028,11 +10120,22 @@ async function auditRepoCi(repo, deps) {
10028
10120
  const allPaths = await listWorkflowPaths(deps, repo, baseBranch) ?? [];
10029
10121
  const prPaths = await filterPullRequestTriggered(deps, repo, baseBranch, allPaths);
10030
10122
  const missing = [];
10123
+ const gateBodies = [];
10031
10124
  for (const path2 of prPaths) {
10032
10125
  const body = await fetchFileContent(deps, repo, baseBranch, path2);
10033
10126
  if (body == null) continue;
10127
+ if (isGateWorkflowPath(path2)) gateBodies.push({ path: path2, body });
10034
10128
  if (!/^\s*concurrency:/m.test(body) || !/cancel-in-progress:\s*true/.test(body)) missing.push(path2);
10035
10129
  }
10130
+ if (gateBodies.length > 0) {
10131
+ const budget = checkGateBudget(gateBodies, { hubLocalAllowed: repoClass === "hub" });
10132
+ checks.push({
10133
+ ok: budget.ok,
10134
+ label: "gate runs under the org wall-clock budget (#3178)",
10135
+ detail: [...budget.failures, ...budget.notes].join("; ") || void 0,
10136
+ remediation: budget.ok ? void 0 : `Wrap the gate's check command in mutmutco/MMI-Hub/.github/actions/run-with-budget@${BLESSED_RUN_WITH_BUDGET_SHA} with a measured max-seconds \u2014 see skills/bootstrap/seeds/gate.template.yml`
10137
+ });
10138
+ }
10036
10139
  if (prPaths.length > 0) {
10037
10140
  checks.push({
10038
10141
  ok: missing.length === 0,
@@ -12338,6 +12441,8 @@ function parseVerifyBroker(stdout) {
12338
12441
  }
12339
12442
 
12340
12443
  // src/train-apply.ts
12444
+ var import_node_fs16 = require("node:fs");
12445
+ var import_node_path14 = require("node:path");
12341
12446
  function resolveDeployModel2(meta, repo) {
12342
12447
  const m = meta?.deployModel;
12343
12448
  if (isDeployModel(m)) return m;
@@ -13181,6 +13286,34 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
13181
13286
  }
13182
13287
  return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
13183
13288
  }
13289
+ function readLocalGateWorkflows() {
13290
+ const dir = (0, import_node_path14.join)(".github", "workflows");
13291
+ let names;
13292
+ try {
13293
+ names = (0, import_node_fs16.readdirSync)(dir);
13294
+ } catch {
13295
+ return null;
13296
+ }
13297
+ const files = [];
13298
+ for (const name of names.filter(isGateWorkflowPath)) {
13299
+ try {
13300
+ files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs16.readFileSync)((0, import_node_path14.join)(dir, name), "utf8") });
13301
+ } catch {
13302
+ }
13303
+ }
13304
+ return files;
13305
+ }
13306
+ function enforceGateBudget(deps, repo) {
13307
+ const files = (deps.readGateWorkflows ?? readLocalGateWorkflows)();
13308
+ if (files == null || files.length === 0) return;
13309
+ const budget = checkGateBudget(files, { hubLocalAllowed: repo.toLowerCase() === "mutmutco/mmi-hub" });
13310
+ for (const note of budget.notes) deps.warn?.(`gate budget: ${note}`);
13311
+ if (!budget.ok) {
13312
+ throw new Error(
13313
+ `release refused \u2014 the gate runs without the org wall-clock budget (#3178): ${budget.failures.join("; ")}. Wrap the check command in the pinned run-with-budget step (see skills/bootstrap/seeds/gate.template.yml in MMI-Hub), or run \`mmi-cli ci audit --repo ${repo}\` for the full picture.`
13314
+ );
13315
+ }
13316
+ }
13184
13317
  async function preflight(deps, ctx, stage, meta) {
13185
13318
  const model = resolveDeployModel2(meta, ctx.repo);
13186
13319
  if (model === "content") {
@@ -13190,6 +13323,7 @@ async function preflight(deps, ctx, stage, meta) {
13190
13323
  throw new Error(`${ctx.repo} is not Hub-deployed (deployModel=none) \u2014 the release train does not apply; use the project's own release path`);
13191
13324
  }
13192
13325
  await deps.runSelf(["secrets", "preflight", "--stage", stage, "--repo", ctx.repo]);
13326
+ enforceGateBudget(deps, ctx.repo);
13193
13327
  return model;
13194
13328
  }
13195
13329
  async function preflightMergeToMain(deps, deployModel, remoteRef, blockingPrefix, realignMessage) {
@@ -15030,8 +15164,8 @@ function renderAccessReport(report) {
15030
15164
  }
15031
15165
 
15032
15166
  // src/docs-index-command.ts
15033
- var import_node_fs16 = require("node:fs");
15034
- var import_node_path14 = require("node:path");
15167
+ var import_node_fs17 = require("node:fs");
15168
+ var import_node_path15 = require("node:path");
15035
15169
  var DOCS_INDEX_PATH = "docs/index.md";
15036
15170
  function isRoutableDocsPath(relPath) {
15037
15171
  const normalized = relPath.replace(/\\/g, "/");
@@ -15109,25 +15243,25 @@ function walkMarkdown(dir) {
15109
15243
  const stack = [dir];
15110
15244
  while (stack.length) {
15111
15245
  const current = stack.pop();
15112
- for (const entry of (0, import_node_fs16.readdirSync)(current, { withFileTypes: true })) {
15113
- const full = (0, import_node_path14.join)(current, entry.name);
15246
+ for (const entry of (0, import_node_fs17.readdirSync)(current, { withFileTypes: true })) {
15247
+ const full = (0, import_node_path15.join)(current, entry.name);
15114
15248
  if (entry.isDirectory()) {
15115
15249
  stack.push(full);
15116
15250
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
15117
- out.push((0, import_node_path14.relative)(dir, full).split(import_node_path14.sep).join("/"));
15251
+ out.push((0, import_node_path15.relative)(dir, full).split(import_node_path15.sep).join("/"));
15118
15252
  }
15119
15253
  }
15120
15254
  }
15121
15255
  return out;
15122
15256
  }
15123
15257
  function createDocsIndexDeps(repoRoot2) {
15124
- const docsDir = (0, import_node_path14.join)(repoRoot2, "docs");
15125
- const indexPath = (0, import_node_path14.join)(repoRoot2, DOCS_INDEX_PATH);
15258
+ const docsDir = (0, import_node_path15.join)(repoRoot2, "docs");
15259
+ const indexPath = (0, import_node_path15.join)(repoRoot2, DOCS_INDEX_PATH);
15126
15260
  return {
15127
- listDocs: () => (0, import_node_fs16.existsSync)(docsDir) ? walkMarkdown(docsDir).filter(isRoutableDocsPath).sort() : [],
15128
- readDoc: (relPath) => (0, import_node_fs16.readFileSync)((0, import_node_path14.join)(docsDir, relPath), "utf8"),
15129
- readIndex: () => (0, import_node_fs16.existsSync)(indexPath) ? (0, import_node_fs16.readFileSync)(indexPath, "utf8") : null,
15130
- writeIndex: (content) => (0, import_node_fs16.writeFileSync)(indexPath, content, "utf8")
15261
+ listDocs: () => (0, import_node_fs17.existsSync)(docsDir) ? walkMarkdown(docsDir).filter(isRoutableDocsPath).sort() : [],
15262
+ readDoc: (relPath) => (0, import_node_fs17.readFileSync)((0, import_node_path15.join)(docsDir, relPath), "utf8"),
15263
+ readIndex: () => (0, import_node_fs17.existsSync)(indexPath) ? (0, import_node_fs17.readFileSync)(indexPath, "utf8") : null,
15264
+ writeIndex: (content) => (0, import_node_fs17.writeFileSync)(indexPath, content, "utf8")
15131
15265
  };
15132
15266
  }
15133
15267
 
@@ -16054,8 +16188,8 @@ function writeError(res) {
16054
16188
  }
16055
16189
 
16056
16190
  // src/secrets-commands.ts
16057
- var import_node_fs17 = require("node:fs");
16058
- var import_node_path15 = require("node:path");
16191
+ var import_node_fs18 = require("node:fs");
16192
+ var import_node_path16 = require("node:path");
16059
16193
  var import_node_os5 = require("node:os");
16060
16194
 
16061
16195
  // src/project-runtime.ts
@@ -16179,18 +16313,18 @@ function collectMap(value, previous = []) {
16179
16313
  return [...previous, value];
16180
16314
  }
16181
16315
  async function decryptRailsCredentials(input) {
16182
- const appDir = (0, import_node_path15.resolve)(input.appDir ?? process.cwd());
16316
+ const appDir = (0, import_node_path16.resolve)(input.appDir ?? process.cwd());
16183
16317
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
16184
16318
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
16185
- const credentialsPath = (0, import_node_path15.resolve)(appDir, credentialsFile);
16186
- const masterKeyPath = (0, import_node_path15.resolve)(appDir, masterKeyFile);
16319
+ const credentialsPath = (0, import_node_path16.resolve)(appDir, credentialsFile);
16320
+ const masterKeyPath = (0, import_node_path16.resolve)(appDir, masterKeyFile);
16187
16321
  const env = {
16188
16322
  ...process.env,
16189
16323
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
16190
16324
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
16191
16325
  };
16192
- if ((0, import_node_fs17.existsSync)(masterKeyPath)) {
16193
- env.RAILS_MASTER_KEY = (0, import_node_fs17.readFileSync)(masterKeyPath, "utf8").trim();
16326
+ if ((0, import_node_fs18.existsSync)(masterKeyPath)) {
16327
+ env.RAILS_MASTER_KEY = (0, import_node_fs18.readFileSync)(masterKeyPath, "utf8").trim();
16194
16328
  }
16195
16329
  const script = [
16196
16330
  'require "json"',
@@ -16200,9 +16334,9 @@ async function decryptRailsCredentials(input) {
16200
16334
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
16201
16335
  "puts JSON.generate(config.config)"
16202
16336
  ].join("\n");
16203
- const scriptDir = (0, import_node_fs17.mkdtempSync)((0, import_node_path15.join)((0, import_node_os5.tmpdir)(), "mmi-rails-decrypt-"));
16204
- const scriptPath = (0, import_node_path15.join)(scriptDir, "decrypt.rb");
16205
- (0, import_node_fs17.writeFileSync)(scriptPath, script, "utf8");
16337
+ const scriptDir = (0, import_node_fs18.mkdtempSync)((0, import_node_path16.join)((0, import_node_os5.tmpdir)(), "mmi-rails-decrypt-"));
16338
+ const scriptPath = (0, import_node_path16.join)(scriptDir, "decrypt.rb");
16339
+ (0, import_node_fs18.writeFileSync)(scriptPath, script, "utf8");
16206
16340
  try {
16207
16341
  const args = ["exec", "ruby", scriptPath];
16208
16342
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -16214,7 +16348,7 @@ async function decryptRailsCredentials(input) {
16214
16348
  });
16215
16349
  return JSON.parse(stdout);
16216
16350
  } finally {
16217
- (0, import_node_fs17.rmSync)(scriptDir, { recursive: true, force: true });
16351
+ (0, import_node_fs18.rmSync)(scriptDir, { recursive: true, force: true });
16218
16352
  }
16219
16353
  }
16220
16354
  async function readSecretStdin() {
@@ -16289,7 +16423,7 @@ function registerSecretsCommands(program3) {
16289
16423
  secrets.command("org-catalog").description("MASTER-ONLY: set the _org/<provider>/* catalog declarations from a JSON file (#2244)").requiredOption("--file <path>", 'JSON file: { "entries": { "<provider>/<KEY>": {key,purpose,group,owner,provider,stages[],consumers[]} } }').action((o) => withSecrets(async (d) => {
16290
16424
  let body;
16291
16425
  try {
16292
- body = (0, import_node_fs17.readFileSync)((0, import_node_path15.resolve)(o.file), "utf8");
16426
+ body = (0, import_node_fs18.readFileSync)((0, import_node_path16.resolve)(o.file), "utf8");
16293
16427
  } catch (e) {
16294
16428
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
16295
16429
  }
@@ -16392,7 +16526,7 @@ function registerSecretsCommands(program3) {
16392
16526
  {
16393
16527
  ...d,
16394
16528
  decryptRailsCredentials,
16395
- removeFile: (path2) => (0, import_node_fs17.unlinkSync)((0, import_node_path15.resolve)(o.appDir ?? process.cwd(), path2))
16529
+ removeFile: (path2) => (0, import_node_fs18.unlinkSync)((0, import_node_path16.resolve)(o.appDir ?? process.cwd(), path2))
16396
16530
  },
16397
16531
  {
16398
16532
  repo: o.repo,
@@ -16911,6 +17045,10 @@ function spliceDoc(docText, generatedSection) {
16911
17045
  }
16912
17046
  return docText.slice(0, start) + generatedSection + docText.slice(end + DOC_END_MARKER.length);
16913
17047
  }
17048
+ var HARBOUR_LLM_LAUNCHERS = /* @__PURE__ */ new Set(["cursor-agent"]);
17049
+ function isHarbourLlmLauncher(executor) {
17050
+ return HARBOUR_LLM_LAUNCHERS.has(executor);
17051
+ }
16914
17052
  function splitCadence(cadence) {
16915
17053
  return cadence.split("+").map((c) => c.replace(/\s+/g, " ").trim()).filter(Boolean);
16916
17054
  }
@@ -16972,6 +17110,38 @@ function reconcileGithubActions(liveGithub, registry2, readRepos) {
16972
17110
  function renderDrift(d) {
16973
17111
  return `${d.class}: ${d.name} (${d.executor}) \u2014 ${d.detail}; remedy: ${d.remedy}`;
16974
17112
  }
17113
+ function strayCronDrift(name) {
17114
+ return {
17115
+ class: "stray-cron",
17116
+ name,
17117
+ executor: "github-actions",
17118
+ detail: "workflow still carries on: schedule",
17119
+ remedy: "strip on: schedule \u2192 workflow_dispatch and let the harbour dispatcher fire on the registry cadence (docs/schedules.md rule 1)"
17120
+ };
17121
+ }
17122
+ function strayCronDrifts(workflows) {
17123
+ return workflows.filter((w) => workflowTriggersSchedule(w.yamlText)).map((w) => strayCronDrift(w.name)).sort((a, b) => a.name.localeCompare(b.name));
17124
+ }
17125
+ function unlauncheredLlmDrift(entry) {
17126
+ if (entry.llm !== "yes") return null;
17127
+ if (isHarbourLlmLauncher(entry.executor)) return null;
17128
+ const allowed = [...HARBOUR_LLM_LAUNCHERS].join(", ");
17129
+ return {
17130
+ class: "unlaunchered-llm",
17131
+ name: entry.name,
17132
+ executor: entry.executor,
17133
+ detail: `llm: yes with executor outside the harbour launcher set (${allowed})`,
17134
+ remedy: `move the lane to executor: ${allowed} and run it through the Hub harbour key (docs/schedules.md rule 4)`
17135
+ };
17136
+ }
17137
+ function unlauncheredLlmDrifts(entries) {
17138
+ const drifts = [];
17139
+ for (const e of entries) {
17140
+ const d = unlauncheredLlmDrift(e);
17141
+ if (d) drifts.push(d);
17142
+ }
17143
+ return drifts.sort((a, b) => a.name.localeCompare(b.name));
17144
+ }
16975
17145
  function assembleReconciliation(githubEntries2, readRepos, registry2) {
16976
17146
  if (registry2 === null) {
16977
17147
  return {
@@ -17006,10 +17176,11 @@ async function listWorkflows(client, repo) {
17006
17176
  }
17007
17177
  }
17008
17178
  async function repoWorkflowEntries(client, repo) {
17009
- const workflows = await listWorkflows(client, repo);
17179
+ const workflowsList = await listWorkflows(client, repo);
17010
17180
  const entries = [];
17011
17181
  const failures = [];
17012
- for (const wf of workflows) {
17182
+ const workflows = [];
17183
+ for (const wf of workflowsList) {
17013
17184
  if (wf?.state !== "active" || typeof wf.path !== "string" || !wf.path) continue;
17014
17185
  if (!wf.path.startsWith(".github/workflows/")) continue;
17015
17186
  try {
@@ -17019,6 +17190,9 @@ async function repoWorkflowEntries(client, repo) {
17019
17190
  );
17020
17191
  if (typeof contents?.content !== "string" || contents.encoding !== "base64") continue;
17021
17192
  const text = Buffer.from(contents.content, "base64").toString("utf8");
17193
+ const basename3 = wf.path.split("/").pop() ?? wf.path;
17194
+ const name = `${repo}/${basename3.replace(/\.ya?ml$/, "")}`;
17195
+ workflows.push({ name, yamlText: text });
17022
17196
  const entry = workflowEntry(repo, wf.path, text);
17023
17197
  if (entry) entries.push(entry);
17024
17198
  } catch (e) {
@@ -17027,19 +17201,20 @@ async function repoWorkflowEntries(client, repo) {
17027
17201
  else throw e;
17028
17202
  }
17029
17203
  }
17030
- return { entries, failures };
17204
+ return { entries, failures, workflows };
17031
17205
  }
17032
17206
  async function githubEntries(client) {
17033
17207
  const entries = [];
17034
17208
  const incomplete = [];
17035
17209
  const drift = [];
17036
17210
  const readRepos = [];
17211
+ const workflows = [];
17037
17212
  let repos;
17038
17213
  try {
17039
17214
  const listing = await client.restPaginate(`/orgs/${ORG}/repos?per_page=100`);
17040
17215
  repos = listing.filter((r) => typeof r?.name === "string" && r.archived !== true).map((r) => r.name).sort();
17041
17216
  } catch (e) {
17042
- return { entries, incomplete: [`github: could not list ${ORG} repos \u2014 ${e.message}`], drift, readRepos };
17217
+ return { entries, incomplete: [`github: could not list ${ORG} repos \u2014 ${e.message}`], drift, reconciliation: [], readRepos };
17043
17218
  }
17044
17219
  const results = await Promise.all(
17045
17220
  repos.map(async (repo) => {
@@ -17055,12 +17230,15 @@ async function githubEntries(client) {
17055
17230
  else {
17056
17231
  readRepos.push(r.repo);
17057
17232
  entries.push(...r.entries);
17233
+ workflows.push(...r.workflows);
17058
17234
  if (r.failures.length) {
17059
17235
  drift.push(`${r.repo}: ${r.failures.length} workflow record(s) listed active with no file on the default branch (deleted one-offs?) \u2014 deregister them: ${r.failures.map((f) => f.split(": ")[1]).join(", ")}`);
17060
17236
  }
17061
17237
  }
17062
17238
  }
17063
- return { entries, incomplete, drift, readRepos };
17239
+ const reconciliation = [...strayCronDrifts(workflows), ...unlauncheredLlmDrifts(entries)];
17240
+ drift.push(...reconciliation.map(renderDrift));
17241
+ return { entries, incomplete, drift, reconciliation, readRepos };
17064
17242
  }
17065
17243
  async function awsJson(args) {
17066
17244
  const run = async () => {
@@ -17095,7 +17273,8 @@ async function awsEntries() {
17095
17273
  } catch (e) {
17096
17274
  incomplete.push(`aws: scheduler listing failed \u2014 ${e.message}`);
17097
17275
  }
17098
- return { entries, incomplete, drift: [] };
17276
+ const reconciliation = unlauncheredLlmDrifts(entries);
17277
+ return { entries, incomplete, drift: reconciliation.map(renderDrift), reconciliation };
17099
17278
  }
17100
17279
  async function defaultRegistryRead() {
17101
17280
  return fetchSchedulesList(registryClientDeps(await loadConfig()));
@@ -17111,7 +17290,7 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
17111
17290
  entries: sortEntries([...gh.entries, ...aws.entries, ...DECLARED_ENTRIES]),
17112
17291
  incomplete: [...gh.incomplete, ...aws.incomplete, ...recon.incomplete],
17113
17292
  drift: [...gh.drift, ...aws.drift, ...recon.driftLines],
17114
- reconciliation: recon.reconciliation
17293
+ reconciliation: [...gh.reconciliation, ...aws.reconciliation, ...recon.reconciliation]
17115
17294
  };
17116
17295
  }
17117
17296
  function warnIncomplete2(incomplete) {
@@ -17152,6 +17331,169 @@ function registerSchedulesCommands(program3) {
17152
17331
  });
17153
17332
  }
17154
17333
 
17334
+ // src/schedules-lift-command.ts
17335
+ var import_promises3 = require("node:fs/promises");
17336
+ var import_node_path17 = require("node:path");
17337
+
17338
+ // src/schedules-lift.ts
17339
+ var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
17340
+ function parseScheduleHeader(yamlText) {
17341
+ const out = {};
17342
+ for (const rawLine of yamlText.split("\n")) {
17343
+ const m = /^#\s*(schedule|what|owner|cadence|executor|llm|output|breaks|kill):\s*(.+?)\s*$/.exec(rawLine);
17344
+ if (m && out[m[1]] === void 0) out[m[1]] = m[2].trim();
17345
+ }
17346
+ return out;
17347
+ }
17348
+ function cadenceContainsCron(cadence, cron) {
17349
+ const cadenceTokens = cadence.split(/\s+/).filter(Boolean);
17350
+ const cronTokens = cron.split(/\s+/).filter(Boolean);
17351
+ if (!cronTokens.length || cronTokens.length > cadenceTokens.length) return false;
17352
+ return cadenceTokens.some((_, i) => i + cronTokens.length <= cadenceTokens.length && cronTokens.every((t, j) => cadenceTokens[i + j] === t));
17353
+ }
17354
+ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
17355
+ const crons = extractWorkflowCrons(yamlText);
17356
+ const header = parseScheduleHeader(yamlText);
17357
+ if (!crons.length && !header.schedule) return null;
17358
+ const basename3 = workflowPath.split("/").pop() ?? workflowPath;
17359
+ const expectedId = `${repo}/${basename3.replace(/\.ya?ml$/, "")}`;
17360
+ const missing = SCHEDULE_HEADER_FIELDS.filter((f) => !header[f]);
17361
+ if (missing.length) {
17362
+ throw new Error(`schedules lift: ${expectedId} (${workflowPath}) is a scheduled workflow but is missing the eight-field entry-template header field(s): ${missing.join(", ")} \u2014 see docs/schedules.md "The entry template".`);
17363
+ }
17364
+ const h = header;
17365
+ if (h.schedule !== expectedId) {
17366
+ throw new Error(`schedules lift: ${workflowPath} header \`schedule: ${h.schedule}\` must equal the join key \`${expectedId}\` (<repo>/<workflow-basename>).`);
17367
+ }
17368
+ for (const cron of crons) {
17369
+ if (!cadenceContainsCron(h.cadence, cron)) {
17370
+ throw new Error(`schedules lift: ${expectedId} cadence \`${h.cadence}\` does not contain the workflow cron \`${cron}\` (whole-token comparison) \u2014 the declared cadence disagrees with the actual schedule.`);
17371
+ }
17372
+ }
17373
+ if (llmFromHeader(yamlText) === "unknown") {
17374
+ throw new Error(`schedules lift: ${expectedId} header \`llm: ${h.llm}\` does not classify (yes / no / embeddings) \u2014 an unreadable declaration would register a row whose LLM column lies.`);
17375
+ }
17376
+ return {
17377
+ id: expectedId,
17378
+ what: h.what,
17379
+ owner: h.owner,
17380
+ cadence: h.cadence,
17381
+ executor: h.executor,
17382
+ llm: h.llm,
17383
+ output: h.output,
17384
+ breaks: h.breaks,
17385
+ kill: h.kill,
17386
+ repo,
17387
+ sourcePath: workflowPath
17388
+ };
17389
+ }
17390
+ function scheduleRecordsFromWorkflows(repo, files) {
17391
+ const records = [];
17392
+ const seen = /* @__PURE__ */ new Set();
17393
+ for (const { path: path2, text } of files) {
17394
+ const rec = scheduleRecordFromWorkflow(repo, path2, text);
17395
+ if (!rec) continue;
17396
+ if (seen.has(rec.id)) {
17397
+ throw new Error(`schedules lift: duplicate schedule id \`${rec.id}\` \u2014 two workflow files in ${repo} share one join key (${rec.sourcePath}).`);
17398
+ }
17399
+ seen.add(rec.id);
17400
+ records.push(rec);
17401
+ }
17402
+ return records.sort((a, b) => a.id.localeCompare(b.id));
17403
+ }
17404
+
17405
+ // src/schedules-lift-command.ts
17406
+ var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
17407
+ var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
17408
+ var SchedulesLiftUsageError = class extends Error {
17409
+ constructor(message) {
17410
+ super(message);
17411
+ this.name = "SchedulesLiftUsageError";
17412
+ }
17413
+ };
17414
+ var SCHEDULES_LIFT_EXIT_UNREACHABLE = 75;
17415
+ var RegistryUnreachableError = class extends Error {
17416
+ exitCode = SCHEDULES_LIFT_EXIT_UNREACHABLE;
17417
+ constructor(message) {
17418
+ super(message);
17419
+ this.name = "RegistryUnreachableError";
17420
+ }
17421
+ };
17422
+ async function readWorkflowFiles(dir) {
17423
+ let names;
17424
+ try {
17425
+ names = await (0, import_promises3.readdir)(dir);
17426
+ } catch {
17427
+ return [];
17428
+ }
17429
+ const files = [];
17430
+ for (const name of names.sort()) {
17431
+ if (!/\.ya?ml$/.test(name)) continue;
17432
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises3.readFile)((0, import_node_path17.join)(dir, name), "utf8") });
17433
+ }
17434
+ return files;
17435
+ }
17436
+ function repoNameFromRemoteUrl(url) {
17437
+ const base = url.trim().replace(/\.git$/, "").split(/[/:]/).filter(Boolean).pop();
17438
+ return base ?? null;
17439
+ }
17440
+ async function defaultPostLift(repo, records) {
17441
+ return postSchedulesLift(repo, records, registryClientDeps(await loadConfig()));
17442
+ }
17443
+ async function runSchedulesLift(opts, deps = {}) {
17444
+ let repo = opts.repo?.trim();
17445
+ if (!repo) {
17446
+ const url = await (deps.originUrl ?? (() => gitOut(["remote", "get-url", "origin"])))();
17447
+ repo = repoNameFromRemoteUrl(url) ?? void 0;
17448
+ if (!repo) {
17449
+ throw new Error("schedules lift: could not resolve the repo name from the origin remote \u2014 pass --repo <name>.");
17450
+ }
17451
+ }
17452
+ if (!SCHEDULE_REPO_RE.test(repo)) {
17453
+ throw new SchedulesLiftUsageError(`schedules lift: repo \`${repo}\` is not a bare repo segment (allowed: A-Z a-z 0-9 _ . -; no \`/\` or \`#\`) \u2014 the schedule id is <repo>/<name>, so a separator would form an invalid multi-segment id.`);
17454
+ }
17455
+ const dir = opts.dir ?? DEFAULT_WORKFLOWS_DIR;
17456
+ const files = await (deps.readFiles ?? readWorkflowFiles)(dir);
17457
+ if (!files.length) {
17458
+ throw new Error(`schedules lift: no workflow files under ${dir} \u2014 refusing to post an empty lift (the route prunes; run from the repo checkout or pass --dir).`);
17459
+ }
17460
+ const records = scheduleRecordsFromWorkflows(repo, files);
17461
+ const payload = { repo, schedules: records };
17462
+ if (opts.dryRun) return { repo, records, payload };
17463
+ const response = await (deps.postLift ?? defaultPostLift)(repo, records);
17464
+ if (!response.ok) {
17465
+ const unreachable = response.unreachable ?? (response.status === 404 ? "route-absent" : void 0);
17466
+ if (unreachable) {
17467
+ throw new RegistryUnreachableError(`schedules lift: registry unreachable (${unreachable}) \u2014 ${response.error ?? `HTTP ${response.status}`}`);
17468
+ }
17469
+ if (response.error) throw new Error(`schedules lift: ${response.error}`);
17470
+ const detail = response.body?.error ?? "";
17471
+ throw new Error(`schedules lift: HTTP ${response.status}${detail ? ` \u2014 ${detail}` : ""}`);
17472
+ }
17473
+ return { repo, records, payload, response };
17474
+ }
17475
+ function registerSchedulesLiftCommand(program3, deps = {}) {
17476
+ const schedules = program3.commands.find((c) => c.name() === "schedules");
17477
+ if (!schedules) throw new Error("schedules lift: registerSchedulesCommands must run first \u2014 the lift attaches to the `schedules` command");
17478
+ schedules.command("lift").description("lift this repo's eight-field workflow headers into SCHEDULE# registry rows (C2, #3186) \u2014 a per-repo replace; aborts the whole lift on any header violation; exits 75 when the registry is unreachable (#3187)").option("--repo <name>", "bare repo name for the join key (defaults to the origin remote basename, case-preserved)").option("--dir <path>", "workflows directory to lift (defaults to .github/workflows)").option("--dry-run", "print the {repo, schedules} body that would be POSTed; never write").action(async (o) => {
17479
+ try {
17480
+ const result = await runSchedulesLift({ repo: o.repo, dir: o.dir, dryRun: Boolean(o.dryRun) }, deps);
17481
+ if (o.dryRun) {
17482
+ console.log(JSON.stringify(result.payload, null, 2));
17483
+ return;
17484
+ }
17485
+ console.log(JSON.stringify(result.response?.body));
17486
+ } catch (e) {
17487
+ const err = e;
17488
+ if (err instanceof RegistryUnreachableError) {
17489
+ console.error(`mmi-cli ${err.message}`);
17490
+ return await cleanExit(err.exitCode);
17491
+ }
17492
+ return await failGraceful(err.message);
17493
+ }
17494
+ });
17495
+ }
17496
+
17155
17497
  // src/edge-tunnel.ts
17156
17498
  var HOSTNAME_RE = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/;
17157
17499
  var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
@@ -17611,7 +17953,7 @@ function registerQueryCommands(program3) {
17611
17953
  }
17612
17954
 
17613
17955
  // src/bootstrap-commands.ts
17614
- var import_node_fs18 = require("node:fs");
17956
+ var import_node_fs19 = require("node:fs");
17615
17957
 
17616
17958
  // src/bootstrap-verify.ts
17617
17959
  var TRAIN_BRANCHES2 = ["development", "rc", "main"];
@@ -18159,7 +18501,7 @@ function registerBootstrapCommands(program3) {
18159
18501
  client: defaultGitHubClient(),
18160
18502
  projectMeta: meta,
18161
18503
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
18162
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null,
18504
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null,
18163
18505
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
18164
18506
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
18165
18507
  requiredGcpApis: (() => {
@@ -18210,12 +18552,12 @@ function registerBootstrapCommands(program3) {
18210
18552
  return fail(`bootstrap apply: ${e.message}`);
18211
18553
  }
18212
18554
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
18213
- if (!(0, import_node_fs18.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`);
18214
- const manifest = loadBootstrapSeeds((0, import_node_fs18.readFileSync)(manifestPath, "utf8"));
18555
+ if (!(0, import_node_fs19.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`);
18556
+ const manifest = loadBootstrapSeeds((0, import_node_fs19.readFileSync)(manifestPath, "utf8"));
18215
18557
  const baseBranch = o.class === "content" ? "main" : "development";
18216
18558
  const slug = parsedRepo.slug;
18217
18559
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
18218
- const readFile5 = (p) => (0, import_node_fs18.existsSync)(p) ? (0, import_node_fs18.readFileSync)(p, "utf8") : null;
18560
+ const readFile6 = (p) => (0, import_node_fs19.existsSync)(p) ? (0, import_node_fs19.readFileSync)(p, "utf8") : null;
18219
18561
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
18220
18562
  const rawVars = {};
18221
18563
  for (const value of cmdOpts.var ?? []) {
@@ -18293,7 +18635,7 @@ function registerBootstrapCommands(program3) {
18293
18635
  }
18294
18636
  const planned = planSeedAction(resolved, exists);
18295
18637
  const isBlock = resolved.source === "managed-block";
18296
- const content = planned.action === "create" || planned.action === "update" ? isBlock ? upsertManagedGitignoreBlock(remoteContent).content : resolveSeedContent(resolved, vars, readFile5) : null;
18638
+ const content = planned.action === "create" || planned.action === "update" ? isBlock ? upsertManagedGitignoreBlock(remoteContent).content : resolveSeedContent(resolved, vars, readFile6) : null;
18297
18639
  const action = reconcileSeedAction(planned, content, isBlock, remoteContent);
18298
18640
  actions.push(action);
18299
18641
  if (o.execute && (action.action === "create" || action.action === "update")) {
@@ -18348,7 +18690,7 @@ function registerBootstrapCommands(program3) {
18348
18690
  }
18349
18691
  const rulesetSeed = manifest.seeds.find((s) => s.target === ".github/rulesets/mmi-product-required-checks.json");
18350
18692
  if (rulesetSeed) {
18351
- const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars, readFile5);
18693
+ const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars, readFile6);
18352
18694
  if (rulesetContent) {
18353
18695
  try {
18354
18696
  const activation = await activateProductRuleset(repo, stripRulesetComment(rulesetContent), defaultGitHubClient());
@@ -18411,12 +18753,12 @@ LIVE apply to ${repo}:
18411
18753
  }
18412
18754
 
18413
18755
  // src/stage-commands.ts
18414
- var import_node_fs20 = require("node:fs");
18415
- var import_node_path17 = require("node:path");
18756
+ var import_node_fs21 = require("node:fs");
18757
+ var import_node_path19 = require("node:path");
18416
18758
 
18417
18759
  // src/port-registry.ts
18418
- var import_node_fs19 = require("node:fs");
18419
- var import_node_path16 = require("node:path");
18760
+ var import_node_fs20 = require("node:fs");
18761
+ var import_node_path18 = require("node:path");
18420
18762
 
18421
18763
  // ../infra/port-geometry.mjs
18422
18764
  var PORT_BLOCK = 100;
@@ -18430,8 +18772,8 @@ function nextPortBlock(registry2) {
18430
18772
  return [base, base + PORT_SPAN];
18431
18773
  }
18432
18774
  function loadPortRegistry(path2) {
18433
- if (!(0, import_node_fs19.existsSync)(path2)) return {};
18434
- const raw = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
18775
+ if (!(0, import_node_fs20.existsSync)(path2)) return {};
18776
+ const raw = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
18435
18777
  const out = {};
18436
18778
  for (const [key, value] of Object.entries(raw)) {
18437
18779
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -18445,9 +18787,9 @@ function ensurePortRange(repo, path2) {
18445
18787
  const existing = registry2[repo];
18446
18788
  if (existing) return existing;
18447
18789
  const range = nextPortBlock(registry2);
18448
- const raw = (0, import_node_fs19.existsSync)(path2) ? JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8")) : {};
18790
+ const raw = (0, import_node_fs20.existsSync)(path2) ? JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8")) : {};
18449
18791
  raw[repo] = range;
18450
- (0, import_node_fs19.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
18792
+ (0, import_node_fs20.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
18451
18793
  return range;
18452
18794
  }
18453
18795
  function portCursorSeed(registry2) {
@@ -18469,22 +18811,22 @@ function existingPortRange(repo, registry2) {
18469
18811
  return registry2[repo] ?? null;
18470
18812
  }
18471
18813
  function portRangeInfraAt(root, source) {
18472
- const registryPath = (0, import_node_path16.join)(root, "infra", "port-ranges.json");
18473
- const ddbScriptPath = (0, import_node_path16.join)(root, "infra", "port-ddb.mjs");
18474
- if (!(0, import_node_fs19.existsSync)(registryPath) || !(0, import_node_fs19.existsSync)(ddbScriptPath)) return null;
18814
+ const registryPath = (0, import_node_path18.join)(root, "infra", "port-ranges.json");
18815
+ const ddbScriptPath = (0, import_node_path18.join)(root, "infra", "port-ddb.mjs");
18816
+ if (!(0, import_node_fs20.existsSync)(registryPath) || !(0, import_node_fs20.existsSync)(ddbScriptPath)) return null;
18475
18817
  return { root, source, registryPath, ddbScriptPath };
18476
18818
  }
18477
18819
  function resolvePortRangeInfra(cwd, packageDir) {
18478
18820
  const direct = portRangeInfraAt(cwd, "cwd");
18479
18821
  if (direct) return direct;
18480
- for (let dir = cwd; ; dir = (0, import_node_path16.dirname)(dir)) {
18481
- const sibling = portRangeInfraAt((0, import_node_path16.join)(dir, "MMI-Hub"), "sibling-hub");
18822
+ for (let dir = cwd; ; dir = (0, import_node_path18.dirname)(dir)) {
18823
+ const sibling = portRangeInfraAt((0, import_node_path18.join)(dir, "MMI-Hub"), "sibling-hub");
18482
18824
  if (sibling) return sibling;
18483
- const parent = (0, import_node_path16.dirname)(dir);
18825
+ const parent = (0, import_node_path18.dirname)(dir);
18484
18826
  if (parent === dir) break;
18485
18827
  }
18486
18828
  if (packageDir) {
18487
- const pkgRoot = (0, import_node_path16.join)(packageDir, "..", "..");
18829
+ const pkgRoot = (0, import_node_path18.join)(packageDir, "..", "..");
18488
18830
  const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
18489
18831
  if (pkgFrom) return pkgFrom;
18490
18832
  }
@@ -18660,8 +19002,8 @@ function registerStageCommands(program3) {
18660
19002
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
18661
19003
  return decideStage({
18662
19004
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
18663
- hasCompose: (0, import_node_fs20.existsSync)((0, import_node_path17.join)(process.cwd(), "docker-compose.yml")),
18664
- hasEnvExample: (0, import_node_fs20.existsSync)((0, import_node_path17.join)(process.cwd(), ".env.example"))
19005
+ hasCompose: (0, import_node_fs21.existsSync)((0, import_node_path19.join)(process.cwd(), "docker-compose.yml")),
19006
+ hasEnvExample: (0, import_node_fs21.existsSync)((0, import_node_path19.join)(process.cwd(), ".env.example"))
18665
19007
  });
18666
19008
  }
18667
19009
  async function fetchStageVaultEnvMerge() {
@@ -19099,8 +19441,8 @@ function registerBoardCommands(program3) {
19099
19441
  }
19100
19442
 
19101
19443
  // src/merge-cleanup.ts
19102
- var import_node_fs21 = require("node:fs");
19103
- var import_promises4 = require("node:fs/promises");
19444
+ var import_node_fs22 = require("node:fs");
19445
+ var import_promises5 = require("node:fs/promises");
19104
19446
  var import_node_child_process10 = require("node:child_process");
19105
19447
 
19106
19448
  // src/board-advance.ts
@@ -19186,23 +19528,23 @@ function boardAdvanceFailureMessage(result) {
19186
19528
  }
19187
19529
 
19188
19530
  // src/deferred-registry-store.ts
19189
- var import_promises3 = require("node:fs/promises");
19190
- var import_node_path18 = require("node:path");
19531
+ var import_promises4 = require("node:fs/promises");
19532
+ var import_node_path20 = require("node:path");
19191
19533
  var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
19192
19534
  async function atomicWrite(target, contents) {
19193
19535
  const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
19194
- await (0, import_promises3.writeFile)(tmp, contents, "utf8");
19536
+ await (0, import_promises4.writeFile)(tmp, contents, "utf8");
19195
19537
  try {
19196
19538
  await renameWithRetry(tmp, target);
19197
19539
  } catch (e) {
19198
- await (0, import_promises3.unlink)(tmp).catch(() => void 0);
19540
+ await (0, import_promises4.unlink)(tmp).catch(() => void 0);
19199
19541
  throw e;
19200
19542
  }
19201
19543
  }
19202
19544
  async function renameWithRetry(from, to, attempts = 5, backoffMs = 20) {
19203
19545
  for (let i = 0; ; i++) {
19204
19546
  try {
19205
- await (0, import_promises3.rename)(from, to);
19547
+ await (0, import_promises4.rename)(from, to);
19206
19548
  return;
19207
19549
  } catch (e) {
19208
19550
  const code = e.code;
@@ -19214,7 +19556,7 @@ async function renameWithRetry(from, to, attempts = 5, backoffMs = 20) {
19214
19556
  async function readStrict(registryPath) {
19215
19557
  let text;
19216
19558
  try {
19217
- text = await (0, import_promises3.readFile)(registryPath, "utf8");
19559
+ text = await (0, import_promises4.readFile)(registryPath, "utf8");
19218
19560
  } catch (e) {
19219
19561
  if (e.code === "ENOENT") return [];
19220
19562
  throw e;
@@ -19225,15 +19567,15 @@ async function acquireLock(lockPath, opts, deadline) {
19225
19567
  for (; ; ) {
19226
19568
  let handle;
19227
19569
  try {
19228
- handle = await (0, import_promises3.open)(lockPath, "wx");
19570
+ handle = await (0, import_promises4.open)(lockPath, "wx");
19229
19571
  } catch (e) {
19230
19572
  const code = e.code;
19231
19573
  if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
19232
19574
  try {
19233
- const age = Date.now() - (await (0, import_promises3.stat)(lockPath)).mtimeMs;
19575
+ const age = Date.now() - (await (0, import_promises4.stat)(lockPath)).mtimeMs;
19234
19576
  if (age > opts.staleMs) {
19235
19577
  try {
19236
- await (0, import_promises3.unlink)(lockPath);
19578
+ await (0, import_promises4.unlink)(lockPath);
19237
19579
  continue;
19238
19580
  } catch (unlinkError) {
19239
19581
  const code2 = unlinkError.code;
@@ -19262,7 +19604,7 @@ async function acquireLock(lockPath, opts, deadline) {
19262
19604
  }
19263
19605
  async function lockHeldBy(lockPath, token) {
19264
19606
  try {
19265
- return await (0, import_promises3.readFile)(lockPath, "utf8") === token;
19607
+ return await (0, import_promises4.readFile)(lockPath, "utf8") === token;
19266
19608
  } catch {
19267
19609
  return false;
19268
19610
  }
@@ -19270,7 +19612,7 @@ async function lockHeldBy(lockPath, token) {
19270
19612
  async function releaseLock(lockPath, guard) {
19271
19613
  await guard.handle.close().catch(() => void 0);
19272
19614
  if (await lockHeldBy(lockPath, guard.token)) {
19273
- await (0, import_promises3.unlink)(lockPath).catch(() => void 0);
19615
+ await (0, import_promises4.unlink)(lockPath).catch(() => void 0);
19274
19616
  }
19275
19617
  }
19276
19618
  function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
@@ -19284,19 +19626,19 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
19284
19626
  // Lenient read for the sweep's initial fetch: any error (missing/corrupt) yields an empty queue.
19285
19627
  read: async () => {
19286
19628
  try {
19287
- return parseDeferredWorktreesFile(await (0, import_promises3.readFile)(registryPath, "utf8"));
19629
+ return parseDeferredWorktreesFile(await (0, import_promises4.readFile)(registryPath, "utf8"));
19288
19630
  } catch {
19289
19631
  return [];
19290
19632
  }
19291
19633
  },
19292
19634
  // Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
19293
19635
  write: async (entries) => {
19294
- await (0, import_promises3.mkdir)((0, import_node_path18.dirname)(registryPath), { recursive: true });
19636
+ await (0, import_promises4.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
19295
19637
  await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
19296
19638
  },
19297
19639
  // Serialized read-modify-write under the repo-wide lock (#2846).
19298
19640
  update: async (mutate) => {
19299
- await (0, import_promises3.mkdir)((0, import_node_path18.dirname)(registryPath), { recursive: true });
19641
+ await (0, import_promises4.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
19300
19642
  const deadline = Date.now() + opts.maxWaitMs;
19301
19643
  for (; ; ) {
19302
19644
  const guard = await acquireLock(lockPath, opts, deadline);
@@ -19423,7 +19765,7 @@ async function applyGcPlan(plan, remote) {
19423
19765
  return cleanupPrMergeLocalBranch(branch.branch, {
19424
19766
  beforeWorktrees,
19425
19767
  startingPath: branch.worktreePath,
19426
- pathExists: (p) => (0, import_node_fs21.existsSync)(p),
19768
+ pathExists: (p) => (0, import_node_fs22.existsSync)(p),
19427
19769
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
19428
19770
  teardownWorktreeStage,
19429
19771
  deferredStore,
@@ -19449,7 +19791,7 @@ async function applyGcPlan(plan, remote) {
19449
19791
  for (const wt of plan.worktreeDirs) {
19450
19792
  try {
19451
19793
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
19452
- realpath: (path2) => (0, import_node_fs21.realpathSync)(path2)
19794
+ realpath: (path2) => (0, import_node_fs22.realpathSync)(path2)
19453
19795
  });
19454
19796
  if (!cleanupTarget.ok) {
19455
19797
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -19576,13 +19918,13 @@ var realWorktreeDirRemover = {
19576
19918
  probe: (p) => {
19577
19919
  let st;
19578
19920
  try {
19579
- st = (0, import_node_fs21.lstatSync)(p);
19921
+ st = (0, import_node_fs22.lstatSync)(p);
19580
19922
  } catch {
19581
19923
  return null;
19582
19924
  }
19583
19925
  if (st.isSymbolicLink()) return "link";
19584
19926
  try {
19585
- (0, import_node_fs21.readlinkSync)(p);
19927
+ (0, import_node_fs22.readlinkSync)(p);
19586
19928
  return "link";
19587
19929
  } catch {
19588
19930
  }
@@ -19590,7 +19932,7 @@ var realWorktreeDirRemover = {
19590
19932
  },
19591
19933
  readdir: (p) => {
19592
19934
  try {
19593
- return (0, import_node_fs21.readdirSync)(p);
19935
+ return (0, import_node_fs22.readdirSync)(p);
19594
19936
  } catch {
19595
19937
  return [];
19596
19938
  }
@@ -19599,12 +19941,12 @@ var realWorktreeDirRemover = {
19599
19941
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
19600
19942
  detachLink: (p) => {
19601
19943
  try {
19602
- (0, import_node_fs21.rmdirSync)(p);
19944
+ (0, import_node_fs22.rmdirSync)(p);
19603
19945
  } catch {
19604
- (0, import_node_fs21.unlinkSync)(p);
19946
+ (0, import_node_fs22.unlinkSync)(p);
19605
19947
  }
19606
19948
  },
19607
- removeTree: (p) => (0, import_promises4.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
19949
+ removeTree: (p) => (0, import_promises5.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
19608
19950
  };
19609
19951
  async function resolvePrimaryCheckout(execGit) {
19610
19952
  try {
@@ -19634,9 +19976,9 @@ async function worktreeHasStageState(worktreePath) {
19634
19976
  }
19635
19977
  }
19636
19978
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
19637
- if (!(0, import_node_fs21.existsSync)(statePath)) return false;
19979
+ if (!(0, import_node_fs22.existsSync)(statePath)) return false;
19638
19980
  try {
19639
- const state = JSON.parse((0, import_node_fs21.readFileSync)(statePath, "utf8"));
19981
+ const state = JSON.parse((0, import_node_fs22.readFileSync)(statePath, "utf8"));
19640
19982
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
19641
19983
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
19642
19984
  } catch {
@@ -19764,8 +20106,8 @@ async function fetchRestCorePool(gh = defaultGhApi) {
19764
20106
  }
19765
20107
 
19766
20108
  // src/worktree-lifecycle-commands.ts
19767
- var import_node_fs22 = require("node:fs");
19768
- var import_node_path19 = require("node:path");
20109
+ var import_node_fs23 = require("node:fs");
20110
+ var import_node_path21 = require("node:path");
19769
20111
  var GH_TIMEOUT_MS = 2e4;
19770
20112
  var DEFAULT_BASE = "origin/development";
19771
20113
  var DEFAULT_REMOTE = "origin";
@@ -19902,13 +20244,13 @@ function registerWorktreeCommands(program3) {
19902
20244
  if (PROTECTED_BRANCHES2.has(branch)) {
19903
20245
  return fail(`worktree land: refusing to land protected branch '${branch}'`, { code: ERROR_CODES.ERR_BAD_ENUM });
19904
20246
  }
19905
- const gitFile = (0, import_node_path19.join)(wtPath, ".git");
19906
- const isLinked = (0, import_node_fs22.existsSync)(gitFile) && (0, import_node_fs22.statSync)(gitFile).isFile();
20247
+ const gitFile = (0, import_node_path21.join)(wtPath, ".git");
20248
+ const isLinked = (0, import_node_fs23.existsSync)(gitFile) && (0, import_node_fs23.statSync)(gitFile).isFile();
19907
20249
  if (apply && !isLinked) {
19908
20250
  return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
19909
20251
  }
19910
20252
  const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
19911
- const primaryCheckout = commonDir ? (0, import_node_path19.dirname)(commonDir) : wtPath;
20253
+ const primaryCheckout = commonDir ? (0, import_node_path21.dirname)(commonDir) : wtPath;
19912
20254
  const stageSummary = readStageSummary(wtPath);
19913
20255
  const hasStage = stageSummary != null;
19914
20256
  const steps = landSteps(branch, true, hasStage);
@@ -20020,10 +20362,10 @@ async function gatherWorktreeContext() {
20020
20362
  }
20021
20363
  const orphanDirs = [];
20022
20364
  const wtRoot = siblingMmiWorktreesRoot(repoRoot2);
20023
- if ((0, import_node_fs22.existsSync)(wtRoot)) {
20365
+ if ((0, import_node_fs23.existsSync)(wtRoot)) {
20024
20366
  let entries = [];
20025
20367
  try {
20026
- entries = (0, import_node_fs22.readdirSync)(wtRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path19.join)(wtRoot, e.name));
20368
+ entries = (0, import_node_fs23.readdirSync)(wtRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path21.join)(wtRoot, e.name));
20027
20369
  } catch {
20028
20370
  }
20029
20371
  const known = new Set(worktrees.map((w) => w.path));
@@ -20048,7 +20390,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
20048
20390
  }
20049
20391
 
20050
20392
  // src/issue-commands.ts
20051
- var import_node_fs23 = require("node:fs");
20393
+ var import_node_fs24 = require("node:fs");
20052
20394
  var import_node_crypto5 = require("node:crypto");
20053
20395
  var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
20054
20396
  async function editIssue(client, options, deps = {}) {
@@ -20062,7 +20404,7 @@ async function editIssue(client, options, deps = {}) {
20062
20404
  if (options.body !== void 0 || options.bodyFile !== void 0) {
20063
20405
  let body;
20064
20406
  if (options.bodyFile) {
20065
- const td = deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs23.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
20407
+ const td = deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs24.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
20066
20408
  body = await resolveIssueBody({ body: options.body, bodyFile: options.bodyFile }, td);
20067
20409
  } else {
20068
20410
  body = options.body ?? "";
@@ -20536,7 +20878,7 @@ function extendCreateCommand(issue2) {
20536
20878
  if (opts.batch) {
20537
20879
  let specs;
20538
20880
  try {
20539
- const raw = (0, import_node_fs23.readFileSync)(opts.batch, "utf8");
20881
+ const raw = (0, import_node_fs24.readFileSync)(opts.batch, "utf8");
20540
20882
  specs = JSON.parse(raw);
20541
20883
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
20542
20884
  } catch (e) {
@@ -20580,12 +20922,12 @@ ${lines}`);
20580
20922
  }
20581
20923
 
20582
20924
  // src/train-commands.ts
20583
- var import_node_fs25 = require("node:fs");
20584
- var import_node_path21 = require("node:path");
20925
+ var import_node_fs26 = require("node:fs");
20926
+ var import_node_path23 = require("node:path");
20585
20927
 
20586
20928
  // src/plugin-guard-io.ts
20587
- var import_node_fs24 = require("node:fs");
20588
- var import_node_path20 = require("node:path");
20929
+ var import_node_fs25 = require("node:fs");
20930
+ var import_node_path22 = require("node:path");
20589
20931
  var import_node_os6 = require("node:os");
20590
20932
 
20591
20933
  // src/plugin-guard.ts
@@ -20715,11 +21057,11 @@ function runHostBin(bin, args, opts) {
20715
21057
  }
20716
21058
  var installedPluginsPath2 = (surface = detectSurface(process.env)) => {
20717
21059
  const homeDir = surface === "codex" ? ".codex" : ".claude";
20718
- return (0, import_node_path20.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
21060
+ return (0, import_node_path22.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
20719
21061
  };
20720
21062
  function readInstalledPlugins(surface = detectSurface(process.env)) {
20721
21063
  try {
20722
- return JSON.parse((0, import_node_fs24.readFileSync)(installedPluginsPath2(surface), "utf8"));
21064
+ return JSON.parse((0, import_node_fs25.readFileSync)(installedPluginsPath2(surface), "utf8"));
20723
21065
  } catch {
20724
21066
  return null;
20725
21067
  }
@@ -20727,13 +21069,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
20727
21069
  function marketplaceCloneCandidates(surface, home) {
20728
21070
  if (surface === "codex") {
20729
21071
  return [
20730
- (0, import_node_path20.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
20731
- (0, import_node_path20.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
21072
+ (0, import_node_path22.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
21073
+ (0, import_node_path22.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
20732
21074
  ];
20733
21075
  }
20734
- return [(0, import_node_path20.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
21076
+ return [(0, import_node_path22.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
20735
21077
  }
20736
- function marketplaceClonePresent(surface, home, exists = import_node_fs24.existsSync) {
21078
+ function marketplaceClonePresent(surface, home, exists = import_node_fs25.existsSync) {
20737
21079
  return marketplaceCloneCandidates(surface, home).some(exists);
20738
21080
  }
20739
21081
  async function fetchNpmReleasedVersion() {
@@ -20751,7 +21093,7 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
20751
21093
  isOrgRepo,
20752
21094
  installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
20753
21095
  marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
20754
- pluginCachePresent: (0, import_node_fs24.existsSync)((0, import_node_path20.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
21096
+ pluginCachePresent: (0, import_node_fs25.existsSync)((0, import_node_path22.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
20755
21097
  };
20756
21098
  }
20757
21099
  function claudePluginGuardState(isOrgRepo) {
@@ -20875,7 +21217,7 @@ function formatTrainStatus(r) {
20875
21217
  // src/train-commands.ts
20876
21218
  function readRepoVersion() {
20877
21219
  try {
20878
- return JSON.parse((0, import_node_fs25.readFileSync)((0, import_node_path21.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
21220
+ return JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path23.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
20879
21221
  } catch {
20880
21222
  return void 0;
20881
21223
  }
@@ -21375,7 +21717,7 @@ function registerExplainCommand(program3) {
21375
21717
  }
21376
21718
 
21377
21719
  // src/pr-commands.ts
21378
- var import_promises5 = require("node:fs/promises");
21720
+ var import_promises6 = require("node:fs/promises");
21379
21721
  var GC_GH_TIMEOUT_MS4 = 2e4;
21380
21722
  var CHECKS_WATCH_POLL_MS = 15e3;
21381
21723
  var CHECKS_WATCH_TIMEOUT_MS = 10 * 6e4;
@@ -21519,10 +21861,10 @@ function registerPrLifecycleCommands(program3) {
21519
21861
  let body;
21520
21862
  try {
21521
21863
  if (o.title || o.titleFile) {
21522
- title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises5.readFile, readStdin });
21864
+ title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises6.readFile, readStdin });
21523
21865
  }
21524
21866
  if (o.body || o.bodyFile) {
21525
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises5.readFile, readStdin });
21867
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises6.readFile, readStdin });
21526
21868
  }
21527
21869
  } catch (e) {
21528
21870
  return fail(`pr edit: ${e.message}`);
@@ -21552,7 +21894,7 @@ function registerPrLifecycleCommands(program3) {
21552
21894
  }
21553
21895
  let body;
21554
21896
  try {
21555
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises5.readFile, readStdin });
21897
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises6.readFile, readStdin });
21556
21898
  } catch (e) {
21557
21899
  return fail(`pr comment: ${e.message}`);
21558
21900
  }
@@ -22105,7 +22447,7 @@ function checkSchedules(probe) {
22105
22447
  ok: false,
22106
22448
  label: "schedules",
22107
22449
  detail: `${probe.drift.length} drift finding(s)`,
22108
- fix: "run `mmi-cli org schedules` \u2014 file/registry/live disagree; each drift line names its per-class remedy, applied in the owning repo",
22450
+ fix: "run `mmi-cli org schedules` \u2014 each drift line names its per-class remedy (file/registry/live mismatch or harbour enforcement), applied in the owning repo",
22109
22451
  verbose: evidence
22110
22452
  };
22111
22453
  }
@@ -22277,9 +22619,9 @@ async function runDoctorClean(opts, io, deps) {
22277
22619
  }
22278
22620
 
22279
22621
  // src/doctor-io.ts
22280
- var import_node_fs26 = require("node:fs");
22622
+ var import_node_fs27 = require("node:fs");
22281
22623
  var import_node_os7 = require("node:os");
22282
- var import_node_path22 = require("node:path");
22624
+ var import_node_path24 = require("node:path");
22283
22625
  var import_node_child_process11 = require("node:child_process");
22284
22626
  var import_node_util8 = require("node:util");
22285
22627
  var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process11.execFile);
@@ -22287,7 +22629,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
22287
22629
  function installedClaudePluginVersion() {
22288
22630
  try {
22289
22631
  const file = JSON.parse(
22290
- (0, import_node_fs26.readFileSync)((0, import_node_path22.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
22632
+ (0, import_node_fs27.readFileSync)((0, import_node_path24.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
22291
22633
  );
22292
22634
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
22293
22635
  if (versions.length === 0) return void 0;
@@ -22308,13 +22650,13 @@ function worktreeRootSync() {
22308
22650
  }
22309
22651
  var gitignorePath = () => {
22310
22652
  const root = worktreeRootSync();
22311
- return root === null ? null : (0, import_node_path22.join)(root, ".gitignore");
22653
+ return root === null ? null : (0, import_node_path24.join)(root, ".gitignore");
22312
22654
  };
22313
22655
  function readGitignore() {
22314
22656
  const path2 = gitignorePath();
22315
22657
  if (path2 === null) return null;
22316
22658
  try {
22317
- return (0, import_node_fs26.readFileSync)(path2, "utf8");
22659
+ return (0, import_node_fs27.readFileSync)(path2, "utf8");
22318
22660
  } catch {
22319
22661
  return null;
22320
22662
  }
@@ -22323,7 +22665,7 @@ function writeGitignore(content) {
22323
22665
  const path2 = gitignorePath();
22324
22666
  if (path2 === null) return false;
22325
22667
  try {
22326
- (0, import_node_fs26.writeFileSync)(path2, content, "utf8");
22668
+ (0, import_node_fs27.writeFileSync)(path2, content, "utf8");
22327
22669
  return true;
22328
22670
  } catch {
22329
22671
  return false;
@@ -22347,7 +22689,7 @@ async function repoRoot() {
22347
22689
  }
22348
22690
  function hasRepoLocalWorktrees() {
22349
22691
  const root = worktreeRootSync();
22350
- return root !== null && (0, import_node_fs26.existsSync)((0, import_node_path22.join)(root, ".worktrees"));
22692
+ return root !== null && (0, import_node_fs27.existsSync)((0, import_node_path24.join)(root, ".worktrees"));
22351
22693
  }
22352
22694
 
22353
22695
  // src/index.ts
@@ -22520,19 +22862,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
22520
22862
  });
22521
22863
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
22522
22864
  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) => {
22523
- const path2 = (0, import_node_path23.join)(process.cwd(), ".gitignore");
22524
- const current = (0, import_node_fs27.existsSync)(path2) ? (0, import_node_fs27.readFileSync)(path2, "utf8") : null;
22865
+ const path2 = (0, import_node_path25.join)(process.cwd(), ".gitignore");
22866
+ const current = (0, import_node_fs28.existsSync)(path2) ? (0, import_node_fs28.readFileSync)(path2, "utf8") : null;
22525
22867
  const plan = planManagedGitignore(current);
22526
22868
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
22527
22869
  if (opts.json) {
22528
- if (opts.write && plan.changed) (0, import_node_fs27.writeFileSync)(path2, plan.content, "utf8");
22870
+ if (opts.write && plan.changed) (0, import_node_fs28.writeFileSync)(path2, plan.content, "utf8");
22529
22871
  console.log(JSON.stringify(plan, null, 2));
22530
22872
  if (!opts.write && plan.changed) process.exitCode = 1;
22531
22873
  return;
22532
22874
  }
22533
22875
  if (opts.write) {
22534
22876
  if (plan.changed) {
22535
- (0, import_node_fs27.writeFileSync)(path2, plan.content, "utf8");
22877
+ (0, import_node_fs28.writeFileSync)(path2, plan.content, "utf8");
22536
22878
  console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
22537
22879
  } else {
22538
22880
  console.log("mmi-cli org rules gitignore: up to date");
@@ -22722,26 +23064,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
22722
23064
  function acquireWorktreeSetupLock(worktreeRoot) {
22723
23065
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
22724
23066
  const take = () => {
22725
- const fd = (0, import_node_fs27.openSync)(lockPath, "wx");
23067
+ const fd = (0, import_node_fs28.openSync)(lockPath, "wx");
22726
23068
  try {
22727
- (0, import_node_fs27.writeSync)(fd, String(Date.now()));
23069
+ (0, import_node_fs28.writeSync)(fd, String(Date.now()));
22728
23070
  } finally {
22729
- (0, import_node_fs27.closeSync)(fd);
23071
+ (0, import_node_fs28.closeSync)(fd);
22730
23072
  }
22731
23073
  return () => {
22732
23074
  try {
22733
- (0, import_node_fs27.rmSync)(lockPath, { force: true });
23075
+ (0, import_node_fs28.rmSync)(lockPath, { force: true });
22734
23076
  } catch {
22735
23077
  }
22736
23078
  };
22737
23079
  };
22738
23080
  try {
22739
- (0, import_node_fs27.mkdirSync)((0, import_node_path23.dirname)(lockPath), { recursive: true });
23081
+ (0, import_node_fs28.mkdirSync)((0, import_node_path25.dirname)(lockPath), { recursive: true });
22740
23082
  return take();
22741
23083
  } catch {
22742
23084
  try {
22743
- if (Date.now() - (0, import_node_fs27.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
22744
- (0, import_node_fs27.rmSync)(lockPath, { force: true });
23085
+ if (Date.now() - (0, import_node_fs28.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
23086
+ (0, import_node_fs28.rmSync)(lockPath, { force: true });
22745
23087
  return take();
22746
23088
  }
22747
23089
  } catch {
@@ -22929,6 +23271,7 @@ registerSecretsCommands(program2);
22929
23271
  registerEdgeCommands(program2);
22930
23272
  registerBoxCommands(program2);
22931
23273
  registerSchedulesCommands(program2);
23274
+ registerSchedulesLiftCommand(program2);
22932
23275
  var docs = program2.command("docs").description("generated docs surfaces \u2014 the routing index (org knowledge layer)");
22933
23276
  docs.command("index").description("regenerate docs/index.md from the docs/ tree (--write, the default) or fail on drift (--check) \u2014 the generated routing index, never hand-maintained").option("--check", "compare against the committed docs/index.md and exit 1 on drift; never write").option("--write", "regenerate docs/index.md when it has drifted (the default)").action(async (o) => {
22934
23277
  try {
@@ -23161,7 +23504,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
23161
23504
  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`);
23162
23505
  if (o.secretsFile) {
23163
23506
  try {
23164
- vars.push(`secrets=${(0, import_node_fs27.readFileSync)(o.secretsFile, "utf8")}`);
23507
+ vars.push(`secrets=${(0, import_node_fs28.readFileSync)(o.secretsFile, "utf8")}`);
23165
23508
  } catch (e) {
23166
23509
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
23167
23510
  }
@@ -23463,8 +23806,8 @@ withExamples(mutating(
23463
23806
  let extraLabels = [];
23464
23807
  try {
23465
23808
  issueType = resolveCreateType(o.type, "issue create");
23466
- title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises6.readFile, readStdin });
23467
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises6.readFile, readStdin });
23809
+ title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
23810
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
23468
23811
  if (o.idempotencyKey) body = appendIdempotencyMarker(body, o.idempotencyKey);
23469
23812
  priority = resolveCreatePriority(o.priority, "issue create");
23470
23813
  extraLabels = [...o.label ?? []];
@@ -23600,7 +23943,7 @@ issue.command("comment <ref>").description("post a Markdown comment to an issue
23600
23943
  }
23601
23944
  let body;
23602
23945
  try {
23603
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises6.readFile, readStdin });
23946
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
23604
23947
  } catch (e) {
23605
23948
  return fail(`issue comment: ${e.message}`);
23606
23949
  }
@@ -23660,8 +24003,8 @@ program2.command("report").description("file a friction report on the Hub board
23660
24003
  let title;
23661
24004
  const sourceRepo = o.repo ?? await resolveRepo(void 0);
23662
24005
  try {
23663
- title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises6.readFile, readStdin });
23664
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises6.readFile, readStdin });
24006
+ title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
24007
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
23665
24008
  priority = resolveCreatePriority(o.priority, "report");
23666
24009
  if (!ISSUE_TYPES.includes(o.type)) {
23667
24010
  throw new Error(`unknown issue type "${o.type}" \u2014 expected one of: ${ISSUE_TYPES.join(", ")}`);
@@ -23710,8 +24053,8 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
23710
24053
  let args;
23711
24054
  try {
23712
24055
  skill = assertSkillName(o.skill);
23713
- rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises6.readFile, readStdin });
23714
- const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises6.readFile, readStdin });
24056
+ rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
24057
+ const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
23715
24058
  title = buildSkillLessonTitle(skill, rawTitle);
23716
24059
  priority = resolveCreatePriority(o.priority, "skill-lesson");
23717
24060
  body = buildSkillLessonBody(rawBody, sourceRepo, pluginSha);
@@ -23762,8 +24105,8 @@ withExamples(pr.command("create").description("create a PR and print {number,url
23762
24105
  let body;
23763
24106
  let title;
23764
24107
  try {
23765
- title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises6.readFile, readStdin });
23766
- body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises6.readFile, readStdin });
24108
+ title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
24109
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
23767
24110
  } catch (e) {
23768
24111
  return fail(`pr create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
23769
24112
  }
@@ -23798,11 +24141,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
23798
24141
  }
23799
24142
  });
23800
24143
  async function listCiWorkflowPaths(cwd = process.cwd()) {
23801
- const wfDir = (0, import_node_path23.join)(cwd, ".github", "workflows");
23802
- if (!(0, import_node_fs27.existsSync)(wfDir)) return [];
23803
- return (0, import_node_fs27.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
24144
+ const wfDir = (0, import_node_path25.join)(cwd, ".github", "workflows");
24145
+ if (!(0, import_node_fs28.existsSync)(wfDir)) return [];
24146
+ return (0, import_node_fs28.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
23804
24147
  try {
23805
- return workflowReportsPrChecks((0, import_node_fs27.readFileSync)((0, import_node_path23.join)(wfDir, name), "utf8"));
24148
+ return workflowReportsPrChecks((0, import_node_fs28.readFileSync)((0, import_node_path25.join)(wfDir, name), "utf8"));
23806
24149
  } catch {
23807
24150
  return true;
23808
24151
  }
@@ -23829,16 +24172,16 @@ function ciAuditDeps() {
23829
24172
  // gate re-seed step is skipped gracefully rather than failing mid-run.
23830
24173
  readSeedFile: (path2) => {
23831
24174
  if (!root) return null;
23832
- const fullPath = (0, import_node_path23.join)(root, path2);
23833
- return (0, import_node_fs27.existsSync)(fullPath) ? (0, import_node_fs27.readFileSync)(fullPath, "utf8") : null;
24175
+ const fullPath = (0, import_node_path25.join)(root, path2);
24176
+ return (0, import_node_fs28.existsSync)(fullPath) ? (0, import_node_fs28.readFileSync)(fullPath, "utf8") : null;
23834
24177
  }
23835
24178
  };
23836
24179
  }
23837
24180
  function hubRoot() {
23838
- const fromPkg = (0, import_node_path23.join)(__dirname, "..", "..");
24181
+ const fromPkg = (0, import_node_path25.join)(__dirname, "..", "..");
23839
24182
  const marker = "skills/bootstrap/seeds/manifest.json";
23840
- if ((0, import_node_fs27.existsSync)((0, import_node_path23.join)(fromPkg, marker))) return fromPkg;
23841
- if ((0, import_node_fs27.existsSync)((0, import_node_path23.join)(process.cwd(), marker))) return process.cwd();
24183
+ if ((0, import_node_fs28.existsSync)((0, import_node_path25.join)(fromPkg, marker))) return fromPkg;
24184
+ if ((0, import_node_fs28.existsSync)((0, import_node_path25.join)(process.cwd(), marker))) return process.cwd();
23842
24185
  return null;
23843
24186
  }
23844
24187
  pr.command("ci-policy").description("report merge CI policy: wait-for-checks vs no-ci (for grind/build agents)").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current checkout)").action(async (o) => {
@@ -24056,7 +24399,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
24056
24399
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
24057
24400
  beforeWorktrees,
24058
24401
  startingPath,
24059
- pathExists: (p) => (0, import_node_fs27.existsSync)(p),
24402
+ pathExists: (p) => (0, import_node_fs28.existsSync)(p),
24060
24403
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
24061
24404
  teardownWorktreeStage,
24062
24405
  deferredStore,
@@ -24166,8 +24509,8 @@ function trainApplyDeps() {
24166
24509
  // Slack release announcement (#883): Hub-only + best-effort inside announceRelease itself.
24167
24510
  announce: (args) => announceRelease({
24168
24511
  run: async (file, cmdArgs) => (await execFileP2(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
24169
- readFile: (path2) => (0, import_promises6.readFile)(path2, "utf8"),
24170
- removeFile: (path2) => (0, import_promises6.unlink)(path2)
24512
+ readFile: (path2) => (0, import_promises7.readFile)(path2, "utf8"),
24513
+ removeFile: (path2) => (0, import_promises7.unlink)(path2)
24171
24514
  }, args),
24172
24515
  fetchEdgeDomains: async (slug) => {
24173
24516
  const proj = await fetchProjectBySlug(slug, registryClientDeps(await loadConfig()));
@@ -24396,10 +24739,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
24396
24739
  targets = resolution.targets;
24397
24740
  }
24398
24741
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
24399
- const fileMatrix = (0, import_node_fs27.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs27.readFileSync)("access-matrix.json", "utf8")) : {};
24742
+ const fileMatrix = (0, import_node_fs28.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs28.readFileSync)("access-matrix.json", "utf8")) : {};
24400
24743
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
24401
24744
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
24402
- const fileContracts = (0, import_node_fs27.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs27.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
24745
+ const fileContracts = (0, import_node_fs28.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs28.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
24403
24746
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
24404
24747
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
24405
24748
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -24432,16 +24775,16 @@ function directoryBytes(path2) {
24432
24775
  let total = 0;
24433
24776
  let entries;
24434
24777
  try {
24435
- entries = (0, import_node_fs27.readdirSync)(path2, { withFileTypes: true });
24778
+ entries = (0, import_node_fs28.readdirSync)(path2, { withFileTypes: true });
24436
24779
  } catch {
24437
24780
  return 0;
24438
24781
  }
24439
24782
  for (const entry of entries) {
24440
- const child2 = (0, import_node_path23.join)(path2, entry.name);
24783
+ const child2 = (0, import_node_path25.join)(path2, entry.name);
24441
24784
  if (entry.isDirectory()) total += directoryBytes(child2);
24442
24785
  else {
24443
24786
  try {
24444
- total += (0, import_node_fs27.statSync)(child2).size;
24787
+ total += (0, import_node_fs28.statSync)(child2).size;
24445
24788
  } catch {
24446
24789
  }
24447
24790
  }
@@ -24449,25 +24792,25 @@ function directoryBytes(path2) {
24449
24792
  return total;
24450
24793
  }
24451
24794
  function listDirEntries(dir) {
24452
- return (0, import_node_fs27.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
24795
+ return (0, import_node_fs28.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
24453
24796
  }
24454
24797
  function readInstalledPluginRefs(home) {
24455
24798
  const p = installedPluginsPath(home);
24456
- if (!(0, import_node_fs27.existsSync)(p)) return [];
24799
+ if (!(0, import_node_fs28.existsSync)(p)) return [];
24457
24800
  try {
24458
- return installedPluginPaths((0, import_node_fs27.readFileSync)(p, "utf8"));
24801
+ return installedPluginPaths((0, import_node_fs28.readFileSync)(p, "utf8"));
24459
24802
  } catch {
24460
24803
  return null;
24461
24804
  }
24462
24805
  }
24463
24806
  function pluginCacheFsDeps(home, dirBytes) {
24464
24807
  return {
24465
- exists: (p) => (0, import_node_fs27.existsSync)(p),
24466
- listVersionDirs: (root) => (0, import_node_fs27.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
24808
+ exists: (p) => (0, import_node_fs28.existsSync)(p),
24809
+ listVersionDirs: (root) => (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
24467
24810
  dirBytes,
24468
- listStagingDirs: (root) => (0, import_node_fs27.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
24811
+ listStagingDirs: (root) => (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
24469
24812
  try {
24470
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path23.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs27.statSync)(p).mtimeMs) };
24813
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path25.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs28.statSync)(p).mtimeMs) };
24471
24814
  } catch {
24472
24815
  return { name: d.name, mtimeMs: Date.now() };
24473
24816
  }
@@ -24481,10 +24824,10 @@ function stagingApplyFsGuard(home) {
24481
24824
  return {
24482
24825
  referencedPaths: () => readInstalledPluginRefs(home),
24483
24826
  mtimeMs: (name) => {
24484
- const p = (0, import_node_path23.join)(stagingRoot, name);
24485
- if (!(0, import_node_fs27.existsSync)(p)) return null;
24827
+ const p = (0, import_node_path25.join)(stagingRoot, name);
24828
+ if (!(0, import_node_fs28.existsSync)(p)) return null;
24486
24829
  try {
24487
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs27.statSync)(q).mtimeMs);
24830
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs28.statSync)(q).mtimeMs);
24488
24831
  } catch {
24489
24832
  return null;
24490
24833
  }
@@ -24500,7 +24843,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
24500
24843
  { withBytes: true }
24501
24844
  );
24502
24845
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
24503
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs27.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os8.homedir)())) : void 0;
24846
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs28.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os8.homedir)())) : void 0;
24504
24847
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
24505
24848
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
24506
24849
  else console.log(renderPluginCachePlan(plan, result));