@mutmutco/cli 2.32.2 → 2.32.4

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 +295 -131
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3392,7 +3392,7 @@ var program = new Command();
3392
3392
 
3393
3393
  // src/index.ts
3394
3394
  var import_promises5 = require("node:fs/promises");
3395
- var import_node_fs14 = require("node:fs");
3395
+ var import_node_fs15 = require("node:fs");
3396
3396
 
3397
3397
  // src/rules-sync.ts
3398
3398
  function normalizeEol(s) {
@@ -3423,7 +3423,7 @@ function resolveRulesBase(orgRulesSource, defaultBase) {
3423
3423
  }
3424
3424
 
3425
3425
  // src/index.ts
3426
- var import_node_child_process9 = require("node:child_process");
3426
+ var import_node_child_process10 = require("node:child_process");
3427
3427
 
3428
3428
  // src/cli-shared.ts
3429
3429
  var import_promises = require("node:fs/promises");
@@ -6857,8 +6857,8 @@ async function runNorthstarContext(io, deps) {
6857
6857
  }
6858
6858
 
6859
6859
  // src/index.ts
6860
- var import_node_path13 = require("node:path");
6861
- var import_node_os4 = require("node:os");
6860
+ var import_node_path14 = require("node:path");
6861
+ var import_node_os5 = require("node:os");
6862
6862
 
6863
6863
  // src/gh-create.ts
6864
6864
  var import_promises4 = require("node:fs/promises");
@@ -8247,6 +8247,126 @@ function decideStage(inputs) {
8247
8247
  return { source: "none", gap, staleIgnored: stale || void 0, staleFields: stale ? staleFields : void 0, registryError: registry2.error };
8248
8248
  }
8249
8249
 
8250
+ // src/cursor-plugin-seed.ts
8251
+ var import_node_child_process7 = require("node:child_process");
8252
+ var import_node_fs11 = require("node:fs");
8253
+ var import_node_os4 = require("node:os");
8254
+ var import_node_path11 = require("node:path");
8255
+ var import_node_util6 = require("node:util");
8256
+ function isSemverVersion(v) {
8257
+ return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
8258
+ }
8259
+ var MMI_HUB_REPO = "mutmutco/MMI-Hub";
8260
+ var CURSOR_THIRD_PARTY_STATE_KEY = "cursor/thirdPartyExtensibilityEnabled";
8261
+ var PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
8262
+ var execFileBuffer = (0, import_node_util6.promisify)(import_node_child_process7.execFile);
8263
+ function gitFetchReleaseTagArgs(hubCheckout, tag) {
8264
+ return ["-C", hubCheckout, "fetch", "origin", "tag", tag, "--quiet"];
8265
+ }
8266
+ function ghReleaseTarballApiArgs(tag) {
8267
+ return ["api", `repos/${MMI_HUB_REPO}/tarball/refs/tags/${tag}`];
8268
+ }
8269
+ function cursorUserGlobalStatePath() {
8270
+ if (process.platform === "win32") {
8271
+ const base2 = process.env.APPDATA || (0, import_node_path11.join)((0, import_node_os4.homedir)(), "AppData", "Roaming");
8272
+ return (0, import_node_path11.join)(base2, "Cursor", "User", "globalStorage", "state.vscdb");
8273
+ }
8274
+ if (process.platform === "darwin") {
8275
+ return (0, import_node_path11.join)((0, import_node_os4.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
8276
+ }
8277
+ return (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
8278
+ }
8279
+ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
8280
+ const dbPath = cursorUserGlobalStatePath();
8281
+ if (!(0, import_node_fs11.existsSync)(dbPath)) return void 0;
8282
+ try {
8283
+ const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
8284
+ timeout: 5e3
8285
+ });
8286
+ const value = stdout.trim().toLowerCase();
8287
+ if (value === "true") return true;
8288
+ if (value === "false") return false;
8289
+ return void 0;
8290
+ } catch {
8291
+ return void 0;
8292
+ }
8293
+ }
8294
+ function syncDirContents(src, dest) {
8295
+ (0, import_node_fs11.mkdirSync)(dest, { recursive: true });
8296
+ for (const name of (0, import_node_fs11.readdirSync)(dest)) {
8297
+ (0, import_node_fs11.rmSync)((0, import_node_path11.join)(dest, name), { recursive: true, force: true });
8298
+ }
8299
+ (0, import_node_fs11.cpSync)(src, dest, { recursive: true });
8300
+ }
8301
+ function releaseTag(releasedVersion) {
8302
+ return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
8303
+ }
8304
+ async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
8305
+ const tarFile = (0, import_node_path11.join)(tmpRoot, "archive.tar");
8306
+ try {
8307
+ await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
8308
+ await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
8309
+ timeout: 6e4
8310
+ });
8311
+ await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
8312
+ const pluginMmi = (0, import_node_path11.join)(tmpRoot, "plugins", "mmi");
8313
+ return (0, import_node_fs11.existsSync)((0, import_node_path11.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
8314
+ } catch {
8315
+ return void 0;
8316
+ }
8317
+ }
8318
+ async function downloadPluginMmiViaGh(tag, tmpRoot) {
8319
+ const tarPath = (0, import_node_path11.join)(tmpRoot, "repo.tgz");
8320
+ try {
8321
+ (0, import_node_fs11.mkdirSync)(tmpRoot, { recursive: true });
8322
+ const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
8323
+ timeout: 12e4,
8324
+ maxBuffer: 100 * 1024 * 1024,
8325
+ encoding: "buffer",
8326
+ windowsHide: true
8327
+ });
8328
+ (0, import_node_fs11.writeFileSync)(tarPath, stdout);
8329
+ await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
8330
+ const top = (0, import_node_fs11.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
8331
+ if (!top) return void 0;
8332
+ const pluginMmi = (0, import_node_path11.join)(tmpRoot, top, "plugins", "mmi");
8333
+ return (0, import_node_fs11.existsSync)((0, import_node_path11.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
8334
+ } catch {
8335
+ return void 0;
8336
+ }
8337
+ }
8338
+ async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
8339
+ (0, import_node_fs11.mkdirSync)(tmpRoot, { recursive: true });
8340
+ const tag = releaseTag(releasedVersion);
8341
+ if (hubCheckout) {
8342
+ const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
8343
+ if (fromHub) return fromHub;
8344
+ }
8345
+ return downloadPluginMmiViaGh(tag, (0, import_node_path11.join)(tmpRoot, "gh"));
8346
+ }
8347
+ function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
8348
+ if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
8349
+ return pins.filter((pin) => {
8350
+ if (!pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty) return true;
8351
+ if (isSemverVersion(pin.version) && compareVersions(pin.version, releasedVersion) < 0) return true;
8352
+ return false;
8353
+ });
8354
+ }
8355
+ async function applyCursorPluginCacheSeed(input) {
8356
+ if (!isSemverVersion(input.releasedVersion)) return false;
8357
+ const pinsToSeed = cursorPluginPinsNeedingSeed(input.pins, input.releasedVersion);
8358
+ if (pinsToSeed.length === 0) return false;
8359
+ const tmpRoot = await input.mkdtemp("mmi-cursor-seed-");
8360
+ const source = await resolvePluginMmiSource(input.releasedVersion, input.hubCheckout, tmpRoot, input.execFileP);
8361
+ if (!source) return false;
8362
+ input.log(` \u21BB seeding Cursor MMI plugin cache \u2192 ${input.releasedVersion}\u2026`);
8363
+ for (const pin of pinsToSeed) {
8364
+ syncDirContents(source, pin.path);
8365
+ }
8366
+ (0, import_node_fs11.rmSync)(tmpRoot, { recursive: true, force: true });
8367
+ return true;
8368
+ }
8369
+
8250
8370
  // src/bootstrap-seeds.ts
8251
8371
  var PLACEHOLDER_RE = /\{\{([A-Z0-9_]+)\}\}/g;
8252
8372
  function loadBootstrapSeeds(manifestJson) {
@@ -8622,7 +8742,7 @@ var PLUGIN_UPDATE_RECIPES = {
8622
8742
  };
8623
8743
  function highestSemver(versions) {
8624
8744
  return versions.reduce((best, v) => {
8625
- if (!isSemverVersion(v)) return best;
8745
+ if (!isSemverVersion2(v)) return best;
8626
8746
  if (best === void 0) return v;
8627
8747
  return compareVersions(v, best) > 0 ? v : best;
8628
8748
  }, void 0);
@@ -8633,11 +8753,11 @@ function buildPluginUpdateReport(input) {
8633
8753
  const codexActiveCache = highestSemver(input.codexCacheVersions ?? []);
8634
8754
  return {
8635
8755
  versions: {
8636
- ...isSemverVersion(input.cliVersion) ? { cli: input.cliVersion } : {},
8756
+ ...isSemverVersion2(input.cliVersion) ? { cli: input.cliVersion } : {},
8637
8757
  ...claudePlugin ? { claudePlugin } : {},
8638
8758
  ...codexMarketplace ? { codexMarketplace } : {},
8639
8759
  ...codexActiveCache ? { codexActiveCache } : {},
8640
- ...isSemverVersion(input.releasedVersion) ? { released: input.releasedVersion } : {}
8760
+ ...isSemverVersion2(input.releasedVersion) ? { released: input.releasedVersion } : {}
8641
8761
  },
8642
8762
  recipes: PLUGIN_UPDATE_RECIPES
8643
8763
  };
@@ -8667,7 +8787,7 @@ function buildDoctorJsonPayload(input) {
8667
8787
  };
8668
8788
  }
8669
8789
  var INSTALLED_PLUGIN_VERSION_LABEL = "installed MMI plugin version (vs latest release)";
8670
- function isSemverVersion(v) {
8790
+ function isSemverVersion2(v) {
8671
8791
  return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
8672
8792
  }
8673
8793
  function staleRecordCommand(surface) {
@@ -8688,7 +8808,7 @@ function buildInstalledPluginVersionCheck(input) {
8688
8808
  fix: pluginRecoveryFix(input.surface),
8689
8809
  pluginId
8690
8810
  };
8691
- if (!input.isOrgRepo || !isSemverVersion(input.releasedVersion)) return base2;
8811
+ if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base2;
8692
8812
  if (input.surface === "cursor") return base2;
8693
8813
  const stale = [];
8694
8814
  let sawRecord = false;
@@ -8698,7 +8818,7 @@ function buildInstalledPluginVersionCheck(input) {
8698
8818
  if (!Array.isArray(records) || records.length === 0) continue;
8699
8819
  sawRecord = true;
8700
8820
  const installedVersion = bestRecord(records).version;
8701
- if (!isSemverVersion(installedVersion)) continue;
8821
+ if (!isSemverVersion2(installedVersion)) continue;
8702
8822
  if (compareVersions(installedVersion, input.releasedVersion) >= 0) {
8703
8823
  currentVersion = currentVersion ?? installedVersion;
8704
8824
  } else {
@@ -8730,15 +8850,16 @@ function joinCachePath(root, ...parts) {
8730
8850
  function cursorPluginInstallFix(input) {
8731
8851
  const logHint = "check %APPDATA%\\Cursor\\logs\\<session>\\window*\\exthost\\anysphere.cursor-agent-exec\\Cursor Plugins.*.log for `unable to get password from user`";
8732
8852
  const authSteps = "ensure GitHub SSH auth works for Cursor's background git (start ssh-agent, add your GitHub key, verify `ssh -T git@github.com`) \u2014 Cursor disables credential.helper during marketplace clone";
8733
- const marketplaceRefresh = "in Cursor Dashboard \u2192 Settings \u2192 Plugins, click Update next to the MMI Team Marketplace, enable MMI, then restart Cursor";
8853
+ const marketplaceRefresh = "when Dashboard \u2192 Settings \u2192 Plugins shows Update next to the MMI Team Marketplace, refresh it there; otherwise run `mmi-cli doctor --apply` to seed the cache from the latest release";
8734
8854
  const guide = `full recovery: ${CURSOR_MARKETPLACE_INSTALL_GUIDE}`;
8735
8855
  if (input.reason === "missing-cache") {
8736
8856
  return `${marketplaceRefresh}; ${authSteps}; ${guide}`;
8737
8857
  }
8738
8858
  const pin = input.pinName ?? "<commit-pin>";
8739
8859
  const cacheDir = joinCachePath(input.cacheRoot, pin);
8860
+ const autoSeed = "run `mmi-cli doctor --apply` to seed plugins/mmi from the latest release into the active pin";
8740
8861
  const localSeed = input.hubCheckout ? `temporary fallback: copy ${joinCachePath(input.hubCheckout, "plugins", "mmi")} to ${cacheDir}, then restart Cursor` : `temporary fallback: copy plugins/mmi from a local MMI-Hub checkout to ${cacheDir}, then restart Cursor`;
8741
- return `Cursor plugin cache at ${cacheDir} is empty or missing ${CURSOR_PLUGIN_JSON_REL} or ${CURSOR_HOOKS_JSON_REL} \u2014 ${marketplaceRefresh}; ${authSteps}; ${localSeed}; ${logHint}; ${guide}`;
8862
+ return `Cursor plugin cache at ${cacheDir} is empty or missing ${CURSOR_PLUGIN_JSON_REL} or ${CURSOR_HOOKS_JSON_REL} \u2014 ${autoSeed}; ${marketplaceRefresh}; ${authSteps}; ${localSeed}; ${logHint}; ${guide}`;
8742
8863
  }
8743
8864
  function buildCursorPluginInstallCheck(input) {
8744
8865
  const base2 = {
@@ -8776,18 +8897,29 @@ function buildCursorPluginInstallCheck(input) {
8776
8897
  };
8777
8898
  }
8778
8899
  }
8779
- if (input.surface === "cursor" && isSemverVersion(input.releasedVersion) && input.pins.some((pin) => isSemverVersion(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
8780
- const stale = input.pins.filter((pin) => isSemverVersion(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
8900
+ if (input.surface === "cursor" && isSemverVersion2(input.releasedVersion) && input.pins.some((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
8901
+ const stale = input.pins.filter((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
8781
8902
  return {
8782
8903
  ...base2,
8783
8904
  ok: false,
8784
8905
  cacheRoot: input.cacheRoot,
8785
8906
  pins: input.pins,
8786
- fix: `Cursor MMI plugin cache is behind ${input.releasedVersion} (${stale}) \u2014 in Cursor Dashboard \u2192 Settings \u2192 Plugins, click Update next to the MMI Team Marketplace, enable MMI, then restart Cursor; ${CURSOR_MARKETPLACE_INSTALL_GUIDE}`
8907
+ fix: `Cursor MMI plugin cache is behind ${input.releasedVersion} (${stale}) \u2014 run \`mmi-cli doctor --apply\` to seed the cache from the latest release (effective after restart Cursor); when Dashboard \u2192 Settings \u2192 Plugins shows Update next to the MMI Team Marketplace, a master-admin can refresh the pin there instead; ${CURSOR_MARKETPLACE_INSTALL_GUIDE}`
8787
8908
  };
8788
8909
  }
8789
8910
  return { ...base2, cacheRoot: input.cacheRoot, pins: input.pins };
8790
8911
  }
8912
+ var CURSOR_THIRD_PARTY_EXTENSIBILITY_LABEL = "Cursor third-party plugin import";
8913
+ var CURSOR_THIRD_PARTY_EXTENSIBILITY_FIX = "disable Cursor Settings \u2192 Rules, Skills, Subagents \u2192 Include third-party Plugins, Skills, and other configs (cursor/thirdPartyExtensibilityEnabled) \u2014 it imports Claude/Codex hooks that break the MMI Team Marketplace plugin; use MMI from the Team Marketplace only, then restart Cursor";
8914
+ function buildCursorThirdPartyExtensibilityCheck(input) {
8915
+ const base2 = {
8916
+ ok: true,
8917
+ label: CURSOR_THIRD_PARTY_EXTENSIBILITY_LABEL,
8918
+ fix: CURSOR_THIRD_PARTY_EXTENSIBILITY_FIX
8919
+ };
8920
+ if (!input.isOrgRepo || input.surface !== "cursor" || input.enabled === void 0) return base2;
8921
+ return { ...base2, ok: !input.enabled };
8922
+ }
8791
8923
  function buildCursorHookCliCheck(input) {
8792
8924
  const fix = "update the MMI Team Marketplace plugin (releases ship cli/dist under plugins/mmi/cli/dist) or install mmi-cli on PATH \u2014 Cursor hooks fall back to PATH when the bundled CLI is missing";
8793
8925
  const base2 = { ok: true, label: CURSOR_HOOK_CLI_LABEL, fix };
@@ -8936,12 +9068,12 @@ async function runStageLiveDown(deps, t) {
8936
9068
  }
8937
9069
 
8938
9070
  // src/stage-runner.ts
8939
- var import_node_child_process7 = require("node:child_process");
8940
- var import_node_fs11 = require("node:fs");
8941
- var import_node_path11 = require("node:path");
9071
+ var import_node_child_process8 = require("node:child_process");
9072
+ var import_node_fs12 = require("node:fs");
9073
+ var import_node_path12 = require("node:path");
8942
9074
  var import_node_net2 = require("node:net");
8943
- var import_node_util6 = require("node:util");
8944
- var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process7.execFile);
9075
+ var import_node_util7 = require("node:util");
9076
+ var execFileP4 = (0, import_node_util7.promisify)(import_node_child_process8.execFile);
8945
9077
  var EARLY_EXIT_GRACE_MS = 2e3;
8946
9078
  function waitForProcessStability(child, graceMs = EARLY_EXIT_GRACE_MS) {
8947
9079
  return new Promise((resolve, reject) => {
@@ -8983,7 +9115,7 @@ function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
8983
9115
  return void 0;
8984
9116
  }
8985
9117
  function stageStatePath(cwd = process.cwd()) {
8986
- return (0, import_node_path11.join)(cwd, "tmp", "stage", "state.json");
9118
+ return (0, import_node_path12.join)(cwd, "tmp", "stage", "state.json");
8987
9119
  }
8988
9120
  var POSIX_ONLY_VERBS = ["cp", "mv", "rm", "ln", "cat", "touch", "chmod", "export"];
8989
9121
  function posixOnlyShellProblems(command, field, platform = process.platform) {
@@ -9045,9 +9177,9 @@ async function shell(command, cwd, timeoutMs) {
9045
9177
  });
9046
9178
  }
9047
9179
  function readState(path2) {
9048
- if (!(0, import_node_fs11.existsSync)(path2)) return null;
9180
+ if (!(0, import_node_fs12.existsSync)(path2)) return null;
9049
9181
  try {
9050
- return JSON.parse((0, import_node_fs11.readFileSync)(path2, "utf8"));
9182
+ return JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8"));
9051
9183
  } catch {
9052
9184
  return null;
9053
9185
  }
@@ -9099,7 +9231,7 @@ async function stopStage(opts = {}) {
9099
9231
  return { ok: true, action: "stop", statePath, message: "no previous stage state found" };
9100
9232
  }
9101
9233
  await killTree(state.pid);
9102
- (0, import_node_fs11.rmSync)(statePath, { force: true });
9234
+ (0, import_node_fs12.rmSync)(statePath, { force: true });
9103
9235
  return { ok: true, action: "stop", statePath, pid: state.pid, message: `stopped previous stage pid ${state.pid}` };
9104
9236
  }
9105
9237
  async function startStage(config = {}, opts = {}) {
@@ -9108,7 +9240,7 @@ async function startStage(config = {}, opts = {}) {
9108
9240
  const cwd = opts.cwd ?? process.cwd();
9109
9241
  const statePath = opts.statePath ?? stageStatePath(cwd);
9110
9242
  const dir = statePath.slice(0, Math.max(statePath.lastIndexOf("/"), statePath.lastIndexOf("\\")));
9111
- (0, import_node_fs11.mkdirSync)(dir, { recursive: true });
9243
+ (0, import_node_fs12.mkdirSync)(dir, { recursive: true });
9112
9244
  let stagePort;
9113
9245
  if (config.portRange) {
9114
9246
  const [s, e] = config.portRange;
@@ -9118,14 +9250,14 @@ async function startStage(config = {}, opts = {}) {
9118
9250
  }
9119
9251
  const sub = (s) => s != null && stagePort != null ? s.replace(/\$\{?STAGE_PORT\}?/g, String(stagePort)) : s;
9120
9252
  if (config.ensureEnv) {
9121
- const target = (0, import_node_path11.join)(cwd, config.ensureEnv.target);
9122
- const example = (0, import_node_path11.join)(cwd, config.ensureEnv.example);
9123
- if (!(0, import_node_fs11.existsSync)(target) && (0, import_node_fs11.existsSync)(example)) {
9124
- (0, import_node_fs11.copyFileSync)(example, target);
9125
- } else if ((0, import_node_fs11.existsSync)(target) && (0, import_node_fs11.existsSync)(example)) {
9126
- const stale = detectStaleEnvFile((0, import_node_fs11.readFileSync)(example, "utf8"), (0, import_node_fs11.readFileSync)(target, "utf8"), {
9127
- exampleMtimeMs: (0, import_node_fs11.statSync)(example).mtimeMs,
9128
- targetMtimeMs: (0, import_node_fs11.statSync)(target).mtimeMs
9253
+ const target = (0, import_node_path12.join)(cwd, config.ensureEnv.target);
9254
+ const example = (0, import_node_path12.join)(cwd, config.ensureEnv.example);
9255
+ if (!(0, import_node_fs12.existsSync)(target) && (0, import_node_fs12.existsSync)(example)) {
9256
+ (0, import_node_fs12.copyFileSync)(example, target);
9257
+ } else if ((0, import_node_fs12.existsSync)(target) && (0, import_node_fs12.existsSync)(example)) {
9258
+ const stale = detectStaleEnvFile((0, import_node_fs12.readFileSync)(example, "utf8"), (0, import_node_fs12.readFileSync)(target, "utf8"), {
9259
+ exampleMtimeMs: (0, import_node_fs12.statSync)(example).mtimeMs,
9260
+ targetMtimeMs: (0, import_node_fs12.statSync)(target).mtimeMs
9129
9261
  });
9130
9262
  if (stale) {
9131
9263
  const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
@@ -9137,7 +9269,7 @@ async function startStage(config = {}, opts = {}) {
9137
9269
  const extraEnv = {};
9138
9270
  for (const [k, v] of Object.entries(config.env ?? {})) extraEnv[k] = sub(v) ?? v;
9139
9271
  const up = sub(config.up.trim());
9140
- const child = (0, import_node_child_process7.spawn)(up, {
9272
+ const child = (0, import_node_child_process8.spawn)(up, {
9141
9273
  cwd,
9142
9274
  shell: true,
9143
9275
  // POSIX-only: the process group exists for the group-kill in stopStage. On win32 teardown is
@@ -9156,13 +9288,13 @@ async function startStage(config = {}, opts = {}) {
9156
9288
  healthUrl: sub(config.healthUrl?.trim()) || void 0,
9157
9289
  port: stagePort
9158
9290
  };
9159
- (0, import_node_fs11.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
9291
+ (0, import_node_fs12.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
9160
9292
  try {
9161
9293
  if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
9162
9294
  else await waitForProcessStability(child);
9163
9295
  } catch (e) {
9164
9296
  await killTree(state.pid);
9165
- (0, import_node_fs11.rmSync)(statePath, { force: true });
9297
+ (0, import_node_fs12.rmSync)(statePath, { force: true });
9166
9298
  throw e;
9167
9299
  }
9168
9300
  const result = { ok: true, action: "start", statePath, pid: state.pid, port: stagePort, message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}` };
@@ -10228,12 +10360,12 @@ async function runTenantRedeploy(deps, options) {
10228
10360
  }
10229
10361
 
10230
10362
  // src/hotfix-coverage.ts
10231
- var import_node_child_process8 = require("node:child_process");
10363
+ var import_node_child_process9 = require("node:child_process");
10232
10364
  var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
10233
10365
  function checkHotfixCoverage(options = {}) {
10234
10366
  const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
10235
10367
  const ack = (options.ack ?? []).filter(Boolean);
10236
- const git = options.git ?? ((args, opts) => (0, import_node_child_process8.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
10368
+ const git = options.git ?? ((args, opts) => (0, import_node_child_process9.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
10237
10369
  const revList = (range) => {
10238
10370
  const out = git(["rev-list", "--no-merges", range]).trim();
10239
10371
  return out ? out.split("\n") : [];
@@ -10902,7 +11034,7 @@ async function announceRelease(deps, args) {
10902
11034
  }
10903
11035
 
10904
11036
  // src/port-registry.ts
10905
- var import_node_fs12 = require("node:fs");
11037
+ var import_node_fs13 = require("node:fs");
10906
11038
 
10907
11039
  // ../infra/port-geometry.mjs
10908
11040
  var PORT_BLOCK = 100;
@@ -10916,8 +11048,8 @@ function nextPortBlock(registry2) {
10916
11048
  return [base2, base2 + PORT_SPAN];
10917
11049
  }
10918
11050
  function loadPortRegistry(path2) {
10919
- if (!(0, import_node_fs12.existsSync)(path2)) return {};
10920
- const raw = JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8"));
11051
+ if (!(0, import_node_fs13.existsSync)(path2)) return {};
11052
+ const raw = JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8"));
10921
11053
  const out = {};
10922
11054
  for (const [key, value] of Object.entries(raw)) {
10923
11055
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -10931,9 +11063,9 @@ function ensurePortRange(repo, path2) {
10931
11063
  const existing = registry2[repo];
10932
11064
  if (existing) return existing;
10933
11065
  const range = nextPortBlock(registry2);
10934
- const raw = (0, import_node_fs12.existsSync)(path2) ? JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8")) : {};
11066
+ const raw = (0, import_node_fs13.existsSync)(path2) ? JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8")) : {};
10935
11067
  raw[repo] = range;
10936
- (0, import_node_fs12.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
11068
+ (0, import_node_fs13.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
10937
11069
  return range;
10938
11070
  }
10939
11071
  function portCursorSeed(registry2) {
@@ -12719,10 +12851,10 @@ function parseKbTree(stdout, prefix) {
12719
12851
  }
12720
12852
 
12721
12853
  // src/plan.ts
12722
- var import_node_path12 = require("node:path");
12854
+ var import_node_path13 = require("node:path");
12723
12855
  var PLANS_DIR = "plans";
12724
- var META_FILE = (0, import_node_path12.join)(PLANS_DIR, ".plan-meta.json");
12725
- var planPath = (slug) => (0, import_node_path12.join)(PLANS_DIR, `${slug}.md`);
12856
+ var META_FILE = (0, import_node_path13.join)(PLANS_DIR, ".plan-meta.json");
12857
+ var planPath = (slug) => (0, import_node_path13.join)(PLANS_DIR, `${slug}.md`);
12726
12858
  var metaKey = (project2, slug) => `${project2}/${slug}`;
12727
12859
  function parseMeta(raw) {
12728
12860
  if (!raw) return {};
@@ -12747,7 +12879,7 @@ function hashContent(s) {
12747
12879
  function staleHint(slug) {
12748
12880
  return `remote "${slug}" is newer \u2014 run \`mmi-cli northstar pull ${slug}\` first (your local is based on an older version), or re-push with \`--force\` to overwrite`;
12749
12881
  }
12750
- var INDEX_FILE = (0, import_node_path12.join)(PLANS_DIR, ".index.json");
12882
+ var INDEX_FILE = (0, import_node_path13.join)(PLANS_DIR, ".index.json");
12751
12883
  var INDEX_TTL_MS = 6e4;
12752
12884
  function parseIndex(raw) {
12753
12885
  if (!raw) return null;
@@ -12776,7 +12908,7 @@ function mergeIndex(idx, scope, plans, now) {
12776
12908
  const mergedScope = idx.scope === null ? null : [.../* @__PURE__ */ new Set([...idx.scope, ...scope])];
12777
12909
  return { fetchedAt: now, scope: mergedScope, plans: [...kept, ...plans] };
12778
12910
  }
12779
- var QUEUE_FILE = (0, import_node_path12.join)(PLANS_DIR, ".sync-queue.json");
12911
+ var QUEUE_FILE = (0, import_node_path13.join)(PLANS_DIR, ".sync-queue.json");
12780
12912
  var QUEUE_MAX_ATTEMPTS = 10;
12781
12913
  function isValidQueueEntry(e) {
12782
12914
  if (!e || typeof e !== "object") return false;
@@ -13225,11 +13357,11 @@ async function planGraduate(deps, slug, opts = {}) {
13225
13357
  }
13226
13358
 
13227
13359
  // src/atomic-write.ts
13228
- var import_node_fs13 = require("node:fs");
13360
+ var import_node_fs14 = require("node:fs");
13229
13361
  function atomicWriteFileSync(path2, content) {
13230
13362
  const tmp = `${path2}.${process.pid}.tmp`;
13231
- (0, import_node_fs13.writeFileSync)(tmp, content, "utf8");
13232
- (0, import_node_fs13.renameSync)(tmp, path2);
13363
+ (0, import_node_fs14.writeFileSync)(tmp, content, "utf8");
13364
+ (0, import_node_fs14.renameSync)(tmp, path2);
13233
13365
  }
13234
13366
 
13235
13367
  // src/oauth.ts
@@ -13460,7 +13592,7 @@ async function fetchHubVersionInfo(baseUrl) {
13460
13592
  }
13461
13593
  function readRepoVersion() {
13462
13594
  try {
13463
- return JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path13.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
13595
+ return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
13464
13596
  } catch {
13465
13597
  return void 0;
13466
13598
  }
@@ -13570,10 +13702,10 @@ async function runRulesSync(opts, io = consoleIo) {
13570
13702
  for (const entry of fetched) {
13571
13703
  if ("error" in entry) continue;
13572
13704
  const { file, source } = entry;
13573
- const current = (0, import_node_fs14.existsSync)(file) ? await (0, import_promises5.readFile)(file, "utf8") : null;
13705
+ const current = (0, import_node_fs15.existsSync)(file) ? await (0, import_promises5.readFile)(file, "utf8") : null;
13574
13706
  if (needsUpdate(source, current)) {
13575
13707
  const slash = file.lastIndexOf("/");
13576
- if (slash > 0) (0, import_node_fs14.mkdirSync)(file.slice(0, slash), { recursive: true });
13708
+ if (slash > 0) (0, import_node_fs15.mkdirSync)(file.slice(0, slash), { recursive: true });
13577
13709
  await (0, import_promises5.writeFile)(file, normalizeEol(source), "utf8");
13578
13710
  changed++;
13579
13711
  if (!opts.quiet) io.log(`mmi-cli rules: updated ${file}`);
@@ -13599,7 +13731,7 @@ async function runDocsSync(opts, io = consoleIo) {
13599
13731
  return null;
13600
13732
  }
13601
13733
  },
13602
- localContent: async (f) => (0, import_node_fs14.existsSync)(f) ? await (0, import_promises5.readFile)(f, "utf8") : null,
13734
+ localContent: async (f) => (0, import_node_fs15.existsSync)(f) ? await (0, import_promises5.readFile)(f, "utf8") : null,
13603
13735
  writeDoc: async (f, c) => {
13604
13736
  await (0, import_promises5.writeFile)(f, c, "utf8");
13605
13737
  }
@@ -13748,7 +13880,7 @@ function scheduleRelatedDiscovery(o) {
13748
13880
  try {
13749
13881
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body];
13750
13882
  if (o.repo) args.push("--repo", o.repo);
13751
- (0, import_node_child_process9.spawn)(process.execPath, [process.argv[1], ...args], {
13883
+ (0, import_node_child_process10.spawn)(process.execPath, [process.argv[1], ...args], {
13752
13884
  detached: true,
13753
13885
  stdio: "ignore",
13754
13886
  windowsHide: true,
@@ -13762,7 +13894,7 @@ function detachPlanSync() {
13762
13894
  if (planSyncDetached) return;
13763
13895
  planSyncDetached = true;
13764
13896
  try {
13765
- (0, import_node_child_process9.spawn)(process.execPath, [process.argv[1], "northstar", "sync", "--quiet"], {
13897
+ (0, import_node_child_process10.spawn)(process.execPath, [process.argv[1], "northstar", "sync", "--quiet"], {
13766
13898
  detached: true,
13767
13899
  stdio: "ignore",
13768
13900
  windowsHide: true,
@@ -13772,7 +13904,7 @@ function detachPlanSync() {
13772
13904
  }
13773
13905
  }
13774
13906
  function makePlanDeps(cfg, io = consoleIo) {
13775
- const ensureDir = () => (0, import_node_fs14.mkdirSync)(PLANS_DIR, { recursive: true });
13907
+ const ensureDir = () => (0, import_node_fs15.mkdirSync)(PLANS_DIR, { recursive: true });
13776
13908
  return {
13777
13909
  apiUrl: cfg.sagaApiUrl,
13778
13910
  fetch: (url, init = {}) => fetch(url, { ...init, signal: init.signal ?? AbortSignal.timeout(1e4) }),
@@ -13780,24 +13912,24 @@ function makePlanDeps(cfg, io = consoleIo) {
13780
13912
  project: async () => (await sagaKey(cfg)).project,
13781
13913
  readLocal: (slug) => {
13782
13914
  try {
13783
- return (0, import_node_fs14.readFileSync)(planPath(slug), "utf8");
13915
+ return (0, import_node_fs15.readFileSync)(planPath(slug), "utf8");
13784
13916
  } catch {
13785
13917
  return null;
13786
13918
  }
13787
13919
  },
13788
13920
  writeLocal: (slug, content) => {
13789
13921
  ensureDir();
13790
- (0, import_node_fs14.writeFileSync)(planPath(slug), content, "utf8");
13922
+ (0, import_node_fs15.writeFileSync)(planPath(slug), content, "utf8");
13791
13923
  },
13792
13924
  removeLocal: (slug) => {
13793
13925
  try {
13794
- (0, import_node_fs14.rmSync)(planPath(slug));
13926
+ (0, import_node_fs15.rmSync)(planPath(slug));
13795
13927
  } catch {
13796
13928
  }
13797
13929
  },
13798
13930
  readMetaRaw: () => {
13799
13931
  try {
13800
- return (0, import_node_fs14.readFileSync)(META_FILE, "utf8");
13932
+ return (0, import_node_fs15.readFileSync)(META_FILE, "utf8");
13801
13933
  } catch {
13802
13934
  return null;
13803
13935
  }
@@ -13808,7 +13940,7 @@ function makePlanDeps(cfg, io = consoleIo) {
13808
13940
  },
13809
13941
  readIndexRaw: () => {
13810
13942
  try {
13811
- return (0, import_node_fs14.readFileSync)(INDEX_FILE, "utf8");
13943
+ return (0, import_node_fs15.readFileSync)(INDEX_FILE, "utf8");
13812
13944
  } catch {
13813
13945
  return null;
13814
13946
  }
@@ -13819,7 +13951,7 @@ function makePlanDeps(cfg, io = consoleIo) {
13819
13951
  },
13820
13952
  readQueueRaw: () => {
13821
13953
  try {
13822
- return (0, import_node_fs14.readFileSync)(QUEUE_FILE, "utf8");
13954
+ return (0, import_node_fs15.readFileSync)(QUEUE_FILE, "utf8");
13823
13955
  } catch {
13824
13956
  return null;
13825
13957
  }
@@ -13841,7 +13973,7 @@ function openInEditor(path2) {
13841
13973
  return;
13842
13974
  }
13843
13975
  try {
13844
- (0, import_node_child_process9.spawn)(editor, [path2], { stdio: "inherit" });
13976
+ (0, import_node_child_process10.spawn)(editor, [path2], { stdio: "inherit" });
13845
13977
  } catch {
13846
13978
  console.log(`open ${path2} manually`);
13847
13979
  }
@@ -14675,7 +14807,7 @@ async function createDeferredWorktreeStore() {
14675
14807
  },
14676
14808
  write: async (entries) => {
14677
14809
  try {
14678
- await (0, import_promises5.mkdir)((0, import_node_path13.dirname)(registryPath), { recursive: true });
14810
+ await (0, import_promises5.mkdir)((0, import_node_path14.dirname)(registryPath), { recursive: true });
14679
14811
  await (0, import_promises5.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
14680
14812
  } catch {
14681
14813
  }
@@ -14694,7 +14826,7 @@ function worktreeRemoveDeps(execGit) {
14694
14826
  }
14695
14827
  function teardownWorktreeStage(worktreePath) {
14696
14828
  return runWorktreeStageTeardown(worktreePath, {
14697
- hasStageState: (wt) => (0, import_node_fs14.existsSync)(stageStatePath(wt)),
14829
+ hasStageState: (wt) => (0, import_node_fs15.existsSync)(stageStatePath(wt)),
14698
14830
  stopRecordedStage: async (wt) => (await stopStage({ cwd: wt })).pid,
14699
14831
  listComposeProjects: async () => {
14700
14832
  const { stdout } = await execFileP2("docker", ["compose", "ls", "--all", "--format", "json"], { timeout: GC_GH_TIMEOUT_MS });
@@ -14757,7 +14889,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
14757
14889
  } : await cleanupPrMergeLocalBranch(headRef, {
14758
14890
  beforeWorktrees,
14759
14891
  startingPath,
14760
- pathExists: (p) => (0, import_node_fs14.existsSync)(p),
14892
+ pathExists: (p) => (0, import_node_fs15.existsSync)(p),
14761
14893
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
14762
14894
  teardownWorktreeStage,
14763
14895
  deferredStore,
@@ -14910,7 +15042,7 @@ function rawValues(flag) {
14910
15042
  return out;
14911
15043
  }
14912
15044
  function printLine(value) {
14913
- (0, import_node_fs14.writeSync)(1, `${value}
15045
+ (0, import_node_fs15.writeSync)(1, `${value}
14914
15046
  `);
14915
15047
  }
14916
15048
  function stageKeepAlive() {
@@ -14927,8 +15059,8 @@ async function resolveStage() {
14927
15059
  local,
14928
15060
  shell: shellFor(),
14929
15061
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
14930
- hasCompose: (0, import_node_fs14.existsSync)((0, import_node_path13.join)(process.cwd(), "docker-compose.yml")),
14931
- hasEnvExample: (0, import_node_fs14.existsSync)((0, import_node_path13.join)(process.cwd(), ".env.example"))
15062
+ hasCompose: (0, import_node_fs15.existsSync)((0, import_node_path14.join)(process.cwd(), "docker-compose.yml")),
15063
+ hasEnvExample: (0, import_node_fs15.existsSync)((0, import_node_path14.join)(process.cwd(), ".env.example"))
14932
15064
  });
14933
15065
  }
14934
15066
  function stageStepsFor(res, stops = true) {
@@ -14964,9 +15096,9 @@ program2.command("port-range <repo>").description("assign (idempotently) + print
14964
15096
  printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
14965
15097
  return;
14966
15098
  }
14967
- const path2 = (0, import_node_path13.join)(process.cwd(), "infra", "port-ranges.json");
15099
+ const path2 = (0, import_node_path14.join)(process.cwd(), "infra", "port-ranges.json");
14968
15100
  const allocate = async (seed) => {
14969
- const { stdout } = await execFileP2("node", [(0, import_node_path13.join)(process.cwd(), "infra", "port-ddb.mjs"), String(seed)], { timeout: 15e3 });
15101
+ const { stdout } = await execFileP2("node", [(0, import_node_path14.join)(process.cwd(), "infra", "port-ddb.mjs"), String(seed)], { timeout: 15e3 });
14970
15102
  const parsed = JSON.parse(stdout);
14971
15103
  if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
14972
15104
  return parsed.range;
@@ -15300,7 +15432,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
15300
15432
  const report = await verifyBootstrap(repo, o.class, {
15301
15433
  client: defaultGitHubClient(),
15302
15434
  projectMeta: meta,
15303
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs14.existsSync)(path2) ? (0, import_node_fs14.readFileSync)(path2, "utf8") : null,
15435
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs15.existsSync)(path2) ? (0, import_node_fs15.readFileSync)(path2, "utf8") : null,
15304
15436
  // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
15305
15437
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
15306
15438
  requiredGcpApis: (() => {
@@ -15343,12 +15475,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
15343
15475
  return fail(`bootstrap apply: ${e.message}`);
15344
15476
  }
15345
15477
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
15346
- if (!(0, import_node_fs14.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
15347
- const manifest = loadBootstrapSeeds((0, import_node_fs14.readFileSync)(manifestPath, "utf8"));
15478
+ if (!(0, import_node_fs15.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
15479
+ const manifest = loadBootstrapSeeds((0, import_node_fs15.readFileSync)(manifestPath, "utf8"));
15348
15480
  const baseBranch = o.class === "content" ? "main" : "development";
15349
15481
  const slug = parsedRepo.slug;
15350
15482
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
15351
- const readFile5 = (p) => (0, import_node_fs14.existsSync)(p) ? (0, import_node_fs14.readFileSync)(p, "utf8") : null;
15483
+ const readFile5 = (p) => (0, import_node_fs15.existsSync)(p) ? (0, import_node_fs15.readFileSync)(p, "utf8") : null;
15352
15484
  const enc2 = (p) => p.split("/").map(encodeURIComponent).join("/");
15353
15485
  const rawVars = {};
15354
15486
  for (const value of rawValues("--var")) {
@@ -15557,16 +15689,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
15557
15689
  if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
15558
15690
  targets = [{ repo: o.repo, class: o.class }];
15559
15691
  } else {
15560
- const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs14.existsSync)("projects.json") ? (0, import_node_fs14.readFileSync)("projects.json", "utf8") : null;
15692
+ const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs15.existsSync)("projects.json") ? (0, import_node_fs15.readFileSync)("projects.json", "utf8") : null;
15561
15693
  if (!projectsJson) return failGraceful("access audit: no project registry \u2014 Hub API unreachable and projects.json not found; run from the MMI-Hub repo root or pass --repo <owner/repo>");
15562
- const fanoutJson = (0, import_node_fs14.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs14.readFileSync)(".github/fanout-targets.json", "utf8") : null;
15694
+ const fanoutJson = (0, import_node_fs15.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs15.readFileSync)(".github/fanout-targets.json", "utf8") : null;
15563
15695
  targets = loadAccessTargets(projectsJson, fanoutJson);
15564
15696
  }
15565
15697
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
15566
- const fileMatrix = (0, import_node_fs14.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs14.readFileSync)("access-matrix.json", "utf8")) : {};
15698
+ const fileMatrix = (0, import_node_fs15.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs15.readFileSync)("access-matrix.json", "utf8")) : {};
15567
15699
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
15568
15700
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
15569
- const fileContracts = (0, import_node_fs14.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs14.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
15701
+ const fileContracts = (0, import_node_fs15.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs15.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
15570
15702
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
15571
15703
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
15572
15704
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -15575,20 +15707,20 @@ access.command("audit").description("audit collaborator roles + train-branch pus
15575
15707
  var isWin = process.platform === "win32";
15576
15708
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
15577
15709
  const homeDir = surface === "codex" ? ".codex" : ".claude";
15578
- return (0, import_node_path13.join)((0, import_node_os4.homedir)(), homeDir, "plugins", "installed_plugins.json");
15710
+ return (0, import_node_path14.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "installed_plugins.json");
15579
15711
  };
15580
15712
  function readInstalledPlugins() {
15581
15713
  try {
15582
- return JSON.parse((0, import_node_fs14.readFileSync)(installedPluginsPath(), "utf8"));
15714
+ return JSON.parse((0, import_node_fs15.readFileSync)(installedPluginsPath(), "utf8"));
15583
15715
  } catch {
15584
15716
  return null;
15585
15717
  }
15586
15718
  }
15587
15719
  function installedPluginSources() {
15588
15720
  return ["claude", "codex"].map((surface) => {
15589
- const recordPath = (0, import_node_path13.join)((0, import_node_os4.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
15721
+ const recordPath = (0, import_node_path14.join)((0, import_node_os5.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
15590
15722
  try {
15591
- return { surface, installed: JSON.parse((0, import_node_fs14.readFileSync)(recordPath, "utf8")), recordPath };
15723
+ return { surface, installed: JSON.parse((0, import_node_fs15.readFileSync)(recordPath, "utf8")), recordPath };
15592
15724
  } catch {
15593
15725
  return { surface, installed: null, recordPath };
15594
15726
  }
@@ -15596,7 +15728,7 @@ function installedPluginSources() {
15596
15728
  }
15597
15729
  function readClaudeSettings() {
15598
15730
  try {
15599
- return JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path13.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
15731
+ return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
15600
15732
  } catch {
15601
15733
  return null;
15602
15734
  }
@@ -15618,7 +15750,7 @@ function writeProjectInstallRecord(record) {
15618
15750
  const list = file.plugins[MMI_PLUGIN_ID] ?? [];
15619
15751
  list.push(record);
15620
15752
  file.plugins[MMI_PLUGIN_ID] = list;
15621
- (0, import_node_fs14.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
15753
+ (0, import_node_fs15.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
15622
15754
  `, "utf8");
15623
15755
  return true;
15624
15756
  } catch {
@@ -15631,9 +15763,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
15631
15763
  if (!file) return false;
15632
15764
  if (!file.plugins) file.plugins = {};
15633
15765
  const path2 = installedPluginsPath();
15634
- (0, import_node_fs14.copyFileSync)(path2, `${path2}.bak`);
15766
+ (0, import_node_fs15.copyFileSync)(path2, `${path2}.bak`);
15635
15767
  file.plugins[pluginId] = records;
15636
- (0, import_node_fs14.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
15768
+ (0, import_node_fs15.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
15637
15769
  `, "utf8");
15638
15770
  return true;
15639
15771
  } catch {
@@ -15641,35 +15773,35 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
15641
15773
  }
15642
15774
  }
15643
15775
  function cursorPluginCacheRoot() {
15644
- return (0, import_node_path13.join)((0, import_node_os4.homedir)(), ".cursor", "plugins", "cache", "mmi", "mmi");
15776
+ return (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mmi", "mmi");
15645
15777
  }
15646
15778
  function cursorPluginCachePinSnapshots() {
15647
15779
  const root = cursorPluginCacheRoot();
15648
15780
  try {
15649
- return (0, import_node_fs14.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
15650
- const path2 = (0, import_node_path13.join)(root, entry.name);
15651
- const pluginJson = (0, import_node_path13.join)(path2, ".cursor-plugin", "plugin.json");
15652
- const hooksJson = (0, import_node_path13.join)(path2, "hooks", "hooks.json");
15653
- const cliBundle = (0, import_node_path13.join)(path2, "cli", "dist", "index.cjs");
15781
+ return (0, import_node_fs15.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
15782
+ const path2 = (0, import_node_path14.join)(root, entry.name);
15783
+ const pluginJson = (0, import_node_path14.join)(path2, ".cursor-plugin", "plugin.json");
15784
+ const hooksJson = (0, import_node_path14.join)(path2, "hooks", "hooks.json");
15785
+ const cliBundle = (0, import_node_path14.join)(path2, "cli", "dist", "index.cjs");
15654
15786
  let version;
15655
15787
  try {
15656
- const raw = JSON.parse((0, import_node_fs14.readFileSync)(pluginJson, "utf8"));
15788
+ const raw = JSON.parse((0, import_node_fs15.readFileSync)(pluginJson, "utf8"));
15657
15789
  version = typeof raw.version === "string" ? raw.version : void 0;
15658
15790
  } catch {
15659
15791
  version = void 0;
15660
15792
  }
15661
15793
  let isEmpty = true;
15662
15794
  try {
15663
- isEmpty = (0, import_node_fs14.readdirSync)(path2).length === 0;
15795
+ isEmpty = (0, import_node_fs15.readdirSync)(path2).length === 0;
15664
15796
  } catch {
15665
15797
  isEmpty = true;
15666
15798
  }
15667
15799
  return {
15668
15800
  name: entry.name,
15669
15801
  path: path2,
15670
- hasPluginJson: (0, import_node_fs14.existsSync)(pluginJson),
15671
- hasHooksJson: (0, import_node_fs14.existsSync)(hooksJson),
15672
- hasCliBundle: (0, import_node_fs14.existsSync)(cliBundle),
15802
+ hasPluginJson: (0, import_node_fs15.existsSync)(pluginJson),
15803
+ hasHooksJson: (0, import_node_fs15.existsSync)(hooksJson),
15804
+ hasCliBundle: (0, import_node_fs15.existsSync)(cliBundle),
15673
15805
  isEmpty,
15674
15806
  version
15675
15807
  };
@@ -15679,19 +15811,19 @@ function cursorPluginCachePinSnapshots() {
15679
15811
  }
15680
15812
  }
15681
15813
  function hubCheckoutForCursorSeed() {
15682
- const manifest = (0, import_node_path13.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
15683
- return (0, import_node_fs14.existsSync)(manifest) ? process.cwd() : void 0;
15814
+ const manifest = (0, import_node_path14.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
15815
+ return (0, import_node_fs15.existsSync)(manifest) ? process.cwd() : void 0;
15684
15816
  }
15685
15817
  function mmiPluginCacheRootSnapshots() {
15686
15818
  const roots = [
15687
- { surface: "claude", root: (0, import_node_path13.join)((0, import_node_os4.homedir)(), ".claude", "plugins", "cache", "mmi", "mmi") },
15688
- { surface: "codex", root: (0, import_node_path13.join)((0, import_node_os4.homedir)(), ".codex", "plugins", "cache", "mmi", "mmi") }
15819
+ { surface: "claude", root: (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".claude", "plugins", "cache", "mmi", "mmi") },
15820
+ { surface: "codex", root: (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".codex", "plugins", "cache", "mmi", "mmi") }
15689
15821
  ];
15690
15822
  return roots.flatMap(({ surface, root }) => {
15691
15823
  try {
15692
- const entries = (0, import_node_fs14.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
15824
+ const entries = (0, import_node_fs15.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
15693
15825
  name: entry.name,
15694
- path: (0, import_node_path13.join)(root, entry.name),
15826
+ path: (0, import_node_path14.join)(root, entry.name),
15695
15827
  isDirectory: entry.isDirectory()
15696
15828
  }));
15697
15829
  return [{ surface, root, entries }];
@@ -15702,7 +15834,7 @@ function mmiPluginCacheRootSnapshots() {
15702
15834
  }
15703
15835
  function hasNestedMmiChild(versionDir) {
15704
15836
  try {
15705
- return (0, import_node_fs14.statSync)((0, import_node_path13.join)(versionDir, "mmi")).isDirectory();
15837
+ return (0, import_node_fs15.statSync)((0, import_node_path14.join)(versionDir, "mmi")).isDirectory();
15706
15838
  } catch {
15707
15839
  return false;
15708
15840
  }
@@ -15713,10 +15845,10 @@ function nestedPluginTreeSnapshot() {
15713
15845
  );
15714
15846
  }
15715
15847
  function uniqueQuarantineTarget(path2) {
15716
- if (!(0, import_node_fs14.existsSync)(path2)) return path2;
15848
+ if (!(0, import_node_fs15.existsSync)(path2)) return path2;
15717
15849
  for (let i = 1; i < 100; i += 1) {
15718
15850
  const candidate = `${path2}-${i}`;
15719
- if (!(0, import_node_fs14.existsSync)(candidate)) return candidate;
15851
+ if (!(0, import_node_fs15.existsSync)(candidate)) return candidate;
15720
15852
  }
15721
15853
  return `${path2}-${Date.now()}`;
15722
15854
  }
@@ -15724,32 +15856,32 @@ function quarantinePluginCacheDirs(plan2) {
15724
15856
  let moved = 0;
15725
15857
  for (const move of plan2) {
15726
15858
  try {
15727
- if (!(0, import_node_fs14.existsSync)(move.from)) continue;
15859
+ if (!(0, import_node_fs15.existsSync)(move.from)) continue;
15728
15860
  const target = uniqueQuarantineTarget(move.to);
15729
- (0, import_node_fs14.mkdirSync)((0, import_node_path13.dirname)(target), { recursive: true });
15730
- (0, import_node_fs14.renameSync)(move.from, target);
15861
+ (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(target), { recursive: true });
15862
+ (0, import_node_fs15.renameSync)(move.from, target);
15731
15863
  moved += 1;
15732
15864
  } catch {
15733
15865
  }
15734
15866
  }
15735
15867
  return moved;
15736
15868
  }
15737
- var gitignorePath = () => (0, import_node_path13.join)(process.cwd(), ".gitignore");
15869
+ var gitignorePath = () => (0, import_node_path14.join)(process.cwd(), ".gitignore");
15738
15870
  function readTextFile(path2) {
15739
15871
  try {
15740
- if (!(0, import_node_fs14.existsSync)(path2)) return null;
15741
- return (0, import_node_fs14.readFileSync)(path2, "utf8");
15872
+ if (!(0, import_node_fs15.existsSync)(path2)) return null;
15873
+ return (0, import_node_fs15.readFileSync)(path2, "utf8");
15742
15874
  } catch {
15743
15875
  return null;
15744
15876
  }
15745
15877
  }
15746
15878
  function playwrightMcpConfigSnapshots() {
15747
15879
  const cwd = process.cwd();
15748
- const home = (0, import_node_os4.homedir)();
15880
+ const home = (0, import_node_os5.homedir)();
15749
15881
  const candidates = [
15750
- (0, import_node_path13.join)(cwd, ".cursor", "mcp.json"),
15751
- (0, import_node_path13.join)(home, ".cursor", "mcp.json"),
15752
- (0, import_node_path13.join)(home, ".codex", "config.toml")
15882
+ (0, import_node_path14.join)(cwd, ".cursor", "mcp.json"),
15883
+ (0, import_node_path14.join)(home, ".cursor", "mcp.json"),
15884
+ (0, import_node_path14.join)(home, ".codex", "config.toml")
15753
15885
  ];
15754
15886
  const out = [];
15755
15887
  for (const path2 of candidates) {
@@ -15762,7 +15894,7 @@ function strayBrowserArtifactPaths() {
15762
15894
  const cwd = process.cwd();
15763
15895
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
15764
15896
  try {
15765
- return (0, import_node_fs14.existsSync)((0, import_node_path13.join)(cwd, rel));
15897
+ return (0, import_node_fs15.existsSync)((0, import_node_path14.join)(cwd, rel));
15766
15898
  } catch {
15767
15899
  return false;
15768
15900
  }
@@ -15770,14 +15902,14 @@ function strayBrowserArtifactPaths() {
15770
15902
  }
15771
15903
  function readGitignore() {
15772
15904
  try {
15773
- return (0, import_node_fs14.readFileSync)(gitignorePath(), "utf8");
15905
+ return (0, import_node_fs15.readFileSync)(gitignorePath(), "utf8");
15774
15906
  } catch {
15775
15907
  return null;
15776
15908
  }
15777
15909
  }
15778
15910
  function writeGitignore(content) {
15779
15911
  try {
15780
- (0, import_node_fs14.writeFileSync)(gitignorePath(), content, "utf8");
15912
+ (0, import_node_fs15.writeFileSync)(gitignorePath(), content, "utf8");
15781
15913
  return true;
15782
15914
  } catch {
15783
15915
  return false;
@@ -15816,7 +15948,7 @@ async function runDoctor(opts, io = consoleIo) {
15816
15948
  let onPath = pathProbe;
15817
15949
  if (!onPath) {
15818
15950
  const root = process.env.CLAUDE_PLUGIN_ROOT;
15819
- if (root && (0, import_node_fs14.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
15951
+ if (root && (0, import_node_fs15.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
15820
15952
  }
15821
15953
  checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
15822
15954
  const surface = detectSurface(process.env);
@@ -15945,18 +16077,50 @@ async function runDoctor(opts, io = consoleIo) {
15945
16077
  })
15946
16078
  );
15947
16079
  const cursorCacheRoot = cursorPluginCacheRoot();
16080
+ let cursorPins = cursorPluginCachePinSnapshots() ?? [];
16081
+ let cursorPluginCheck = buildCursorPluginInstallCheck({
16082
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
16083
+ surface,
16084
+ cacheRoot: cursorCacheRoot,
16085
+ cacheRootExists: (0, import_node_fs15.existsSync)(cursorCacheRoot),
16086
+ pins: cursorPins,
16087
+ hubCheckout: hubCheckoutForCursorSeed(),
16088
+ releasedVersion
16089
+ });
16090
+ if (!cursorPluginCheck.ok && repairLocal) {
16091
+ const seeded = await applyCursorPluginCacheSeed({
16092
+ pins: cursorPins,
16093
+ releasedVersion,
16094
+ hubCheckout: hubCheckoutForCursorSeed(),
16095
+ execFileP: execFileP2,
16096
+ mkdtemp: (prefix) => (0, import_promises5.mkdtemp)((0, import_node_path14.join)((0, import_node_os5.tmpdir)(), prefix)),
16097
+ log: (m) => io.err(m)
16098
+ });
16099
+ if (seeded) {
16100
+ cursorPins = cursorPluginCachePinSnapshots() ?? [];
16101
+ cursorPluginCheck = buildCursorPluginInstallCheck({
16102
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
16103
+ surface,
16104
+ cacheRoot: cursorCacheRoot,
16105
+ cacheRootExists: (0, import_node_fs15.existsSync)(cursorCacheRoot),
16106
+ pins: cursorPins,
16107
+ hubCheckout: hubCheckoutForCursorSeed(),
16108
+ releasedVersion
16109
+ });
16110
+ if (cursorPluginCheck.ok) {
16111
+ io.err(` \u21BB seeded Cursor MMI plugin cache \u2192 ${releasedVersion ?? "latest"} \u2014 ${reloadAction(surface)}`);
16112
+ }
16113
+ }
16114
+ }
16115
+ checks.push(cursorPluginCheck);
16116
+ const cursorThirdPartyEnabled = await readCursorThirdPartyExtensibilityEnabled(execFileP2);
15948
16117
  checks.push(
15949
- buildCursorPluginInstallCheck({
16118
+ buildCursorThirdPartyExtensibilityCheck({
15950
16119
  isOrgRepo: Boolean(cfg.sagaApiUrl),
15951
16120
  surface,
15952
- cacheRoot: cursorCacheRoot,
15953
- cacheRootExists: (0, import_node_fs14.existsSync)(cursorCacheRoot),
15954
- pins: cursorPluginCachePinSnapshots() ?? [],
15955
- hubCheckout: hubCheckoutForCursorSeed(),
15956
- releasedVersion
16121
+ enabled: cursorThirdPartyEnabled
15957
16122
  })
15958
16123
  );
15959
- const cursorPins = cursorPluginCachePinSnapshots() ?? [];
15960
16124
  checks.push(
15961
16125
  buildCursorHookCliCheck({
15962
16126
  isOrgRepo: Boolean(cfg.sagaApiUrl),
@@ -16016,7 +16180,7 @@ program2.command("session-start").description("run the SessionStart verbs (rules
16016
16180
  } catch (e) {
16017
16181
  console.error(`[mmi-hook] saga session failed: ${e.message}`);
16018
16182
  }
16019
- spawnDetachedSelf(["docs", "sync", "--quiet"], { spawn: import_node_child_process9.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
16183
+ spawnDetachedSelf(["docs", "sync", "--quiet"], { spawn: import_node_child_process10.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
16020
16184
  let northstarInjected = false;
16021
16185
  const { parallel, sequential } = buildSessionStartPlan({
16022
16186
  rulesSync: (io) => runRulesSync({ quiet: true }, io),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "2.32.2",
3
+ "version": "2.32.4",
4
4
  "description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",