@mutmutco/cli 2.41.0 → 2.42.0

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 +374 -227
  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_promises7 = require("node:fs/promises");
3395
- var import_node_fs22 = require("node:fs");
3395
+ var import_node_fs23 = require("node:fs");
3396
3396
 
3397
3397
  // src/rules-sync.ts
3398
3398
  function normalizeEol(s) {
@@ -5168,11 +5168,11 @@ function parseHandoffText(text) {
5168
5168
  const sourceSessionId = clean(raw.sourceSessionId);
5169
5169
  const createdAt = clean(raw.createdAt);
5170
5170
  if (state !== "open" && state !== "claimed" && state !== "cancelled") return null;
5171
- if (!key || !northStarSlug || !summary || !sourceSessionId || !createdAt) return null;
5171
+ if (!key || !summary || !sourceSessionId || !createdAt) return null;
5172
5172
  return {
5173
5173
  state,
5174
5174
  key,
5175
- northStarSlug,
5175
+ northStarSlug: northStarSlug || void 0,
5176
5176
  summary,
5177
5177
  sourceSessionId,
5178
5178
  sourceBranch: clean(raw.sourceBranch) || void 0,
@@ -5190,20 +5190,19 @@ function listHandoffs(head, opts = {}) {
5190
5190
  }
5191
5191
  function findOpenHandoff(head, key) {
5192
5192
  const normalized = normalizeHandoffKey(key).toLowerCase();
5193
- return listHandoffs(head).find((item) => item.record.key.toLowerCase() === normalized || item.record.northStarSlug.toLowerCase() === normalized);
5193
+ return listHandoffs(head).find((item) => item.record.key.toLowerCase() === normalized || item.record.northStarSlug?.toLowerCase() === normalized);
5194
5194
  }
5195
5195
  function planOpenHandoff(input) {
5196
5196
  const key = normalizeHandoffKey(input.key);
5197
5197
  const northStarSlug = clean(input.northStarSlug);
5198
5198
  const summary = clean(input.summary);
5199
5199
  const sourceSessionId = clean(input.sourceSessionId);
5200
- if (!northStarSlug) throw new Error("north star slug is required");
5201
5200
  if (!summary) throw new Error("handoff summary is required");
5202
5201
  if (!sourceSessionId) throw new Error("source session id is required");
5203
5202
  return {
5204
5203
  state: "open",
5205
5204
  key,
5206
- northStarSlug,
5205
+ northStarSlug: northStarSlug || void 0,
5207
5206
  summary,
5208
5207
  sourceSessionId,
5209
5208
  sourceBranch: clean(input.sourceBranch) || void 0,
@@ -5222,7 +5221,7 @@ function formatHandoffLine(item) {
5222
5221
  const r = item.record;
5223
5222
  const branch = r.sourceBranch ? ` branch:${r.sourceBranch}` : "";
5224
5223
  const suffix = r.state === "claimed" && r.claimedBySessionId ? ` -> ${r.claimedBySessionId}` : "";
5225
- return `${r.key} [${r.state}] northstar:${r.northStarSlug}${branch} source:${r.sourceSessionId}${suffix} - ${r.summary}`;
5224
+ return `${r.key} [${r.state}] northstar:${r.northStarSlug ?? "-"}${branch} source:${r.sourceSessionId}${suffix} - ${r.summary}`;
5226
5225
  }
5227
5226
 
5228
5227
  // src/handoff-commands.ts
@@ -5298,7 +5297,7 @@ async function locateClaimedHandoff(key, claimedBySessionId, retry = FOREGROUND_
5298
5297
  const normalized = key.toLowerCase();
5299
5298
  return handoffs.find((item) => {
5300
5299
  const r = item.record;
5301
- return r.state === "claimed" && r.claimedBySessionId === claimedBySessionId && (r.key.toLowerCase() === normalized || r.northStarSlug.toLowerCase() === normalized);
5300
+ return r.state === "claimed" && r.claimedBySessionId === claimedBySessionId && (r.key.toLowerCase() === normalized || r.northStarSlug?.toLowerCase() === normalized);
5302
5301
  }) ?? null;
5303
5302
  }
5304
5303
  async function postKeyedNote(key, summary, options) {
@@ -5312,9 +5311,12 @@ async function postKeyedNote(key, summary, options) {
5312
5311
  function deriveOpenFields(summary, opts, head, key) {
5313
5312
  const northStarSlug = opts.northStarSlug ?? head.anchor?.slug;
5314
5313
  const handoffKey = opts.key ?? northStarSlug;
5314
+ if (!handoffKey?.trim()) {
5315
+ throw new Error("a handoff needs a key: pass --key <slug|#issue> (or --next), or open it on a session with a North Star anchor");
5316
+ }
5315
5317
  const text = summary?.trim() || head.next?.trim() || head.anchor?.intent?.trim();
5316
5318
  return planOpenHandoff({
5317
- key: handoffKey ?? "",
5319
+ key: handoffKey,
5318
5320
  northStarSlug: northStarSlug ?? "",
5319
5321
  summary: text ?? "",
5320
5322
  sourceSessionId: key.sessionId,
@@ -5390,7 +5392,7 @@ async function closeSourceHandoff(key, state, opts = {}, io = consoleIo) {
5390
5392
  anchor: located.item.record.summary,
5391
5393
  anchorSlug: located.item.record.northStarSlug,
5392
5394
  anchorForce: true,
5393
- decision: `accepted handoff ${located.item.record.key} from session ${located.item.record.sourceSessionId}; northstar ${located.item.record.northStarSlug}`,
5395
+ decision: `accepted handoff ${located.item.record.key} from session ${located.item.record.sourceSessionId}; northstar ${located.item.record.northStarSlug ?? "(none)"}`,
5394
5396
  verified: true
5395
5397
  });
5396
5398
  }
@@ -5412,7 +5414,7 @@ async function runHandoffDecline(key, opts = {}, io = consoleIo) {
5412
5414
  }
5413
5415
  function registerHandoffCommands(program3) {
5414
5416
  const handoff = program3.command("handoff").description("explicit saga + North Star handoff lifecycle");
5415
- handoff.command("open [summary]").description("open a handoff bound to the current North Star slug").option("--key <slug|#issue>", "handoff key (defaults to the current North Star slug)").option("--north-star-slug <slug>", "North Star slug to bind (defaults to current saga anchor slug)").option("--message-file <path|->", "read the handoff summary from a UTF-8 file, or from stdin with - (avoids cmd.exe quoting)").option("--json", "machine-readable output").action((summary, opts) => runHandoffOpen(summary, opts));
5417
+ handoff.command("open [summary]").description("open a handoff bound to a North Star slug or a board issue (--key/--next)").option("--key <slug|#issue>", "handoff key: a North Star slug or a board issue (defaults to the current North Star slug)").option("--next <slug|#issue>", "alias for --key (mirrors `saga note --next`)").option("--north-star-slug <slug>", "North Star slug to bind (optional; omit for an issue-bound handoff)").option("--message-file <path|->", "read the handoff summary from a UTF-8 file, or from stdin with - (avoids cmd.exe quoting)").option("--json", "machine-readable output").action((summary, opts) => runHandoffOpen(summary, { ...opts, key: opts.key ?? opts.next }));
5416
5418
  handoff.command("list").description("list open handoffs for the current repo (any branch)").option("--all", "include claimed/cancelled records").option("--json", "machine-readable output").action(async (opts) => {
5417
5419
  await runHandoffList(opts);
5418
5420
  });
@@ -7105,6 +7107,9 @@ function northstarPointer(injected = false) {
7105
7107
  }
7106
7108
  return "North Stars: run `mmi-cli northstar relevant` to load plans relevant to your task (`northstar list` for all).";
7107
7109
  }
7110
+ function kbPointer() {
7111
+ return "MM-KB (org knowledge): start at `mmi-cli kb get kb/INDEX.md`, then `kb get <path>` \u2014 consult before inventing conventions or storing long-term docs.";
7112
+ }
7108
7113
 
7109
7114
  // src/board.ts
7110
7115
  var import_node_child_process7 = require("node:child_process");
@@ -8023,9 +8028,47 @@ function ghError(e) {
8023
8028
  return (err.stderr || err.message || String(e)).trim();
8024
8029
  }
8025
8030
 
8031
+ // src/board-slice-cache.ts
8032
+ var import_node_fs14 = require("node:fs");
8033
+ var import_node_path12 = require("node:path");
8034
+ var BOARD_SLICE_CACHE_FILE = (0, import_node_path12.join)(".mmi", "board-slice.json");
8035
+ var BOARD_SLICE_CACHE_TTL_MS = 10 * 60 * 1e3;
8036
+ function boardSliceCachePath(cwd) {
8037
+ return (0, import_node_path12.join)(cwd, BOARD_SLICE_CACHE_FILE);
8038
+ }
8039
+ function readCachedBoardSlice(cwd) {
8040
+ try {
8041
+ const parsed = JSON.parse((0, import_node_fs14.readFileSync)(boardSliceCachePath(cwd), "utf8"));
8042
+ if (typeof parsed.ts !== "number") return null;
8043
+ return { block: typeof parsed.block === "string" ? parsed.block : null, ts: parsed.ts };
8044
+ } catch {
8045
+ return null;
8046
+ }
8047
+ }
8048
+ function writeCachedBoardSlice(cwd, slice) {
8049
+ const path2 = boardSliceCachePath(cwd);
8050
+ const tmp = `${path2}.${process.pid}.tmp`;
8051
+ try {
8052
+ (0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path2), { recursive: true });
8053
+ (0, import_node_fs14.writeFileSync)(tmp, JSON.stringify(slice));
8054
+ (0, import_node_fs14.renameSync)(tmp, path2);
8055
+ } catch {
8056
+ try {
8057
+ (0, import_node_fs14.rmSync)(tmp, { force: true });
8058
+ } catch {
8059
+ }
8060
+ }
8061
+ }
8062
+ function boardSliceCacheStale(cached, now, ttlMs = BOARD_SLICE_CACHE_TTL_MS) {
8063
+ return !cached || now - cached.ts >= ttlMs;
8064
+ }
8065
+
8026
8066
  // src/board-slice.ts
8027
8067
  var SESSION_START_BOARD_TIMEOUT_MS = 3e3;
8068
+ var BOARD_SLICE_REFRESH_TIMEOUT_MS = 3e4;
8028
8069
  var BOARD_SLICE_FRAMING = "BOARD SLICE \u2014 reconcile with saga HEAD; board Status is authoritative for work lifecycle.";
8070
+ var BOARD_SLICE_EMPTY = "Board: nothing assigned or claimable right now \u2014 `mmi-cli board read` (or `/mmi`) for the full board.";
8071
+ var BOARD_SLICE_PENDING = "Board: loading your slice in the background \u2014 run `/mmi` now; it renders here next session.";
8029
8072
  var BOARD_SLICE_MAX_LINES = 5;
8030
8073
  var ACTIVE_STATUS_ORDER = {
8031
8074
  "In Progress": 0,
@@ -8086,12 +8129,25 @@ async function withTimeout(promise, ms) {
8086
8129
  }
8087
8130
  }
8088
8131
  async function runBoardSlice(io, deps, opts) {
8132
+ const cwd = deps.cwd ?? process.cwd();
8133
+ const now = deps.now ?? Date.now();
8134
+ let cfg;
8135
+ try {
8136
+ cfg = await deps.loadConfig();
8137
+ } catch {
8138
+ return;
8139
+ }
8140
+ if (!boardMetaConfigured(cfg)) return;
8141
+ const cached = readCachedBoardSlice(cwd);
8142
+ if (boardSliceCacheStale(cached, now)) deps.scheduleRefresh?.();
8143
+ if (cached) {
8144
+ io.log(cached.block ?? BOARD_SLICE_EMPTY);
8145
+ return;
8146
+ }
8089
8147
  const budget = deps.timeoutMs ?? SESSION_START_BOARD_TIMEOUT_MS;
8090
8148
  const controller = new AbortController();
8091
8149
  const deadline = setTimeout(() => controller.abort(), budget);
8092
8150
  try {
8093
- const cfg = await deps.loadConfig();
8094
- if (!boardMetaConfigured(cfg)) return;
8095
8151
  const client = deps.client ?? createGitHubClient({ defaultTimeoutMs: budget, signal: controller.signal });
8096
8152
  const report = await withTimeout(
8097
8153
  deps.readBoard({ config: cfg, allowPartial: true }, { client }),
@@ -8099,7 +8155,26 @@ async function runBoardSlice(io, deps, opts) {
8099
8155
  );
8100
8156
  if (opts?.expectedLogin && report.viewer && opts.expectedLogin !== report.viewer) return;
8101
8157
  const block = renderBoardSlice(report);
8102
- if (block) io.log(block);
8158
+ writeCachedBoardSlice(cwd, { block, ts: now });
8159
+ io.log(block ?? BOARD_SLICE_EMPTY);
8160
+ } catch {
8161
+ io.log(BOARD_SLICE_PENDING);
8162
+ } finally {
8163
+ clearTimeout(deadline);
8164
+ }
8165
+ }
8166
+ async function refreshBoardSliceCache(deps) {
8167
+ const cwd = deps.cwd ?? process.cwd();
8168
+ const now = deps.now ?? Date.now();
8169
+ const budget = deps.timeoutMs ?? BOARD_SLICE_REFRESH_TIMEOUT_MS;
8170
+ const controller = new AbortController();
8171
+ const deadline = setTimeout(() => controller.abort(), budget);
8172
+ try {
8173
+ const cfg = await deps.loadConfig();
8174
+ if (!boardMetaConfigured(cfg)) return;
8175
+ const client = deps.client ?? createGitHubClient({ defaultTimeoutMs: budget, signal: controller.signal });
8176
+ const report = await withTimeout(deps.readBoard({ config: cfg, allowPartial: true }, { client }), budget);
8177
+ writeCachedBoardSlice(cwd, { block: renderBoardSlice(report), ts: now });
8103
8178
  } catch {
8104
8179
  } finally {
8105
8180
  clearTimeout(deadline);
@@ -8107,8 +8182,8 @@ async function runBoardSlice(io, deps, opts) {
8107
8182
  }
8108
8183
 
8109
8184
  // src/worktree.ts
8110
- var import_node_fs14 = require("node:fs");
8111
- var import_node_path12 = require("node:path");
8185
+ var import_node_fs15 = require("node:fs");
8186
+ var import_node_path13 = require("node:path");
8112
8187
  var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
8113
8188
  var PKG = "package.json";
8114
8189
  var LOCKFILE = "package-lock.json";
@@ -8116,21 +8191,21 @@ var NODE_MODULES = "node_modules";
8116
8191
  var realFsProbe = {
8117
8192
  isDir: (p) => {
8118
8193
  try {
8119
- return (0, import_node_fs14.statSync)(p).isDirectory();
8194
+ return (0, import_node_fs15.statSync)(p).isDirectory();
8120
8195
  } catch {
8121
8196
  return false;
8122
8197
  }
8123
8198
  },
8124
8199
  isFile: (p) => {
8125
8200
  try {
8126
- return (0, import_node_fs14.statSync)(p).isFile();
8201
+ return (0, import_node_fs15.statSync)(p).isFile();
8127
8202
  } catch {
8128
8203
  return false;
8129
8204
  }
8130
8205
  },
8131
8206
  listDirs: (p) => {
8132
8207
  try {
8133
- return (0, import_node_fs14.readdirSync)(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
8208
+ return (0, import_node_fs15.readdirSync)(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
8134
8209
  } catch {
8135
8210
  return [];
8136
8211
  }
@@ -8138,12 +8213,12 @@ var realFsProbe = {
8138
8213
  };
8139
8214
  function scanInstallDirs(root, fs2 = realFsProbe) {
8140
8215
  const factsFor = (dir) => {
8141
- const abs = dir ? (0, import_node_path12.join)(root, dir) : root;
8216
+ const abs = dir ? (0, import_node_path13.join)(root, dir) : root;
8142
8217
  return {
8143
8218
  dir,
8144
- hasPackageJson: fs2.isFile((0, import_node_path12.join)(abs, PKG)),
8145
- hasLockfile: fs2.isFile((0, import_node_path12.join)(abs, LOCKFILE)),
8146
- hasNodeModules: fs2.isDir((0, import_node_path12.join)(abs, NODE_MODULES))
8219
+ hasPackageJson: fs2.isFile((0, import_node_path13.join)(abs, PKG)),
8220
+ hasLockfile: fs2.isFile((0, import_node_path13.join)(abs, LOCKFILE)),
8221
+ hasNodeModules: fs2.isDir((0, import_node_path13.join)(abs, NODE_MODULES))
8147
8222
  };
8148
8223
  };
8149
8224
  const children = fs2.listDirs(root).filter((name) => name !== NODE_MODULES && name !== ".git");
@@ -8153,7 +8228,7 @@ function npmInstallTargets(dirs) {
8153
8228
  return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install" }));
8154
8229
  }
8155
8230
  function isLinkedWorktree(root, fs2 = realFsProbe) {
8156
- return fs2.isFile((0, import_node_path12.join)(root, ".git"));
8231
+ return fs2.isFile((0, import_node_path13.join)(root, ".git"));
8157
8232
  }
8158
8233
  function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
8159
8234
  if (!isLinkedWorktree(root, fs2)) return null;
@@ -8163,8 +8238,8 @@ function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
8163
8238
  return `[worktree] provisioning tooling in the background (deps in ${where} + local config) \u2014 \`mmi-cli worktree setup\` to redo`;
8164
8239
  }
8165
8240
  function defaultCopyFile(from, to) {
8166
- (0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(to), { recursive: true });
8167
- (0, import_node_fs14.copyFileSync)(from, to);
8241
+ (0, import_node_fs15.mkdirSync)((0, import_node_path13.dirname)(to), { recursive: true });
8242
+ (0, import_node_fs15.copyFileSync)(from, to);
8168
8243
  }
8169
8244
  async function provisionWorktree(worktreeRoot, deps) {
8170
8245
  const fs2 = deps.fs ?? realFsProbe;
@@ -8176,7 +8251,7 @@ async function provisionWorktree(worktreeRoot, deps) {
8176
8251
  const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules).map((d) => d.dir);
8177
8252
  const installed = [];
8178
8253
  for (const target of targets) {
8179
- const cwd = target.dir ? (0, import_node_path12.join)(worktreeRoot, target.dir) : worktreeRoot;
8254
+ const cwd = target.dir ? (0, import_node_path13.join)(worktreeRoot, target.dir) : worktreeRoot;
8180
8255
  log(`installing deps: ${target.command} in ${target.dir || "."}`);
8181
8256
  await deps.runInstall(target.command, cwd);
8182
8257
  installed.push(target);
@@ -8185,7 +8260,7 @@ async function provisionWorktree(worktreeRoot, deps) {
8185
8260
  const copySkipped = [];
8186
8261
  const primary = await deps.primaryCheckout();
8187
8262
  for (const rel of LOCAL_ONLY_FILES) {
8188
- const dest = (0, import_node_path12.join)(worktreeRoot, rel);
8263
+ const dest = (0, import_node_path13.join)(worktreeRoot, rel);
8189
8264
  if (fs2.isFile(dest)) {
8190
8265
  copySkipped.push({ file: rel, reason: "already-present" });
8191
8266
  continue;
@@ -8194,11 +8269,11 @@ async function provisionWorktree(worktreeRoot, deps) {
8194
8269
  copySkipped.push({ file: rel, reason: "no-primary" });
8195
8270
  continue;
8196
8271
  }
8197
- if (!fs2.isFile((0, import_node_path12.join)(primary, rel))) {
8272
+ if (!fs2.isFile((0, import_node_path13.join)(primary, rel))) {
8198
8273
  copySkipped.push({ file: rel, reason: "absent-in-primary" });
8199
8274
  continue;
8200
8275
  }
8201
- copyFile((0, import_node_path12.join)(primary, rel), dest);
8276
+ copyFile((0, import_node_path13.join)(primary, rel), dest);
8202
8277
  copied.push(rel);
8203
8278
  log(`copied local config: ${rel}`);
8204
8279
  }
@@ -8206,7 +8281,7 @@ async function provisionWorktree(worktreeRoot, deps) {
8206
8281
  }
8207
8282
  function defaultWorktreePath(repoRoot, branch) {
8208
8283
  const safe = branch.replace(/[/\\]+/g, "-");
8209
- return (0, import_node_path12.join)((0, import_node_path12.dirname)(repoRoot), "mmi-worktrees", safe);
8284
+ return (0, import_node_path13.join)((0, import_node_path13.dirname)(repoRoot), "mmi-worktrees", safe);
8210
8285
  }
8211
8286
 
8212
8287
  // src/frontmatter.ts
@@ -8404,7 +8479,7 @@ async function runNorthstarContext(io, deps) {
8404
8479
  }
8405
8480
 
8406
8481
  // src/index.ts
8407
- var import_node_path19 = require("node:path");
8482
+ var import_node_path20 = require("node:path");
8408
8483
 
8409
8484
  // src/merge-ci-policy.ts
8410
8485
  function resolveMergeCiPolicy(input) {
@@ -8705,6 +8780,11 @@ function diffManagedGitignoreBlock(current) {
8705
8780
  seeded: false
8706
8781
  };
8707
8782
  }
8783
+ function planManagedGitignore(current) {
8784
+ const { content, changed } = upsertManagedGitignoreBlock(current);
8785
+ const { added, removed } = diffManagedGitignoreBlock(current);
8786
+ return { changed, content, added, removed };
8787
+ }
8708
8788
 
8709
8789
  // src/project-model.ts
8710
8790
  var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-game", "non-deployable", "cli-tool", "worker"];
@@ -9603,7 +9683,7 @@ var import_node_os6 = require("node:os");
9603
9683
  // src/gh-create.ts
9604
9684
  var import_promises5 = require("node:fs/promises");
9605
9685
  var import_node_os4 = require("node:os");
9606
- var import_node_path13 = require("node:path");
9686
+ var import_node_path14 = require("node:path");
9607
9687
  var import_node_crypto6 = require("node:crypto");
9608
9688
  var ISSUE_TYPES = ["bug", "feature", "task"];
9609
9689
  var GH_MUTATION_TIMEOUT_MS = 12e4;
@@ -9644,7 +9724,7 @@ async function bodyArgsViaFile(args, deps = {}) {
9644
9724
  } };
9645
9725
  const write = deps.write ?? import_promises5.writeFile;
9646
9726
  const remove = deps.remove ?? import_promises5.unlink;
9647
- const file = (0, import_node_path13.join)(deps.dir ?? (0, import_node_os4.tmpdir)(), `mmi-gh-body-${process.pid}-${(0, import_node_crypto6.randomBytes)(4).toString("hex")}.md`);
9727
+ const file = (0, import_node_path14.join)(deps.dir ?? (0, import_node_os4.tmpdir)(), `mmi-gh-body-${process.pid}-${(0, import_node_crypto6.randomBytes)(4).toString("hex")}.md`);
9648
9728
  await write(file, args[i + 1], "utf8");
9649
9729
  return {
9650
9730
  args: [...args.slice(0, i), "--body-file", file, ...args.slice(i + 2)],
@@ -11245,9 +11325,9 @@ function decideStage(inputs) {
11245
11325
 
11246
11326
  // src/cursor-plugin-seed.ts
11247
11327
  var import_node_child_process8 = require("node:child_process");
11248
- var import_node_fs15 = require("node:fs");
11328
+ var import_node_fs16 = require("node:fs");
11249
11329
  var import_node_os5 = require("node:os");
11250
- var import_node_path14 = require("node:path");
11330
+ var import_node_path15 = require("node:path");
11251
11331
  var import_node_util6 = require("node:util");
11252
11332
  function isSemverVersion(v) {
11253
11333
  return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
@@ -11264,17 +11344,17 @@ function ghReleaseTarballApiArgs(tag) {
11264
11344
  }
11265
11345
  function cursorUserGlobalStatePath() {
11266
11346
  if (process.platform === "win32") {
11267
- const base2 = process.env.APPDATA || (0, import_node_path14.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
11268
- return (0, import_node_path14.join)(base2, "Cursor", "User", "globalStorage", "state.vscdb");
11347
+ const base2 = process.env.APPDATA || (0, import_node_path15.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
11348
+ return (0, import_node_path15.join)(base2, "Cursor", "User", "globalStorage", "state.vscdb");
11269
11349
  }
11270
11350
  if (process.platform === "darwin") {
11271
- return (0, import_node_path14.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
11351
+ return (0, import_node_path15.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
11272
11352
  }
11273
- return (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
11353
+ return (0, import_node_path15.join)((0, import_node_os5.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
11274
11354
  }
11275
11355
  async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
11276
11356
  const dbPath = cursorUserGlobalStatePath();
11277
- if (!(0, import_node_fs15.existsSync)(dbPath)) return void 0;
11357
+ if (!(0, import_node_fs16.existsSync)(dbPath)) return void 0;
11278
11358
  try {
11279
11359
  const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
11280
11360
  timeout: 5e3
@@ -11288,57 +11368,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
11288
11368
  }
11289
11369
  }
11290
11370
  function syncDirContents(src, dest) {
11291
- (0, import_node_fs15.mkdirSync)(dest, { recursive: true });
11292
- for (const name of (0, import_node_fs15.readdirSync)(dest)) {
11293
- (0, import_node_fs15.rmSync)((0, import_node_path14.join)(dest, name), { recursive: true, force: true });
11371
+ (0, import_node_fs16.mkdirSync)(dest, { recursive: true });
11372
+ for (const name of (0, import_node_fs16.readdirSync)(dest)) {
11373
+ (0, import_node_fs16.rmSync)((0, import_node_path15.join)(dest, name), { recursive: true, force: true });
11294
11374
  }
11295
- (0, import_node_fs15.cpSync)(src, dest, { recursive: true });
11375
+ (0, import_node_fs16.cpSync)(src, dest, { recursive: true });
11296
11376
  }
11297
11377
  function releaseTag(releasedVersion) {
11298
11378
  return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
11299
11379
  }
11300
11380
  async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
11301
- const tarFile = (0, import_node_path14.join)(tmpRoot, "archive.tar");
11381
+ const tarFile = (0, import_node_path15.join)(tmpRoot, "archive.tar");
11302
11382
  try {
11303
11383
  await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
11304
11384
  await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
11305
11385
  timeout: 6e4
11306
11386
  });
11307
11387
  await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
11308
- const pluginMmi = (0, import_node_path14.join)(tmpRoot, "plugins", "mmi");
11309
- return (0, import_node_fs15.existsSync)((0, import_node_path14.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
11388
+ const pluginMmi = (0, import_node_path15.join)(tmpRoot, "plugins", "mmi");
11389
+ return (0, import_node_fs16.existsSync)((0, import_node_path15.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
11310
11390
  } catch {
11311
11391
  return void 0;
11312
11392
  }
11313
11393
  }
11314
11394
  async function downloadPluginMmiViaGh(tag, tmpRoot) {
11315
- const tarPath = (0, import_node_path14.join)(tmpRoot, "repo.tgz");
11395
+ const tarPath = (0, import_node_path15.join)(tmpRoot, "repo.tgz");
11316
11396
  try {
11317
- (0, import_node_fs15.mkdirSync)(tmpRoot, { recursive: true });
11397
+ (0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
11318
11398
  const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
11319
11399
  timeout: 12e4,
11320
11400
  maxBuffer: 100 * 1024 * 1024,
11321
11401
  encoding: "buffer",
11322
11402
  windowsHide: true
11323
11403
  });
11324
- (0, import_node_fs15.writeFileSync)(tarPath, stdout);
11404
+ (0, import_node_fs16.writeFileSync)(tarPath, stdout);
11325
11405
  await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
11326
- const top = (0, import_node_fs15.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
11406
+ const top = (0, import_node_fs16.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
11327
11407
  if (!top) return void 0;
11328
- const pluginMmi = (0, import_node_path14.join)(tmpRoot, top, "plugins", "mmi");
11329
- return (0, import_node_fs15.existsSync)((0, import_node_path14.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
11408
+ const pluginMmi = (0, import_node_path15.join)(tmpRoot, top, "plugins", "mmi");
11409
+ return (0, import_node_fs16.existsSync)((0, import_node_path15.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
11330
11410
  } catch {
11331
11411
  return void 0;
11332
11412
  }
11333
11413
  }
11334
11414
  async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
11335
- (0, import_node_fs15.mkdirSync)(tmpRoot, { recursive: true });
11415
+ (0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
11336
11416
  const tag = releaseTag(releasedVersion);
11337
11417
  if (hubCheckout) {
11338
11418
  const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
11339
11419
  if (fromHub) return fromHub;
11340
11420
  }
11341
- return downloadPluginMmiViaGh(tag, (0, import_node_path14.join)(tmpRoot, "gh"));
11421
+ return downloadPluginMmiViaGh(tag, (0, import_node_path15.join)(tmpRoot, "gh"));
11342
11422
  }
11343
11423
  function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
11344
11424
  if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
@@ -11359,7 +11439,7 @@ async function applyCursorPluginCacheSeed(input) {
11359
11439
  for (const pin of pinsToSeed) {
11360
11440
  syncDirContents(source, pin.path);
11361
11441
  }
11362
- (0, import_node_fs15.rmSync)(tmpRoot, { recursive: true, force: true });
11442
+ (0, import_node_fs16.rmSync)(tmpRoot, { recursive: true, force: true });
11363
11443
  return true;
11364
11444
  }
11365
11445
 
@@ -11400,6 +11480,17 @@ function pluginInstallManualFix(projectPath, surface = "claude-cli") {
11400
11480
  function isMmiPluginEnabled(settings) {
11401
11481
  return Boolean(settings?.enabledPlugins?.[MMI_PLUGIN_ID]);
11402
11482
  }
11483
+ function buildSettingsPluginDriftCheck(input) {
11484
+ const base2 = {
11485
+ ok: true,
11486
+ label: "org plugin wiring in .claude/settings.json (mmi@mutmutco)",
11487
+ fix: "the Claude Code app pruned mmi@mutmutco from the tracked .claude/settings.json (it does this at session start when the mutmutco marketplace source does not resolve, #1805); restore it with `git checkout -- .claude/settings.json` before committing, or the whole org skill set is disabled for the branch"
11488
+ };
11489
+ const enabled = input.settings?.enabledPlugins;
11490
+ if (!input.isOrgRepo || !enabled || Object.keys(enabled).length === 0) return base2;
11491
+ if (!enabled[MMI_PLUGIN_ID]) return { ...base2, ok: false };
11492
+ return base2;
11493
+ }
11403
11494
  function hasProjectInstallRecord(file, pluginId, projectPath) {
11404
11495
  const records = file?.plugins?.[pluginId];
11405
11496
  if (!Array.isArray(records)) return false;
@@ -12016,8 +12107,8 @@ async function runStageLiveDown(deps, t) {
12016
12107
  }
12017
12108
 
12018
12109
  // src/design-system.ts
12019
- var import_node_fs16 = require("node:fs");
12020
- var import_node_path15 = require("node:path");
12110
+ var import_node_fs17 = require("node:fs");
12111
+ var import_node_path16 = require("node:path");
12021
12112
  var UI_PACKAGE_CANDIDATES = ["@mutmutco/ui-dashboard", "@mutmutco/ui", "@mutmutco/theme"];
12022
12113
  var DESIGN_SYSTEM_VERSION_LABEL = "@mutmutco design-system npm package (vs @latest)";
12023
12114
  function dashboardConsumerRegistryFix(error) {
@@ -12066,17 +12157,17 @@ function buildDesignSystemVersionCheck(input) {
12066
12157
  }
12067
12158
  function readJsonFile(path2) {
12068
12159
  try {
12069
- return JSON.parse((0, import_node_fs16.readFileSync)(path2, "utf8"));
12160
+ return JSON.parse((0, import_node_fs17.readFileSync)(path2, "utf8"));
12070
12161
  } catch {
12071
12162
  return void 0;
12072
12163
  }
12073
12164
  }
12074
12165
  function isUiFactoryCheckout(root) {
12075
- const pkg = readJsonFile((0, import_node_path15.join)(root, "package.json"));
12166
+ const pkg = readJsonFile((0, import_node_path16.join)(root, "package.json"));
12076
12167
  return pkg?.name === "mmd-ui" && pkg?.private === true;
12077
12168
  }
12078
12169
  function resolveDeclaredUiPackage(root) {
12079
- const pkg = readJsonFile((0, import_node_path15.join)(root, "package.json"));
12170
+ const pkg = readJsonFile((0, import_node_path16.join)(root, "package.json"));
12080
12171
  if (!pkg) return void 0;
12081
12172
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
12082
12173
  for (const name of UI_PACKAGE_CANDIDATES) {
@@ -12086,8 +12177,8 @@ function resolveDeclaredUiPackage(root) {
12086
12177
  return void 0;
12087
12178
  }
12088
12179
  function readLockfileInstalledVersion(root, packageName) {
12089
- const lockPath = (0, import_node_path15.join)(root, "package-lock.json");
12090
- if (!(0, import_node_fs16.existsSync)(lockPath)) return void 0;
12180
+ const lockPath = (0, import_node_path16.join)(root, "package-lock.json");
12181
+ if (!(0, import_node_fs17.existsSync)(lockPath)) return void 0;
12091
12182
  const lock = readJsonFile(lockPath);
12092
12183
  const node = lock?.packages?.[`node_modules/${packageName}`];
12093
12184
  const version = node?.version?.trim();
@@ -12116,15 +12207,15 @@ function designSystemSnapshot(root) {
12116
12207
 
12117
12208
  // src/design-system-registry.ts
12118
12209
  var import_node_crypto7 = require("node:crypto");
12119
- var import_node_fs18 = require("node:fs");
12120
- var import_node_path16 = require("node:path");
12210
+ var import_node_fs19 = require("node:fs");
12211
+ var import_node_path17 = require("node:path");
12121
12212
 
12122
12213
  // src/atomic-write.ts
12123
- var import_node_fs17 = require("node:fs");
12214
+ var import_node_fs18 = require("node:fs");
12124
12215
  function atomicWriteFileSync(path2, content) {
12125
12216
  const tmp = `${path2}.${process.pid}.tmp`;
12126
- (0, import_node_fs17.writeFileSync)(tmp, content, "utf8");
12127
- (0, import_node_fs17.renameSync)(tmp, path2);
12217
+ (0, import_node_fs18.writeFileSync)(tmp, content, "utf8");
12218
+ (0, import_node_fs18.renameSync)(tmp, path2);
12128
12219
  }
12129
12220
 
12130
12221
  // src/design-system-registry.ts
@@ -12135,13 +12226,13 @@ var REGISTRY_FIX = "run `mmi-cli doctor --apply` to pull registry components int
12135
12226
  var REGISTRY_UNREACHABLE_FIX = "live @mutmutco registry unreachable \u2014 verify `components.json` `@mutmutco` registry URL and network, then retry `mmi-cli doctor`";
12136
12227
  function readJsonFile2(path2) {
12137
12228
  try {
12138
- return JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
12229
+ return JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
12139
12230
  } catch {
12140
12231
  return void 0;
12141
12232
  }
12142
12233
  }
12143
12234
  function readComponentsJson(root) {
12144
- return readJsonFile2((0, import_node_path16.join)(root, "components.json"));
12235
+ return readJsonFile2((0, import_node_path17.join)(root, "components.json"));
12145
12236
  }
12146
12237
  function hasMutmutcoRegistry(root) {
12147
12238
  const url = readComponentsJson(root)?.registries?.["@mutmutco"];
@@ -12149,7 +12240,7 @@ function hasMutmutcoRegistry(root) {
12149
12240
  }
12150
12241
  function resolveCacheDir(root) {
12151
12242
  const custom = readComponentsJson(root)?.mmi?.cacheDir;
12152
- return (0, import_node_path16.join)(root, custom ?? DESIGN_SYSTEM_CACHE_DIR);
12243
+ return (0, import_node_path17.join)(root, custom ?? DESIGN_SYSTEM_CACHE_DIR);
12153
12244
  }
12154
12245
  function resolveRegistryUrlTemplate(root) {
12155
12246
  return readComponentsJson(root)?.registries?.["@mutmutco"];
@@ -12158,7 +12249,7 @@ function registryItemUrl(template, name) {
12158
12249
  return template.replace("{name}", name);
12159
12250
  }
12160
12251
  function readDesignSystemManifest(root) {
12161
- const raw = readJsonFile2((0, import_node_path16.join)(root, DESIGN_SYSTEM_MANIFEST_PATH));
12252
+ const raw = readJsonFile2((0, import_node_path17.join)(root, DESIGN_SYSTEM_MANIFEST_PATH));
12162
12253
  if (!raw || !Array.isArray(raw.components)) return void 0;
12163
12254
  return raw;
12164
12255
  }
@@ -12170,11 +12261,11 @@ function listInstalledRegistryComponents(root) {
12170
12261
  return scanCachedComponentNames(resolveCacheDir(root));
12171
12262
  }
12172
12263
  function scanCachedComponentNames(cacheDir) {
12173
- if (!(0, import_node_fs18.existsSync)(cacheDir)) return [];
12264
+ if (!(0, import_node_fs19.existsSync)(cacheDir)) return [];
12174
12265
  const names = /* @__PURE__ */ new Set();
12175
12266
  const walk = (dir) => {
12176
- for (const ent of (0, import_node_fs18.readdirSync)(dir, { withFileTypes: true })) {
12177
- const p = (0, import_node_path16.join)(dir, ent.name);
12267
+ for (const ent of (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true })) {
12268
+ const p = (0, import_node_path17.join)(dir, ent.name);
12178
12269
  if (ent.isDirectory()) walk(p);
12179
12270
  else if (ent.isFile() && /\.(tsx?|jsx?)$/.test(ent.name)) {
12180
12271
  names.add(ent.name.replace(/\.(tsx|ts|jsx|js)$/, ""));
@@ -12263,13 +12354,13 @@ async function gatherRegistryComponentsState(root, targetVersion, deps) {
12263
12354
  let componentStale = false;
12264
12355
  for (const file of item.files) {
12265
12356
  if (!file.target || file.content == null) continue;
12266
- const cachePath = (0, import_node_path16.join)(cacheDir, cacheRelativePath(file.target));
12267
- if (!(0, import_node_fs18.existsSync)(cachePath)) {
12357
+ const cachePath = (0, import_node_path17.join)(cacheDir, cacheRelativePath(file.target));
12358
+ if (!(0, import_node_fs19.existsSync)(cachePath)) {
12268
12359
  componentStale = true;
12269
12360
  break;
12270
12361
  }
12271
12362
  try {
12272
- if (contentHash((0, import_node_fs18.readFileSync)(cachePath, "utf8")) !== contentHash(file.content)) {
12363
+ if (contentHash((0, import_node_fs19.readFileSync)(cachePath, "utf8")) !== contentHash(file.content)) {
12273
12364
  componentStale = true;
12274
12365
  break;
12275
12366
  }
@@ -12279,7 +12370,7 @@ async function gatherRegistryComponentsState(root, targetVersion, deps) {
12279
12370
  }
12280
12371
  }
12281
12372
  if (componentStale) {
12282
- if ((0, import_node_fs18.existsSync)((0, import_node_path16.join)(cacheDir, "ui", `${name}.tsx`)) || (0, import_node_fs18.existsSync)((0, import_node_path16.join)(cacheDir, `${name}.tsx`))) {
12373
+ if ((0, import_node_fs19.existsSync)((0, import_node_path17.join)(cacheDir, "ui", `${name}.tsx`)) || (0, import_node_fs19.existsSync)((0, import_node_path17.join)(cacheDir, `${name}.tsx`))) {
12283
12374
  stale.push(name);
12284
12375
  } else {
12285
12376
  missing.push(name);
@@ -12307,15 +12398,15 @@ async function applyRegistryComponentsSync(root, components, targetVersion, log,
12307
12398
  if (!item) return { ok: false };
12308
12399
  for (const file of item.files) {
12309
12400
  if (!file.target || file.content == null) continue;
12310
- const outPath = (0, import_node_path16.join)(cacheDir, cacheRelativePath(file.target));
12311
- deps.mkdir((0, import_node_path16.dirname)(outPath));
12401
+ const outPath = (0, import_node_path17.join)(cacheDir, cacheRelativePath(file.target));
12402
+ deps.mkdir((0, import_node_path17.dirname)(outPath));
12312
12403
  const body = file.content.endsWith("\n") ? file.content : `${file.content}
12313
12404
  `;
12314
12405
  deps.writeFile(outPath, body);
12315
12406
  }
12316
12407
  }
12317
- const manifestPath = (0, import_node_path16.join)(root, DESIGN_SYSTEM_MANIFEST_PATH);
12318
- deps.mkdir((0, import_node_path16.dirname)(manifestPath));
12408
+ const manifestPath = (0, import_node_path17.join)(root, DESIGN_SYSTEM_MANIFEST_PATH);
12409
+ deps.mkdir((0, import_node_path17.dirname)(manifestPath));
12319
12410
  const manifest = {
12320
12411
  version: targetVersion,
12321
12412
  syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -12330,14 +12421,14 @@ function defaultRegistrySyncDeps() {
12330
12421
  return {
12331
12422
  fetch,
12332
12423
  writeFile: (path2, content) => atomicWriteFileSync(path2, content),
12333
- mkdir: (path2) => (0, import_node_fs18.mkdirSync)(path2, { recursive: true })
12424
+ mkdir: (path2) => (0, import_node_fs19.mkdirSync)(path2, { recursive: true })
12334
12425
  };
12335
12426
  }
12336
12427
 
12337
12428
  // src/stage-runner.ts
12338
12429
  var import_node_child_process9 = require("node:child_process");
12339
- var import_node_fs19 = require("node:fs");
12340
- var import_node_path17 = require("node:path");
12430
+ var import_node_fs20 = require("node:fs");
12431
+ var import_node_path18 = require("node:path");
12341
12432
  var import_node_net2 = require("node:net");
12342
12433
  var import_node_util7 = require("node:util");
12343
12434
  var execFileP4 = (0, import_node_util7.promisify)(import_node_child_process9.execFile);
@@ -12382,7 +12473,7 @@ function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
12382
12473
  return void 0;
12383
12474
  }
12384
12475
  function stageStatePath(cwd = process.cwd()) {
12385
- return (0, import_node_path17.join)(cwd, "tmp", "stage", "state.json");
12476
+ return (0, import_node_path18.join)(cwd, "tmp", "stage", "state.json");
12386
12477
  }
12387
12478
  function mergeEnvSecretsIntoFile(content, secrets) {
12388
12479
  const lines = content.split(/\r?\n/);
@@ -12465,9 +12556,9 @@ async function shell(command, cwd, timeoutMs) {
12465
12556
  });
12466
12557
  }
12467
12558
  function readState(path2) {
12468
- if (!(0, import_node_fs19.existsSync)(path2)) return null;
12559
+ if (!(0, import_node_fs20.existsSync)(path2)) return null;
12469
12560
  try {
12470
- return JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
12561
+ return JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
12471
12562
  } catch {
12472
12563
  return null;
12473
12564
  }
@@ -12519,7 +12610,7 @@ async function stopStage(opts = {}) {
12519
12610
  return { ok: true, action: "stop", statePath, message: "no previous stage state found" };
12520
12611
  }
12521
12612
  await killTree(state.pid);
12522
- (0, import_node_fs19.rmSync)(statePath, { force: true });
12613
+ (0, import_node_fs20.rmSync)(statePath, { force: true });
12523
12614
  return { ok: true, action: "stop", statePath, pid: state.pid, message: `stopped previous stage pid ${state.pid}` };
12524
12615
  }
12525
12616
  async function startStage(config = {}, opts = {}) {
@@ -12528,7 +12619,7 @@ async function startStage(config = {}, opts = {}) {
12528
12619
  const cwd = opts.cwd ?? process.cwd();
12529
12620
  const statePath = opts.statePath ?? stageStatePath(cwd);
12530
12621
  const dir = statePath.slice(0, Math.max(statePath.lastIndexOf("/"), statePath.lastIndexOf("\\")));
12531
- (0, import_node_fs19.mkdirSync)(dir, { recursive: true });
12622
+ (0, import_node_fs20.mkdirSync)(dir, { recursive: true });
12532
12623
  let stagePort;
12533
12624
  if (config.portRange) {
12534
12625
  const [s, e] = config.portRange;
@@ -12538,14 +12629,14 @@ async function startStage(config = {}, opts = {}) {
12538
12629
  }
12539
12630
  const sub = (s) => s != null && stagePort != null ? s.replace(/\$\{?STAGE_PORT\}?/g, String(stagePort)) : s;
12540
12631
  if (config.ensureEnv) {
12541
- const target = (0, import_node_path17.join)(cwd, config.ensureEnv.target);
12542
- const example = (0, import_node_path17.join)(cwd, config.ensureEnv.example);
12543
- if (!(0, import_node_fs19.existsSync)(target) && (0, import_node_fs19.existsSync)(example)) {
12544
- (0, import_node_fs19.copyFileSync)(example, target);
12545
- } else if ((0, import_node_fs19.existsSync)(target) && (0, import_node_fs19.existsSync)(example)) {
12546
- const stale = detectStaleEnvFile((0, import_node_fs19.readFileSync)(example, "utf8"), (0, import_node_fs19.readFileSync)(target, "utf8"), {
12547
- exampleMtimeMs: (0, import_node_fs19.statSync)(example).mtimeMs,
12548
- targetMtimeMs: (0, import_node_fs19.statSync)(target).mtimeMs
12632
+ const target = (0, import_node_path18.join)(cwd, config.ensureEnv.target);
12633
+ const example = (0, import_node_path18.join)(cwd, config.ensureEnv.example);
12634
+ if (!(0, import_node_fs20.existsSync)(target) && (0, import_node_fs20.existsSync)(example)) {
12635
+ (0, import_node_fs20.copyFileSync)(example, target);
12636
+ } else if ((0, import_node_fs20.existsSync)(target) && (0, import_node_fs20.existsSync)(example)) {
12637
+ const stale = detectStaleEnvFile((0, import_node_fs20.readFileSync)(example, "utf8"), (0, import_node_fs20.readFileSync)(target, "utf8"), {
12638
+ exampleMtimeMs: (0, import_node_fs20.statSync)(example).mtimeMs,
12639
+ targetMtimeMs: (0, import_node_fs20.statSync)(target).mtimeMs
12549
12640
  });
12550
12641
  if (stale) {
12551
12642
  const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
@@ -12553,8 +12644,8 @@ async function startStage(config = {}, opts = {}) {
12553
12644
  console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
12554
12645
  }
12555
12646
  }
12556
- if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs19.existsSync)(target)) {
12557
- (0, import_node_fs19.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs19.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
12647
+ if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs20.existsSync)(target)) {
12648
+ (0, import_node_fs20.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs20.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
12558
12649
  }
12559
12650
  }
12560
12651
  const extraEnv = {};
@@ -12579,13 +12670,13 @@ async function startStage(config = {}, opts = {}) {
12579
12670
  healthUrl: sub(config.healthUrl?.trim()) || void 0,
12580
12671
  port: stagePort
12581
12672
  };
12582
- (0, import_node_fs19.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
12673
+ (0, import_node_fs20.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
12583
12674
  try {
12584
12675
  if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
12585
12676
  else await waitForProcessStability(child);
12586
12677
  } catch (e) {
12587
12678
  await killTree(state.pid);
12588
- (0, import_node_fs19.rmSync)(statePath, { force: true });
12679
+ (0, import_node_fs20.rmSync)(statePath, { force: true });
12589
12680
  throw e;
12590
12681
  }
12591
12682
  const result = { ok: true, action: "start", statePath, pid: state.pid, port: stagePort, message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}` };
@@ -13043,18 +13134,25 @@ async function rollDevelopmentForward(deps, ctx, tag) {
13043
13134
  }
13044
13135
  function resolveContextState(context, checkRuns, statuses) {
13045
13136
  let sawFailure = false;
13137
+ let sawSuccess = false;
13138
+ let sawPending = false;
13046
13139
  for (const r of checkRuns) {
13047
13140
  if (r.name !== context) continue;
13048
13141
  if (r.status === "completed") {
13049
- if (r.conclusion === "success") return "success";
13050
- if (r.conclusion !== "skipped" && r.conclusion !== "neutral") sawFailure = true;
13142
+ if (r.conclusion === "success") sawSuccess = true;
13143
+ else if (r.conclusion !== "skipped" && r.conclusion !== "neutral") sawFailure = true;
13144
+ } else {
13145
+ sawPending = true;
13051
13146
  }
13052
13147
  }
13053
13148
  for (const s of statuses) {
13054
13149
  if (s.context !== context) continue;
13055
- if (s.state === "success") return "success";
13056
- if (s.state === "failure" || s.state === "error") sawFailure = true;
13150
+ if (s.state === "success") sawSuccess = true;
13151
+ else if (s.state === "failure" || s.state === "error") sawFailure = true;
13152
+ else sawPending = true;
13057
13153
  }
13154
+ if (sawPending) return "pending";
13155
+ if (sawSuccess) return "success";
13058
13156
  return sawFailure ? "failed" : "pending";
13059
13157
  }
13060
13158
  async function waitForRequiredTrainChecks(deps, ctx, sha, required) {
@@ -14403,7 +14501,7 @@ async function announceRelease(deps, args) {
14403
14501
  }
14404
14502
 
14405
14503
  // src/port-registry.ts
14406
- var import_node_fs20 = require("node:fs");
14504
+ var import_node_fs21 = require("node:fs");
14407
14505
 
14408
14506
  // ../infra/port-geometry.mjs
14409
14507
  var PORT_BLOCK = 100;
@@ -14417,8 +14515,8 @@ function nextPortBlock(registry2) {
14417
14515
  return [base2, base2 + PORT_SPAN];
14418
14516
  }
14419
14517
  function loadPortRegistry(path2) {
14420
- if (!(0, import_node_fs20.existsSync)(path2)) return {};
14421
- const raw = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
14518
+ if (!(0, import_node_fs21.existsSync)(path2)) return {};
14519
+ const raw = JSON.parse((0, import_node_fs21.readFileSync)(path2, "utf8"));
14422
14520
  const out = {};
14423
14521
  for (const [key, value] of Object.entries(raw)) {
14424
14522
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -14432,9 +14530,9 @@ function ensurePortRange(repo, path2) {
14432
14530
  const existing = registry2[repo];
14433
14531
  if (existing) return existing;
14434
14532
  const range = nextPortBlock(registry2);
14435
- const raw = (0, import_node_fs20.existsSync)(path2) ? JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8")) : {};
14533
+ const raw = (0, import_node_fs21.existsSync)(path2) ? JSON.parse((0, import_node_fs21.readFileSync)(path2, "utf8")) : {};
14436
14534
  raw[repo] = range;
14437
- (0, import_node_fs20.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
14535
+ (0, import_node_fs21.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
14438
14536
  return range;
14439
14537
  }
14440
14538
  function portCursorSeed(registry2) {
@@ -15814,7 +15912,7 @@ function parseOauthVar(raw) {
15814
15912
  throw new Error('project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"/auth/callback"}');
15815
15913
  }
15816
15914
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15817
- throw new Error("project set: oauth must be a {subdomains,domains,callbackPath} object");
15915
+ throw new Error("project set: oauth must be a {subdomains,domains,callbackPath,fofuSubdomain} object");
15818
15916
  }
15819
15917
  const map = parsed;
15820
15918
  const out = {};
@@ -15827,8 +15925,11 @@ function parseOauthVar(raw) {
15827
15925
  } else if (key === "callbackPath") {
15828
15926
  if (typeof value !== "string" || !value.trim()) throw new Error("project set: oauth.callbackPath must be a non-empty string");
15829
15927
  out.callbackPath = value.trim();
15928
+ } else if (key === "fofuSubdomain") {
15929
+ if (typeof value !== "string") throw new Error('project set: oauth.fofuSubdomain must be a string ("" selects the apex fofu.ai)');
15930
+ out.fofuSubdomain = value.trim();
15830
15931
  } else {
15831
- throw new Error(`project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath`);
15932
+ throw new Error(`project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/fofuSubdomain`);
15832
15933
  }
15833
15934
  }
15834
15935
  return out;
@@ -15943,7 +16044,7 @@ var SETTABLE_VAR_HINTS = {
15943
16044
  dashboard: "true|false",
15944
16045
  fofuEnabled: "true|false",
15945
16046
  repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
15946
- oauth: "JSON {subdomains,domains,callbackPath}",
16047
+ oauth: "JSON {subdomains,domains,callbackPath,fofuSubdomain}",
15947
16048
  requiredGcpApis: "comma-string",
15948
16049
  requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
15949
16050
  edgeDomains: "JSON {dev,rc,main} domain map",
@@ -16134,15 +16235,15 @@ function parseKbTree(stdout, prefix) {
16134
16235
  }
16135
16236
 
16136
16237
  // src/northstar-commands.ts
16137
- var import_node_fs21 = require("node:fs");
16238
+ var import_node_fs22 = require("node:fs");
16138
16239
  var import_node_child_process11 = require("node:child_process");
16139
16240
  var import_promises6 = require("node:fs/promises");
16140
16241
 
16141
16242
  // src/plan.ts
16142
- var import_node_path18 = require("node:path");
16243
+ var import_node_path19 = require("node:path");
16143
16244
  var PLANS_DIR = "plans";
16144
- var META_FILE = (0, import_node_path18.join)(PLANS_DIR, ".plan-meta.json");
16145
- var planPath = (slug) => (0, import_node_path18.join)(PLANS_DIR, `${slug}.md`);
16245
+ var META_FILE = (0, import_node_path19.join)(PLANS_DIR, ".plan-meta.json");
16246
+ var planPath = (slug) => (0, import_node_path19.join)(PLANS_DIR, `${slug}.md`);
16146
16247
  var metaKey = (project2, slug) => `${project2}/${slug}`;
16147
16248
  function parseMeta(raw) {
16148
16249
  if (!raw) return {};
@@ -16167,7 +16268,7 @@ function hashContent(s) {
16167
16268
  function staleHint(slug) {
16168
16269
  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`;
16169
16270
  }
16170
- var INDEX_FILE = (0, import_node_path18.join)(PLANS_DIR, ".index.json");
16271
+ var INDEX_FILE = (0, import_node_path19.join)(PLANS_DIR, ".index.json");
16171
16272
  var INDEX_TTL_MS = 6e4;
16172
16273
  function parseIndex(raw) {
16173
16274
  if (!raw) return null;
@@ -16196,7 +16297,7 @@ function mergeIndex(idx, scope, plans, now) {
16196
16297
  const mergedScope = idx.scope === null ? null : [.../* @__PURE__ */ new Set([...idx.scope, ...scope])];
16197
16298
  return { fetchedAt: now, scope: mergedScope, plans: [...kept, ...plans] };
16198
16299
  }
16199
- var QUEUE_FILE = (0, import_node_path18.join)(PLANS_DIR, ".sync-queue.json");
16300
+ var QUEUE_FILE = (0, import_node_path19.join)(PLANS_DIR, ".sync-queue.json");
16200
16301
  var QUEUE_MAX_ATTEMPTS = 10;
16201
16302
  function isValidQueueEntry(e) {
16202
16303
  if (!e || typeof e !== "object") return false;
@@ -16671,7 +16772,7 @@ function detachPlanSync() {
16671
16772
  }
16672
16773
  }
16673
16774
  function makePlanDeps(cfg, io = consoleIo) {
16674
- const ensureDir = () => (0, import_node_fs21.mkdirSync)(PLANS_DIR, { recursive: true });
16775
+ const ensureDir = () => (0, import_node_fs22.mkdirSync)(PLANS_DIR, { recursive: true });
16675
16776
  return {
16676
16777
  apiUrl: cfg.sagaApiUrl,
16677
16778
  fetch: (url, init = {}) => fetch(url, { ...init, signal: init.signal ?? AbortSignal.timeout(1e4) }),
@@ -16679,24 +16780,24 @@ function makePlanDeps(cfg, io = consoleIo) {
16679
16780
  project: async () => (await sagaKey(cfg)).project,
16680
16781
  readLocal: (slug) => {
16681
16782
  try {
16682
- return (0, import_node_fs21.readFileSync)(planPath(slug), "utf8");
16783
+ return (0, import_node_fs22.readFileSync)(planPath(slug), "utf8");
16683
16784
  } catch {
16684
16785
  return null;
16685
16786
  }
16686
16787
  },
16687
16788
  writeLocal: (slug, content) => {
16688
16789
  ensureDir();
16689
- (0, import_node_fs21.writeFileSync)(planPath(slug), content, "utf8");
16790
+ (0, import_node_fs22.writeFileSync)(planPath(slug), content, "utf8");
16690
16791
  },
16691
16792
  removeLocal: (slug) => {
16692
16793
  try {
16693
- (0, import_node_fs21.rmSync)(planPath(slug));
16794
+ (0, import_node_fs22.rmSync)(planPath(slug));
16694
16795
  } catch {
16695
16796
  }
16696
16797
  },
16697
16798
  readMetaRaw: () => {
16698
16799
  try {
16699
- return (0, import_node_fs21.readFileSync)(META_FILE, "utf8");
16800
+ return (0, import_node_fs22.readFileSync)(META_FILE, "utf8");
16700
16801
  } catch {
16701
16802
  return null;
16702
16803
  }
@@ -16707,7 +16808,7 @@ function makePlanDeps(cfg, io = consoleIo) {
16707
16808
  },
16708
16809
  readIndexRaw: () => {
16709
16810
  try {
16710
- return (0, import_node_fs21.readFileSync)(INDEX_FILE, "utf8");
16811
+ return (0, import_node_fs22.readFileSync)(INDEX_FILE, "utf8");
16711
16812
  } catch {
16712
16813
  return null;
16713
16814
  }
@@ -16718,7 +16819,7 @@ function makePlanDeps(cfg, io = consoleIo) {
16718
16819
  },
16719
16820
  readQueueRaw: () => {
16720
16821
  try {
16721
- return (0, import_node_fs21.readFileSync)(QUEUE_FILE, "utf8");
16822
+ return (0, import_node_fs22.readFileSync)(QUEUE_FILE, "utf8");
16722
16823
  } catch {
16723
16824
  return null;
16724
16825
  }
@@ -16753,7 +16854,7 @@ async function withPlan(quiet, run, io = consoleIo) {
16753
16854
  }
16754
16855
  await run(makePlanDeps(cfg, io));
16755
16856
  }
16756
- async function gatherRelevanceSignals() {
16857
+ async function gatherRelevanceSignals(opts = {}) {
16757
16858
  const branch = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => "") || void 0;
16758
16859
  const changed = (await gitOut(["diff", "--name-only", "HEAD"]).catch(() => "")).split("\n").map((s) => s.trim()).filter(Boolean);
16759
16860
  const signals = { branch, changedFiles: changed.length ? changed : void 0 };
@@ -16771,6 +16872,10 @@ async function gatherRelevanceSignals() {
16771
16872
  } catch {
16772
16873
  }
16773
16874
  }
16875
+ if (opts.anchorSlug) {
16876
+ const slug = (await opts.anchorSlug().catch(() => void 0))?.trim();
16877
+ if (slug) signals.anchorSlug = slug;
16878
+ }
16774
16879
  return signals;
16775
16880
  }
16776
16881
  function registerNorthStarCommands(cmd) {
@@ -16891,9 +16996,7 @@ function registerSecretsCommands(program3) {
16891
16996
  });
16892
16997
  const centralContainer = meta?.deployModel === "tenant-container" || meta?.deployModel === "solo-container";
16893
16998
  if (!o.required?.length && centralContainer && meta && !hasRuntimeSecretContract(meta.requiredRuntimeSecrets)) {
16894
- d.err("secrets preflight: requiredRuntimeSecrets is unset for this deployable tenant \u2014 declare the per-stage contract in registry META (or an explicit empty map) before promoting");
16895
- process.exitCode = 1;
16896
- return;
16999
+ d.err('secrets preflight: requiredRuntimeSecrets is unset: treating as an empty contract (no required keys). Declare an explicit {"dev":[],"rc":[],"main":[]} in registry META to silence this warning.');
16897
17000
  }
16898
17001
  const ok = await secretsPreflight(d, { repo: o.repo, stage: o.stage, required });
16899
17002
  if (!ok) process.exitCode = 1;
@@ -16958,6 +17061,9 @@ function expectedHosts(cfg) {
16958
17061
  for (const env of ENV_PREFIXES) out.push(env ? `${env}.${base2}` : base2);
16959
17062
  }
16960
17063
  }
17064
+ if (cfg.fofuSubdomain !== void 0) {
17065
+ out.push(cfg.fofuSubdomain ? `${cfg.fofuSubdomain}.fofu.ai` : "fofu.ai");
17066
+ }
16961
17067
  return uniq(out);
16962
17068
  }
16963
17069
  function expectedJsOrigins(cfg) {
@@ -17002,7 +17108,10 @@ function parseOauthConfig(mmiConfig, slug) {
17002
17108
  if (!callbackPath.startsWith("/")) {
17003
17109
  throw new Error(`oauth.callbackPath must start with "/" (got ${JSON.stringify(callbackPath)})`);
17004
17110
  }
17005
- return { subdomains, domains, callbackPath };
17111
+ const meta = mmiConfig ?? {};
17112
+ const rawFofuSub = raw.fofuSubdomain;
17113
+ const fofuSubdomain = meta.fofuEnabled === true ? typeof rawFofuSub === "string" ? rawFofuSub : defaultSubdomain2(slug) : void 0;
17114
+ return { subdomains, domains, callbackPath, fofuSubdomain };
17006
17115
  }
17007
17116
  function probeRedirectUri(callbackPath, port = 9123) {
17008
17117
  return `http://localhost:${port}${callbackPath}`;
@@ -17166,7 +17275,7 @@ async function fetchHubVersionInfo(baseUrl) {
17166
17275
  }
17167
17276
  function readRepoVersion() {
17168
17277
  try {
17169
- return JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path19.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
17278
+ return JSON.parse((0, import_node_fs23.readFileSync)((0, import_node_path20.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
17170
17279
  } catch {
17171
17280
  return void 0;
17172
17281
  }
@@ -17341,10 +17450,10 @@ async function runRulesSync(opts, io = consoleIo) {
17341
17450
  for (const entry of fetched) {
17342
17451
  if ("error" in entry) continue;
17343
17452
  const { file, source } = entry;
17344
- const current = (0, import_node_fs22.existsSync)(file) ? await (0, import_promises7.readFile)(file, "utf8") : null;
17453
+ const current = (0, import_node_fs23.existsSync)(file) ? await (0, import_promises7.readFile)(file, "utf8") : null;
17345
17454
  if (needsUpdate(source, current)) {
17346
17455
  const slash = file.lastIndexOf("/");
17347
- if (slash > 0) (0, import_node_fs22.mkdirSync)(file.slice(0, slash), { recursive: true });
17456
+ if (slash > 0) (0, import_node_fs23.mkdirSync)(file.slice(0, slash), { recursive: true });
17348
17457
  await (0, import_promises7.writeFile)(file, normalizeEol(source), "utf8");
17349
17458
  changed++;
17350
17459
  if (!opts.quiet) io.log(`mmi-cli rules: updated ${file}`);
@@ -17357,6 +17466,27 @@ var rules = program2.command("rules").description("org rules delivery");
17357
17466
  rules.command("sync").option("--quiet", "stay silent unless something changed or errored").description("fetch the org-delivered files (AGENTS.md / CLAUDE.md / .claude/settings.json / output style / Cursor rule) from MMI-Hub and write them verbatim (org-owned, whole-file)").action(async (opts) => {
17358
17467
  if (!await runRulesSync(opts)) process.exitCode = 1;
17359
17468
  });
17469
+ rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
17470
+ const path2 = (0, import_node_path20.join)(process.cwd(), ".gitignore");
17471
+ const current = (0, import_node_fs23.existsSync)(path2) ? (0, import_node_fs23.readFileSync)(path2, "utf8") : null;
17472
+ const plan2 = planManagedGitignore(current);
17473
+ const drift = [...plan2.added.map((l) => `+${l}`), ...plan2.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
17474
+ if (opts.write) {
17475
+ if (plan2.changed) {
17476
+ (0, import_node_fs23.writeFileSync)(path2, plan2.content, "utf8");
17477
+ console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
17478
+ } else {
17479
+ console.log("mmi-cli rules gitignore: up to date");
17480
+ }
17481
+ return;
17482
+ }
17483
+ if (plan2.changed) {
17484
+ console.error(`mmi-cli rules gitignore: managed block drift (${drift}) \u2014 run \`mmi-cli rules gitignore --write\` and commit`);
17485
+ process.exitCode = 1;
17486
+ } else {
17487
+ console.log("mmi-cli rules gitignore: up to date");
17488
+ }
17489
+ });
17360
17490
  async function runDocsSync(opts, io = consoleIo) {
17361
17491
  const ref = await gitOut(["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
17362
17492
  const def = (ref.startsWith("origin/") ? ref.slice("origin/".length) : ref) || "development";
@@ -17370,7 +17500,7 @@ async function runDocsSync(opts, io = consoleIo) {
17370
17500
  return null;
17371
17501
  }
17372
17502
  },
17373
- localContent: async (f) => (0, import_node_fs22.existsSync)(f) ? await (0, import_promises7.readFile)(f, "utf8") : null,
17503
+ localContent: async (f) => (0, import_node_fs23.existsSync)(f) ? await (0, import_promises7.readFile)(f, "utf8") : null,
17374
17504
  writeDoc: async (f, c) => {
17375
17505
  await (0, import_promises7.writeFile)(f, c, "utf8");
17376
17506
  }
@@ -17469,7 +17599,7 @@ function runWorktreeInstall(command, cwd, quiet) {
17469
17599
  async function primaryCheckoutRoot(worktreeRoot) {
17470
17600
  try {
17471
17601
  const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
17472
- return out ? (0, import_node_path19.dirname)(out) : void 0;
17602
+ return out ? (0, import_node_path20.dirname)(out) : void 0;
17473
17603
  } catch {
17474
17604
  return void 0;
17475
17605
  }
@@ -17482,28 +17612,28 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
17482
17612
  };
17483
17613
  }
17484
17614
  function acquireWorktreeSetupLock(worktreeRoot) {
17485
- const lockPath = (0, import_node_path19.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
17615
+ const lockPath = (0, import_node_path20.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
17486
17616
  const take = () => {
17487
- const fd = (0, import_node_fs22.openSync)(lockPath, "wx");
17617
+ const fd = (0, import_node_fs23.openSync)(lockPath, "wx");
17488
17618
  try {
17489
- (0, import_node_fs22.writeSync)(fd, String(Date.now()));
17619
+ (0, import_node_fs23.writeSync)(fd, String(Date.now()));
17490
17620
  } finally {
17491
- (0, import_node_fs22.closeSync)(fd);
17621
+ (0, import_node_fs23.closeSync)(fd);
17492
17622
  }
17493
17623
  return () => {
17494
17624
  try {
17495
- (0, import_node_fs22.rmSync)(lockPath, { force: true });
17625
+ (0, import_node_fs23.rmSync)(lockPath, { force: true });
17496
17626
  } catch {
17497
17627
  }
17498
17628
  };
17499
17629
  };
17500
17630
  try {
17501
- (0, import_node_fs22.mkdirSync)((0, import_node_path19.dirname)(lockPath), { recursive: true });
17631
+ (0, import_node_fs23.mkdirSync)((0, import_node_path20.dirname)(lockPath), { recursive: true });
17502
17632
  return take();
17503
17633
  } catch {
17504
17634
  try {
17505
- if (Date.now() - (0, import_node_fs22.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
17506
- (0, import_node_fs22.rmSync)(lockPath, { force: true });
17635
+ if (Date.now() - (0, import_node_fs23.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
17636
+ (0, import_node_fs23.rmSync)(lockPath, { force: true });
17507
17637
  return take();
17508
17638
  }
17509
17639
  } catch {
@@ -18466,9 +18596,9 @@ pr.command("create").description("create a PR and print {number,url} JSON").opti
18466
18596
  console.log(JSON.stringify(created));
18467
18597
  });
18468
18598
  async function listCiWorkflowPaths(cwd = process.cwd()) {
18469
- const wfDir = (0, import_node_path19.join)(cwd, ".github", "workflows");
18470
- if (!(0, import_node_fs22.existsSync)(wfDir)) return [];
18471
- return (0, import_node_fs22.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
18599
+ const wfDir = (0, import_node_path20.join)(cwd, ".github", "workflows");
18600
+ if (!(0, import_node_fs23.existsSync)(wfDir)) return [];
18601
+ return (0, import_node_fs23.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
18472
18602
  }
18473
18603
  async function resolveMergeCiPolicyForCheckout(repoOpt) {
18474
18604
  const repo = repoOpt ?? await resolveRepo();
@@ -18487,7 +18617,7 @@ function ciAuditDeps() {
18487
18617
  // Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates. The
18488
18618
  // reconcile runs IN the Hub checkout, so this is local-file I/O (no network fetch). Path is relative to
18489
18619
  // the repo root (e.g. skills/bootstrap/seeds/gate.template.yml).
18490
- readSeedFile: (path2) => (0, import_node_fs22.existsSync)(path2) ? (0, import_node_fs22.readFileSync)(path2, "utf8") : null
18620
+ readSeedFile: (path2) => (0, import_node_fs23.existsSync)(path2) ? (0, import_node_fs23.readFileSync)(path2, "utf8") : null
18491
18621
  };
18492
18622
  }
18493
18623
  pr.command("ci-policy").description("report merge CI policy: wait-for-checks vs no-ci (for grind/build agents)").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current checkout)").action(async (o) => {
@@ -18623,7 +18753,7 @@ async function createDeferredWorktreeStore() {
18623
18753
  },
18624
18754
  write: async (entries) => {
18625
18755
  try {
18626
- await (0, import_promises7.mkdir)((0, import_node_path19.dirname)(registryPath), { recursive: true });
18756
+ await (0, import_promises7.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
18627
18757
  await (0, import_promises7.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
18628
18758
  } catch {
18629
18759
  }
@@ -18637,13 +18767,13 @@ var realWorktreeDirRemover = {
18637
18767
  probe: (p) => {
18638
18768
  let st;
18639
18769
  try {
18640
- st = (0, import_node_fs22.lstatSync)(p);
18770
+ st = (0, import_node_fs23.lstatSync)(p);
18641
18771
  } catch {
18642
18772
  return null;
18643
18773
  }
18644
18774
  if (st.isSymbolicLink()) return "link";
18645
18775
  try {
18646
- (0, import_node_fs22.readlinkSync)(p);
18776
+ (0, import_node_fs23.readlinkSync)(p);
18647
18777
  return "link";
18648
18778
  } catch {
18649
18779
  }
@@ -18651,7 +18781,7 @@ var realWorktreeDirRemover = {
18651
18781
  },
18652
18782
  readdir: (p) => {
18653
18783
  try {
18654
- return (0, import_node_fs22.readdirSync)(p);
18784
+ return (0, import_node_fs23.readdirSync)(p);
18655
18785
  } catch {
18656
18786
  return [];
18657
18787
  }
@@ -18660,9 +18790,9 @@ var realWorktreeDirRemover = {
18660
18790
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
18661
18791
  detachLink: (p) => {
18662
18792
  try {
18663
- (0, import_node_fs22.rmdirSync)(p);
18793
+ (0, import_node_fs23.rmdirSync)(p);
18664
18794
  } catch {
18665
- (0, import_node_fs22.unlinkSync)(p);
18795
+ (0, import_node_fs23.unlinkSync)(p);
18666
18796
  }
18667
18797
  },
18668
18798
  removeTree: (p) => (0, import_promises7.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -18683,7 +18813,7 @@ function worktreeRemoveDeps(execGit) {
18683
18813
  }
18684
18814
  function teardownWorktreeStage(worktreePath) {
18685
18815
  return runWorktreeStageTeardown(worktreePath, {
18686
- hasStageState: (wt) => (0, import_node_fs22.existsSync)(stageStatePath(wt)),
18816
+ hasStageState: (wt) => (0, import_node_fs23.existsSync)(stageStatePath(wt)),
18687
18817
  stopRecordedStage: async (wt) => (await stopStage({ cwd: wt })).pid,
18688
18818
  listComposeProjects: async () => {
18689
18819
  const { stdout } = await execFileP2("docker", ["compose", "ls", "--all", "--format", "json"], { timeout: GC_GH_TIMEOUT_MS });
@@ -18746,7 +18876,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
18746
18876
  } : await cleanupPrMergeLocalBranch(headRef, {
18747
18877
  beforeWorktrees,
18748
18878
  startingPath,
18749
- pathExists: (p) => (0, import_node_fs22.existsSync)(p),
18879
+ pathExists: (p) => (0, import_node_fs23.existsSync)(p),
18750
18880
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
18751
18881
  teardownWorktreeStage,
18752
18882
  deferredStore,
@@ -18789,6 +18919,9 @@ async function runBoardRead(o) {
18789
18919
  }
18790
18920
  var board = program2.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
18791
18921
  board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--allow-partial", "return partial board results when later page/detail reads fail").action((o) => runBoardRead(o));
18922
+ board.command("slice-refresh", { hidden: true }).description("internal: refresh the cached SessionStart board slice (background worker)").option("--quiet", "silent (background worker)").action(async () => {
18923
+ await refreshBoardSliceCache({ loadConfig: () => loadConfigForRepo(), readBoard });
18924
+ });
18792
18925
  board.command("claim <issues...>").description("assign Todo issues and move their Project v2 Status to In Progress (one or more refs)").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails").action(async (issueRefs, o) => {
18793
18926
  if (issueRefs.length === 1) {
18794
18927
  const issueRef = issueRefs[0];
@@ -18921,7 +19054,7 @@ function rawValues(flag) {
18921
19054
  return out;
18922
19055
  }
18923
19056
  function printLine(value) {
18924
- (0, import_node_fs22.writeSync)(1, `${value}
19057
+ (0, import_node_fs23.writeSync)(1, `${value}
18925
19058
  `);
18926
19059
  }
18927
19060
  function stageKeepAlive() {
@@ -18938,8 +19071,8 @@ async function resolveStage() {
18938
19071
  local,
18939
19072
  shell: shellFor(),
18940
19073
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
18941
- hasCompose: (0, import_node_fs22.existsSync)((0, import_node_path19.join)(process.cwd(), "docker-compose.yml")),
18942
- hasEnvExample: (0, import_node_fs22.existsSync)((0, import_node_path19.join)(process.cwd(), ".env.example"))
19074
+ hasCompose: (0, import_node_fs23.existsSync)((0, import_node_path20.join)(process.cwd(), "docker-compose.yml")),
19075
+ hasEnvExample: (0, import_node_fs23.existsSync)((0, import_node_path20.join)(process.cwd(), ".env.example"))
18943
19076
  });
18944
19077
  }
18945
19078
  async function fetchStageVaultEnvMerge() {
@@ -18991,9 +19124,9 @@ program2.command("port-range <repo>").description("assign (idempotently) + print
18991
19124
  printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
18992
19125
  return;
18993
19126
  }
18994
- const path2 = (0, import_node_path19.join)(process.cwd(), "infra", "port-ranges.json");
19127
+ const path2 = (0, import_node_path20.join)(process.cwd(), "infra", "port-ranges.json");
18995
19128
  const allocate = async (seed) => {
18996
- const { stdout } = await execFileP2("node", [(0, import_node_path19.join)(process.cwd(), "infra", "port-ddb.mjs"), String(seed)], { timeout: 15e3 });
19129
+ const { stdout } = await execFileP2("node", [(0, import_node_path20.join)(process.cwd(), "infra", "port-ddb.mjs"), String(seed)], { timeout: 15e3 });
18997
19130
  const parsed = JSON.parse(stdout);
18998
19131
  if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
18999
19132
  return parsed.range;
@@ -19388,7 +19521,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
19388
19521
  client: defaultGitHubClient(),
19389
19522
  projectMeta: meta,
19390
19523
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
19391
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs22.existsSync)(path2) ? (0, import_node_fs22.readFileSync)(path2, "utf8") : null,
19524
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs23.existsSync)(path2) ? (0, import_node_fs23.readFileSync)(path2, "utf8") : null,
19392
19525
  // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
19393
19526
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
19394
19527
  requiredGcpApis: (() => {
@@ -19432,12 +19565,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
19432
19565
  return fail(`bootstrap apply: ${e.message}`);
19433
19566
  }
19434
19567
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
19435
- if (!(0, import_node_fs22.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
19436
- const manifest = loadBootstrapSeeds((0, import_node_fs22.readFileSync)(manifestPath, "utf8"));
19568
+ if (!(0, import_node_fs23.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
19569
+ const manifest = loadBootstrapSeeds((0, import_node_fs23.readFileSync)(manifestPath, "utf8"));
19437
19570
  const baseBranch = o.class === "content" ? "main" : "development";
19438
19571
  const slug = parsedRepo.slug;
19439
19572
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
19440
- const readFile7 = (p) => (0, import_node_fs22.existsSync)(p) ? (0, import_node_fs22.readFileSync)(p, "utf8") : null;
19573
+ const readFile7 = (p) => (0, import_node_fs23.existsSync)(p) ? (0, import_node_fs23.readFileSync)(p, "utf8") : null;
19441
19574
  const enc2 = (p) => p.split("/").map(encodeURIComponent).join("/");
19442
19575
  const rawVars = {};
19443
19576
  for (const value of rawValues("--var")) {
@@ -19688,16 +19821,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
19688
19821
  if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
19689
19822
  targets = [{ repo: o.repo, class: o.class }];
19690
19823
  } else {
19691
- const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs22.existsSync)("projects.json") ? (0, import_node_fs22.readFileSync)("projects.json", "utf8") : null;
19824
+ const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs23.existsSync)("projects.json") ? (0, import_node_fs23.readFileSync)("projects.json", "utf8") : null;
19692
19825
  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>");
19693
- const fanoutJson = (0, import_node_fs22.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs22.readFileSync)(".github/fanout-targets.json", "utf8") : null;
19826
+ const fanoutJson = (0, import_node_fs23.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs23.readFileSync)(".github/fanout-targets.json", "utf8") : null;
19694
19827
  targets = loadAccessTargets(projectsJson, fanoutJson);
19695
19828
  }
19696
19829
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
19697
- const fileMatrix = (0, import_node_fs22.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs22.readFileSync)("access-matrix.json", "utf8")) : {};
19830
+ const fileMatrix = (0, import_node_fs23.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs23.readFileSync)("access-matrix.json", "utf8")) : {};
19698
19831
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
19699
19832
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
19700
- const fileContracts = (0, import_node_fs22.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs22.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
19833
+ const fileContracts = (0, import_node_fs23.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs23.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
19701
19834
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
19702
19835
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
19703
19836
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -19707,20 +19840,20 @@ access.command("capabilities").description("enumerate your effective vault reach
19707
19840
  var isWin = process.platform === "win32";
19708
19841
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
19709
19842
  const homeDir = surface === "codex" ? ".codex" : ".claude";
19710
- return (0, import_node_path19.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
19843
+ return (0, import_node_path20.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
19711
19844
  };
19712
19845
  function readInstalledPlugins() {
19713
19846
  try {
19714
- return JSON.parse((0, import_node_fs22.readFileSync)(installedPluginsPath(), "utf8"));
19847
+ return JSON.parse((0, import_node_fs23.readFileSync)(installedPluginsPath(), "utf8"));
19715
19848
  } catch {
19716
19849
  return null;
19717
19850
  }
19718
19851
  }
19719
19852
  function installedPluginSources() {
19720
19853
  return ["claude", "codex"].map((surface) => {
19721
- const recordPath = (0, import_node_path19.join)((0, import_node_os6.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
19854
+ const recordPath = (0, import_node_path20.join)((0, import_node_os6.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
19722
19855
  try {
19723
- return { surface, installed: JSON.parse((0, import_node_fs22.readFileSync)(recordPath, "utf8")), recordPath };
19856
+ return { surface, installed: JSON.parse((0, import_node_fs23.readFileSync)(recordPath, "utf8")), recordPath };
19724
19857
  } catch {
19725
19858
  return { surface, installed: null, recordPath };
19726
19859
  }
@@ -19728,7 +19861,7 @@ function installedPluginSources() {
19728
19861
  }
19729
19862
  function readClaudeSettings() {
19730
19863
  try {
19731
- return JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path19.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
19864
+ return JSON.parse((0, import_node_fs23.readFileSync)((0, import_node_path20.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
19732
19865
  } catch {
19733
19866
  return null;
19734
19867
  }
@@ -19750,7 +19883,7 @@ function writeProjectInstallRecord(record) {
19750
19883
  const list = file.plugins[MMI_PLUGIN_ID] ?? [];
19751
19884
  list.push(record);
19752
19885
  file.plugins[MMI_PLUGIN_ID] = list;
19753
- (0, import_node_fs22.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
19886
+ (0, import_node_fs23.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
19754
19887
  `, "utf8");
19755
19888
  return true;
19756
19889
  } catch {
@@ -19763,9 +19896,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
19763
19896
  if (!file) return false;
19764
19897
  if (!file.plugins) file.plugins = {};
19765
19898
  const path2 = installedPluginsPath();
19766
- (0, import_node_fs22.copyFileSync)(path2, `${path2}.bak`);
19899
+ (0, import_node_fs23.copyFileSync)(path2, `${path2}.bak`);
19767
19900
  file.plugins[pluginId] = records;
19768
- (0, import_node_fs22.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
19901
+ (0, import_node_fs23.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
19769
19902
  `, "utf8");
19770
19903
  return true;
19771
19904
  } catch {
@@ -19773,35 +19906,35 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
19773
19906
  }
19774
19907
  }
19775
19908
  function cursorPluginCacheRoot() {
19776
- return (0, import_node_path19.join)((0, import_node_os6.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
19909
+ return (0, import_node_path20.join)((0, import_node_os6.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
19777
19910
  }
19778
19911
  function cursorPluginCachePinSnapshots() {
19779
19912
  const root = cursorPluginCacheRoot();
19780
19913
  try {
19781
- return (0, import_node_fs22.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
19782
- const path2 = (0, import_node_path19.join)(root, entry.name);
19783
- const pluginJson = (0, import_node_path19.join)(path2, ".cursor-plugin", "plugin.json");
19784
- const hooksJson = (0, import_node_path19.join)(path2, "hooks", "hooks.json");
19785
- const cliBundle = (0, import_node_path19.join)(path2, "cli", "dist", "index.cjs");
19914
+ return (0, import_node_fs23.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
19915
+ const path2 = (0, import_node_path20.join)(root, entry.name);
19916
+ const pluginJson = (0, import_node_path20.join)(path2, ".cursor-plugin", "plugin.json");
19917
+ const hooksJson = (0, import_node_path20.join)(path2, "hooks", "hooks.json");
19918
+ const cliBundle = (0, import_node_path20.join)(path2, "cli", "dist", "index.cjs");
19786
19919
  let version;
19787
19920
  try {
19788
- const raw = JSON.parse((0, import_node_fs22.readFileSync)(pluginJson, "utf8"));
19921
+ const raw = JSON.parse((0, import_node_fs23.readFileSync)(pluginJson, "utf8"));
19789
19922
  version = typeof raw.version === "string" ? raw.version : void 0;
19790
19923
  } catch {
19791
19924
  version = void 0;
19792
19925
  }
19793
19926
  let isEmpty = true;
19794
19927
  try {
19795
- isEmpty = (0, import_node_fs22.readdirSync)(path2).length === 0;
19928
+ isEmpty = (0, import_node_fs23.readdirSync)(path2).length === 0;
19796
19929
  } catch {
19797
19930
  isEmpty = true;
19798
19931
  }
19799
19932
  return {
19800
19933
  name: entry.name,
19801
19934
  path: path2,
19802
- hasPluginJson: (0, import_node_fs22.existsSync)(pluginJson),
19803
- hasHooksJson: (0, import_node_fs22.existsSync)(hooksJson),
19804
- hasCliBundle: (0, import_node_fs22.existsSync)(cliBundle),
19935
+ hasPluginJson: (0, import_node_fs23.existsSync)(pluginJson),
19936
+ hasHooksJson: (0, import_node_fs23.existsSync)(hooksJson),
19937
+ hasCliBundle: (0, import_node_fs23.existsSync)(cliBundle),
19805
19938
  isEmpty,
19806
19939
  version
19807
19940
  };
@@ -19811,19 +19944,19 @@ function cursorPluginCachePinSnapshots() {
19811
19944
  }
19812
19945
  }
19813
19946
  function hubCheckoutForCursorSeed() {
19814
- const manifest = (0, import_node_path19.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
19815
- return (0, import_node_fs22.existsSync)(manifest) ? process.cwd() : void 0;
19947
+ const manifest = (0, import_node_path20.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
19948
+ return (0, import_node_fs23.existsSync)(manifest) ? process.cwd() : void 0;
19816
19949
  }
19817
19950
  function mmiPluginCacheRootSnapshots() {
19818
19951
  const roots = [
19819
- { surface: "claude", root: (0, import_node_path19.join)((0, import_node_os6.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
19820
- { surface: "codex", root: (0, import_node_path19.join)((0, import_node_os6.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
19952
+ { surface: "claude", root: (0, import_node_path20.join)((0, import_node_os6.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
19953
+ { surface: "codex", root: (0, import_node_path20.join)((0, import_node_os6.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
19821
19954
  ];
19822
19955
  return roots.flatMap(({ surface, root }) => {
19823
19956
  try {
19824
- const entries = (0, import_node_fs22.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
19957
+ const entries = (0, import_node_fs23.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
19825
19958
  name: entry.name,
19826
- path: (0, import_node_path19.join)(root, entry.name),
19959
+ path: (0, import_node_path20.join)(root, entry.name),
19827
19960
  isDirectory: entry.isDirectory()
19828
19961
  }));
19829
19962
  return [{ surface, root, entries }];
@@ -19834,7 +19967,7 @@ function mmiPluginCacheRootSnapshots() {
19834
19967
  }
19835
19968
  function hasNestedMmiChild(versionDir) {
19836
19969
  try {
19837
- return (0, import_node_fs22.statSync)((0, import_node_path19.join)(versionDir, "mmi")).isDirectory();
19970
+ return (0, import_node_fs23.statSync)((0, import_node_path20.join)(versionDir, "mmi")).isDirectory();
19838
19971
  } catch {
19839
19972
  return false;
19840
19973
  }
@@ -19845,10 +19978,10 @@ function nestedPluginTreeSnapshot() {
19845
19978
  );
19846
19979
  }
19847
19980
  function uniqueQuarantineTarget(path2) {
19848
- if (!(0, import_node_fs22.existsSync)(path2)) return path2;
19981
+ if (!(0, import_node_fs23.existsSync)(path2)) return path2;
19849
19982
  for (let i = 1; i < 100; i += 1) {
19850
19983
  const candidate = `${path2}-${i}`;
19851
- if (!(0, import_node_fs22.existsSync)(candidate)) return candidate;
19984
+ if (!(0, import_node_fs23.existsSync)(candidate)) return candidate;
19852
19985
  }
19853
19986
  return `${path2}-${Date.now()}`;
19854
19987
  }
@@ -19857,10 +19990,10 @@ function quarantinePluginCacheDirs(plan2) {
19857
19990
  const failed = [];
19858
19991
  for (const move of plan2) {
19859
19992
  try {
19860
- if (!(0, import_node_fs22.existsSync)(move.from)) continue;
19993
+ if (!(0, import_node_fs23.existsSync)(move.from)) continue;
19861
19994
  const target = uniqueQuarantineTarget(move.to);
19862
- (0, import_node_fs22.mkdirSync)((0, import_node_path19.dirname)(target), { recursive: true });
19863
- (0, import_node_fs22.renameSync)(move.from, target);
19995
+ (0, import_node_fs23.mkdirSync)((0, import_node_path20.dirname)(target), { recursive: true });
19996
+ (0, import_node_fs23.renameSync)(move.from, target);
19864
19997
  moved += 1;
19865
19998
  } catch {
19866
19999
  failed.push(move);
@@ -19879,23 +20012,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
19879
20012
  }
19880
20013
  async function clearNestedPluginTreeDir(targetPath) {
19881
20014
  try {
19882
- if (!(0, import_node_fs22.existsSync)(targetPath)) return true;
20015
+ if (!(0, import_node_fs23.existsSync)(targetPath)) return true;
19883
20016
  if (isWin) {
19884
- const emptyDir = (0, import_node_path19.join)((0, import_node_os6.tmpdir)(), `mmi-empty-${Date.now()}`);
19885
- (0, import_node_fs22.mkdirSync)(emptyDir, { recursive: true });
20017
+ const emptyDir = (0, import_node_path20.join)((0, import_node_os6.tmpdir)(), `mmi-empty-${Date.now()}`);
20018
+ (0, import_node_fs23.mkdirSync)(emptyDir, { recursive: true });
19886
20019
  try {
19887
20020
  await robocopyMirrorEmpty(emptyDir, targetPath);
19888
- (0, import_node_fs22.rmSync)(targetPath, { recursive: true, force: true });
20021
+ (0, import_node_fs23.rmSync)(targetPath, { recursive: true, force: true });
19889
20022
  } finally {
19890
20023
  try {
19891
- (0, import_node_fs22.rmSync)(emptyDir, { recursive: true, force: true });
20024
+ (0, import_node_fs23.rmSync)(emptyDir, { recursive: true, force: true });
19892
20025
  } catch {
19893
20026
  }
19894
20027
  }
19895
- return !(0, import_node_fs22.existsSync)(targetPath);
20028
+ return !(0, import_node_fs23.existsSync)(targetPath);
19896
20029
  }
19897
- (0, import_node_fs22.rmSync)(targetPath, { recursive: true, force: true });
19898
- return !(0, import_node_fs22.existsSync)(targetPath);
20030
+ (0, import_node_fs23.rmSync)(targetPath, { recursive: true, force: true });
20031
+ return !(0, import_node_fs23.existsSync)(targetPath);
19899
20032
  } catch {
19900
20033
  return false;
19901
20034
  }
@@ -19908,11 +20041,11 @@ async function applyNestedPluginTreeCleanup(paths, log) {
19908
20041
  }
19909
20042
  return true;
19910
20043
  }
19911
- var gitignorePath = () => (0, import_node_path19.join)(process.cwd(), ".gitignore");
20044
+ var gitignorePath = () => (0, import_node_path20.join)(process.cwd(), ".gitignore");
19912
20045
  function readTextFile(path2) {
19913
20046
  try {
19914
- if (!(0, import_node_fs22.existsSync)(path2)) return null;
19915
- return (0, import_node_fs22.readFileSync)(path2, "utf8");
20047
+ if (!(0, import_node_fs23.existsSync)(path2)) return null;
20048
+ return (0, import_node_fs23.readFileSync)(path2, "utf8");
19916
20049
  } catch {
19917
20050
  return null;
19918
20051
  }
@@ -19921,9 +20054,9 @@ function playwrightMcpConfigSnapshots() {
19921
20054
  const cwd = process.cwd();
19922
20055
  const home = (0, import_node_os6.homedir)();
19923
20056
  const candidates = [
19924
- (0, import_node_path19.join)(cwd, ".cursor", "mcp.json"),
19925
- (0, import_node_path19.join)(home, ".cursor", "mcp.json"),
19926
- (0, import_node_path19.join)(home, ".codex", "config.toml")
20057
+ (0, import_node_path20.join)(cwd, ".cursor", "mcp.json"),
20058
+ (0, import_node_path20.join)(home, ".cursor", "mcp.json"),
20059
+ (0, import_node_path20.join)(home, ".codex", "config.toml")
19927
20060
  ];
19928
20061
  const out = [];
19929
20062
  for (const path2 of candidates) {
@@ -19936,7 +20069,7 @@ function strayBrowserArtifactPaths() {
19936
20069
  const cwd = process.cwd();
19937
20070
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
19938
20071
  try {
19939
- return (0, import_node_fs22.existsSync)((0, import_node_path19.join)(cwd, rel));
20072
+ return (0, import_node_fs23.existsSync)((0, import_node_path20.join)(cwd, rel));
19940
20073
  } catch {
19941
20074
  return false;
19942
20075
  }
@@ -19944,14 +20077,14 @@ function strayBrowserArtifactPaths() {
19944
20077
  }
19945
20078
  function readGitignore() {
19946
20079
  try {
19947
- return (0, import_node_fs22.readFileSync)(gitignorePath(), "utf8");
20080
+ return (0, import_node_fs23.readFileSync)(gitignorePath(), "utf8");
19948
20081
  } catch {
19949
20082
  return null;
19950
20083
  }
19951
20084
  }
19952
20085
  function writeGitignore(content) {
19953
20086
  try {
19954
- (0, import_node_fs22.writeFileSync)(gitignorePath(), content, "utf8");
20087
+ (0, import_node_fs23.writeFileSync)(gitignorePath(), content, "utf8");
19955
20088
  return true;
19956
20089
  } catch {
19957
20090
  return false;
@@ -19990,7 +20123,7 @@ async function runDoctor(opts, io = consoleIo) {
19990
20123
  let onPath = pathProbe;
19991
20124
  if (!onPath) {
19992
20125
  const root = process.env.CLAUDE_PLUGIN_ROOT;
19993
- if (root && (0, import_node_fs22.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
20126
+ if (root && (0, import_node_fs23.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
19994
20127
  }
19995
20128
  checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
19996
20129
  const surface = detectSurface(process.env);
@@ -20023,9 +20156,10 @@ async function runDoctor(opts, io = consoleIo) {
20023
20156
  }
20024
20157
  checks.push({ ok: cloneOk, label: "plugin git clone (SSH\u2192HTTPS rewrite)", fix: CLONE_FIX });
20025
20158
  const installed = readInstalledPlugins();
20159
+ const claudeSettings = readClaudeSettings();
20026
20160
  let pluginCheck = buildPluginInstallRecordCheck({
20027
20161
  isOrgRepo: Boolean(cfg.sagaApiUrl),
20028
- settings: readClaudeSettings(),
20162
+ settings: claudeSettings,
20029
20163
  installed,
20030
20164
  projectPath: process.cwd(),
20031
20165
  mirrorFrom: existingMirrorRecord(installed),
@@ -20038,6 +20172,7 @@ async function runDoctor(opts, io = consoleIo) {
20038
20172
  }
20039
20173
  }
20040
20174
  checks.push(pluginCheck);
20175
+ checks.push(buildSettingsPluginDriftCheck({ isOrgRepo: Boolean(cfg.sagaApiUrl), settings: claudeSettings }));
20041
20176
  let legacyPluginCheck = buildLegacyPluginInstallCheck({
20042
20177
  isOrgRepo: Boolean(cfg.sagaApiUrl),
20043
20178
  sources: installedPluginSources(),
@@ -20192,7 +20327,7 @@ async function runDoctor(opts, io = consoleIo) {
20192
20327
  isOrgRepo: Boolean(cfg.sagaApiUrl),
20193
20328
  surface,
20194
20329
  cacheRoot: cursorCacheRoot,
20195
- cacheRootExists: (0, import_node_fs22.existsSync)(cursorCacheRoot),
20330
+ cacheRootExists: (0, import_node_fs23.existsSync)(cursorCacheRoot),
20196
20331
  pins: cursorPins,
20197
20332
  hubCheckout: hubCheckoutForCursorSeed(),
20198
20333
  releasedVersion
@@ -20203,7 +20338,7 @@ async function runDoctor(opts, io = consoleIo) {
20203
20338
  releasedVersion,
20204
20339
  hubCheckout: hubCheckoutForCursorSeed(),
20205
20340
  execFileP: execFileP2,
20206
- mkdtemp: (prefix) => (0, import_promises7.mkdtemp)((0, import_node_path19.join)((0, import_node_os6.tmpdir)(), prefix)),
20341
+ mkdtemp: (prefix) => (0, import_promises7.mkdtemp)((0, import_node_path20.join)((0, import_node_os6.tmpdir)(), prefix)),
20207
20342
  log: (m) => io.err(m)
20208
20343
  });
20209
20344
  if (seeded) {
@@ -20212,7 +20347,7 @@ async function runDoctor(opts, io = consoleIo) {
20212
20347
  isOrgRepo: Boolean(cfg.sagaApiUrl),
20213
20348
  surface,
20214
20349
  cacheRoot: cursorCacheRoot,
20215
- cacheRootExists: (0, import_node_fs22.existsSync)(cursorCacheRoot),
20350
+ cacheRootExists: (0, import_node_fs23.existsSync)(cursorCacheRoot),
20216
20351
  pins: cursorPins,
20217
20352
  hubCheckout: hubCheckoutForCursorSeed(),
20218
20353
  releasedVersion
@@ -20417,7 +20552,15 @@ program2.command("session-start").description("run the SessionStart verbs (rules
20417
20552
  northstarInjected = await runNorthstarContext(io, {
20418
20553
  loadPlans: () => scopedPlanList(planDeps),
20419
20554
  readLocal: (slug) => planDeps.readLocal(slug),
20420
- gatherSignals: gatherRelevanceSignals
20555
+ // #1812: thread the saga HEAD's North Star anchor (its NEXT slug) into the relevance gate so
20556
+ // the plan the agent is actively on is force-injected even on a generic branch with no token
20557
+ // overlap. fetchSagaHead errors are swallowed via a silent io — a missing/failed HEAD just
20558
+ // falls back to token-overlap scoring, never noises or blocks the banner.
20559
+ gatherSignals: () => gatherRelevanceSignals({
20560
+ anchorSlug: () => fetchSagaHead({ log: () => {
20561
+ }, err: () => {
20562
+ } }).then((h) => h?.anchor?.slug ?? void 0)
20563
+ })
20421
20564
  });
20422
20565
  },
20423
20566
  sagaHealth: (io) => runSagaHealth({ banner: true, quiet: true }, io),
@@ -20433,12 +20576,16 @@ program2.command("session-start").description("run the SessionStart verbs (rules
20433
20576
  },
20434
20577
  boardSlice: (io) => runBoardSlice(io, {
20435
20578
  loadConfig: () => loadConfigForRepo(),
20436
- readBoard
20579
+ readBoard,
20580
+ // #1813: warm the slice cache out-of-band (detached, like docs sync) so the ~20s live read
20581
+ // never costs banner time and next session's glance renders instantly within budget.
20582
+ scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
20437
20583
  }),
20438
20584
  doctor: (io) => runDoctor({ banner: true }, io)
20439
20585
  });
20440
20586
  await runSessionStart(parallel, sequential, consoleIo);
20441
20587
  consoleIo.log(northstarPointer(northstarInjected));
20588
+ consoleIo.log(kbPointer());
20442
20589
  for (const line of planStoreLines(process.cwd())) consoleIo.log(line);
20443
20590
  for (const line of scratchGcLines(process.cwd())) consoleIo.log(line);
20444
20591
  const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());