@mutmutco/cli 4.5.42 → 4.5.44

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 +553 -194
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -5496,8 +5496,8 @@ function resolveVerbAliasShim(argv) {
5496
5496
  }
5497
5497
 
5498
5498
  // src/command-composition.ts
5499
- var import_node_os22 = require("node:os");
5500
- var import_node_path51 = require("node:path");
5499
+ var import_node_os23 = require("node:os");
5500
+ var import_node_path52 = require("node:path");
5501
5501
 
5502
5502
  // src/board-read.ts
5503
5503
  var import_node_fs13 = require("node:fs");
@@ -7620,6 +7620,33 @@ function cadenceContainsCron(cadence, cron) {
7620
7620
  if (!cronTokens.length || cronTokens.length > cadenceTokens.length) return false;
7621
7621
  return cadenceTokens.some((_, i) => i + cronTokens.length <= cadenceTokens.length && cronTokens.every((t, j) => cadenceTokens[i + j] === t));
7622
7622
  }
7623
+ var DECLARED_UNSCHEDULED = /* @__PURE__ */ new Set(["on demand", "on-demand", "manual", "manual only", "event-driven"]);
7624
+ var CRON_FIELD2 = /^[0-9A-Za-z*,/\-]+$/;
7625
+ var DOW_NAMES = /* @__PURE__ */ new Set(["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]);
7626
+ function dowOk(dow) {
7627
+ return dow.split(",").every((part) => {
7628
+ const slash2 = part.indexOf("/");
7629
+ const step = slash2 === -1 ? "" : part.slice(slash2);
7630
+ if (step !== "" && !/^\/[1-9]\d*$/.test(step)) return false;
7631
+ const tokens2 = (slash2 === -1 ? part : part.slice(0, slash2)).split("-");
7632
+ if (tokens2.length > 2 || tokens2.some((t) => t === "")) return false;
7633
+ return tokens2.every((t) => t === "*" || (/^\d+$/.test(t) ? Number(t) <= 7 : DOW_NAMES.has(t.toUpperCase())));
7634
+ });
7635
+ }
7636
+ function cadenceProblem(cadence) {
7637
+ const c = cadence.trim();
7638
+ const cut = c.search(/[(+]/);
7639
+ const lead = (cut === -1 ? c : c.slice(0, cut)).trim();
7640
+ if (DECLARED_UNSCHEDULED.has(lead.toLowerCase())) return null;
7641
+ if (/^((?:rate|cron)\([^()]+\))(?:\s*[(+].*)?$/is.test(c)) return null;
7642
+ const f = lead.split(/\s+/);
7643
+ if (f.length !== 5) return `cadence not mappable to a Scheduler expression: ${JSON.stringify(cadence)}`;
7644
+ const [minute, hour, dom, month, dow] = f;
7645
+ if (![minute, hour, dom, month].every((x) => CRON_FIELD2.test(x)) || dom !== "*" && dow !== "*" || !dowOk(dow)) {
7646
+ return `cadence not mappable to a Scheduler expression: ${JSON.stringify(cadence)}`;
7647
+ }
7648
+ return null;
7649
+ }
7623
7650
  function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
7624
7651
  const crons = extractWorkflowCrons(yamlText);
7625
7652
  const header = parseScheduleHeader(yamlText);
@@ -7638,6 +7665,10 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
7638
7665
  if (!SCHEDULE_EXECUTORS.has(h.executor)) {
7639
7666
  throw new Error(`schedules register: ${expectedId} header \`executor: ${h.executor}\` is not a member of the closed executor taxonomy (${[...SCHEDULE_EXECUTORS].join(", ")}) \u2014 an unknown executor would register a row the harbour can never dispatch (docs/schedules.md "The entry template", H2 #4429).`);
7640
7667
  }
7668
+ const problem = cadenceProblem(h.cadence);
7669
+ if (problem) {
7670
+ throw new Error(`schedules register: ${expectedId} ${problem} \u2014 lead with a 5-field cron, rate(...) or cron(...), then any note in (...) or after +; or declare one of: ${[...DECLARED_UNSCHEDULED].join(", ")} (docs/schedules.md "The entry template", #7415).`);
7671
+ }
7641
7672
  for (const cron of crons) {
7642
7673
  if (!cadenceContainsCron(h.cadence, cron)) {
7643
7674
  throw new Error(`schedules register: ${expectedId} cadence \`${h.cadence}\` does not contain the workflow cron \`${cron}\` (whole-token comparison) \u2014 the declared cadence disagrees with the actual schedule.`);
@@ -14366,7 +14397,7 @@ function loadBootstrapSeeds(manifestJson) {
14366
14397
  };
14367
14398
  }
14368
14399
  function renderSeed(template, vars) {
14369
- return template.replace(PLACEHOLDER_RE, (match, key) => key in vars ? vars[key] : match);
14400
+ return template.replace(PLACEHOLDER_RE, (match, key) => key in vars ? vars[key].replace(PLACEHOLDER_RE, (inner, innerKey) => innerKey !== key && innerKey in vars ? vars[innerKey] : inner) : match);
14370
14401
  }
14371
14402
  function missingPlaceholders(rendered) {
14372
14403
  const out = /* @__PURE__ */ new Set();
@@ -15370,7 +15401,10 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
15370
15401
  GATE_MAX_SECONDS: DEFAULT_GATE_MAX_SECONDS,
15371
15402
  // The pin is the CLI's blessed SHA, not an operator knob — one central place to bump (#3178).
15372
15403
  GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA,
15373
- GATE_RUNNER_SHA: BLESSED_RUNNER_GATE_SHA
15404
+ GATE_RUNNER_SHA: BLESSED_RUNNER_GATE_SHA,
15405
+ // #7407: repo-declared service containers and extra check steps; empty renders today's gate byte-for-byte.
15406
+ GATE_SERVICES_YAML: "",
15407
+ GATE_EXTRA_STEPS_YAML: ""
15374
15408
  };
15375
15409
  const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
15376
15410
  const trackBranches = track === "trunk" ? ["main"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
@@ -15417,6 +15451,112 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranc
15417
15451
  if (!out.GATE_AFFECTED_CMD.trim()) out.GATE_AFFECTED_CMD = out.GATE_CMD;
15418
15452
  return out;
15419
15453
  }
15454
+ var GATE_SERVICE_KEYS = /* @__PURE__ */ new Set(["image", "env", "ports", "options", "tmpfs"]);
15455
+ var GATE_STEP_KEYS = /* @__PURE__ */ new Set(["name", "command", "env", "maxSeconds"]);
15456
+ function gateLine(value, field) {
15457
+ if (typeof value !== "string" || !value.trim() || /[\r\n]/.test(value)) throw new Error(`${field} must be a non-empty single-line string`);
15458
+ return value.trim();
15459
+ }
15460
+ function gateEnv(value, field) {
15461
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${field} must be an object of NAME: "value" strings`);
15462
+ const out = {};
15463
+ for (const [name, entry] of Object.entries(value)) {
15464
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error(`${field}.${name} is not a valid environment variable name`);
15465
+ if (typeof entry !== "string" || /[\r\n]/.test(entry)) throw new Error(`${field}.${name} must be a single-line string`);
15466
+ out[name] = entry;
15467
+ }
15468
+ return out;
15469
+ }
15470
+ function gateSeconds(value, field) {
15471
+ const text = typeof value === "number" ? String(value) : value;
15472
+ if (typeof text !== "string" || !/^\d+$/.test(text.trim()) || Number(text) <= 0) throw new Error(`${field} must be a positive whole number of seconds`);
15473
+ return text.trim();
15474
+ }
15475
+ function validateGateExtras(gate) {
15476
+ const out = {};
15477
+ if (gate.services !== void 0) {
15478
+ if (!gate.services || typeof gate.services !== "object" || Array.isArray(gate.services)) throw new Error("gate.services must be an object keyed by service name");
15479
+ out.services = {};
15480
+ for (const [name, raw] of Object.entries(gate.services)) {
15481
+ const field = `gate.services.${name}`;
15482
+ if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`${field}: service name must be lowercase letters, digits, - or _`);
15483
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`${field} must be an object with an image`);
15484
+ const service = raw;
15485
+ for (const key of Object.keys(service)) if (!GATE_SERVICE_KEYS.has(key)) throw new Error(`${field}.${key} is not a service field (expected image/env/ports/options/tmpfs)`);
15486
+ const parsed = { image: gateLine(service.image, `${field}.image`) };
15487
+ if (service.env !== void 0) parsed.env = gateEnv(service.env, `${field}.env`);
15488
+ if (service.ports !== void 0) {
15489
+ if (!Array.isArray(service.ports) || service.ports.length === 0) throw new Error(`${field}.ports must be a non-empty array`);
15490
+ parsed.ports = service.ports.map((port, index) => {
15491
+ const ok = typeof port === "number" && Number.isInteger(port) && port > 0 && port < 65536 || typeof port === "string" && /^\d{1,5}(:\d{1,5})?$/.test(port);
15492
+ if (!ok) throw new Error(`${field}.ports[${index}] must be a port number or "host:container"`);
15493
+ return port;
15494
+ });
15495
+ }
15496
+ if (service.options !== void 0) parsed.options = gateLine(service.options, `${field}.options`);
15497
+ if (service.tmpfs !== void 0) {
15498
+ if (!Array.isArray(service.tmpfs) || service.tmpfs.length === 0) throw new Error(`${field}.tmpfs must be a non-empty array of paths`);
15499
+ parsed.tmpfs = service.tmpfs.map((path2, index) => gateLine(path2, `${field}.tmpfs[${index}]`));
15500
+ }
15501
+ out.services[name] = parsed;
15502
+ }
15503
+ }
15504
+ if (gate.steps !== void 0) {
15505
+ if (!Array.isArray(gate.steps)) throw new Error("gate.steps must be an array");
15506
+ out.steps = gate.steps.map((raw, index) => {
15507
+ const field = `gate.steps[${index}]`;
15508
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`${field} must be an object with name and command`);
15509
+ const step = raw;
15510
+ for (const key of Object.keys(step)) if (!GATE_STEP_KEYS.has(key)) throw new Error(`${field}.${key} is not a step field (expected name/command/env/maxSeconds)`);
15511
+ const parsed = { name: gateLine(step.name, `${field}.name`), command: gateLine(step.command, `${field}.command`) };
15512
+ if (step.env !== void 0) parsed.env = gateEnv(step.env, `${field}.env`);
15513
+ if (step.maxSeconds !== void 0) parsed.maxSeconds = gateSeconds(step.maxSeconds, `${field}.maxSeconds`);
15514
+ return parsed;
15515
+ });
15516
+ }
15517
+ return out;
15518
+ }
15519
+ function yamlScalar(value) {
15520
+ const plainSafe = /^[A-Za-z0-9_./$(][^\r\n]*$/.test(value) && !/: |\s#|[\s:]$/.test(value) && !/^(?:true|false|yes|no|on|off|null|~|[-+]?[\d.][\d_.eE+-]*)$/i.test(value);
15521
+ return plainSafe ? value : JSON.stringify(value);
15522
+ }
15523
+ function envYaml(env, indent) {
15524
+ return Object.entries(env).map(([name, value]) => `
15525
+ ${indent}${name}: ${yamlScalar(value)}`).join("");
15526
+ }
15527
+ function gateServicesYaml(services) {
15528
+ let out = "\n services:";
15529
+ for (const [name, service] of Object.entries(services)) {
15530
+ out += `
15531
+ ${name}:
15532
+ image: ${yamlScalar(service.image)}`;
15533
+ if (service.env && Object.keys(service.env).length) out += `
15534
+ env:${envYaml(service.env, " ")}`;
15535
+ if (service.ports) out += `
15536
+ ports:${service.ports.map((port) => `
15537
+ - ${typeof port === "number" ? port : yamlScalar(port)}`).join("")}`;
15538
+ const options = [...service.options ? [service.options] : [], ...(service.tmpfs ?? []).map((path2) => `--tmpfs ${path2}`)];
15539
+ if (options.length) out += `
15540
+ options: >-${options.map((line) => `
15541
+ ${line}`).join("")}`;
15542
+ }
15543
+ return out;
15544
+ }
15545
+ function gateExtraStepsYaml(steps) {
15546
+ return steps.map((step) => [
15547
+ `
15548
+ - name: ${yamlScalar(step.name)}`,
15549
+ step.env && Object.keys(step.env).length ? `
15550
+ env:${envYaml(step.env, " ")}` : "",
15551
+ "\n uses: mutmutco/MMI-Hub/.github/actions/run-with-budget@{{GATE_BUDGET_SHA}}",
15552
+ "\n with:",
15553
+ `
15554
+ command: ${yamlScalar(step.command)}`,
15555
+ `
15556
+ max-seconds: ${step.maxSeconds ?? "{{GATE_MAX_SECONDS}}"}`,
15557
+ "\n working-directory: {{GATE_WORKDIR}}"
15558
+ ].join("")).join("");
15559
+ }
15420
15560
  function gateConfigToVars(gate) {
15421
15561
  const out = {};
15422
15562
  if (!gate || typeof gate !== "object") return out;
@@ -15428,6 +15568,9 @@ function gateConfigToVars(gate) {
15428
15568
  if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
15429
15569
  const seconds = typeof gate.maxSeconds === "number" ? String(gate.maxSeconds) : gate.maxSeconds;
15430
15570
  if (typeof seconds === "string" && /^\d+$/.test(seconds.trim()) && Number(seconds) > 0) out.GATE_MAX_SECONDS = seconds.trim();
15571
+ const extras = validateGateExtras(gate);
15572
+ if (extras.services && Object.keys(extras.services).length) out.GATE_SERVICES_YAML = gateServicesYaml(extras.services);
15573
+ if (extras.steps?.length) out.GATE_EXTRA_STEPS_YAML = gateExtraStepsYaml(extras.steps);
15431
15574
  return out;
15432
15575
  }
15433
15576
  function seedMatchesDeployModel(seed, deployModel) {
@@ -16871,10 +17014,10 @@ var rollout_plan_default = {
16871
17014
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
16872
17015
  },
16873
17016
  baseline: {
16874
- version: "4.5.42",
16875
- tag: "v4.5.42",
16876
- commit: "448720f2bc43",
16877
- npm: "@mutmutco/cli@4.5.42"
17017
+ version: "4.5.44",
17018
+ tag: "v4.5.44",
17019
+ commit: "dda7c3cc34f2",
17020
+ npm: "@mutmutco/cli@4.5.44"
16878
17021
  },
16879
17022
  exitCriterion: "fleet-n-of-n",
16880
17023
  hubOnlyShortcut: "forbidden",
@@ -16891,14 +17034,14 @@ var rollout_plan_default = {
16891
17034
  repo: "mutmutco/mmi-hub",
16892
17035
  role: "canary",
16893
17036
  schedule: "train",
16894
- v3Target: "v4.5.42"
17037
+ v3Target: "v4.5.44"
16895
17038
  }
16896
17039
  ],
16897
17040
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
16898
17041
  rollback: {
16899
17042
  independent: true,
16900
- mechanism: "npm dist-tag latest -> 4.5.42 and redeploy the Hub Lambda from tag v4.5.42 (448720f2bc43); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16901
- v3Target: "v4.5.42 (@mutmutco/cli@4.5.42, tag commit 448720f2bc43 \u2014 last known-good release carrying the repo-index v4-only contract)"
17043
+ mechanism: "npm dist-tag latest -> 4.5.44 and redeploy the Hub Lambda from tag v4.5.44 (dda7c3cc34f2); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17044
+ v3Target: "v4.5.44 (@mutmutco/cli@4.5.44, tag commit dda7c3cc34f2 \u2014 last known-good release carrying the repo-index v4-only contract)"
16902
17045
  }
16903
17046
  },
16904
17047
  {
@@ -27801,8 +27944,15 @@ async function seedGateYml(repo, deps, meta, result) {
27801
27944
  result.skipped.push(`gate.yml missing \u2014 no registry gate config; set \`mmi-cli oracle org project set ${repo} --var gate={...}\` first, then re-run reconcile`);
27802
27945
  return "none";
27803
27946
  }
27947
+ let gateVars;
27948
+ try {
27949
+ gateVars = gateConfigToVars(gate);
27950
+ } catch (e) {
27951
+ result.errors.push(`gate.yml re-seed: registry gate config is invalid \u2014 ${e.message}`);
27952
+ return "none";
27953
+ }
27804
27954
  const derivedVars = withDerivedRepoVars(
27805
- { ...gateConfigToVars(gate), REPO_SLUG: parsed.slug },
27955
+ { ...gateVars, REPO_SLUG: parsed.slug },
27806
27956
  parsed,
27807
27957
  "deployable",
27808
27958
  releaseTrack,
@@ -29577,8 +29727,8 @@ function registerBoardCommands(program3) {
29577
29727
 
29578
29728
  // src/bootstrap-commands.ts
29579
29729
  var import_node_fs28 = require("node:fs");
29580
- var import_node_os16 = require("node:os");
29581
- var import_node_path27 = require("node:path");
29730
+ var import_node_os17 = require("node:os");
29731
+ var import_node_path28 = require("node:path");
29582
29732
  init_cli_shared();
29583
29733
  init_clean_exit();
29584
29734
 
@@ -29890,6 +30040,134 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
29890
30040
 
29891
30041
  // src/box-commands.ts
29892
30042
  var import_node_fs27 = require("node:fs");
30043
+ var import_node_child_process10 = require("node:child_process");
30044
+ var import_node_os16 = require("node:os");
30045
+ var import_node_path27 = require("node:path");
30046
+
30047
+ // src/box-control.ts
30048
+ var import_node_crypto8 = require("node:crypto");
30049
+ var MANIFEST_PATH = "infra/box-control/manifest.json";
30050
+ var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
30051
+ function parseManifest(text) {
30052
+ const raw = JSON.parse(text);
30053
+ if (!Array.isArray(raw.files)) throw new Error('box control manifest: "files" must be an array');
30054
+ const seen = /* @__PURE__ */ new Set();
30055
+ return raw.files.map((f, i) => {
30056
+ const where = `box control manifest: files[${i}]`;
30057
+ if (typeof f.src !== "string" || !f.src || f.src.startsWith("/") || f.src.includes("..")) throw new Error(`${where}: src must be a repo-relative path`);
30058
+ if (f.mode !== "0644" && f.mode !== "0755") throw new Error(`${where} (${f.src}): mode must be "0644" or "0755"`);
30059
+ const targets = [];
30060
+ if (Array.isArray(f.targets)) targets.push(...f.targets);
30061
+ if (f.lanes !== void 0) targets.push(...expandLanes(f.lanes, where));
30062
+ if (!targets.length) throw new Error(`${where} (${f.src}): needs targets or lanes`);
30063
+ const ownSysctl = typeof f.post === "string" && /^sysctl -p (\/etc\/sysctl\.d\/[A-Za-z0-9._-]+\.conf)$/.exec(f.post);
30064
+ const postOk = f.post === void 0 || typeof f.post === "string" && ALLOWED_POST.has(f.post) || !!ownSysctl && targets.every((t) => t.dest === ownSysctl[1]);
30065
+ if (!postOk) {
30066
+ throw new Error(`${where} (${f.src}): post must be one of ${[...ALLOWED_POST].join(", ")}, or \`sysctl -p <its own /etc/sysctl.d dest>\``);
30067
+ }
30068
+ for (const t of targets) {
30069
+ if (typeof t.box !== "string" || !/^[a-z0-9-]+$/.test(t.box)) throw new Error(`${where} (${f.src}): bad box name`);
30070
+ if (typeof t.dest !== "string" || !/^\/[A-Za-z0-9._\/-]+$/.test(t.dest) || t.dest.includes("..")) {
30071
+ throw new Error(`${where} (${f.src}): dest must be an absolute path of plain characters`);
30072
+ }
30073
+ if (t.dest.startsWith("/opt/mmi-control/secrets") || t.dest.endsWith(".env")) {
30074
+ throw new Error(`${where} (${f.src}): ${t.dest} is vault-owned and cannot be a manifest destination`);
30075
+ }
30076
+ const key = `${t.box}:${t.dest}`;
30077
+ if (seen.has(key)) throw new Error(`${where} (${f.src}): duplicate destination ${key}`);
30078
+ seen.add(key);
30079
+ }
30080
+ return { src: f.src, mode: f.mode, ...f.post ? { post: f.post } : {}, targets };
30081
+ });
30082
+ }
30083
+ function expandLanes(l, where) {
30084
+ if (!l || typeof l.box !== "string" || !Number.isInteger(l.from) || !Number.isInteger(l.to) || l.from < 1 || l.to < l.from || typeof l.dest !== "string" || !l.dest.includes("{N}")) {
30085
+ throw new Error(`${where}: lanes needs box, integer from <= to, and a dest containing {N}`);
30086
+ }
30087
+ const out = [];
30088
+ for (let n = l.from; n <= l.to; n += 1) out.push({ box: l.box, dest: l.dest.replace("{N}", String(n)) });
30089
+ return out;
30090
+ }
30091
+ function filesForBox(files, box) {
30092
+ return files.flatMap((file) => file.targets.filter((t) => t.box === box).map((t) => ({ file, dest: t.dest })));
30093
+ }
30094
+ function md5(content) {
30095
+ return (0, import_node_crypto8.createHash)("md5").update(content).digest("hex");
30096
+ }
30097
+ function planRemoteScript(dests) {
30098
+ const lines2 = ["set -u"];
30099
+ for (const d of dests) {
30100
+ lines2.push(`if [ -f '${d}' ]; then printf '%s %s %s\\n' "$(md5sum < '${d}' | cut -d' ' -f1)" "$(stat -c %a '${d}')" '${d}'; else printf 'MISSING - %s\\n' '${d}'; fi`);
30101
+ }
30102
+ lines2.push(`for f in ${ALERT_DIR}/*; do [ -f "$f" ] && printf 'ALERT %s %s\\n' "$(basename "$f")" "$(head -c 300 "$f" | tr '\\n' ' ')"; done; true`);
30103
+ return `${lines2.join("\n")}
30104
+ `;
30105
+ }
30106
+ var ALERT_DIR = "/var/lib/mmi-control/alerts";
30107
+ function parseAlerts(output) {
30108
+ const out = [];
30109
+ for (const line of output.split("\n")) {
30110
+ const m = /^ALERT ([A-Za-z0-9._-]+) ?(.*)$/.exec(line.trim());
30111
+ if (m) out.push({ name: m[1], message: m[2].trim() });
30112
+ }
30113
+ return out;
30114
+ }
30115
+ function parsePlanOutput(output, rows) {
30116
+ const seen = /* @__PURE__ */ new Map();
30117
+ for (const line of output.split("\n")) {
30118
+ const m = /^([0-9a-f]{32}|MISSING) (\d{3,4}|-) (\/\S+)$/.exec(line.trim());
30119
+ if (m) seen.set(m[3], { md5: m[1], mode: m[2] });
30120
+ }
30121
+ return rows.map(({ file, dest, expectedMd5 }) => {
30122
+ const expectedMode = file.mode.replace(/^0/, "");
30123
+ const got = seen.get(dest);
30124
+ const base = { src: file.src, dest, expectedMd5, expectedMode: file.mode };
30125
+ if (!got) return { ...base, status: "unread", installedMd5: null, installedMode: null };
30126
+ if (got.md5 === "MISSING") return { ...base, status: "missing", installedMd5: null, installedMode: null };
30127
+ const status = got.md5 !== expectedMd5 ? "drift" : got.mode !== expectedMode ? "mode-drift" : "in-sync";
30128
+ return { ...base, status, installedMd5: got.md5, installedMode: `0${got.mode}` };
30129
+ });
30130
+ }
30131
+ function selectApplyRows(rows, sel) {
30132
+ const unknown = sel.only.filter((s) => !rows.some((r) => r.file.src === s));
30133
+ if (unknown.length) return `not a manifest src for this box: ${unknown.join(", ")}`;
30134
+ const chosen = rows.filter((r) => sel.only.includes(r.file.src));
30135
+ const strayDest = sel.dest.filter((d) => !chosen.some((r) => r.dest === d));
30136
+ if (strayDest.length) return `--dest is not a destination of the chosen --only source(s): ${strayDest.join(", ")}`;
30137
+ const out = [];
30138
+ for (const src of sel.only) {
30139
+ const targets = chosen.filter((r) => r.file.src === src);
30140
+ if (targets.length === 1 || sel.allTargets) {
30141
+ out.push(...targets);
30142
+ continue;
30143
+ }
30144
+ const named = targets.filter((r) => sel.dest.includes(r.dest));
30145
+ if (!named.length) {
30146
+ return `${src} has ${targets.length} destinations on this box; name each with --dest <path>, or pass --all-targets`;
30147
+ }
30148
+ out.push(...named);
30149
+ }
30150
+ return out;
30151
+ }
30152
+ function backupPath(dest, date) {
30153
+ if (dest.startsWith("/etc/cron.d/")) return `/root/${dest.slice("/etc/cron.d/".length)}.cron.bak-${date}`;
30154
+ return `${dest}.bak-${date}`;
30155
+ }
30156
+ function applyRemoteScript(items, date) {
30157
+ const lines2 = ["set -eu", "tmp=$(mktemp)", `trap 'rm -f "$tmp"' EXIT`];
30158
+ for (const it of items) {
30159
+ const b64 = Buffer.from(it.content).toString("base64");
30160
+ lines2.push(`if [ -f '${it.dest}' ]; then cp -p '${it.dest}' '${backupPath(it.dest, date)}'; fi`);
30161
+ lines2.push(`printf '%s' '${b64}' | base64 -d > "$tmp"`);
30162
+ lines2.push(`install -D -m ${it.mode} "$tmp" '${it.dest}'`);
30163
+ }
30164
+ for (const post of [...new Set(items.map((i) => i.post).filter((p) => !!p))]) lines2.push(post);
30165
+ lines2.push(planRemoteScript(items.map((i) => i.dest)).replace(/^set -u\n/, ""));
30166
+ return `${lines2.join("\n")}
30167
+ `;
30168
+ }
30169
+
30170
+ // src/box-commands.ts
29893
30171
  init_clean_exit();
29894
30172
 
29895
30173
  // src/secrets-commands.ts
@@ -30299,14 +30577,20 @@ function parseGateVar(raw) {
30299
30577
  const map = parsed;
30300
30578
  const out = {};
30301
30579
  for (const [key, value] of Object.entries(map)) {
30302
- if (key === "runtime") {
30580
+ if (key === "services" || key === "steps") {
30581
+ try {
30582
+ Object.assign(out, validateGateExtras({ [key]: value }));
30583
+ } catch (error) {
30584
+ throw new Error(`org project set: ${error.message}`);
30585
+ }
30586
+ } else if (key === "runtime") {
30303
30587
  if (value !== "node" && value !== "python") throw new Error('org project set: gate.runtime must be "node" or "python"');
30304
30588
  out.runtime = value;
30305
30589
  } else if (key === "cmd" || key === "affectedCmd" || key === "workdir" || key === "cacheDepPath" || key === "pyVersion") {
30306
30590
  if (typeof value !== "string" || !value.trim()) throw new Error(`org project set: gate.${key} must be a non-empty string`);
30307
30591
  out[key] = value.trim();
30308
30592
  } else {
30309
- throw new Error(`org project set: gate key "${key}" \u2014 expected only runtime/cmd/affectedCmd/workdir/cacheDepPath/pyVersion`);
30593
+ throw new Error(`org project set: gate key "${key}" \u2014 expected only runtime/cmd/affectedCmd/workdir/cacheDepPath/pyVersion/services/steps`);
30310
30594
  }
30311
30595
  }
30312
30596
  return out;
@@ -31479,6 +31763,81 @@ ${SSH_RECIPE_AGENT_NOTE}`);
31479
31763
  await failGraceful(e.message);
31480
31764
  }
31481
31765
  });
31766
+ box.command("control <name>").description("box-side control files (#7402): --plan (default, read-only) reports drift from the repo; --apply --only <src> installs with backups").option("--plan", "read-only drift report (the default)").option("--apply", "install the files named by --only on this box, backing up each previous copy").option("--only <src>", "with --apply: a manifest src to install (repeatable; approval is file by file)", (v, acc) => [...acc, v], []).option("--dest <path>", "with --apply: a destination of a multi-target --only source to install (repeatable, #7409)", (v, acc) => [...acc, v], []).option("--all-targets", "with --apply: install a multi-target --only source to every one of its destinations on this box").option("--ref <ref>", "git ref the repo content is read from", "origin/development").option("--json", "machine-readable output").action(async (name, o) => {
31767
+ try {
31768
+ if (o.apply && o.plan) throw new UsageError("box control: --plan and --apply are exclusive");
31769
+ if (o.apply && !o.only.length) throw new UsageError("box control: --apply needs --only <src> for each file to install; approval is file by file");
31770
+ if (!o.apply && (o.only.length || o.dest.length || o.allTargets)) throw new UsageError("box control: --only, --dest and --all-targets apply to --apply");
31771
+ const show = hubGitShow(o.ref);
31772
+ const files = parseManifest(show(MANIFEST_PATH).toString("utf8"));
31773
+ const { boxes, incomplete } = await fetchInventory();
31774
+ const found = lookupBox(name, boxes, incomplete);
31775
+ if (found.kind !== "found") {
31776
+ const why = found.kind === "unknown" ? `the inventory is incomplete (${found.reasons.join("; ")})` : `no box named '${name}'`;
31777
+ throw new UnreadableError(`box control: ${why}`);
31778
+ }
31779
+ const rows = filesForBox(files, found.box.name).map(({ file, dest }) => {
31780
+ const content = show(file.src);
31781
+ return { file, dest, content, expectedMd5: md5(content) };
31782
+ });
31783
+ if (!rows.length) throw new UsageError(`box control: ${MANIFEST_PATH} lists no files for ${found.box.name}`);
31784
+ let probeRows = rows;
31785
+ if (o.apply) {
31786
+ const picked = selectApplyRows(rows, { only: o.only, dest: o.dest, allTargets: !!o.allTargets });
31787
+ if (typeof picked === "string") throw new UsageError(`box control: ${picked}`);
31788
+ probeRows = picked;
31789
+ }
31790
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
31791
+ const script = o.apply ? applyRemoteScript(probeRows.map((r) => ({ dest: r.dest, mode: r.file.mode, content: r.content, post: r.file.post })), date) : planRemoteScript(probeRows.map((r) => r.dest));
31792
+ const output = runOnBox(found.box, script);
31793
+ const results = parsePlanOutput(output, probeRows);
31794
+ const drift = results.filter((r) => r.status !== "in-sync").length;
31795
+ const unread = results.some((r) => r.status === "unread");
31796
+ const alerts = parseAlerts(output);
31797
+ const payload = { box: found.box.name, ref: o.ref, mode: o.apply ? "apply" : "plan", files: results, drift, alerts, ok: drift === 0 && alerts.length === 0 };
31798
+ if (o.json) console.log(JSON.stringify(payload, null, 2));
31799
+ else console.log([formatControl(payload.mode, found.box.name, results), ...alerts.map((a) => `ALERT ${a.name}: ${a.message}`)].join("\n"));
31800
+ process.exitCode = unread ? 2 : drift || alerts.length ? 1 : 0;
31801
+ } catch (e) {
31802
+ if (o.json) console.log(JSON.stringify({ box: name, ref: o.ref, ok: false, error: e.message }, null, 2));
31803
+ else console.error(e.message);
31804
+ process.exitCode = 2;
31805
+ }
31806
+ });
31807
+ }
31808
+ var UsageError = class extends Error {
31809
+ };
31810
+ var UnreadableError = class extends Error {
31811
+ };
31812
+ function hubGitShow(ref) {
31813
+ const git3 = (args) => (0, import_node_child_process10.execFileSync)("git", args, { maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
31814
+ try {
31815
+ git3(["cat-file", "-e", `HEAD:${MANIFEST_PATH}`]);
31816
+ } catch {
31817
+ throw new UsageError(`box control: run this from an MMI-Hub checkout (no ${MANIFEST_PATH} here)`);
31818
+ }
31819
+ if (ref.startsWith("origin/")) git3(["fetch", "--quiet", "origin"]);
31820
+ return (path2) => git3(["show", `${ref}:${path2}`]);
31821
+ }
31822
+ function runOnBox(found, script) {
31823
+ const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path27.join)((0, import_node_os16.tmpdir)(), "mmi-box-control-"));
31824
+ try {
31825
+ const recipe = (0, import_node_path27.join)(dir, "recipe.sh");
31826
+ (0, import_node_fs27.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
31827
+ const r = (0, import_node_child_process10.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
31828
+ if (r.status !== 0) {
31829
+ throw new UnreadableError(`box control: the ssh session to ${found.name} failed (exit ${r.status ?? r.signal}): ${(r.stderr || "").trim().split("\n").slice(-3).join(" | ")}`);
31830
+ }
31831
+ return r.stdout;
31832
+ } finally {
31833
+ (0, import_node_fs27.rmSync)(dir, { recursive: true, force: true });
31834
+ }
31835
+ }
31836
+ function formatControl(mode, box, rows) {
31837
+ const lines2 = rows.map((r) => `${r.status.padEnd(10)} ${r.dest} <- ${r.src}`);
31838
+ const bad = rows.filter((r) => r.status !== "in-sync").length;
31839
+ lines2.push(`${box}: ${mode} \u2014 ${rows.length} file(s), ${bad} not in sync`);
31840
+ return lines2.join("\n");
31482
31841
  }
31483
31842
 
31484
31843
  // src/bootstrap-box-ports.ts
@@ -31599,7 +31958,7 @@ function renderDeployPortDoctor(report) {
31599
31958
  }
31600
31959
 
31601
31960
  // src/bootstrap-drift.ts
31602
- var import_node_crypto8 = require("node:crypto");
31961
+ var import_node_crypto9 = require("node:crypto");
31603
31962
  function byteComparableSeeds(manifest, cls) {
31604
31963
  return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
31605
31964
  }
@@ -31619,7 +31978,7 @@ function compareSeedBytes(hubContent, repoContent) {
31619
31978
  return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
31620
31979
  }
31621
31980
  function seedContentHash(content) {
31622
- return (0, import_node_crypto8.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
31981
+ return (0, import_node_crypto9.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
31623
31982
  }
31624
31983
  function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
31625
31984
  const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
@@ -31781,7 +32140,7 @@ function renderPropagationReport(plan) {
31781
32140
  }
31782
32141
 
31783
32142
  // src/bootstrap-propagation-identity.ts
31784
- var import_node_crypto9 = require("node:crypto");
32143
+ var import_node_crypto10 = require("node:crypto");
31785
32144
  var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
31786
32145
  var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
31787
32146
  function safeBranchPart(value, maxLength, fallback) {
@@ -31794,7 +32153,7 @@ function repoSlug2(repo) {
31794
32153
  function propagationBranch(repo, target) {
31795
32154
  const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
31796
32155
  const targetPart = safeBranchPart(target, 48, "target");
31797
- const hash = (0, import_node_crypto9.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
32156
+ const hash = (0, import_node_crypto10.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
31798
32157
  return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
31799
32158
  }
31800
32159
  function legacyPropagationBranch(repo) {
@@ -32950,7 +33309,7 @@ function renderBootstrapVerifyReport(report) {
32950
33309
  }
32951
33310
 
32952
33311
  // src/bootstrap-seed-delivery.ts
32953
- var import_node_crypto10 = require("node:crypto");
33312
+ var import_node_crypto11 = require("node:crypto");
32954
33313
  init_github_client();
32955
33314
  var SHA = /^[a-f0-9]{40}$/;
32956
33315
  async function readSeedFile(repo, target, ref, client) {
@@ -32983,7 +33342,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
32983
33342
  }
32984
33343
  const baseSha = base?.object?.sha;
32985
33344
  if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
32986
- const identity = (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
33345
+ const identity = (0, import_node_crypto11.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
32987
33346
  return { ...planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`), baseSha, baseBranch };
32988
33347
  }
32989
33348
  async function verifyTargetedSeedBranch(repo, plan, target, client, allowMissing = false) {
@@ -33378,7 +33737,7 @@ function registerBootstrapCommands(program3) {
33378
33737
  const readFile9 = (p) => (0, import_node_fs28.existsSync)(p) ? (0, import_node_fs28.readFileSync)(p, "utf8") : null;
33379
33738
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
33380
33739
  const putSeed = async (target, content, ref, sha) => {
33381
- const tmp = (0, import_node_path27.join)((0, import_node_os16.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
33740
+ const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
33382
33741
  (0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
33383
33742
  try {
33384
33743
  await gh2(contentPutInputArgs(repo, target, tmp));
@@ -34125,7 +34484,7 @@ LIVE apply to ${repo}:
34125
34484
  } catch {
34126
34485
  existingSha = void 0;
34127
34486
  }
34128
- const tmp = (0, import_node_path27.join)((0, import_node_os16.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
34487
+ const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
34129
34488
  const desiredContent = desiredByRepo.get(rec.repo);
34130
34489
  if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
34131
34490
  (0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
@@ -34300,7 +34659,7 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
34300
34659
  } catch {
34301
34660
  existingSha = void 0;
34302
34661
  }
34303
- const tmp = (0, import_node_path27.join)((0, import_node_os16.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
34662
+ const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
34304
34663
  (0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
34305
34664
  try {
34306
34665
  await gh2(contentPutInputArgs(repo, seed.target, tmp));
@@ -34586,8 +34945,8 @@ function formatSuiteStatus(report) {
34586
34945
 
34587
34946
  // src/suite-train.ts
34588
34947
  var import_node_fs29 = require("node:fs");
34589
- var import_node_path28 = require("node:path");
34590
- var import_node_crypto11 = require("node:crypto");
34948
+ var import_node_path29 = require("node:path");
34949
+ var import_node_crypto12 = require("node:crypto");
34591
34950
  var SCHEMA = "mmi-suite-cut/v1";
34592
34951
  var SHA2 = /^[a-f0-9]{40}$/i;
34593
34952
  var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
@@ -34622,8 +34981,8 @@ function readSuiteCut(path2) {
34622
34981
  return record;
34623
34982
  }
34624
34983
  function writeSuiteCut(path2, record) {
34625
- (0, import_node_fs29.mkdirSync)((0, import_node_path28.dirname)(path2), { recursive: true });
34626
- const temp = `${path2}.${(0, import_node_crypto11.randomBytes)(8).toString("hex")}.tmp`;
34984
+ (0, import_node_fs29.mkdirSync)((0, import_node_path29.dirname)(path2), { recursive: true });
34985
+ const temp = `${path2}.${(0, import_node_crypto12.randomBytes)(8).toString("hex")}.tmp`;
34627
34986
  try {
34628
34987
  (0, import_node_fs29.writeFileSync)(temp, JSON.stringify(record, null, 2), { flag: "wx" });
34629
34988
  (0, import_node_fs29.renameSync)(temp, path2);
@@ -34828,7 +35187,7 @@ async function suiteRelease(deps, ownerGo) {
34828
35187
 
34829
35188
  // src/suite-train-io.ts
34830
35189
  var import_node_fs43 = require("node:fs");
34831
- var import_node_path42 = require("node:path");
35190
+ var import_node_path43 = require("node:path");
34832
35191
  init_cli_shared();
34833
35192
 
34834
35193
  // src/command-register-train-operations.ts
@@ -35108,9 +35467,9 @@ A hotfix branch is cut from origin/main and carries ONLY the fixes --from names,
35108
35467
  async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
35109
35468
  if (sources.length === 0) return { failures: [], skipped: [] };
35110
35469
  const { mkdtemp: mkdtemp2, rm: rm2 } = await import("node:fs/promises");
35111
- const { tmpdir: tmpdir10 } = await import("node:os");
35112
- const { join: join44 } = await import("node:path");
35113
- const dir = await mkdtemp2(join44(tmpdir10(), "mmi-hotfix-preflight-"));
35470
+ const { tmpdir: tmpdir11 } = await import("node:os");
35471
+ const { join: join45 } = await import("node:path");
35472
+ const dir = await mkdtemp2(join45(tmpdir11(), "mmi-hotfix-preflight-"));
35114
35473
  const wt = { run: (file, args) => deps.run(file, file === "git" ? ["-C", dir, ...args] : args) };
35115
35474
  const failures = [];
35116
35475
  const skipped = [];
@@ -36309,13 +36668,13 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag, workflows = H
36309
36668
  }
36310
36669
 
36311
36670
  // src/hotfix-coverage.ts
36312
- var import_node_child_process10 = require("node:child_process");
36671
+ var import_node_child_process11 = require("node:child_process");
36313
36672
  var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
36314
36673
  var GIT_MAX_BUFFER = 512 * 1024 * 1024;
36315
36674
  function checkHotfixCoverage(options = {}) {
36316
36675
  const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
36317
36676
  const ack = (options.ack ?? []).filter(Boolean);
36318
- const git3 = options.git ?? ((args, opts) => (0, import_node_child_process10.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER }));
36677
+ const git3 = options.git ?? ((args, opts) => (0, import_node_child_process11.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER }));
36319
36678
  const revList = (range) => {
36320
36679
  const out = git3(["rev-list", "--no-merges", range]).trim();
36321
36680
  return out ? out.split("\n") : [];
@@ -36383,7 +36742,7 @@ function checkHotfixCoverage(options = {}) {
36383
36742
  }
36384
36743
  function checkHotfixCarries(options) {
36385
36744
  const { cwd = process.cwd(), branch, baseRef, targets } = options;
36386
- const git3 = options.git ?? ((args, opts) => (0, import_node_child_process10.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER }));
36745
+ const git3 = options.git ?? ((args, opts) => (0, import_node_child_process11.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER }));
36387
36746
  const isAncestor = (sha, ref) => {
36388
36747
  try {
36389
36748
  git3(["merge-base", "--is-ancestor", sha, ref]);
@@ -36416,8 +36775,8 @@ function checkHotfixCarries(options) {
36416
36775
 
36417
36776
  // src/merge-cleanup.ts
36418
36777
  var import_node_fs31 = require("node:fs");
36419
- var import_node_path30 = require("node:path");
36420
- var import_node_os17 = require("node:os");
36778
+ var import_node_path31 = require("node:path");
36779
+ var import_node_os18 = require("node:os");
36421
36780
  init_cli_shared();
36422
36781
  init_github_client();
36423
36782
 
@@ -36706,12 +37065,12 @@ async function runPrLand(prNumber, options, deps) {
36706
37065
  }
36707
37066
 
36708
37067
  // src/test-policy-core.ts
36709
- var import_node_child_process12 = require("node:child_process");
37068
+ var import_node_child_process13 = require("node:child_process");
36710
37069
  var import_node_fs30 = require("node:fs");
36711
- var import_node_path29 = require("node:path");
37070
+ var import_node_path30 = require("node:path");
36712
37071
 
36713
37072
  // src/test-command-policy-shared.mjs
36714
- var import_node_child_process11 = require("node:child_process");
37073
+ var import_node_child_process12 = require("node:child_process");
36715
37074
  var TEST_COMMAND_CLASS = "test";
36716
37075
  var TRAILER_KEY = "Test-Policy-Override";
36717
37076
  var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
@@ -36828,7 +37187,7 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, overrid
36828
37187
  };
36829
37188
  }
36830
37189
  function git(args, cwd) {
36831
- return (0, import_node_child_process11.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
37190
+ return (0, import_node_child_process12.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
36832
37191
  }
36833
37192
  function parseScope(value) {
36834
37193
  const scoped = /^\[([^\]]*)\]\s*([\s\S]*)$/.exec(value);
@@ -37184,7 +37543,7 @@ function isMeaningfulRow(file) {
37184
37543
  return file.meaningful !== false;
37185
37544
  }
37186
37545
  function loadPolicy(root, readFile9 = readFileOrNull2) {
37187
- const raw = readFile9((0, import_node_path29.join)(root, POLICY_FILE));
37546
+ const raw = readFile9((0, import_node_path30.join)(root, POLICY_FILE));
37188
37547
  if (raw == null) return { mandatory: [], declared: false };
37189
37548
  return parsePolicy(raw, POLICY_FILE);
37190
37549
  }
@@ -37290,11 +37649,11 @@ function classify(changed, policy, present = () => false) {
37290
37649
  return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
37291
37650
  }
37292
37651
  function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
37293
- return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path29.join)(root, p)));
37652
+ return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path30.join)(root, p)));
37294
37653
  }
37295
37654
  function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
37296
37655
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
37297
- return [...new Set(declared)].filter((p) => !exists((0, import_node_path29.join)(root, p)));
37656
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path30.join)(root, p)));
37298
37657
  }
37299
37658
  function evaluate(changed, policy, present = () => false) {
37300
37659
  const { mandatoryHits, untestedHits, meaningfulTestChanges, addedTests, removedProtected } = classify(changed, policy, present);
@@ -37327,14 +37686,14 @@ function evaluate(changed, policy, present = () => false) {
37327
37686
  return findings;
37328
37687
  }
37329
37688
  function git2(args, cwd) {
37330
- return (0, import_node_child_process12.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
37689
+ return (0, import_node_child_process13.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
37331
37690
  }
37332
37691
  var COAUTHOR_KEY = "Co-authored-by";
37333
37692
  var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
37334
37693
  var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
37335
37694
  function parseTrailers(message2, cwd) {
37336
37695
  try {
37337
- return (0, import_node_child_process12.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
37696
+ return (0, import_node_child_process13.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
37338
37697
  windowsHide: true,
37339
37698
  cwd,
37340
37699
  input: message2,
@@ -37440,7 +37799,7 @@ function runTestPolicy(root, deps = {}) {
37440
37799
  const raw = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
37441
37800
  const changed = deps.changed ? raw : annotateChangeMeaning(raw, policy, (path2) => ({
37442
37801
  before: blobAt(base, path2, root),
37443
- after: readFileOrNull2((0, import_node_path29.join)(root, path2))
37802
+ after: readFileOrNull2((0, import_node_path30.join)(root, path2))
37444
37803
  }));
37445
37804
  const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root) : { override: null, refusals: [] };
37446
37805
  const policyRefusals = policy.declared === false ? [{
@@ -37450,8 +37809,8 @@ function runTestPolicy(root, deps = {}) {
37450
37809
  A gate with no policy cannot decide which test commands are required or permitted.
37451
37810
  ` + (deps.base === HOTFIX_DIFF_BASE ? ` Hotfix lane (--base ${HOTFIX_DIFF_BASE}): pass --policy-ref <exact-${HOTFIX_POLICY_REF}-commit> so the current policy is read from ${HOTFIX_POLICY_REF}.` : ` Restore ${POLICY_FILE} on this branch; the policy file belongs on every non-hotfix lane.`)
37452
37811
  }] : [];
37453
- const present = (path2) => exists((0, import_node_path29.join)(root, path2));
37454
- const policyTree = policySource.sha ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", policySource.sha], root).split("\0").filter(Boolean).map((p) => (0, import_node_path29.join)(root, p))) : null;
37812
+ const present = (path2) => exists((0, import_node_path30.join)(root, path2));
37813
+ const policyTree = policySource.sha ? new Set(git2(["ls-tree", "-r", "-z", "--name-only", policySource.sha], root).split("\0").filter(Boolean).map((p) => (0, import_node_path30.join)(root, p))) : null;
37455
37814
  const policyTreeExists = policyTree ? (abs) => policyTree.has(abs) : exists;
37456
37815
  const where = policySource.sha ? `${policySource.ref}@${policySource.sha}` : "this worktree";
37457
37816
  const removedByThisDiff = removedPaths(changed);
@@ -37696,8 +38055,8 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
37696
38055
  return rewritten === base ? null : rewritten;
37697
38056
  }
37698
38057
  function writeSquashBodyFile(body) {
37699
- const dir = (0, import_node_fs31.mkdtempSync)((0, import_node_path30.join)((0, import_node_os17.tmpdir)(), "mmi-squash-body-"));
37700
- const path2 = (0, import_node_path30.join)(dir, "body.txt");
38058
+ const dir = (0, import_node_fs31.mkdtempSync)((0, import_node_path31.join)((0, import_node_os18.tmpdir)(), "mmi-squash-body-"));
38059
+ const path2 = (0, import_node_path31.join)(dir, "body.txt");
37701
38060
  (0, import_node_fs31.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
37702
38061
  `, "utf8");
37703
38062
  return { path: path2, cleanup: () => {
@@ -37910,7 +38269,7 @@ async function deleteMergedRemoteBranch(options) {
37910
38269
 
37911
38270
  // src/project-info-sync.ts
37912
38271
  var import_node_fs32 = require("node:fs");
37913
- var import_node_path31 = require("node:path");
38272
+ var import_node_path32 = require("node:path");
37914
38273
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
37915
38274
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
37916
38275
  projectV2 { id }
@@ -37957,7 +38316,7 @@ function sharedName(entries, fallback) {
37957
38316
  }
37958
38317
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
37959
38318
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
37960
- const readmePath = (0, import_node_path31.join)(repoRoot2, "README.md");
38319
+ const readmePath = (0, import_node_path32.join)(repoRoot2, "README.md");
37961
38320
  if (!(0, import_node_fs32.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
37962
38321
  const entries = entriesFor(project2, projects);
37963
38322
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
@@ -37983,8 +38342,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
37983
38342
  const targetBase = `https://github.com/${targetRepo2}`;
37984
38343
  const targetBranch = branchFor(targetRepo2, projects);
37985
38344
  const orgDocs = [
37986
- (0, import_node_fs32.existsSync)((0, import_node_path31.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
37987
- (0, import_node_fs32.existsSync)((0, import_node_path31.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
38345
+ (0, import_node_fs32.existsSync)((0, import_node_path32.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
38346
+ (0, import_node_fs32.existsSync)((0, import_node_path32.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
37988
38347
  ].filter(Boolean);
37989
38348
  if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
37990
38349
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
@@ -38007,7 +38366,7 @@ async function syncProjectInfo(plan, client, apply) {
38007
38366
 
38008
38367
  // src/train-commands.ts
38009
38368
  var import_node_fs33 = require("node:fs");
38010
- var import_node_path32 = require("node:path");
38369
+ var import_node_path33 = require("node:path");
38011
38370
  init_cli_shared();
38012
38371
  init_clean_exit();
38013
38372
  init_client_version();
@@ -38022,7 +38381,7 @@ function resolveReleaseBumpIntent(raw) {
38022
38381
  }
38023
38382
  function readRepoVersion() {
38024
38383
  try {
38025
- return JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path32.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
38384
+ return JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path33.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
38026
38385
  } catch {
38027
38386
  return void 0;
38028
38387
  }
@@ -38133,7 +38492,7 @@ function releaseProgressVerdict(status, subject = "Release") {
38133
38492
 
38134
38493
  // src/suite-pin.ts
38135
38494
  var import_promises5 = require("node:fs/promises");
38136
- var import_node_path33 = require("node:path");
38495
+ var import_node_path34 = require("node:path");
38137
38496
  async function openSuiteCutPinPr(deps, input) {
38138
38497
  if (!/^[a-f0-9]{40}$/.test(input.cut) || !input.pins.length) throw new Error("suite pin PR needs an anchored cut and at least one module");
38139
38498
  const ids = input.pins.map((p) => p.module);
@@ -38169,15 +38528,15 @@ async function openSuiteCutPinPr(deps, input) {
38169
38528
  const row = rows.find((r) => r.id === p.module);
38170
38529
  return row?.bundles.rc === p.rc && row?.bundles.main === p.main;
38171
38530
  })) throw new Error("pins already match without a merged cut PR \u2014 provenance unverified");
38172
- await (0, import_promises5.mkdir)((0, import_node_path33.join)(deps.root, "tmp"), { recursive: true });
38173
- scratch = await (0, import_promises5.mkdtemp)((0, import_node_path33.join)(deps.root, "tmp", "suite-pin-"));
38531
+ await (0, import_promises5.mkdir)((0, import_node_path34.join)(deps.root, "tmp"), { recursive: true });
38532
+ scratch = await (0, import_promises5.mkdtemp)((0, import_node_path34.join)(deps.root, "tmp", "suite-pin-"));
38174
38533
  const run = (file, args) => deps.run(file, args, scratch);
38175
38534
  await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
38176
38535
  if ((await run("git", ["rev-parse", "HEAD"])).trim().toLowerCase() !== input.cut.toLowerCase())
38177
38536
  throw new Error("MM-Work development moved after the suite snapshot; no pin PR opened");
38178
38537
  await run("git", ["switch", "-c", branch]);
38179
38538
  for (const stage of ["rc", "main"]) {
38180
- const path2 = (0, import_node_path33.join)(scratch, `${stage}-pins.json`);
38539
+ const path2 = (0, import_node_path34.join)(scratch, `${stage}-pins.json`);
38181
38540
  await (0, import_promises5.writeFile)(path2, JSON.stringify(input.pins.map((p) => ({ module: p.module, url: p[stage] }))));
38182
38541
  await run("node", ["scripts/pin-modules.mjs", "--stage", stage, "--pins", path2]);
38183
38542
  }
@@ -38223,8 +38582,8 @@ async function openSuitePinPr(deps, input) {
38223
38582
  url.searchParams.set("v", tag);
38224
38583
  const pin = url.toString();
38225
38584
  const branch = `suite-pin/${stage}-${member.id}-${tag}`;
38226
- await (0, import_promises5.mkdir)((0, import_node_path33.join)(deps.root, "tmp"), { recursive: true });
38227
- scratch = await (0, import_promises5.mkdtemp)((0, import_node_path33.join)(deps.root, "tmp", "suite-pin-"));
38585
+ await (0, import_promises5.mkdir)((0, import_node_path34.join)(deps.root, "tmp"), { recursive: true });
38586
+ scratch = await (0, import_promises5.mkdtemp)((0, import_node_path34.join)(deps.root, "tmp", "suite-pin-"));
38228
38587
  const run = (file, args) => deps.run(file, args, scratch);
38229
38588
  await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
38230
38589
  const existing = JSON.parse(await run("gh", ["pr", "list", "--repo", SUITE_REPO, "--head", branch, "--state", "all", "--json", "url,state"]));
@@ -38267,7 +38626,7 @@ async function openSuitePinPr(deps, input) {
38267
38626
  }
38268
38627
 
38269
38628
  // src/command-register-collaboration.ts
38270
- var import_node_child_process16 = require("node:child_process");
38629
+ var import_node_child_process17 = require("node:child_process");
38271
38630
  var import_node_fs42 = require("node:fs");
38272
38631
  var import_promises7 = require("node:fs/promises");
38273
38632
  init_clean_exit();
@@ -38292,11 +38651,11 @@ function spawnDetachedSelf(args, deps, opts = {}) {
38292
38651
  }
38293
38652
 
38294
38653
  // src/command-register-collaboration.ts
38295
- var import_node_path41 = require("node:path");
38654
+ var import_node_path42 = require("node:path");
38296
38655
  init_house_map();
38297
38656
 
38298
38657
  // src/issue-body.ts
38299
- var import_node_os18 = require("node:os");
38658
+ var import_node_os19 = require("node:os");
38300
38659
  init_error_codes();
38301
38660
  var TextArgError = class extends Error {
38302
38661
  constructor(message2, code, offendingFlag) {
@@ -38309,7 +38668,7 @@ var TextArgError = class extends Error {
38309
38668
  offendingFlag;
38310
38669
  };
38311
38670
  function emptyStdinMessage(fileFlag) {
38312
- if ((0, import_node_os18.platform)() === "win32") {
38671
+ if ((0, import_node_os19.platform)() === "win32") {
38313
38672
  return `${fileFlag} - read empty stdin (on Windows, ${fileFlag} - is unreliable through the npm .cmd shim \u2014 use ${fileFlag} <path>, or pipe to \`node cli/dist/index.cjs\` directly)`;
38314
38673
  }
38315
38674
  return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
@@ -38402,7 +38761,7 @@ init_github_client();
38402
38761
 
38403
38762
  // src/issue-commands.ts
38404
38763
  var import_node_fs34 = require("node:fs");
38405
- var import_node_crypto12 = require("node:crypto");
38764
+ var import_node_crypto13 = require("node:crypto");
38406
38765
  init_cli_shared();
38407
38766
  init_clean_exit();
38408
38767
  init_error_codes();
@@ -38875,7 +39234,7 @@ function rowIdempotencyKey(batchKey, spec) {
38875
39234
  const identity = `${spec.type}
38876
39235
  ${spec.title.trim()}
38877
39236
  ${spec.body ?? ""}`;
38878
- const hash = (0, import_node_crypto12.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
39237
+ const hash = (0, import_node_crypto13.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
38879
39238
  return `${batchKey}:${hash}`;
38880
39239
  }
38881
39240
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -39335,7 +39694,7 @@ ${lines2}`, {
39335
39694
 
39336
39695
  // src/post-merge-recon.ts
39337
39696
  var import_node_fs35 = require("node:fs");
39338
- var import_node_path34 = require("node:path");
39697
+ var import_node_path35 = require("node:path");
39339
39698
 
39340
39699
  // src/cross-repo-filing-issue.ts
39341
39700
  init_github_client();
@@ -39502,7 +39861,7 @@ function buildPostMergeReconRecovery(input) {
39502
39861
  }
39503
39862
  function writePostMergeReconRecovery(cwd, recovery) {
39504
39863
  const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
39505
- (0, import_node_fs35.mkdirSync)((0, import_node_path34.dirname)(path2), { recursive: true });
39864
+ (0, import_node_fs35.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
39506
39865
  (0, import_node_fs35.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
39507
39866
  `, "utf8");
39508
39867
  return path2;
@@ -39937,10 +40296,10 @@ function registerPrLifecycleCommands(program3) {
39937
40296
  }
39938
40297
 
39939
40298
  // src/review-verdict.ts
39940
- var import_node_child_process13 = require("node:child_process");
40299
+ var import_node_child_process14 = require("node:child_process");
39941
40300
  var import_node_fs36 = require("node:fs");
39942
- var import_node_os19 = require("node:os");
39943
- var import_node_path35 = require("node:path");
40301
+ var import_node_os20 = require("node:os");
40302
+ var import_node_path36 = require("node:path");
39944
40303
  init_cli_shared();
39945
40304
  var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
39946
40305
  var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
@@ -40037,8 +40396,8 @@ async function checkPrReview(number, repo, head, deps = {
40037
40396
  }
40038
40397
  function computePrPatchId(number, repo) {
40039
40398
  return new Promise((resolve7, reject) => {
40040
- const gh2 = (0, import_node_child_process13.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
40041
- const git3 = (0, import_node_child_process13.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
40399
+ const gh2 = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
40400
+ const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
40042
40401
  let out = "";
40043
40402
  let ghErr = "";
40044
40403
  let gitErr = "";
@@ -40105,8 +40464,8 @@ async function readPrIssueComments(number, repo) {
40105
40464
  return comments;
40106
40465
  }
40107
40466
  async function postPrCommentFromFile(number, repo, body) {
40108
- const dir = (0, import_node_fs36.mkdtempSync)((0, import_node_path35.join)((0, import_node_os19.tmpdir)(), "mmi-review-verdict-"));
40109
- const path2 = (0, import_node_path35.join)(dir, "body.md");
40467
+ const dir = (0, import_node_fs36.mkdtempSync)((0, import_node_path36.join)((0, import_node_os20.tmpdir)(), "mmi-review-verdict-"));
40468
+ const path2 = (0, import_node_path36.join)(dir, "body.md");
40110
40469
  try {
40111
40470
  (0, import_node_fs36.writeFileSync)(path2, body, "utf8");
40112
40471
  const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
@@ -40120,7 +40479,7 @@ async function postPrCommentFromFile(number, repo, body) {
40120
40479
  }
40121
40480
 
40122
40481
  // src/pr-create-docs-check.ts
40123
- var import_node_child_process14 = require("node:child_process");
40482
+ var import_node_child_process15 = require("node:child_process");
40124
40483
  init_cli_shared();
40125
40484
  var GIT_TIMEOUT_MS2 = 15e3;
40126
40485
  function catFileBatch(root, ref, paths) {
@@ -40128,7 +40487,7 @@ function catFileBatch(root, ref, paths) {
40128
40487
  return new Promise((resolve7) => {
40129
40488
  const chunks = [];
40130
40489
  let settled = false;
40131
- const child2 = (0, import_node_child_process14.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
40490
+ const child2 = (0, import_node_child_process15.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
40132
40491
  const finish = () => {
40133
40492
  if (settled) return;
40134
40493
  settled = true;
@@ -40340,17 +40699,17 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
40340
40699
 
40341
40700
  // src/worktree-residue-sweep.ts
40342
40701
  var import_node_fs41 = require("node:fs");
40343
- var import_node_path40 = require("node:path");
40702
+ var import_node_path41 = require("node:path");
40344
40703
 
40345
40704
  // src/worktree-merge-cleanup.ts
40346
40705
  var import_node_fs40 = require("node:fs");
40347
- var import_node_path39 = require("node:path");
40706
+ var import_node_path40 = require("node:path");
40348
40707
  init_cli_shared();
40349
40708
 
40350
40709
  // src/stage-runner.ts
40351
- var import_node_child_process15 = require("node:child_process");
40710
+ var import_node_child_process16 = require("node:child_process");
40352
40711
  var import_node_fs38 = require("node:fs");
40353
- var import_node_path37 = require("node:path");
40712
+ var import_node_path38 = require("node:path");
40354
40713
  var import_node_net = require("node:net");
40355
40714
  var import_node_util5 = require("node:util");
40356
40715
 
@@ -40361,7 +40720,7 @@ function normalizeEol2(s) {
40361
40720
 
40362
40721
  // src/env-detection.ts
40363
40722
  var import_node_fs37 = require("node:fs");
40364
- var import_node_path36 = require("node:path");
40723
+ var import_node_path37 = require("node:path");
40365
40724
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
40366
40725
  function isEnvBaseName(name) {
40367
40726
  return name.toLowerCase() === ".env";
@@ -40379,13 +40738,13 @@ function findEnvFiles(root) {
40379
40738
  const relPath = rel ? `${rel}/${entry.name}` : entry.name;
40380
40739
  if (entry.isDirectory()) {
40381
40740
  if (SKIPPED_DIRS.has(entry.name)) continue;
40382
- walk2((0, import_node_path36.join)(dir, entry.name), relPath);
40741
+ walk2((0, import_node_path37.join)(dir, entry.name), relPath);
40383
40742
  continue;
40384
40743
  }
40385
40744
  if (isEnvBaseName(entry.name)) {
40386
40745
  let isDir = false;
40387
40746
  try {
40388
- isDir = (0, import_node_fs37.lstatSync)((0, import_node_path36.join)(dir, entry.name)).isDirectory();
40747
+ isDir = (0, import_node_fs37.lstatSync)((0, import_node_path37.join)(dir, entry.name)).isDirectory();
40389
40748
  } catch {
40390
40749
  }
40391
40750
  if (!isDir) found.push(relPath);
@@ -40442,15 +40801,15 @@ function stageComposeFileEnv(files) {
40442
40801
  }
40443
40802
 
40444
40803
  // src/stage-runner.ts
40445
- var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process15.execFile);
40804
+ var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process16.execFile);
40446
40805
  var DOCKER_TIMEOUT_MS = 15e3;
40447
40806
  function stageBash() {
40448
40807
  if (process.platform !== "win32") return "bash";
40449
40808
  const configured = process.env.SHELL;
40450
- if (configured && (0, import_node_path37.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs38.existsSync)(configured)) return configured;
40809
+ if (configured && (0, import_node_path38.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs38.existsSync)(configured)) return configured;
40451
40810
  const candidates = [
40452
- (0, import_node_path37.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
40453
- ...process.env.LOCALAPPDATA ? [(0, import_node_path37.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
40811
+ (0, import_node_path38.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
40812
+ ...process.env.LOCALAPPDATA ? [(0, import_node_path38.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
40454
40813
  ];
40455
40814
  const bash = candidates.find((path2) => (0, import_node_fs38.existsSync)(path2));
40456
40815
  if (!bash) throw new Error("Local stages require Git Bash on Windows. Install Git for Windows or set SHELL to its absolute bash.exe path.");
@@ -40583,7 +40942,7 @@ function appendForceRecreate(up) {
40583
40942
  return `${up.trimEnd()} --force-recreate`;
40584
40943
  }
40585
40944
  function stageStatePath(cwd = process.cwd()) {
40586
- return (0, import_node_path37.join)(cwd, "tmp", "stage", "state.json");
40945
+ return (0, import_node_path38.join)(cwd, "tmp", "stage", "state.json");
40587
40946
  }
40588
40947
  function normPath(path2) {
40589
40948
  return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -40800,7 +41159,7 @@ function stageProcessEnv(stagePort, extraEnv) {
40800
41159
  }
40801
41160
  function composeResolvesPort(cwd) {
40802
41161
  if (process.env.PORT) return true;
40803
- const envFile = (0, import_node_path37.join)(cwd, ".env");
41162
+ const envFile = (0, import_node_path38.join)(cwd, ".env");
40804
41163
  return (0, import_node_fs38.existsSync)(envFile) && envFileKeys((0, import_node_fs38.readFileSync)(envFile, "utf8")).has("PORT");
40805
41164
  }
40806
41165
  function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
@@ -40814,13 +41173,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
40814
41173
  function stageComposeFiles(cwd) {
40815
41174
  const files = ["docker-compose.yml"];
40816
41175
  for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
40817
- if ((0, import_node_fs38.existsSync)((0, import_node_path37.join)(cwd, conventional))) files.push(conventional);
41176
+ if ((0, import_node_fs38.existsSync)((0, import_node_path38.join)(cwd, conventional))) files.push(conventional);
40818
41177
  }
40819
41178
  return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
40820
41179
  }
40821
41180
  async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
40822
41181
  const keys = Object.keys(vaultEnvMerge ?? {});
40823
- if (!keys.length || !(0, import_node_fs38.existsSync)((0, import_node_path37.join)(cwd, "docker-compose.yml"))) return {};
41182
+ if (!keys.length || !(0, import_node_fs38.existsSync)((0, import_node_path38.join)(cwd, "docker-compose.yml"))) return {};
40824
41183
  let configJson;
40825
41184
  try {
40826
41185
  const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
@@ -40836,15 +41195,15 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
40836
41195
  }
40837
41196
  const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys);
40838
41197
  if (!body) return {};
40839
- const overridePath = (0, import_node_path37.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
40840
- (0, import_node_fs38.mkdirSync)((0, import_node_path37.join)(cwd, "tmp", "stage"), { recursive: true });
41198
+ const overridePath = (0, import_node_path38.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
41199
+ (0, import_node_fs38.mkdirSync)((0, import_node_path38.join)(cwd, "tmp", "stage"), { recursive: true });
40841
41200
  (0, import_node_fs38.writeFileSync)(overridePath, body, "utf8");
40842
41201
  return stageComposeFileEnv(stageComposeFiles(cwd));
40843
41202
  }
40844
41203
  async function ensureStageRuntimeEnv(config, opts, cwd) {
40845
41204
  if (!config.ensureEnv) return;
40846
- const target = (0, import_node_path37.join)(cwd, config.ensureEnv.target);
40847
- const example = (0, import_node_path37.join)(cwd, config.ensureEnv.example);
41205
+ const target = (0, import_node_path38.join)(cwd, config.ensureEnv.target);
41206
+ const example = (0, import_node_path38.join)(cwd, config.ensureEnv.example);
40848
41207
  if (!(0, import_node_fs38.existsSync)(target) && (0, import_node_fs38.existsSync)(example)) {
40849
41208
  (0, import_node_fs38.copyFileSync)(example, target);
40850
41209
  } else if ((0, import_node_fs38.existsSync)(target) && (0, import_node_fs38.existsSync)(example)) {
@@ -41025,7 +41384,7 @@ async function startStage(config = {}, opts = {}) {
41025
41384
  let up = sub(config.up.trim());
41026
41385
  if (opts.forceRecreate) up = appendForceRecreate(up);
41027
41386
  const identity = await resolveStageIdentity(cwd);
41028
- const child2 = (0, import_node_child_process15.spawn)(bash, ["-c", up], {
41387
+ const child2 = (0, import_node_child_process16.spawn)(bash, ["-c", up], {
41029
41388
  cwd,
41030
41389
  // POSIX-only: the process group exists for the group-kill in stopStage. On win32 teardown is
41031
41390
  // `taskkill /T /F` (no group needed), and detached+shell defeats windowsHide — every spawn would
@@ -41135,7 +41494,7 @@ async function runStage(config = {}, opts = {}) {
41135
41494
 
41136
41495
  // src/worktree-evidence-archive.ts
41137
41496
  var import_node_fs39 = require("node:fs");
41138
- var import_node_path38 = require("node:path");
41497
+ var import_node_path39 = require("node:path");
41139
41498
  var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
41140
41499
  function sanitizeArchiveSegment(value, max = 80) {
41141
41500
  const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -41167,7 +41526,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
41167
41526
  const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
41168
41527
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
41169
41528
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
41170
- const source = (0, import_node_path38.join)(args.worktreePath, ".jerv");
41529
+ const source = (0, import_node_path39.join)(args.worktreePath, ".jerv");
41171
41530
  if (!exists(source)) return { status: "absent" };
41172
41531
  if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
41173
41532
  const runScope = resolveArtifactRunScope(env);
@@ -41175,7 +41534,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
41175
41534
  const stamp = now().toISOString().replace(/[:.]/g, "-");
41176
41535
  const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
41177
41536
  try {
41178
- mkdirp((0, import_node_path38.dirname)(dest));
41537
+ mkdirp((0, import_node_path39.dirname)(dest));
41179
41538
  copyDir(source, dest);
41180
41539
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
41181
41540
  return { status: "archived", path: dest, runScope };
@@ -41191,7 +41550,7 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
41191
41550
  const exists = deps.exists ?? import_node_fs39.existsSync;
41192
41551
  const stat4 = deps.stat ?? defaultStat;
41193
41552
  const readdir2 = deps.readdir ?? import_node_fs39.readdirSync;
41194
- const tmpRoot = (0, import_node_path38.join)(worktreePath, "tmp");
41553
+ const tmpRoot = (0, import_node_path39.join)(worktreePath, "tmp");
41195
41554
  if (!exists(tmpRoot)) return [];
41196
41555
  const entries = [];
41197
41556
  const walk2 = (dir) => {
@@ -41202,14 +41561,14 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
41202
41561
  return;
41203
41562
  }
41204
41563
  for (const name of names) {
41205
- const full = (0, import_node_path38.join)(dir, name);
41564
+ const full = (0, import_node_path39.join)(dir, name);
41206
41565
  let st;
41207
41566
  try {
41208
41567
  st = stat4(full);
41209
41568
  } catch {
41210
41569
  continue;
41211
41570
  }
41212
- const relPath = (0, import_node_path38.relative)(worktreePath, full).replace(/\\/g, "/");
41571
+ const relPath = (0, import_node_path39.relative)(worktreePath, full).replace(/\\/g, "/");
41213
41572
  if (st.isDirectory()) {
41214
41573
  if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
41215
41574
  walk2(full);
@@ -41234,7 +41593,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
41234
41593
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
41235
41594
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
41236
41595
  const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
41237
- const source = (0, import_node_path38.join)(args.worktreePath, "tmp");
41596
+ const source = (0, import_node_path39.join)(args.worktreePath, "tmp");
41238
41597
  if (!scanned.length) return { status: "absent" };
41239
41598
  if (!exists(source)) return { status: "absent" };
41240
41599
  const runScope = resolveArtifactRunScope(env);
@@ -41242,7 +41601,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
41242
41601
  const stamp = now().toISOString().replace(/[:.]/g, "-");
41243
41602
  const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
41244
41603
  try {
41245
- mkdirp((0, import_node_path38.dirname)(dest));
41604
+ mkdirp((0, import_node_path39.dirname)(dest));
41246
41605
  copyDir(source, dest);
41247
41606
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
41248
41607
  const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
@@ -41311,7 +41670,7 @@ function normPath2(p) {
41311
41670
  return p.replace(/\\/g, "/").replace(/\/+$/, "");
41312
41671
  }
41313
41672
  function unlinkNodeModulesJunction(wtPath) {
41314
- const nm = (0, import_node_path39.join)(wtPath, "node_modules");
41673
+ const nm = (0, import_node_path40.join)(wtPath, "node_modules");
41315
41674
  try {
41316
41675
  if ((0, import_node_fs40.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs40.rmdirSync)(nm);
41317
41676
  return { ok: true };
@@ -41448,7 +41807,7 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
41448
41807
  }
41449
41808
  async function listNestedIgnoredNodeModules(wtPath, execGit) {
41450
41809
  const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
41451
- return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path39.join)(wtPath, line.slice(0, -1)));
41810
+ return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path40.join)(wtPath, line.slice(0, -1)));
41452
41811
  }
41453
41812
  function safeRemoveTree(path2) {
41454
41813
  const stat4 = (0, import_node_fs40.lstatSync)(path2);
@@ -41461,7 +41820,7 @@ function safeRemoveTree(path2) {
41461
41820
  return;
41462
41821
  }
41463
41822
  if (stat4.isDirectory()) {
41464
- for (const entry of (0, import_node_fs40.readdirSync)(path2)) safeRemoveTree((0, import_node_path39.join)(path2, entry));
41823
+ for (const entry of (0, import_node_fs40.readdirSync)(path2)) safeRemoveTree((0, import_node_path40.join)(path2, entry));
41465
41824
  (0, import_node_fs40.rmdirSync)(path2);
41466
41825
  return;
41467
41826
  }
@@ -41469,7 +41828,7 @@ function safeRemoveTree(path2) {
41469
41828
  }
41470
41829
  async function removeWorktreeNodeModules(wtPath) {
41471
41830
  try {
41472
- safeRemoveTree((0, import_node_path39.join)(wtPath, "node_modules"));
41831
+ safeRemoveTree((0, import_node_path40.join)(wtPath, "node_modules"));
41473
41832
  return { ok: true };
41474
41833
  } catch (e) {
41475
41834
  if (e.code === "ENOENT") return { ok: true };
@@ -41513,7 +41872,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
41513
41872
  return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
41514
41873
  }
41515
41874
  for (const entry of entries) {
41516
- const child2 = (0, import_node_path39.join)(dir, entry.name);
41875
+ const child2 = (0, import_node_path40.join)(dir, entry.name);
41517
41876
  if (entry.isSymbolicLink()) {
41518
41877
  let target = "";
41519
41878
  try {
@@ -41840,7 +42199,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
41840
42199
  const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
41841
42200
  if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
41842
42201
  unlinkedReparsePoints.push(...preHelperGuard.unlinked);
41843
- if (pathExists((0, import_node_path39.join)(wtPath, "node_modules"))) {
42202
+ if (pathExists((0, import_node_path40.join)(wtPath, "node_modules"))) {
41844
42203
  const nmRemoved = await (options.removeRealNodeModules ?? removeWorktreeNodeModules)(wtPath);
41845
42204
  if (!nmRemoved.ok) {
41846
42205
  report.worktree = {
@@ -41980,7 +42339,7 @@ function norm(p) {
41980
42339
  return process.platform === "win32" ? s.toLowerCase() : s;
41981
42340
  }
41982
42341
  function residueLedgerPath(gitCommonDir) {
41983
- return (0, import_node_path40.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
42342
+ return (0, import_node_path41.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
41984
42343
  }
41985
42344
  function readPaths(file, deps) {
41986
42345
  try {
@@ -41993,7 +42352,7 @@ function readPaths(file, deps) {
41993
42352
  function writePaths(file, paths, deps) {
41994
42353
  try {
41995
42354
  const write = deps.writeLedger ?? ((f, t) => {
41996
- (0, import_node_fs41.mkdirSync)((0, import_node_path40.dirname)(f), { recursive: true });
42355
+ (0, import_node_fs41.mkdirSync)((0, import_node_path41.dirname)(f), { recursive: true });
41997
42356
  (0, import_node_fs41.writeFileSync)(f, t, "utf8");
41998
42357
  });
41999
42358
  write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
@@ -42044,7 +42403,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
42044
42403
  }
42045
42404
  if (recorded.length) writePaths(file, remaining, deps);
42046
42405
  const roots = /* @__PURE__ */ new Set();
42047
- for (const p of [...registered.slice(1), ...recorded]) roots.add((0, import_node_path40.dirname)(p));
42406
+ for (const p of [...registered.slice(1), ...recorded]) roots.add((0, import_node_path41.dirname)(p));
42048
42407
  const keptKeys = new Set(remaining.map(norm));
42049
42408
  for (const root of roots) {
42050
42409
  if (!isDir(root) || live.has(norm(root)) || norm(root) === primary) continue;
@@ -42055,7 +42414,7 @@ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
42055
42414
  continue;
42056
42415
  }
42057
42416
  for (const name of children) {
42058
- const child2 = (0, import_node_path40.join)(root, name);
42417
+ const child2 = (0, import_node_path41.join)(root, name);
42059
42418
  const key = norm(child2);
42060
42419
  if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
42061
42420
  let entries;
@@ -42123,10 +42482,10 @@ function argvWantsJson2() {
42123
42482
  return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
42124
42483
  }
42125
42484
  function hubRoot() {
42126
- const fromPkg = (0, import_node_path41.join)(__dirname, "..", "..");
42485
+ const fromPkg = (0, import_node_path42.join)(__dirname, "..", "..");
42127
42486
  const marker = "skills/bootstrap/seeds/manifest.json";
42128
- if ((0, import_node_fs42.existsSync)((0, import_node_path41.join)(fromPkg, marker))) return fromPkg;
42129
- if ((0, import_node_fs42.existsSync)((0, import_node_path41.join)(process.cwd(), marker))) return process.cwd();
42487
+ if ((0, import_node_fs42.existsSync)((0, import_node_path42.join)(fromPkg, marker))) return fromPkg;
42488
+ if ((0, import_node_fs42.existsSync)((0, import_node_path42.join)(process.cwd(), marker))) return process.cwd();
42130
42489
  return null;
42131
42490
  }
42132
42491
  function ciAuditDeps() {
@@ -42138,7 +42497,7 @@ function ciAuditDeps() {
42138
42497
  getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
42139
42498
  readSeedFile: (path2) => {
42140
42499
  if (!root) return null;
42141
- const fullPath = (0, import_node_path41.join)(root, path2);
42500
+ const fullPath = (0, import_node_path42.join)(root, path2);
42142
42501
  return (0, import_node_fs42.existsSync)(fullPath) ? (0, import_node_fs42.readFileSync)(fullPath, "utf8") : null;
42143
42502
  }
42144
42503
  };
@@ -42246,7 +42605,7 @@ function scheduleRelatedDiscovery(o) {
42246
42605
  try {
42247
42606
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
42248
42607
  if (o.repo) args.push("--repo", o.repo);
42249
- spawnDetachedSelf(args, { spawn: import_node_child_process16.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
42608
+ spawnDetachedSelf(args, { spawn: import_node_child_process17.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
42250
42609
  } catch {
42251
42610
  }
42252
42611
  }
@@ -42897,11 +43256,11 @@ ${list}`);
42897
43256
  }
42898
43257
  });
42899
43258
  async function listCiWorkflowPaths(cwd = process.cwd()) {
42900
- const wfDir = (0, import_node_path41.join)(cwd, ".github", "workflows");
43259
+ const wfDir = (0, import_node_path42.join)(cwd, ".github", "workflows");
42901
43260
  if (!(0, import_node_fs42.existsSync)(wfDir)) return [];
42902
43261
  return (0, import_node_fs42.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
42903
43262
  try {
42904
- return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0, import_node_path41.join)(wfDir, name), "utf8"));
43263
+ return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
42905
43264
  } catch {
42906
43265
  return true;
42907
43266
  }
@@ -43406,7 +43765,7 @@ ${list}`);
43406
43765
  const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
43407
43766
  let foreignCheckout;
43408
43767
  if (foreignCwd) {
43409
- const sibling = (0, import_node_path41.join)((0, import_node_path41.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
43768
+ const sibling = (0, import_node_path42.join)((0, import_node_path42.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
43410
43769
  const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
43411
43770
  if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
43412
43771
  const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
@@ -43615,7 +43974,7 @@ ${list}`);
43615
43974
  let residueSweep;
43616
43975
  if (!(foreignCwd && !foreignCheckout)) {
43617
43976
  try {
43618
- gitCommonDir = (0, import_node_path41.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
43977
+ gitCommonDir = (0, import_node_path42.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
43619
43978
  residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
43620
43979
  } catch {
43621
43980
  }
@@ -44709,15 +45068,15 @@ function rcRunCauseFromLog(log) {
44709
45068
  }
44710
45069
  function suiteTrainIo(root, summaryFile) {
44711
45070
  const recordPath = repoRuntimeStatePath(root, "suite", "cut.json");
44712
- const base = (0, import_node_path42.join)(repoRuntimeStatePath(root, "suite"), "checkouts");
45071
+ const base = (0, import_node_path43.join)(repoRuntimeStatePath(root, "suite"), "checkouts");
44713
45072
  const checkout = async (repo) => {
44714
45073
  if (repo === SUITE_REPO) return root;
44715
- const target = (0, import_node_path42.join)(base, repo.split("/")[1]);
45074
+ const target = (0, import_node_path43.join)(base, repo.split("/")[1]);
44716
45075
  if (!(0, import_node_fs43.existsSync)(target)) {
44717
45076
  (0, import_node_fs43.mkdirSync)(base, { recursive: true });
44718
45077
  await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
44719
45078
  }
44720
- if (!(0, import_node_fs43.existsSync)((0, import_node_path42.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
45079
+ if (!(0, import_node_fs43.existsSync)((0, import_node_path43.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
44721
45080
  const origin = await exec("git", ["remote", "get-url", "origin"], target);
44722
45081
  if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
44723
45082
  throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
@@ -45106,8 +45465,8 @@ async function collectWaveStatus(deps) {
45106
45465
  init_github_client();
45107
45466
  init_cli_shared();
45108
45467
  var import_node_fs46 = require("node:fs");
45109
- var import_node_os20 = require("node:os");
45110
- var import_node_path43 = require("node:path");
45468
+ var import_node_os21 = require("node:os");
45469
+ var import_node_path44 = require("node:path");
45111
45470
  var GC_GH_TIMEOUT_MS3 = 2e4;
45112
45471
  async function collectStatus() {
45113
45472
  const repo = await resolveRepo();
@@ -45296,10 +45655,10 @@ async function collectOnboardStatus(opts = {}) {
45296
45655
  else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
45297
45656
  else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
45298
45657
  }
45299
- const home = (0, import_node_os20.homedir)();
45658
+ const home = (0, import_node_os21.homedir)();
45300
45659
  const plugin = onboardPluginGate({
45301
- readKnown: () => readFileSyncSafe((0, import_node_path43.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs46.readFileSync),
45302
- readSettings: () => readFileSyncSafe((0, import_node_path43.join)(home, ".claude", "settings.json"), import_node_fs46.readFileSync)
45660
+ readKnown: () => readFileSyncSafe((0, import_node_path44.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs46.readFileSync),
45661
+ readSettings: () => readFileSyncSafe((0, import_node_path44.join)(home, ".claude", "settings.json"), import_node_fs46.readFileSync)
45303
45662
  });
45304
45663
  return {
45305
45664
  guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
@@ -48413,7 +48772,7 @@ function registerSessionReport(program3) {
48413
48772
 
48414
48773
  // src/stage-commands.ts
48415
48774
  var import_node_fs47 = require("node:fs");
48416
- var import_node_path44 = require("node:path");
48775
+ var import_node_path45 = require("node:path");
48417
48776
  init_cli_shared();
48418
48777
  init_clean_exit();
48419
48778
 
@@ -48659,8 +49018,8 @@ function registerStageCommands(program3) {
48659
49018
  return {
48660
49019
  resolution: decideStage({
48661
49020
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
48662
- hasCompose: (0, import_node_fs47.existsSync)((0, import_node_path44.join)(process.cwd(), "docker-compose.yml")),
48663
- hasEnvExample: (0, import_node_fs47.existsSync)((0, import_node_path44.join)(process.cwd(), ".env.example")),
49021
+ hasCompose: (0, import_node_fs47.existsSync)((0, import_node_path45.join)(process.cwd(), "docker-compose.yml")),
49022
+ hasEnvExample: (0, import_node_fs47.existsSync)((0, import_node_path45.join)(process.cwd(), ".env.example")),
48664
49023
  repo
48665
49024
  }),
48666
49025
  project: project2,
@@ -48944,20 +49303,20 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
48944
49303
  }
48945
49304
 
48946
49305
  // src/tenant-artifact.ts
48947
- var import_node_crypto13 = require("node:crypto");
49306
+ var import_node_crypto14 = require("node:crypto");
48948
49307
  var import_node_fs48 = require("node:fs");
48949
49308
  var import_promises10 = require("node:fs/promises");
48950
- var import_node_path45 = require("node:path");
49309
+ var import_node_path46 = require("node:path");
48951
49310
  var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
48952
49311
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
48953
49312
  async function sha256File(path2) {
48954
- const hash = (0, import_node_crypto13.createHash)("sha256");
49313
+ const hash = (0, import_node_crypto14.createHash)("sha256");
48955
49314
  for await (const chunk of (0, import_node_fs48.createReadStream)(path2)) hash.update(chunk);
48956
49315
  return hash.digest("hex");
48957
49316
  }
48958
49317
  async function putTenantArtifact(repo, stage, inputPath, deps) {
48959
49318
  if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
48960
- const path2 = (0, import_node_path45.resolve)(inputPath);
49319
+ const path2 = (0, import_node_path46.resolve)(inputPath);
48961
49320
  const info = await (0, import_promises10.stat)(path2);
48962
49321
  if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
48963
49322
  if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
@@ -49147,7 +49506,7 @@ async function resolveWhoami(deps) {
49147
49506
  }
49148
49507
 
49149
49508
  // src/command-register-developer.ts
49150
- var import_node_path50 = require("node:path");
49509
+ var import_node_path51 = require("node:path");
49151
49510
  init_house_map();
49152
49511
  init_hub_url();
49153
49512
 
@@ -49180,27 +49539,27 @@ async function executeWaveLand(plan, deps) {
49180
49539
  }
49181
49540
 
49182
49541
  // src/dist-drift.ts
49183
- var import_node_child_process17 = require("node:child_process");
49184
- var import_node_crypto15 = require("node:crypto");
49542
+ var import_node_child_process18 = require("node:child_process");
49543
+ var import_node_crypto16 = require("node:crypto");
49185
49544
  var import_node_fs50 = require("node:fs");
49186
- var import_node_os21 = require("node:os");
49187
- var import_node_path47 = require("node:path");
49545
+ var import_node_os22 = require("node:os");
49546
+ var import_node_path48 = require("node:path");
49188
49547
 
49189
49548
  // ../scripts/distribution-digest.mjs
49190
- var import_node_crypto14 = require("node:crypto");
49549
+ var import_node_crypto15 = require("node:crypto");
49191
49550
  var import_node_fs49 = require("node:fs");
49192
- var import_node_path46 = require("node:path");
49551
+ var import_node_path47 = require("node:path");
49193
49552
  var slash = (value) => value.replaceAll("\\", "/");
49194
49553
  function repoPath(root, declaredPath, label) {
49195
- const absoluteRoot = (0, import_node_path46.resolve)(root);
49196
- const target = (0, import_node_path46.resolve)(root, declaredPath);
49197
- if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path46.sep}`)) {
49554
+ const absoluteRoot = (0, import_node_path47.resolve)(root);
49555
+ const target = (0, import_node_path47.resolve)(root, declaredPath);
49556
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path47.sep}`)) {
49198
49557
  throw new Error(`${label} ${declaredPath} escapes the repository root`);
49199
49558
  }
49200
49559
  return target;
49201
49560
  }
49202
49561
  function digestFiles(files) {
49203
- const hash = (0, import_node_crypto14.createHash)("sha256");
49562
+ const hash = (0, import_node_crypto15.createHash)("sha256");
49204
49563
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
49205
49564
  const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs49.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs49.readFileSync)(file.absolute);
49206
49565
  hash.update(file.relative, "utf8");
@@ -49230,7 +49589,7 @@ var DIST_ARTIFACTS = [
49230
49589
  ];
49231
49590
  var BOM_DIST_TREE_ID = "mmi-cli-dist";
49232
49591
  var ABSENT = "absent";
49233
- var sha256 = (bytes) => `sha256:${(0, import_node_crypto15.createHash)("sha256").update(bytes).digest("hex")}`;
49592
+ var sha256 = (bytes) => `sha256:${(0, import_node_crypto16.createHash)("sha256").update(bytes).digest("hex")}`;
49234
49593
  function artifactDrift(path2, committedBytes, rebuiltBytes) {
49235
49594
  const committed = committedBytes ? sha256(committedBytes) : ABSENT;
49236
49595
  const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
@@ -49308,7 +49667,7 @@ function walkFiles(root) {
49308
49667
  const files = [];
49309
49668
  const walk2 = (directory) => {
49310
49669
  for (const entry of (0, import_node_fs50.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
49311
- const child2 = (0, import_node_path47.join)(directory, entry.name);
49670
+ const child2 = (0, import_node_path48.join)(directory, entry.name);
49312
49671
  if (entry.isDirectory()) walk2(child2);
49313
49672
  else files.push(child2);
49314
49673
  }
@@ -49318,14 +49677,14 @@ function walkFiles(root) {
49318
49677
  }
49319
49678
  function bomPathFor(root) {
49320
49679
  try {
49321
- const registry2 = JSON.parse((0, import_node_fs50.readFileSync)((0, import_node_path47.join)(root, "surfaces.json"), "utf8"));
49322
- return (0, import_node_path47.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
49680
+ const registry2 = JSON.parse((0, import_node_fs50.readFileSync)((0, import_node_path48.join)(root, "surfaces.json"), "utf8"));
49681
+ return (0, import_node_path48.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
49323
49682
  } catch {
49324
- return (0, import_node_path47.join)(root, "distribution-bom.json");
49683
+ return (0, import_node_path48.join)(root, "distribution-bom.json");
49325
49684
  }
49326
49685
  }
49327
49686
  function rebuildTo(packageRoot, outDir) {
49328
- (0, import_node_child_process17.execFileSync)(process.execPath, ["build.mjs"], {
49687
+ (0, import_node_child_process18.execFileSync)(process.execPath, ["build.mjs"], {
49329
49688
  cwd: packageRoot,
49330
49689
  env: { ...process.env, MMI_DIST_OUTDIR: outDir },
49331
49690
  windowsHide: true,
@@ -49334,28 +49693,28 @@ function rebuildTo(packageRoot, outDir) {
49334
49693
  });
49335
49694
  }
49336
49695
  function runDistStatus(root) {
49337
- const stage = (0, import_node_fs50.mkdtempSync)((0, import_node_path47.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
49696
+ const stage = (0, import_node_fs50.mkdtempSync)((0, import_node_path48.join)((0, import_node_os22.tmpdir)(), "mmi-dist-drift-"));
49338
49697
  let overlayCount = 0;
49339
49698
  try {
49340
- const cliOut = (0, import_node_path47.join)(stage, "cli-dist");
49341
- const hubOut = (0, import_node_path47.join)(stage, "hub-dist");
49342
- rebuildTo((0, import_node_path47.join)(root, "cli"), cliOut);
49343
- rebuildTo((0, import_node_path47.join)(root, "updater"), hubOut);
49699
+ const cliOut = (0, import_node_path48.join)(stage, "cli-dist");
49700
+ const hubOut = (0, import_node_path48.join)(stage, "hub-dist");
49701
+ rebuildTo((0, import_node_path48.join)(root, "cli"), cliOut);
49702
+ rebuildTo((0, import_node_path48.join)(root, "updater"), hubOut);
49344
49703
  const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
49345
49704
  const rebuilt = (path2) => {
49346
49705
  const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
49347
- return spec ? readOrNull((0, import_node_path47.join)(outDirFor(spec.packageDir), spec.output)) : null;
49706
+ return spec ? readOrNull((0, import_node_path48.join)(outDirFor(spec.packageDir), spec.output)) : null;
49348
49707
  };
49349
- const committed = (path2) => readOrNull((0, import_node_path47.join)(root, path2));
49350
- const tree = (path2) => readOrNull((0, import_node_path47.join)(root, path2));
49351
- const distRoot = (0, import_node_path47.join)(root, "cli", "dist");
49352
- const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path47.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
49708
+ const committed = (path2) => readOrNull((0, import_node_path48.join)(root, path2));
49709
+ const tree = (path2) => readOrNull((0, import_node_path48.join)(root, path2));
49710
+ const distRoot = (0, import_node_path48.join)(root, "cli", "dist");
49711
+ const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path48.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
49353
49712
  const bom = JSON.parse((0, import_node_fs50.readFileSync)(bomPathFor(root), "utf8"));
49354
49713
  const digest = (entries) => {
49355
- const overlay = (0, import_node_path47.join)(stage, `overlay-${overlayCount++}`);
49714
+ const overlay = (0, import_node_path48.join)(stage, `overlay-${overlayCount++}`);
49356
49715
  for (const entry of entries) {
49357
- const target = (0, import_node_path47.join)(overlay, entry.path);
49358
- (0, import_node_fs50.mkdirSync)((0, import_node_path47.dirname)(target), { recursive: true });
49716
+ const target = (0, import_node_path48.join)(overlay, entry.path);
49717
+ (0, import_node_fs50.mkdirSync)((0, import_node_path48.dirname)(target), { recursive: true });
49359
49718
  (0, import_node_fs50.writeFileSync)(target, entry.bytes);
49360
49719
  }
49361
49720
  return digestPackedFiles(overlay, entries.map((entry) => entry.path));
@@ -49441,7 +49800,7 @@ init_hub_auth();
49441
49800
 
49442
49801
  // src/schedules-lift-command.ts
49443
49802
  var import_promises11 = require("node:fs/promises");
49444
- var import_node_path48 = require("node:path");
49803
+ var import_node_path49 = require("node:path");
49445
49804
  init_clean_exit();
49446
49805
  init_cli_shared();
49447
49806
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
@@ -49470,7 +49829,7 @@ async function readWorkflowFiles(dir) {
49470
49829
  const files = [];
49471
49830
  for (const name of names.sort()) {
49472
49831
  if (!/\.ya?ml$/.test(name)) continue;
49473
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises11.readFile)((0, import_node_path48.join)(dir, name), "utf8") });
49832
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises11.readFile)((0, import_node_path49.join)(dir, name), "utf8") });
49474
49833
  }
49475
49834
  return files;
49476
49835
  }
@@ -49552,9 +49911,9 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
49552
49911
  }
49553
49912
 
49554
49913
  // src/spawn-policy-core.ts
49555
- var import_node_child_process18 = require("node:child_process");
49914
+ var import_node_child_process19 = require("node:child_process");
49556
49915
  var import_node_fs51 = require("node:fs");
49557
- var import_node_path49 = require("node:path");
49916
+ var import_node_path50 = require("node:path");
49558
49917
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
49559
49918
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
49560
49919
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -49623,7 +49982,7 @@ function findViolationsInSource(raw) {
49623
49982
  return found;
49624
49983
  }
49625
49984
  function policedFiles(root) {
49626
- const r = (0, import_node_child_process18.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
49985
+ const r = (0, import_node_child_process19.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
49627
49986
  cwd: root,
49628
49987
  encoding: "utf8",
49629
49988
  windowsHide: true,
@@ -49640,7 +49999,7 @@ function runSpawnPolicy(root) {
49640
49999
  for (const file of files) {
49641
50000
  let raw;
49642
50001
  try {
49643
- raw = (0, import_node_fs51.readFileSync)((0, import_node_path49.join)(root, file), "utf8");
50002
+ raw = (0, import_node_fs51.readFileSync)((0, import_node_path50.join)(root, file), "utf8");
49644
50003
  } catch {
49645
50004
  continue;
49646
50005
  }
@@ -49660,7 +50019,7 @@ function runSpawnPolicy(root) {
49660
50019
  function registerDeveloperCommands(program3) {
49661
50020
  const rules = program3.command("rules").description("org-managed .gitignore delivery");
49662
50021
  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) => {
49663
- const path2 = (0, import_node_path50.join)(process.cwd(), ".gitignore");
50022
+ const path2 = (0, import_node_path51.join)(process.cwd(), ".gitignore");
49664
50023
  const current = (0, import_node_fs52.existsSync)(path2) ? (0, import_node_fs52.readFileSync)(path2, "utf8") : null;
49665
50024
  const plan = planManagedGitignore(current);
49666
50025
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
@@ -49935,12 +50294,12 @@ ${r.stderr ?? ""}`).catch(() => "");
49935
50294
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
49936
50295
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
49937
50296
  function envHealLockPath(home) {
49938
- return (0, import_node_path51.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
50297
+ return (0, import_node_path52.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
49939
50298
  }
49940
50299
  async function withEnvHealLock(what, run) {
49941
50300
  try {
49942
50301
  return await withFileLock(
49943
- envHealLockPath((0, import_node_os22.homedir)()),
50302
+ envHealLockPath((0, import_node_os23.homedir)()),
49944
50303
  { staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
49945
50304
  run
49946
50305
  );
@@ -50037,7 +50396,7 @@ function mmiDoctorDeps(opts = {}) {
50037
50396
  const configRoot = surfaceConfigRoot(surface);
50038
50397
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
50039
50398
  const plan = buildPluginCachePlan(
50040
- (0, import_node_os22.homedir)(),
50399
+ (0, import_node_os23.homedir)(),
50041
50400
  running,
50042
50401
  pluginCacheFsDeps(configRoot, () => 0),
50043
50402
  { configRoot, includeStaging: surface !== "codex" }
@@ -50065,7 +50424,7 @@ function mmiDoctorDeps(opts = {}) {
50065
50424
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
50066
50425
  const installed = installedActivePluginVersion(surface);
50067
50426
  const plan = buildPluginCachePlan(
50068
- (0, import_node_os22.homedir)(),
50427
+ (0, import_node_os23.homedir)(),
50069
50428
  running,
50070
50429
  pluginCacheFsDeps(configRoot, () => 0),
50071
50430
  { configRoot, includeStaging: surface !== "codex", installedVersion: installed }
@@ -50107,7 +50466,7 @@ function mmiDoctorDeps(opts = {}) {
50107
50466
  // has no generated routing index would
50108
50467
  // get a permanent — demanding an artifact it never asked for.
50109
50468
  docsIndexState: (root) => {
50110
- if (!(0, import_node_fs53.existsSync)((0, import_node_path51.join)(root, DOCS_INDEX_PATH))) return void 0;
50469
+ if (!(0, import_node_fs53.existsSync)((0, import_node_path52.join)(root, DOCS_INDEX_PATH))) return void 0;
50111
50470
  const real = createDocsIndexDeps(root);
50112
50471
  let docs;
50113
50472
  const listDocs = () => docs ??= real.listDocs();
@@ -50116,7 +50475,7 @@ function mmiDoctorDeps(opts = {}) {
50116
50475
  },
50117
50476
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
50118
50477
  healDocsIndex: (root) => {
50119
- if (!(0, import_node_fs53.existsSync)((0, import_node_path51.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
50478
+ if (!(0, import_node_fs53.existsSync)((0, import_node_path52.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
50120
50479
  const real = createDocsIndexDeps(root);
50121
50480
  let docs;
50122
50481
  const listDocs = () => docs ??= real.listDocs();
@@ -51203,16 +51562,16 @@ function ciAuditDeps2() {
51203
51562
  // gate re-seed step is skipped gracefully rather than failing mid-run.
51204
51563
  readSeedFile: (path2) => {
51205
51564
  if (!root) return null;
51206
- const fullPath = (0, import_node_path51.join)(root, path2);
51565
+ const fullPath = (0, import_node_path52.join)(root, path2);
51207
51566
  return (0, import_node_fs53.existsSync)(fullPath) ? (0, import_node_fs53.readFileSync)(fullPath, "utf8") : null;
51208
51567
  }
51209
51568
  };
51210
51569
  }
51211
51570
  function hubRoot2() {
51212
- const fromPkg = (0, import_node_path51.join)(__dirname, "..", "..");
51571
+ const fromPkg = (0, import_node_path52.join)(__dirname, "..", "..");
51213
51572
  const marker = "skills/bootstrap/seeds/manifest.json";
51214
- if ((0, import_node_fs53.existsSync)((0, import_node_path51.join)(fromPkg, marker))) return fromPkg;
51215
- if ((0, import_node_fs53.existsSync)((0, import_node_path51.join)(process.cwd(), marker))) return process.cwd();
51573
+ if ((0, import_node_fs53.existsSync)((0, import_node_path52.join)(fromPkg, marker))) return fromPkg;
51574
+ if ((0, import_node_fs53.existsSync)((0, import_node_path52.join)(process.cwd(), marker))) return process.cwd();
51216
51575
  return null;
51217
51576
  }
51218
51577
  registerQueryCommands(program2);
@@ -51353,7 +51712,7 @@ function directoryBytes(path2) {
51353
51712
  return 0;
51354
51713
  }
51355
51714
  for (const entry of entries) {
51356
- const child2 = (0, import_node_path51.join)(path2, entry.name);
51715
+ const child2 = (0, import_node_path52.join)(path2, entry.name);
51357
51716
  if (entry.isDirectory()) total += directoryBytes(child2);
51358
51717
  else {
51359
51718
  try {
@@ -51383,7 +51742,7 @@ function pluginCacheFsDeps(configRoot, dirBytes) {
51383
51742
  dirBytes,
51384
51743
  listStagingDirs: (root) => (0, import_node_fs53.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
51385
51744
  try {
51386
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path51.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs53.statSync)(p).mtimeMs) };
51745
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path52.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs53.statSync)(p).mtimeMs) };
51387
51746
  } catch {
51388
51747
  return { name: d.name, mtimeMs: Date.now() };
51389
51748
  }
@@ -51397,7 +51756,7 @@ function stagingApplyFsGuard(configRoot) {
51397
51756
  return {
51398
51757
  referencedPaths: () => readInstalledPluginRefs(configRoot),
51399
51758
  mtimeMs: (name) => {
51400
- const p = (0, import_node_path51.join)(stagingRoot, name);
51759
+ const p = (0, import_node_path52.join)(stagingRoot, name);
51401
51760
  if (!(0, import_node_fs53.existsSync)(p)) return null;
51402
51761
  try {
51403
51762
  return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs53.statSync)(q).mtimeMs);
@@ -51420,7 +51779,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
51420
51779
  return;
51421
51780
  }
51422
51781
  const plan = buildPluginCachePlan(
51423
- (0, import_node_os22.homedir)(),
51782
+ (0, import_node_os23.homedir)(),
51424
51783
  running,
51425
51784
  pluginCacheFsDeps(configRoot, directoryBytes),
51426
51785
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }