@mutmutco/cli 2.51.0 → 2.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/main.cjs +253 -192
  2. package/dist/saga.cjs +14 -7
  3. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -5571,6 +5571,7 @@ var import_node_child_process13 = require("node:child_process");
5571
5571
  // src/cli-shared.ts
5572
5572
  var import_promises = require("node:fs/promises");
5573
5573
  var import_node_fs7 = require("node:fs");
5574
+ var import_node_path7 = require("node:path");
5574
5575
  var import_node_crypto2 = require("node:crypto");
5575
5576
  var import_node_child_process4 = require("node:child_process");
5576
5577
  var import_node_util4 = require("node:util");
@@ -6326,16 +6327,20 @@ async function hubHeaders(extra = {}) {
6326
6327
  const base = { ...clientVersionHeaders(), ...extra };
6327
6328
  return t ? { ...base, Authorization: `Bearer ${t}` } : base;
6328
6329
  }
6330
+ var CONFIG_FILE = ".mmi/config.json";
6329
6331
  async function loadConfig() {
6330
6332
  let file = {};
6331
6333
  try {
6332
- file = JSON.parse(await (0, import_promises.readFile)(".mmi/config.json", "utf8"));
6334
+ file = JSON.parse(await (0, import_promises.readFile)(CONFIG_FILE, "utf8"));
6333
6335
  } catch {
6334
6336
  file = {};
6335
6337
  }
6336
6338
  if (!file.sagaApiUrl) file.sagaApiUrl = defaultHubUrl();
6337
6339
  return file;
6338
6340
  }
6341
+ function isOrgRepoRoot(cwd = process.cwd(), exists = import_node_fs7.existsSync) {
6342
+ return exists((0, import_node_path7.join)(cwd, CONFIG_FILE));
6343
+ }
6339
6344
  var SESSION_FILE = ".mmi/.session";
6340
6345
  var gitOut = async (args) => {
6341
6346
  try {
@@ -6568,8 +6573,8 @@ function memorySyncBanner(report) {
6568
6573
 
6569
6574
  // src/continuity.ts
6570
6575
  var import_node_fs8 = require("node:fs");
6571
- var import_node_path7 = require("node:path");
6572
- var CONTINUITY_FILE = (0, import_node_path7.join)(".mmi", "continuity.json");
6576
+ var import_node_path8 = require("node:path");
6577
+ var CONTINUITY_FILE = (0, import_node_path8.join)(".mmi", "continuity.json");
6573
6578
  function parseContinuityStamp(raw) {
6574
6579
  if (!raw) return {};
6575
6580
  try {
@@ -6595,7 +6600,7 @@ function readContinuityStamp(path2 = CONTINUITY_FILE) {
6595
6600
  function stampSagaNoteContinuity(now = (/* @__PURE__ */ new Date()).toISOString(), path2 = CONTINUITY_FILE) {
6596
6601
  try {
6597
6602
  const current = readContinuityStamp(path2);
6598
- (0, import_node_fs8.mkdirSync)((0, import_node_path7.dirname)(path2), { recursive: true });
6603
+ (0, import_node_fs8.mkdirSync)((0, import_node_path8.dirname)(path2), { recursive: true });
6599
6604
  (0, import_node_fs8.writeFileSync)(path2, serializeContinuityStamp({ ...current, lastSagaNoteAt: now }), "utf8");
6600
6605
  } catch {
6601
6606
  }
@@ -6954,7 +6959,7 @@ var import_node_fs10 = require("node:fs");
6954
6959
  // src/stage-runner.ts
6955
6960
  var import_node_child_process5 = require("node:child_process");
6956
6961
  var import_node_fs9 = require("node:fs");
6957
- var import_node_path8 = require("node:path");
6962
+ var import_node_path9 = require("node:path");
6958
6963
  var import_node_net = require("node:net");
6959
6964
  var import_node_util5 = require("node:util");
6960
6965
  var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process5.execFile);
@@ -7071,11 +7076,11 @@ function appendForceRecreate(up) {
7071
7076
  return `${up.trimEnd()} --force-recreate`;
7072
7077
  }
7073
7078
  function stageStatePath(cwd = process.cwd()) {
7074
- return (0, import_node_path8.join)(cwd, "tmp", "stage", "state.json");
7079
+ return (0, import_node_path9.join)(cwd, "tmp", "stage", "state.json");
7075
7080
  }
7076
7081
  function stageGlobalStatePath(cwd = process.cwd(), gitCommonDir = ".git") {
7077
- const dir = (0, import_node_path8.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path8.resolve)(cwd, gitCommonDir);
7078
- return (0, import_node_path8.join)(dir, "mmi", "stage", "state.json");
7082
+ const dir = (0, import_node_path9.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path9.resolve)(cwd, gitCommonDir);
7083
+ return (0, import_node_path9.join)(dir, "mmi", "stage", "state.json");
7079
7084
  }
7080
7085
  function normPath2(path2) {
7081
7086
  return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -7299,8 +7304,8 @@ function stageProcessEnv(stagePort, extraEnv) {
7299
7304
  }
7300
7305
  async function ensureStageRuntimeEnv(config, opts, cwd) {
7301
7306
  if (!config.ensureEnv) return;
7302
- const target = (0, import_node_path8.join)(cwd, config.ensureEnv.target);
7303
- const example = (0, import_node_path8.join)(cwd, config.ensureEnv.example);
7307
+ const target = (0, import_node_path9.join)(cwd, config.ensureEnv.target);
7308
+ const example = (0, import_node_path9.join)(cwd, config.ensureEnv.example);
7304
7309
  if (!(0, import_node_fs9.existsSync)(target) && (0, import_node_fs9.existsSync)(example)) {
7305
7310
  (0, import_node_fs9.copyFileSync)(example, target);
7306
7311
  } else if ((0, import_node_fs9.existsSync)(target) && (0, import_node_fs9.existsSync)(example)) {
@@ -7811,12 +7816,14 @@ function registerSagaCommands(program3) {
7811
7816
  saga.command("flush").option("--json", "machine-readable {flushed, dropped, remaining}").option("--run", "detached worker: drain the queue silently (spawned by note/capture)").description("roll the local pending-note queue forward (re-POST queued saga writes); reports what landed").action((o) => runSagaFlush(o));
7812
7817
  saga.command("show").option("--quiet", "no-op silently when unconfigured/unreachable (SessionStart hook)").option("--latest-anywhere", "resume the newest saga across all repos (default: current repo)").description("print your resume block \u2014 current repo HEAD + project memory (where you left off)").action((opts) => runSagaShow(opts));
7813
7818
  saga.command("capture").option("--quiet", "capture silently (for the Stop hook)").description("per-turn deterministic capture (Stop hook): turn boundary + current sha + gated HEAD-update").action(async (opts) => {
7819
+ if (!isOrgRepoRoot()) return;
7814
7820
  const hook = parseHookInput(await readStdin());
7815
7821
  if (hook.session_id) persistSession(hook.session_id);
7816
7822
  await postCapture({ event: "stop", id: (0, import_node_crypto3.randomUUID)(), source: "hook", sha: await gitOut(["rev-parse", "--short", "HEAD"]), surface: agentSurface() }, opts.quiet ?? false);
7817
7823
  await maybeSpawnHeadUpdate();
7818
7824
  });
7819
7825
  saga.command("session").option("--quiet", "silent (for the SessionStart hook)").description("persist the harness session id for this repo (SessionStart hook)").action(async () => {
7826
+ if (!isOrgRepoRoot()) return;
7820
7827
  const hook = parseHookInput(await readStdin());
7821
7828
  if (hook.session_id) persistSession(hook.session_id);
7822
7829
  });
@@ -8230,16 +8237,16 @@ var import_node_child_process7 = require("node:child_process");
8230
8237
 
8231
8238
  // src/session-start.ts
8232
8239
  var import_node_fs13 = require("node:fs");
8233
- var import_node_path11 = require("node:path");
8240
+ var import_node_path12 = require("node:path");
8234
8241
 
8235
8242
  // src/scratch-gc.ts
8236
8243
  var import_node_child_process6 = require("node:child_process");
8237
8244
  var import_node_fs12 = require("node:fs");
8238
- var import_node_path10 = require("node:path");
8245
+ var import_node_path11 = require("node:path");
8239
8246
 
8240
8247
  // src/plan.ts
8241
8248
  var import_node_fs11 = require("node:fs");
8242
- var import_node_path9 = require("node:path");
8249
+ var import_node_path10 = require("node:path");
8243
8250
 
8244
8251
  // src/frontmatter.ts
8245
8252
  function splitFrontmatter(content) {
@@ -8322,8 +8329,8 @@ function rankPlansByRelevance(plans, signals, opts = {}) {
8322
8329
 
8323
8330
  // src/plan.ts
8324
8331
  var PLANS_DIR = "plans";
8325
- var META_FILE = (0, import_node_path9.join)(PLANS_DIR, ".plan-meta.json");
8326
- var planPath = (slug) => (0, import_node_path9.join)(PLANS_DIR, `${slug}.md`);
8332
+ var META_FILE = (0, import_node_path10.join)(PLANS_DIR, ".plan-meta.json");
8333
+ var planPath = (slug) => (0, import_node_path10.join)(PLANS_DIR, `${slug}.md`);
8327
8334
  var metaKey = (project2, slug) => `${project2}/${slug}`;
8328
8335
  function parseMeta(raw) {
8329
8336
  if (!raw) return {};
@@ -8348,7 +8355,7 @@ function hashContent(s) {
8348
8355
  function staleHint(slug) {
8349
8356
  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`;
8350
8357
  }
8351
- var INDEX_FILE = (0, import_node_path9.join)(PLANS_DIR, ".index.json");
8358
+ var INDEX_FILE = (0, import_node_path10.join)(PLANS_DIR, ".index.json");
8352
8359
  var INDEX_TTL_MS = 6e4;
8353
8360
  function parseIndex(raw) {
8354
8361
  if (!raw) return null;
@@ -8377,7 +8384,7 @@ function mergeIndex(idx, scope, plans, now) {
8377
8384
  const mergedScope = idx.scope === null ? null : [.../* @__PURE__ */ new Set([...idx.scope, ...scope])];
8378
8385
  return { fetchedAt: now, scope: mergedScope, plans: [...kept, ...plans] };
8379
8386
  }
8380
- var QUEUE_FILE = (0, import_node_path9.join)(PLANS_DIR, ".sync-queue.json");
8387
+ var QUEUE_FILE = (0, import_node_path10.join)(PLANS_DIR, ".sync-queue.json");
8381
8388
  var QUEUE_MAX_ATTEMPTS = 10;
8382
8389
  function isValidQueueEntry(e) {
8383
8390
  if (!e || typeof e !== "object") return false;
@@ -8498,8 +8505,8 @@ function dropQueued(deps, project2, slug) {
8498
8505
  }
8499
8506
  function parsePlanSlugFromPath(cwd, filePath) {
8500
8507
  const norm = (s) => s.replace(/\\/g, "/");
8501
- const cwdNorm = norm((0, import_node_path9.resolve)(cwd)).replace(/\/+$/, "");
8502
- const pathNorm = norm((0, import_node_path9.resolve)(filePath));
8508
+ const cwdNorm = norm((0, import_node_path10.resolve)(cwd)).replace(/\/+$/, "");
8509
+ const pathNorm = norm((0, import_node_path10.resolve)(filePath));
8503
8510
  const rel = pathNorm.startsWith(`${cwdNorm}/`) ? pathNorm.slice(cwdNorm.length + 1) : norm(filePath);
8504
8511
  const m = /^plans\/([A-Za-z0-9][A-Za-z0-9_-]*)\.md$/.exec(rel);
8505
8512
  return m ? m[1] : null;
@@ -9082,7 +9089,7 @@ var PLAN_ADVISORY_AGE_MS = 30 * 24 * 36e5;
9082
9089
  var ROOT_SCRATCH_STALE_MS = 24 * 36e5;
9083
9090
  var SCRATCH_GC_THROTTLE_MS = 24 * 36e5;
9084
9091
  function scratchGcThrottlePath(mmiRoot) {
9085
- return (0, import_node_path10.join)(mmiRoot, "head-ts", ".scratch-gc-last");
9092
+ return (0, import_node_path11.join)(mmiRoot, "head-ts", ".scratch-gc-last");
9086
9093
  }
9087
9094
  function scratchGcDue(stampPath, now = Date.now(), read = import_node_fs12.readFileSync) {
9088
9095
  try {
@@ -9093,7 +9100,7 @@ function scratchGcDue(stampPath, now = Date.now(), read = import_node_fs12.readF
9093
9100
  }
9094
9101
  function markScratchGcRun(stampPath, now = Date.now()) {
9095
9102
  try {
9096
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.dirname)(stampPath), { recursive: true });
9103
+ (0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(stampPath), { recursive: true });
9097
9104
  (0, import_node_fs12.writeFileSync)(stampPath, String(now), "utf8");
9098
9105
  } catch {
9099
9106
  }
@@ -9153,7 +9160,7 @@ function isTmpSidecar(name) {
9153
9160
  function planScratchGc(snap, now = Date.now()) {
9154
9161
  const candidates = [];
9155
9162
  const normalizePath = (p) => p.replace(/\\/g, "/");
9156
- const repoRoot = normalizePath((snap.repoRoot ?? (0, import_node_path10.dirname)(snap.mmiRoot)).replace(/[\\/]+$/, ""));
9163
+ const repoRoot = normalizePath((snap.repoRoot ?? (0, import_node_path11.dirname)(snap.mmiRoot)).replace(/[\\/]+$/, ""));
9157
9164
  const mmiPaths = new Set(snap.mmiFiles.map((f) => normalizePath(f.path)));
9158
9165
  const headTsPrefix = `${snap.mmiRoot.replace(/[\\/]+$/, "")}/head-ts/`.replace(/\\/g, "/");
9159
9166
  const days = (ms) => `${Math.floor(ms / 864e5)}d`;
@@ -9202,7 +9209,7 @@ function planScratchGc(snap, now = Date.now()) {
9202
9209
  continue;
9203
9210
  }
9204
9211
  const orig = conflictCopyOriginal(f.name);
9205
- if (orig && CONFLICT_COPY_ALLOWLIST.has(orig) && mmiPaths.has(normalizePath((0, import_node_path10.join)(f.dir, orig))) && age > CONFLICT_COPY_STALE_MS) {
9212
+ if (orig && CONFLICT_COPY_ALLOWLIST.has(orig) && mmiPaths.has(normalizePath((0, import_node_path11.join)(f.dir, orig))) && age > CONFLICT_COPY_STALE_MS) {
9206
9213
  add("conflict-copy", `cloud-sync conflict copy of ${orig} (${days(age)} old)`);
9207
9214
  }
9208
9215
  }
@@ -9272,7 +9279,7 @@ function syncedPlanMetaEntry(meta, project2, slug, hash) {
9272
9279
  }
9273
9280
  function readProject(repoRoot) {
9274
9281
  try {
9275
- const cfg = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(repoRoot, ".mmi", "config.json"), "utf8"));
9282
+ const cfg = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path11.join)(repoRoot, ".mmi", "config.json"), "utf8"));
9276
9283
  if (typeof cfg.project === "string" && cfg.project.trim()) return cfg.project.trim();
9277
9284
  } catch {
9278
9285
  }
@@ -9280,7 +9287,7 @@ function readProject(repoRoot) {
9280
9287
  }
9281
9288
  function readPlanMeta(plansRoot) {
9282
9289
  try {
9283
- return parseMeta((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(plansRoot, ".plan-meta.json"), "utf8"));
9290
+ return parseMeta((0, import_node_fs12.readFileSync)((0, import_node_path11.join)(plansRoot, ".plan-meta.json"), "utf8"));
9284
9291
  } catch {
9285
9292
  return null;
9286
9293
  }
@@ -9288,7 +9295,7 @@ function readPlanMeta(plansRoot) {
9288
9295
  function readSyncQueueSlugs(plansRoot) {
9289
9296
  let queueRaw;
9290
9297
  try {
9291
- queueRaw = (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(plansRoot, ".sync-queue.json"), "utf8");
9298
+ queueRaw = (0, import_node_fs12.readFileSync)((0, import_node_path11.join)(plansRoot, ".sync-queue.json"), "utf8");
9292
9299
  } catch (e) {
9293
9300
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
9294
9301
  return code === "ENOENT" || code === "ENOTDIR" ? /* @__PURE__ */ new Set() : null;
@@ -9303,8 +9310,8 @@ function readSyncQueueSlugs(plansRoot) {
9303
9310
  }
9304
9311
  function physicalPlanCandidateStillAllowed(candidatePath, repoAnchor, lstat = import_node_fs12.lstatSync) {
9305
9312
  const path2 = candidatePath.replace(/\\/g, "/").replace(/\/+$/, "");
9306
- const parent = (0, import_node_path10.dirname)(path2).replace(/\\/g, "/").replace(/\/+$/, "");
9307
- const name = (0, import_node_path10.basename)(path2);
9313
+ const parent = (0, import_node_path11.dirname)(path2).replace(/\\/g, "/").replace(/\/+$/, "");
9314
+ const name = (0, import_node_path11.basename)(path2);
9308
9315
  const plansDir = `${repoAnchor}/plans`;
9309
9316
  if (parent !== plansDir || !name.endsWith(".md")) return false;
9310
9317
  try {
@@ -9352,7 +9359,7 @@ function treeOlderThan(root, now, floor) {
9352
9359
  if (now - st.mtimeMs <= floor) return false;
9353
9360
  if (!st.isDirectory()) continue;
9354
9361
  for (const ent of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
9355
- const child = (0, import_node_path10.join)(current, ent.name);
9362
+ const child = (0, import_node_path11.join)(current, ent.name);
9356
9363
  if (isLinkLike(child, ent.isSymbolicLink())) return false;
9357
9364
  stack.push(child);
9358
9365
  }
@@ -9364,7 +9371,7 @@ function applyScratchGc(plan2, mmiRoot, now = Date.now()) {
9364
9371
  let repoAnchor;
9365
9372
  let anchor;
9366
9373
  try {
9367
- repoAnchor = (0, import_node_fs12.realpathSync)((0, import_node_path10.dirname)(mmiRoot)).replace(/\\/g, "/").replace(/\/+$/, "");
9374
+ repoAnchor = (0, import_node_fs12.realpathSync)((0, import_node_path11.dirname)(mmiRoot)).replace(/\\/g, "/").replace(/\/+$/, "");
9368
9375
  } catch {
9369
9376
  return result;
9370
9377
  }
@@ -9406,7 +9413,7 @@ function applyScratchGc(plan2, mmiRoot, now = Date.now()) {
9406
9413
  continue;
9407
9414
  }
9408
9415
  const plansRoot = `${repoAnchor}/plans`;
9409
- const slug = (0, import_node_path10.basename)(c.path).replace(/\.md$/, "");
9416
+ const slug = (0, import_node_path11.basename)(c.path).replace(/\.md$/, "");
9410
9417
  const pending = readSyncQueueSlugs(plansRoot);
9411
9418
  if (pending === null || pending.has(slug)) {
9412
9419
  result.skipped += 1;
@@ -9448,15 +9455,15 @@ function pathContained(real, anchor) {
9448
9455
  }
9449
9456
  function rootCandidateStillAllowed(c, repoAnchor) {
9450
9457
  const path2 = c.path.replace(/\\/g, "/").replace(/\/+$/, "");
9451
- const parent = (0, import_node_path10.dirname)(path2).replace(/\\/g, "/").replace(/\/+$/, "");
9452
- const name = (0, import_node_path10.basename)(path2);
9458
+ const parent = (0, import_node_path11.dirname)(path2).replace(/\\/g, "/").replace(/\/+$/, "");
9459
+ const name = (0, import_node_path11.basename)(path2);
9453
9460
  if (c.family === "scratch-dir") return c.kind === "dir" && parent === repoAnchor && ROOT_SCRATCH_DIRS.has(name);
9454
9461
  if (c.family === "scratch-file") return (c.kind ?? "file") === "file" && parent === repoAnchor && ROOT_SCRATCH_FILE_PREFIXES.some((prefix) => name.startsWith(prefix));
9455
9462
  if (c.family === "plan") return physicalPlanCandidateStillAllowed(c.path, repoAnchor);
9456
9463
  return false;
9457
9464
  }
9458
9465
  function trackedPathStatus(c, repoAnchor) {
9459
- const rel = (0, import_node_path10.relative)(repoAnchor, c.path).replace(/\\/g, "/");
9466
+ const rel = (0, import_node_path11.relative)(repoAnchor, c.path).replace(/\\/g, "/");
9460
9467
  if (!rel || rel.startsWith("../") || rel === "..") return null;
9461
9468
  const gitPath = c.kind === "dir" ? `${rel.replace(/\/+$/, "")}/` : rel;
9462
9469
  try {
@@ -9487,7 +9494,7 @@ function rootScratchDirSnapshot(root, readdir, stat) {
9487
9494
  while (stack.length) {
9488
9495
  const current = stack.pop();
9489
9496
  for (const ent of readdir(current, { withFileTypes: true })) {
9490
- const child = (0, import_node_path10.join)(current, ent.name);
9497
+ const child = (0, import_node_path11.join)(current, ent.name);
9491
9498
  if (ent.isSymbolicLink?.()) return null;
9492
9499
  try {
9493
9500
  (0, import_node_fs12.readlinkSync)(child);
@@ -9509,8 +9516,8 @@ function collectScratchSnapshot(repoRoot, deps = {}) {
9509
9516
  const readdir = deps.readdir ?? import_node_fs12.readdirSync;
9510
9517
  const stat = deps.stat ?? import_node_fs12.statSync;
9511
9518
  const readFile7 = deps.readFile ?? import_node_fs12.readFileSync;
9512
- const mmiRoot = (0, import_node_path10.join)(repoRoot, ".mmi");
9513
- const plansRoot = (0, import_node_path10.join)(repoRoot, "plans");
9519
+ const mmiRoot = (0, import_node_path11.join)(repoRoot, ".mmi");
9520
+ const plansRoot = (0, import_node_path11.join)(repoRoot, "plans");
9514
9521
  const rootScratchFiles = [];
9515
9522
  try {
9516
9523
  for (const ent of readdir(repoRoot, { withFileTypes: true })) {
@@ -9519,7 +9526,7 @@ function collectScratchSnapshot(repoRoot, deps = {}) {
9519
9526
  if (!(isDir && ROOT_SCRATCH_DIRS.has(ent.name)) && !(isFile && ROOT_SCRATCH_FILE_PREFIXES.some((prefix) => ent.name.startsWith(prefix)))) {
9520
9527
  continue;
9521
9528
  }
9522
- const full = (0, import_node_path10.join)(repoRoot, ent.name);
9529
+ const full = (0, import_node_path11.join)(repoRoot, ent.name);
9523
9530
  try {
9524
9531
  if (isDir) {
9525
9532
  const snap = rootScratchDirSnapshot(full, readdir, stat);
@@ -9538,7 +9545,7 @@ function collectScratchSnapshot(repoRoot, deps = {}) {
9538
9545
  for (const ent of readdir(mmiRoot, { recursive: true, withFileTypes: true })) {
9539
9546
  if (!ent.isFile()) continue;
9540
9547
  const dir = ent.parentPath ?? ent.path ?? mmiRoot;
9541
- const full = (0, import_node_path10.join)(dir, ent.name);
9548
+ const full = (0, import_node_path11.join)(dir, ent.name);
9542
9549
  try {
9543
9550
  const st = stat(full);
9544
9551
  mmiFiles.push({ path: full, dir, name: ent.name, mtimeMs: st.mtimeMs, bytes: st.size });
@@ -9551,7 +9558,7 @@ function collectScratchSnapshot(repoRoot, deps = {}) {
9551
9558
  try {
9552
9559
  for (const ent of readdir(plansRoot, { withFileTypes: true })) {
9553
9560
  if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
9554
- const full = (0, import_node_path10.join)(plansRoot, ent.name);
9561
+ const full = (0, import_node_path11.join)(plansRoot, ent.name);
9555
9562
  try {
9556
9563
  const st = stat(full);
9557
9564
  const raw = readFile7(full, "utf8");
@@ -9563,20 +9570,20 @@ function collectScratchSnapshot(repoRoot, deps = {}) {
9563
9570
  }
9564
9571
  let planMeta = {};
9565
9572
  try {
9566
- planMeta = parseMeta(readFile7((0, import_node_path10.join)(plansRoot, ".plan-meta.json"), "utf8"));
9573
+ planMeta = parseMeta(readFile7((0, import_node_path11.join)(plansRoot, ".plan-meta.json"), "utf8"));
9567
9574
  } catch {
9568
9575
  planMeta = {};
9569
9576
  }
9570
9577
  let project2;
9571
9578
  try {
9572
- const cfg = JSON.parse(readFile7((0, import_node_path10.join)(repoRoot, ".mmi", "config.json"), "utf8"));
9579
+ const cfg = JSON.parse(readFile7((0, import_node_path11.join)(repoRoot, ".mmi", "config.json"), "utf8"));
9573
9580
  if (typeof cfg.project === "string" && cfg.project.trim()) project2 = cfg.project.trim();
9574
9581
  } catch {
9575
9582
  }
9576
9583
  const syncQueueSlugs = (() => {
9577
9584
  let queueRaw;
9578
9585
  try {
9579
- queueRaw = readFile7((0, import_node_path10.join)(plansRoot, ".sync-queue.json"), "utf8");
9586
+ queueRaw = readFile7((0, import_node_path11.join)(plansRoot, ".sync-queue.json"), "utf8");
9580
9587
  } catch (e) {
9581
9588
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
9582
9589
  return code === "ENOENT" || code === "ENOTDIR" ? /* @__PURE__ */ new Set() : null;
@@ -9649,23 +9656,23 @@ function spawnDetachedSelf(args, deps) {
9649
9656
  }
9650
9657
  }
9651
9658
  function isInsideRepoSubdir(cwd, exists = import_node_fs13.existsSync) {
9652
- if (exists((0, import_node_path11.join)(cwd, ".git"))) return false;
9659
+ if (exists((0, import_node_path12.join)(cwd, ".git"))) return false;
9653
9660
  let dir = cwd;
9654
9661
  for (; ; ) {
9655
- const parent = (0, import_node_path11.dirname)(dir);
9662
+ const parent = (0, import_node_path12.dirname)(dir);
9656
9663
  if (parent === dir) return false;
9657
- if (exists((0, import_node_path11.join)(parent, ".git"))) return true;
9664
+ if (exists((0, import_node_path12.join)(parent, ".git"))) return true;
9658
9665
  dir = parent;
9659
9666
  }
9660
9667
  }
9661
9668
  function planStoreLines(cwd) {
9662
9669
  const mdFiles = (dir, minSize = 0) => {
9663
- const p = (0, import_node_path11.join)(cwd, dir);
9670
+ const p = (0, import_node_path12.join)(cwd, dir);
9664
9671
  if (!(0, import_node_fs13.existsSync)(p)) return [];
9665
9672
  try {
9666
9673
  return (0, import_node_fs13.readdirSync)(p).filter((f) => f.toLowerCase().endsWith(".md")).filter((f) => {
9667
9674
  try {
9668
- return (0, import_node_fs13.statSync)((0, import_node_path11.join)(p, f)).size >= minSize;
9675
+ return (0, import_node_fs13.statSync)((0, import_node_path12.join)(p, f)).size >= minSize;
9669
9676
  } catch {
9670
9677
  return false;
9671
9678
  }
@@ -9686,7 +9693,7 @@ function planStoreLines(cwd) {
9686
9693
  function scratchGcLines(cwd, env = process.env, now = Date.now()) {
9687
9694
  if (env.MMI_NO_AUTO_GC) return [];
9688
9695
  try {
9689
- const stamp = scratchGcThrottlePath((0, import_node_path11.join)(cwd, ".mmi"));
9696
+ const stamp = scratchGcThrottlePath((0, import_node_path12.join)(cwd, ".mmi"));
9690
9697
  if (!scratchGcDue(stamp, now)) return [];
9691
9698
  const run = executeScratchGc(cwd, { apply: true }, now);
9692
9699
  markScratchGcRun(stamp, now);
@@ -9879,8 +9886,8 @@ function registerCoopCommands(program3) {
9879
9886
  // src/throttle-commands.ts
9880
9887
  var import_node_child_process8 = require("node:child_process");
9881
9888
  var import_node_fs14 = require("node:fs");
9882
- var import_node_path12 = require("node:path");
9883
- var THROTTLE_TRACE_REL = (0, import_node_path12.join)(".mmi", "throttle", "trace.jsonl");
9889
+ var import_node_path13 = require("node:path");
9890
+ var THROTTLE_TRACE_REL = (0, import_node_path13.join)(".mmi", "throttle", "trace.jsonl");
9884
9891
  function resolveRepoGitRoot(cwd = process.cwd()) {
9885
9892
  try {
9886
9893
  const root = (0, import_node_child_process8.execFileSync)("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
@@ -9893,7 +9900,7 @@ function resolveRepoGitRoot(cwd = process.cwd()) {
9893
9900
  }
9894
9901
  }
9895
9902
  function resolveThrottleTracePath(cwd = process.cwd()) {
9896
- return (0, import_node_path12.join)(resolveRepoGitRoot(cwd), THROTTLE_TRACE_REL);
9903
+ return (0, import_node_path13.join)(resolveRepoGitRoot(cwd), THROTTLE_TRACE_REL);
9897
9904
  }
9898
9905
  function resolveModeFromEnv() {
9899
9906
  const v = String(process.env.MMI_THROTTLE_MODE ?? "block").trim().toLowerCase();
@@ -10950,11 +10957,11 @@ function ghError(e) {
10950
10957
 
10951
10958
  // src/board-slice-cache.ts
10952
10959
  var import_node_fs15 = require("node:fs");
10953
- var import_node_path13 = require("node:path");
10954
- var BOARD_SLICE_CACHE_FILE = (0, import_node_path13.join)(".mmi", "board-slice.json");
10960
+ var import_node_path14 = require("node:path");
10961
+ var BOARD_SLICE_CACHE_FILE = (0, import_node_path14.join)(".mmi", "board-slice.json");
10955
10962
  var BOARD_SLICE_CACHE_TTL_MS = 10 * 60 * 1e3;
10956
10963
  function boardSliceCachePath(cwd) {
10957
- return (0, import_node_path13.join)(cwd, BOARD_SLICE_CACHE_FILE);
10964
+ return (0, import_node_path14.join)(cwd, BOARD_SLICE_CACHE_FILE);
10958
10965
  }
10959
10966
  function readCachedBoardSlice(cwd) {
10960
10967
  try {
@@ -10969,7 +10976,7 @@ function writeCachedBoardSlice(cwd, slice) {
10969
10976
  const path2 = boardSliceCachePath(cwd);
10970
10977
  const tmp = `${path2}.${process.pid}.tmp`;
10971
10978
  try {
10972
- (0, import_node_fs15.mkdirSync)((0, import_node_path13.dirname)(path2), { recursive: true });
10979
+ (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(path2), { recursive: true });
10973
10980
  (0, import_node_fs15.writeFileSync)(tmp, JSON.stringify(slice));
10974
10981
  (0, import_node_fs15.renameSync)(tmp, path2);
10975
10982
  } catch {
@@ -11103,7 +11110,7 @@ async function refreshBoardSliceCache(deps) {
11103
11110
 
11104
11111
  // src/worktree.ts
11105
11112
  var import_node_fs16 = require("node:fs");
11106
- var import_node_path14 = require("node:path");
11113
+ var import_node_path15 = require("node:path");
11107
11114
  var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
11108
11115
  var PKG = "package.json";
11109
11116
  var LOCKFILE = "package-lock.json";
@@ -11133,12 +11140,12 @@ var realFsProbe = {
11133
11140
  };
11134
11141
  function scanInstallDirs(root, fs2 = realFsProbe) {
11135
11142
  const factsFor = (dir) => {
11136
- const abs = dir ? (0, import_node_path14.join)(root, dir) : root;
11143
+ const abs = dir ? (0, import_node_path15.join)(root, dir) : root;
11137
11144
  return {
11138
11145
  dir,
11139
- hasPackageJson: fs2.isFile((0, import_node_path14.join)(abs, PKG)),
11140
- hasLockfile: fs2.isFile((0, import_node_path14.join)(abs, LOCKFILE)),
11141
- hasNodeModules: fs2.isDir((0, import_node_path14.join)(abs, NODE_MODULES))
11146
+ hasPackageJson: fs2.isFile((0, import_node_path15.join)(abs, PKG)),
11147
+ hasLockfile: fs2.isFile((0, import_node_path15.join)(abs, LOCKFILE)),
11148
+ hasNodeModules: fs2.isDir((0, import_node_path15.join)(abs, NODE_MODULES))
11142
11149
  };
11143
11150
  };
11144
11151
  const children = fs2.listDirs(root).filter((name) => name !== NODE_MODULES && name !== ".git");
@@ -11148,7 +11155,7 @@ function npmInstallTargets(dirs) {
11148
11155
  return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install" }));
11149
11156
  }
11150
11157
  function isLinkedWorktree(root, fs2 = realFsProbe) {
11151
- return fs2.isFile((0, import_node_path14.join)(root, ".git"));
11158
+ return fs2.isFile((0, import_node_path15.join)(root, ".git"));
11152
11159
  }
11153
11160
  function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
11154
11161
  if (!isLinkedWorktree(root, fs2)) return null;
@@ -11158,7 +11165,7 @@ function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
11158
11165
  return `[worktree] provisioning tooling in the background (deps in ${where} + local config) \u2014 \`mmi-cli worktree setup\` to redo`;
11159
11166
  }
11160
11167
  function defaultCopyFile(from, to) {
11161
- (0, import_node_fs16.mkdirSync)((0, import_node_path14.dirname)(to), { recursive: true });
11168
+ (0, import_node_fs16.mkdirSync)((0, import_node_path15.dirname)(to), { recursive: true });
11162
11169
  (0, import_node_fs16.copyFileSync)(from, to);
11163
11170
  }
11164
11171
  async function provisionWorktree(worktreeRoot, deps) {
@@ -11171,7 +11178,7 @@ async function provisionWorktree(worktreeRoot, deps) {
11171
11178
  const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules).map((d) => d.dir);
11172
11179
  const installed = [];
11173
11180
  for (const target of targets) {
11174
- const cwd = target.dir ? (0, import_node_path14.join)(worktreeRoot, target.dir) : worktreeRoot;
11181
+ const cwd = target.dir ? (0, import_node_path15.join)(worktreeRoot, target.dir) : worktreeRoot;
11175
11182
  log(`installing deps: ${target.command} in ${target.dir || "."}`);
11176
11183
  await deps.runInstall(target.command, cwd);
11177
11184
  installed.push(target);
@@ -11180,7 +11187,7 @@ async function provisionWorktree(worktreeRoot, deps) {
11180
11187
  const copySkipped = [];
11181
11188
  const primary = await deps.primaryCheckout();
11182
11189
  for (const rel of LOCAL_ONLY_FILES) {
11183
- const dest = (0, import_node_path14.join)(worktreeRoot, rel);
11190
+ const dest = (0, import_node_path15.join)(worktreeRoot, rel);
11184
11191
  if (fs2.isFile(dest)) {
11185
11192
  copySkipped.push({ file: rel, reason: "already-present" });
11186
11193
  continue;
@@ -11189,11 +11196,11 @@ async function provisionWorktree(worktreeRoot, deps) {
11189
11196
  copySkipped.push({ file: rel, reason: "no-primary" });
11190
11197
  continue;
11191
11198
  }
11192
- if (!fs2.isFile((0, import_node_path14.join)(primary, rel))) {
11199
+ if (!fs2.isFile((0, import_node_path15.join)(primary, rel))) {
11193
11200
  copySkipped.push({ file: rel, reason: "absent-in-primary" });
11194
11201
  continue;
11195
11202
  }
11196
- copyFile((0, import_node_path14.join)(primary, rel), dest);
11203
+ copyFile((0, import_node_path15.join)(primary, rel), dest);
11197
11204
  copied.push(rel);
11198
11205
  log(`copied local config: ${rel}`);
11199
11206
  }
@@ -11201,7 +11208,7 @@ async function provisionWorktree(worktreeRoot, deps) {
11201
11208
  }
11202
11209
  function defaultWorktreePath(repoRoot, branch) {
11203
11210
  const safe = branch.replace(/[/\\]+/g, "-");
11204
- return (0, import_node_path14.join)((0, import_node_path14.dirname)(repoRoot), "mmi-worktrees", safe);
11211
+ return (0, import_node_path15.join)((0, import_node_path15.dirname)(repoRoot), "mmi-worktrees", safe);
11205
11212
  }
11206
11213
 
11207
11214
  // src/northstar-context.ts
@@ -11343,7 +11350,7 @@ function whoamiLine(report) {
11343
11350
  }
11344
11351
 
11345
11352
  // src/index.ts
11346
- var import_node_path23 = require("node:path");
11353
+ var import_node_path24 = require("node:path");
11347
11354
 
11348
11355
  // src/merge-ci-policy.ts
11349
11356
  function resolveMergeCiPolicy(input) {
@@ -12526,7 +12533,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
12526
12533
  // src/gh-create.ts
12527
12534
  var import_promises5 = require("node:fs/promises");
12528
12535
  var import_node_os3 = require("node:os");
12529
- var import_node_path15 = require("node:path");
12536
+ var import_node_path16 = require("node:path");
12530
12537
  var import_node_crypto5 = require("node:crypto");
12531
12538
  var ISSUE_TYPES = ["bug", "feature", "task"];
12532
12539
  var GH_MUTATION_TIMEOUT_MS = 12e4;
@@ -12567,7 +12574,7 @@ async function bodyArgsViaFile(args, deps = {}) {
12567
12574
  } };
12568
12575
  const write = deps.write ?? import_promises5.writeFile;
12569
12576
  const remove = deps.remove ?? import_promises5.unlink;
12570
- const file = (0, import_node_path15.join)(deps.dir ?? (0, import_node_os3.tmpdir)(), `mmi-gh-body-${process.pid}-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}.md`);
12577
+ const file = (0, import_node_path16.join)(deps.dir ?? (0, import_node_os3.tmpdir)(), `mmi-gh-body-${process.pid}-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}.md`);
12571
12578
  await write(file, args[i + 1], "utf8");
12572
12579
  return {
12573
12580
  args: [...args.slice(0, i), "--body-file", file, ...args.slice(i + 2)],
@@ -13679,7 +13686,7 @@ async function runStageLiveDown(deps, t) {
13679
13686
 
13680
13687
  // src/design-system.ts
13681
13688
  var import_node_fs17 = require("node:fs");
13682
- var import_node_path16 = require("node:path");
13689
+ var import_node_path17 = require("node:path");
13683
13690
  var UI_PACKAGE_CANDIDATES = ["@mutmutco/ui-dashboard", "@mutmutco/ui", "@mutmutco/theme"];
13684
13691
  var DESIGN_SYSTEM_VERSION_LABEL = "@mutmutco design-system npm package (vs @latest)";
13685
13692
  function dashboardConsumerRegistryFix(error) {
@@ -13734,11 +13741,11 @@ function readJsonFile(path2) {
13734
13741
  }
13735
13742
  }
13736
13743
  function isUiFactoryCheckout(root) {
13737
- const pkg = readJsonFile((0, import_node_path16.join)(root, "package.json"));
13744
+ const pkg = readJsonFile((0, import_node_path17.join)(root, "package.json"));
13738
13745
  return pkg?.name === "mmd-ui" && pkg?.private === true;
13739
13746
  }
13740
13747
  function resolveDeclaredUiPackage(root) {
13741
- const pkg = readJsonFile((0, import_node_path16.join)(root, "package.json"));
13748
+ const pkg = readJsonFile((0, import_node_path17.join)(root, "package.json"));
13742
13749
  if (!pkg) return void 0;
13743
13750
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
13744
13751
  for (const name of UI_PACKAGE_CANDIDATES) {
@@ -13748,7 +13755,7 @@ function resolveDeclaredUiPackage(root) {
13748
13755
  return void 0;
13749
13756
  }
13750
13757
  function readLockfileInstalledVersion(root, packageName) {
13751
- const lockPath = (0, import_node_path16.join)(root, "package-lock.json");
13758
+ const lockPath = (0, import_node_path17.join)(root, "package-lock.json");
13752
13759
  if (!(0, import_node_fs17.existsSync)(lockPath)) return void 0;
13753
13760
  const lock = readJsonFile(lockPath);
13754
13761
  const node = lock?.packages?.[`node_modules/${packageName}`];
@@ -13779,7 +13786,7 @@ function designSystemSnapshot(root) {
13779
13786
  // src/design-system-registry.ts
13780
13787
  var import_node_crypto6 = require("node:crypto");
13781
13788
  var import_node_fs19 = require("node:fs");
13782
- var import_node_path17 = require("node:path");
13789
+ var import_node_path18 = require("node:path");
13783
13790
 
13784
13791
  // src/atomic-write.ts
13785
13792
  var import_node_fs18 = require("node:fs");
@@ -13803,7 +13810,7 @@ function readJsonFile2(path2) {
13803
13810
  }
13804
13811
  }
13805
13812
  function readComponentsJson(root) {
13806
- return readJsonFile2((0, import_node_path17.join)(root, "components.json"));
13813
+ return readJsonFile2((0, import_node_path18.join)(root, "components.json"));
13807
13814
  }
13808
13815
  function hasMutmutcoRegistry(root) {
13809
13816
  const url = readComponentsJson(root)?.registries?.["@mutmutco"];
@@ -13811,7 +13818,7 @@ function hasMutmutcoRegistry(root) {
13811
13818
  }
13812
13819
  function resolveCacheDir(root) {
13813
13820
  const custom = readComponentsJson(root)?.mmi?.cacheDir;
13814
- return (0, import_node_path17.join)(root, custom ?? DESIGN_SYSTEM_CACHE_DIR);
13821
+ return (0, import_node_path18.join)(root, custom ?? DESIGN_SYSTEM_CACHE_DIR);
13815
13822
  }
13816
13823
  function resolveRegistryUrlTemplate(root) {
13817
13824
  return readComponentsJson(root)?.registries?.["@mutmutco"];
@@ -13820,7 +13827,7 @@ function registryItemUrl(template, name) {
13820
13827
  return template.replace("{name}", name);
13821
13828
  }
13822
13829
  function readDesignSystemManifest(root) {
13823
- const raw = readJsonFile2((0, import_node_path17.join)(root, DESIGN_SYSTEM_MANIFEST_PATH));
13830
+ const raw = readJsonFile2((0, import_node_path18.join)(root, DESIGN_SYSTEM_MANIFEST_PATH));
13824
13831
  if (!raw || !Array.isArray(raw.components)) return void 0;
13825
13832
  return raw;
13826
13833
  }
@@ -13836,7 +13843,7 @@ function scanCachedComponentNames(cacheDir) {
13836
13843
  const names = /* @__PURE__ */ new Set();
13837
13844
  const walk = (dir) => {
13838
13845
  for (const ent of (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true })) {
13839
- const p = (0, import_node_path17.join)(dir, ent.name);
13846
+ const p = (0, import_node_path18.join)(dir, ent.name);
13840
13847
  if (ent.isDirectory()) walk(p);
13841
13848
  else if (ent.isFile() && /\.(tsx?|jsx?)$/.test(ent.name)) {
13842
13849
  names.add(ent.name.replace(/\.(tsx|ts|jsx|js)$/, ""));
@@ -13925,7 +13932,7 @@ async function gatherRegistryComponentsState(root, targetVersion, deps) {
13925
13932
  let componentStale = false;
13926
13933
  for (const file of item.files) {
13927
13934
  if (!file.target || file.content == null) continue;
13928
- const cachePath = (0, import_node_path17.join)(cacheDir, cacheRelativePath(file.target));
13935
+ const cachePath = (0, import_node_path18.join)(cacheDir, cacheRelativePath(file.target));
13929
13936
  if (!(0, import_node_fs19.existsSync)(cachePath)) {
13930
13937
  componentStale = true;
13931
13938
  break;
@@ -13941,7 +13948,7 @@ async function gatherRegistryComponentsState(root, targetVersion, deps) {
13941
13948
  }
13942
13949
  }
13943
13950
  if (componentStale) {
13944
- 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`))) {
13951
+ if ((0, import_node_fs19.existsSync)((0, import_node_path18.join)(cacheDir, "ui", `${name}.tsx`)) || (0, import_node_fs19.existsSync)((0, import_node_path18.join)(cacheDir, `${name}.tsx`))) {
13945
13952
  stale.push(name);
13946
13953
  } else {
13947
13954
  missing.push(name);
@@ -13969,15 +13976,15 @@ async function applyRegistryComponentsSync(root, components, targetVersion, log,
13969
13976
  if (!item) return { ok: false };
13970
13977
  for (const file of item.files) {
13971
13978
  if (!file.target || file.content == null) continue;
13972
- const outPath = (0, import_node_path17.join)(cacheDir, cacheRelativePath(file.target));
13973
- deps.mkdir((0, import_node_path17.dirname)(outPath));
13979
+ const outPath = (0, import_node_path18.join)(cacheDir, cacheRelativePath(file.target));
13980
+ deps.mkdir((0, import_node_path18.dirname)(outPath));
13974
13981
  const body = file.content.endsWith("\n") ? file.content : `${file.content}
13975
13982
  `;
13976
13983
  deps.writeFile(outPath, body);
13977
13984
  }
13978
13985
  }
13979
- const manifestPath = (0, import_node_path17.join)(root, DESIGN_SYSTEM_MANIFEST_PATH);
13980
- deps.mkdir((0, import_node_path17.dirname)(manifestPath));
13986
+ const manifestPath = (0, import_node_path18.join)(root, DESIGN_SYSTEM_MANIFEST_PATH);
13987
+ deps.mkdir((0, import_node_path18.dirname)(manifestPath));
13981
13988
  const manifest = {
13982
13989
  version: targetVersion,
13983
13990
  syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -14746,7 +14753,7 @@ async function announceRelease(deps, args) {
14746
14753
 
14747
14754
  // src/port-registry.ts
14748
14755
  var import_node_fs20 = require("node:fs");
14749
- var import_node_path18 = require("node:path");
14756
+ var import_node_path19 = require("node:path");
14750
14757
 
14751
14758
  // ../infra/port-geometry.mjs
14752
14759
  var PORT_BLOCK = 100;
@@ -14799,18 +14806,18 @@ function existingPortRange(repo, registry2) {
14799
14806
  return registry2[repo] ?? null;
14800
14807
  }
14801
14808
  function portRangeInfraAt(root, source) {
14802
- const registryPath = (0, import_node_path18.join)(root, "infra", "port-ranges.json");
14803
- const ddbScriptPath = (0, import_node_path18.join)(root, "infra", "port-ddb.mjs");
14809
+ const registryPath = (0, import_node_path19.join)(root, "infra", "port-ranges.json");
14810
+ const ddbScriptPath = (0, import_node_path19.join)(root, "infra", "port-ddb.mjs");
14804
14811
  if (!(0, import_node_fs20.existsSync)(registryPath) || !(0, import_node_fs20.existsSync)(ddbScriptPath)) return null;
14805
14812
  return { root, source, registryPath, ddbScriptPath };
14806
14813
  }
14807
14814
  function resolvePortRangeInfra(cwd) {
14808
14815
  const direct = portRangeInfraAt(cwd, "cwd");
14809
14816
  if (direct) return direct;
14810
- for (let dir = cwd; ; dir = (0, import_node_path18.dirname)(dir)) {
14811
- const sibling = portRangeInfraAt((0, import_node_path18.join)(dir, "MMI-Hub"), "sibling-hub");
14817
+ for (let dir = cwd; ; dir = (0, import_node_path19.dirname)(dir)) {
14818
+ const sibling = portRangeInfraAt((0, import_node_path19.join)(dir, "MMI-Hub"), "sibling-hub");
14812
14819
  if (sibling) return sibling;
14813
- const parent = (0, import_node_path18.dirname)(dir);
14820
+ const parent = (0, import_node_path19.dirname)(dir);
14814
14821
  if (parent === dir) return null;
14815
14822
  }
14816
14823
  }
@@ -16075,7 +16082,7 @@ ${section}`.trim();
16075
16082
  }
16076
16083
 
16077
16084
  // src/project-set.ts
16078
- var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "dashboard", "fofuEnabled", "consumesDesignSystem", "ci", "requiredChecks", "gate"];
16085
+ var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "dsManifestPath", "dashboard", "fofuEnabled", "consumesDesignSystem", "ci", "requiredChecks", "gate"];
16079
16086
  var UNSET_KEY_SET = new Set(UNSET_KEYS);
16080
16087
  var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
16081
16088
  function parseRuntimeSecretsVar(raw) {
@@ -16261,6 +16268,16 @@ function parsePublishDirVar(raw) {
16261
16268
  }
16262
16269
  return v;
16263
16270
  }
16271
+ function parseDsManifestPathVar(raw) {
16272
+ const v = raw.trim();
16273
+ if (v === "" || !/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/.test(v) || /(^|\/)\.\.(\/|$)/.test(v)) {
16274
+ throw new Error('project set: dsManifestPath must be a safe relative path \u2014 no leading slash, no ".." segment');
16275
+ }
16276
+ if (v !== "package.json" && !v.endsWith("/package.json")) {
16277
+ throw new Error("project set: dsManifestPath must point to a package.json, e.g. web/package.json");
16278
+ }
16279
+ return v;
16280
+ }
16264
16281
  function parseRequiredChecksVar(raw) {
16265
16282
  let parsed;
16266
16283
  try {
@@ -16312,6 +16329,7 @@ var SETTABLE_VAR_KEYS = [
16312
16329
  "oauth",
16313
16330
  "publishRequired",
16314
16331
  "publishDir",
16332
+ "dsManifestPath",
16315
16333
  "dashboard",
16316
16334
  "fofuEnabled",
16317
16335
  "consumesDesignSystem",
@@ -16332,6 +16350,7 @@ var SETTABLE_VAR_HINTS = {
16332
16350
  projectNumber: "numeric",
16333
16351
  publishRequired: "true|false",
16334
16352
  publishDir: "relative subpath, e.g. packages/ui",
16353
+ dsManifestPath: "relative path to a package.json, e.g. web/package.json",
16335
16354
  dashboard: "true|false",
16336
16355
  fofuEnabled: "true|false",
16337
16356
  consumesDesignSystem: '"fofu"',
@@ -16416,6 +16435,8 @@ function buildProjectSetPatch(input) {
16416
16435
  patch[key] = parseConsumesDesignSystemVar(raw);
16417
16436
  } else if (key === "publishDir") {
16418
16437
  patch[key] = parsePublishDirVar(raw);
16438
+ } else if (key === "dsManifestPath") {
16439
+ patch[key] = parseDsManifestPathVar(raw);
16419
16440
  } else if (key === "ci") {
16420
16441
  if (raw !== "none") throw new Error('project set: ci must be "none" (or use --unset ci to require checks)');
16421
16442
  patch[key] = raw;
@@ -17574,14 +17595,14 @@ function authorizeBodyHasMismatch(body) {
17574
17595
  // src/doctor-run.ts
17575
17596
  var import_node_fs26 = require("node:fs");
17576
17597
  var import_promises7 = require("node:fs/promises");
17577
- var import_node_path22 = require("node:path");
17598
+ var import_node_path23 = require("node:path");
17578
17599
  var import_node_os5 = require("node:os");
17579
17600
 
17580
17601
  // src/cursor-plugin-seed.ts
17581
17602
  var import_node_child_process12 = require("node:child_process");
17582
17603
  var import_node_fs22 = require("node:fs");
17583
17604
  var import_node_os4 = require("node:os");
17584
- var import_node_path19 = require("node:path");
17605
+ var import_node_path20 = require("node:path");
17585
17606
  var import_node_util7 = require("node:util");
17586
17607
  function isSemverVersion(v) {
17587
17608
  return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
@@ -17598,13 +17619,13 @@ function ghReleaseTarballApiArgs(tag) {
17598
17619
  }
17599
17620
  function cursorUserGlobalStatePath() {
17600
17621
  if (process.platform === "win32") {
17601
- const base = process.env.APPDATA || (0, import_node_path19.join)((0, import_node_os4.homedir)(), "AppData", "Roaming");
17602
- return (0, import_node_path19.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
17622
+ const base = process.env.APPDATA || (0, import_node_path20.join)((0, import_node_os4.homedir)(), "AppData", "Roaming");
17623
+ return (0, import_node_path20.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
17603
17624
  }
17604
17625
  if (process.platform === "darwin") {
17605
- return (0, import_node_path19.join)((0, import_node_os4.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
17626
+ return (0, import_node_path20.join)((0, import_node_os4.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
17606
17627
  }
17607
- return (0, import_node_path19.join)((0, import_node_os4.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
17628
+ return (0, import_node_path20.join)((0, import_node_os4.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
17608
17629
  }
17609
17630
  async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
17610
17631
  const dbPath = cursorUserGlobalStatePath();
@@ -17624,7 +17645,7 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
17624
17645
  function syncDirContents(src, dest) {
17625
17646
  (0, import_node_fs22.mkdirSync)(dest, { recursive: true });
17626
17647
  for (const name of (0, import_node_fs22.readdirSync)(dest)) {
17627
- (0, import_node_fs22.rmSync)((0, import_node_path19.join)(dest, name), { recursive: true, force: true });
17648
+ (0, import_node_fs22.rmSync)((0, import_node_path20.join)(dest, name), { recursive: true, force: true });
17628
17649
  }
17629
17650
  (0, import_node_fs22.cpSync)(src, dest, { recursive: true });
17630
17651
  }
@@ -17632,21 +17653,21 @@ function releaseTag(releasedVersion) {
17632
17653
  return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
17633
17654
  }
17634
17655
  async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
17635
- const tarFile = (0, import_node_path19.join)(tmpRoot, "archive.tar");
17656
+ const tarFile = (0, import_node_path20.join)(tmpRoot, "archive.tar");
17636
17657
  try {
17637
17658
  await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
17638
17659
  await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
17639
17660
  timeout: 6e4
17640
17661
  });
17641
17662
  await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
17642
- const pluginMmi = (0, import_node_path19.join)(tmpRoot, "plugins", "mmi");
17643
- return (0, import_node_fs22.existsSync)((0, import_node_path19.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
17663
+ const pluginMmi = (0, import_node_path20.join)(tmpRoot, "plugins", "mmi");
17664
+ return (0, import_node_fs22.existsSync)((0, import_node_path20.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
17644
17665
  } catch {
17645
17666
  return void 0;
17646
17667
  }
17647
17668
  }
17648
17669
  async function downloadPluginMmiViaGh(tag, tmpRoot) {
17649
- const tarPath = (0, import_node_path19.join)(tmpRoot, "repo.tgz");
17670
+ const tarPath = (0, import_node_path20.join)(tmpRoot, "repo.tgz");
17650
17671
  try {
17651
17672
  (0, import_node_fs22.mkdirSync)(tmpRoot, { recursive: true });
17652
17673
  const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
@@ -17659,8 +17680,8 @@ async function downloadPluginMmiViaGh(tag, tmpRoot) {
17659
17680
  await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
17660
17681
  const top = (0, import_node_fs22.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
17661
17682
  if (!top) return void 0;
17662
- const pluginMmi = (0, import_node_path19.join)(tmpRoot, top, "plugins", "mmi");
17663
- return (0, import_node_fs22.existsSync)((0, import_node_path19.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
17683
+ const pluginMmi = (0, import_node_path20.join)(tmpRoot, top, "plugins", "mmi");
17684
+ return (0, import_node_fs22.existsSync)((0, import_node_path20.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
17664
17685
  } catch {
17665
17686
  return void 0;
17666
17687
  }
@@ -17672,7 +17693,7 @@ async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, exe
17672
17693
  const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
17673
17694
  if (fromHub) return fromHub;
17674
17695
  }
17675
- return downloadPluginMmiViaGh(tag, (0, import_node_path19.join)(tmpRoot, "gh"));
17696
+ return downloadPluginMmiViaGh(tag, (0, import_node_path20.join)(tmpRoot, "gh"));
17676
17697
  }
17677
17698
  function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
17678
17699
  if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
@@ -18520,7 +18541,7 @@ function buildCursorPluginInstallCheck(input) {
18520
18541
  };
18521
18542
  }
18522
18543
  }
18523
- if (input.surface === "cursor" && isSemverVersion2(input.releasedVersion) && input.pins.some((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
18544
+ if (input.cacheRootExists && isSemverVersion2(input.releasedVersion) && input.pins.some((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
18524
18545
  const stale = input.pins.filter((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
18525
18546
  return {
18526
18547
  ...base,
@@ -18700,12 +18721,34 @@ function doctorHealPlan(input) {
18700
18721
  const versionUpdate = versionAutoUpdateAction(input.versionReport, input.hasPluginRoot);
18701
18722
  const pluginReinstall = input.isOrgRepo && !input.installedVersionCheck.ok && (input.surface === "claude-cli" || input.surface === "claude-vscode" || input.surface === "codex") && Boolean(input.installedVersionCheck.staleSurfaces?.length);
18702
18723
  const legacyPluginMigration = input.isOrgRepo && !input.legacyPluginCheck.ok;
18703
- const needsEagerHeal = versionUpdate !== "none" || pluginReinstall || legacyPluginMigration;
18704
- return { needsEagerHeal, versionUpdate, pluginReinstall, legacyPluginMigration };
18724
+ const opencodeReinstall = input.isOrgRepo && Boolean(input.opencodeAdapterStale);
18725
+ const cursorReseed = input.isOrgRepo && Boolean(input.cursorCacheStale);
18726
+ const needsEagerHeal = versionUpdate !== "none" || pluginReinstall || legacyPluginMigration || opencodeReinstall || cursorReseed;
18727
+ return { needsEagerHeal, versionUpdate, pluginReinstall, legacyPluginMigration, opencodeReinstall, cursorReseed };
18705
18728
  }
18706
18729
  function doctorPreflightDoneLine(surface) {
18707
18730
  return `\u21BB MMI tooling updated \u2014 ${reloadAction(surface)} to load changes`;
18708
18731
  }
18732
+ function selfUpdateHaltLine(report) {
18733
+ const to = report.releasedVersion ?? "latest";
18734
+ return `\u21BB mmi-cli updated \u2192 ${to}. This process is still the previous version \u2014 rerun \`mmi-cli doctor --apply\` so the remaining surfaces reconcile under the new CLI.`;
18735
+ }
18736
+ function buildSelfUpdateHaltPayload(input) {
18737
+ return {
18738
+ ok: false,
18739
+ rerunRequired: true,
18740
+ reason: "mmi-cli self-updated; rerun doctor so the remaining surfaces reconcile under the new CLI",
18741
+ updatedTo: input.updatedTo ?? null,
18742
+ checks: input.checks
18743
+ };
18744
+ }
18745
+ function preflightOutcome(input) {
18746
+ if (input.gaps.length) {
18747
+ return { healed: false, line: `\u26A0 MMI preflight: ${input.gaps.length} item(s) still need attention \u2014 ${input.gaps.map((g) => g.fix).join(" \xB7 ")}` };
18748
+ }
18749
+ if (input.needsEagerHeal) return { healed: true, line: doctorPreflightDoneLine(input.surface) };
18750
+ return { healed: true };
18751
+ }
18709
18752
  function pluginAutonomousHaltLine(reloadHint) {
18710
18753
  return `\u26A0 PLUGIN RELOAD REQUIRED \u2014 mmi:* skills and agent types are unavailable until you ${reloadHint}. Halt autonomous /grind and /build until then.`;
18711
18754
  }
@@ -18741,14 +18784,14 @@ function renderTerseDoctorReport(input) {
18741
18784
 
18742
18785
  // src/kb-drift-report.ts
18743
18786
  var import_node_fs23 = require("node:fs");
18744
- var import_node_path20 = require("node:path");
18787
+ var import_node_path21 = require("node:path");
18745
18788
  function yesterdayIso() {
18746
18789
  const d = /* @__PURE__ */ new Date();
18747
18790
  d.setUTCDate(d.getUTCDate() - 1);
18748
18791
  return d.toISOString().slice(0, 10);
18749
18792
  }
18750
18793
  async function fetchLatestKbDriftReport(execFileP5, repoRoot) {
18751
- const sagaIo = (0, import_node_path20.join)(repoRoot, "infra", "saga-io.mjs");
18794
+ const sagaIo = (0, import_node_path21.join)(repoRoot, "infra", "saga-io.mjs");
18752
18795
  if (!(0, import_node_fs23.existsSync)(sagaIo)) return null;
18753
18796
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
18754
18797
  for (const date of [today, yesterdayIso()]) {
@@ -18766,7 +18809,7 @@ async function fetchLatestKbDriftReport(execFileP5, repoRoot) {
18766
18809
 
18767
18810
  // src/cli-doctor-shared.ts
18768
18811
  var import_node_fs24 = require("node:fs");
18769
- var import_node_path21 = require("node:path");
18812
+ var import_node_path22 = require("node:path");
18770
18813
  var import_node_fs25 = require("node:fs");
18771
18814
  var GC_GH_TIMEOUT_MS = 2e4;
18772
18815
  async function awsCallerArn() {
@@ -18813,7 +18856,7 @@ async function localBranchHeads() {
18813
18856
  }
18814
18857
  async function currentRepoWorktreeGitRoot(repoRoot) {
18815
18858
  const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
18816
- return gitCommonDir ? (0, import_node_path21.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
18859
+ return gitCommonDir ? (0, import_node_path22.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
18817
18860
  }
18818
18861
  async function worktreeBranches() {
18819
18862
  const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
@@ -18833,7 +18876,7 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
18833
18876
  const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
18834
18877
  if (!match?.[1]) return void 0;
18835
18878
  const raw = match[1].trim();
18836
- return (0, import_node_path21.isAbsolute)(raw) ? raw : (0, import_node_path21.resolve)(worktreePath, raw);
18879
+ return (0, import_node_path22.isAbsolute)(raw) ? raw : (0, import_node_path22.resolve)(worktreePath, raw);
18837
18880
  }
18838
18881
  function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
18839
18882
  if (!worktreeGitRoot) return false;
@@ -18842,9 +18885,9 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
18842
18885
  for (const ent of entries) {
18843
18886
  if (!ent.isDirectory()) continue;
18844
18887
  try {
18845
- const gitdirPath = (0, import_node_fs24.readFileSync)((0, import_node_path21.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
18846
- const resolvedGitdir = (0, import_node_path21.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path21.resolve)(worktreeGitRoot, ent.name, gitdirPath);
18847
- if (sameWorktreeMetadataPath((0, import_node_path21.dirname)(resolvedGitdir), worktreePath)) return true;
18888
+ const gitdirPath = (0, import_node_fs24.readFileSync)((0, import_node_path22.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
18889
+ const resolvedGitdir = (0, import_node_path22.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path22.resolve)(worktreeGitRoot, ent.name, gitdirPath);
18890
+ if (sameWorktreeMetadataPath((0, import_node_path22.dirname)(resolvedGitdir), worktreePath)) return true;
18848
18891
  } catch {
18849
18892
  }
18850
18893
  }
@@ -18863,7 +18906,7 @@ function pathExistsKnown(path2) {
18863
18906
  }
18864
18907
  }
18865
18908
  function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
18866
- const gitPath = (0, import_node_path21.join)(path2, ".git");
18909
+ const gitPath = (0, import_node_path22.join)(path2, ".git");
18867
18910
  let st;
18868
18911
  try {
18869
18912
  st = (0, import_node_fs25.lstatSync)(gitPath);
@@ -18920,7 +18963,7 @@ async function siblingWorktreeDirs() {
18920
18963
  const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
18921
18964
  try {
18922
18965
  const entries = (0, import_node_fs25.readdirSync)(siblingRoot, { withFileTypes: true });
18923
- return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path21.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
18966
+ return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path22.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
18924
18967
  } catch {
18925
18968
  return [];
18926
18969
  }
@@ -18970,7 +19013,7 @@ async function fetchHubVersionInfo(baseUrl) {
18970
19013
  }
18971
19014
  function readRepoVersion() {
18972
19015
  try {
18973
- return JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path22.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
19016
+ return JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path23.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
18974
19017
  } catch {
18975
19018
  return void 0;
18976
19019
  }
@@ -18988,24 +19031,24 @@ var NPM_VIEW_TIMEOUT_MS = 15e3;
18988
19031
  var PLUGIN_PULL_TIMEOUT_MS = 3e4;
18989
19032
  async function applyVersionAutoUpdate(report, log) {
18990
19033
  const action = versionAutoUpdateAction(report, Boolean(process.env.CLAUDE_PLUGIN_ROOT));
18991
- if (action === "none") return report;
19034
+ if (action === "none") return { report, applied: "none" };
18992
19035
  const target = report.releasedVersion ?? "latest";
18993
19036
  if (action === "plugin-pull") {
18994
19037
  try {
18995
19038
  const root = (await execFileP2("git", ["-C", process.env.CLAUDE_PLUGIN_ROOT, "rev-parse", "--show-toplevel"], { timeout: PLUGIN_PULL_TIMEOUT_MS })).stdout.trim();
18996
19039
  log(` \u21BB refreshing MMI plugin ${report.currentVersion} \u2192 ${target} (effective next session)\u2026`);
18997
19040
  await execFileP2("git", ["-C", root, "pull", "--ff-only"], { timeout: PLUGIN_PULL_TIMEOUT_MS });
18998
- return { ...report, ok: true };
19041
+ return { report: { ...report, ok: true }, applied: "plugin-pull" };
18999
19042
  } catch {
19000
- return report;
19043
+ return { report, applied: "none" };
19001
19044
  }
19002
19045
  }
19003
19046
  try {
19004
19047
  log(` \u21BB updating mmi-cli ${report.currentVersion} \u2192 ${target}\u2026`);
19005
19048
  await runHostBin("npm", ["install", "-g", "@mutmutco/cli@latest"], { timeout: NPM_UPDATE_TIMEOUT_MS });
19006
- return { ...report, ok: true };
19049
+ return { report: { ...report, ok: true }, applied: "npm" };
19007
19050
  } catch {
19008
- return report;
19051
+ return { report, applied: "none" };
19009
19052
  }
19010
19053
  }
19011
19054
  async function fetchNpmReleasedVersion() {
@@ -19033,7 +19076,11 @@ async function applyDesignSystemUpdate(check, log) {
19033
19076
  if (check.latestVersion && installedVersion && compareVersions(installedVersion, check.latestVersion) >= 0) {
19034
19077
  return { ...check, ok: true, installedVersion };
19035
19078
  }
19036
- return { ...check, installedVersion };
19079
+ return {
19080
+ ...check,
19081
+ installedVersion,
19082
+ fix: `${check.packageName} is held below ${check.latestVersion ?? "latest"} by this repo's package.json range \u2014 \`npm update\` cannot cross it. Bump the range or run \`npm install ${check.packageName}@latest\` to force the update.`
19083
+ };
19037
19084
  } catch {
19038
19085
  return check;
19039
19086
  }
@@ -19067,6 +19114,9 @@ var CLAUDE_PLUGIN_TIMEOUT_MS = 12e4;
19067
19114
  function runHostBin(bin, args, opts) {
19068
19115
  return isWin ? execFileP2("cmd.exe", ["/c", bin, ...args], opts) : execFileP2(bin, args, opts);
19069
19116
  }
19117
+ function hostBinAvailable(bin) {
19118
+ return execFileP2(isWin ? "where" : "which", [bin]).then(() => true).catch(() => false);
19119
+ }
19070
19120
  async function runClaudePlugin(args) {
19071
19121
  try {
19072
19122
  await runHostBin("claude", args, { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
@@ -19099,7 +19149,7 @@ async function applyPluginHeal(token, surface, log, opts) {
19099
19149
  }
19100
19150
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
19101
19151
  const homeDir = surface === "codex" ? ".codex" : ".claude";
19102
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "installed_plugins.json");
19152
+ return (0, import_node_path23.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "installed_plugins.json");
19103
19153
  };
19104
19154
  function readInstalledPlugins() {
19105
19155
  try {
@@ -19110,7 +19160,7 @@ function readInstalledPlugins() {
19110
19160
  }
19111
19161
  function installedPluginSources() {
19112
19162
  return ["claude", "codex"].map((surface) => {
19113
- const recordPath = (0, import_node_path22.join)((0, import_node_os5.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
19163
+ const recordPath = (0, import_node_path23.join)((0, import_node_os5.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
19114
19164
  try {
19115
19165
  return { surface, installed: JSON.parse((0, import_node_fs26.readFileSync)(recordPath, "utf8")), recordPath };
19116
19166
  } catch {
@@ -19120,7 +19170,7 @@ function installedPluginSources() {
19120
19170
  }
19121
19171
  function readClaudeSettings() {
19122
19172
  try {
19123
- return JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path22.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
19173
+ return JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path23.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
19124
19174
  } catch {
19125
19175
  return null;
19126
19176
  }
@@ -19165,16 +19215,16 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
19165
19215
  }
19166
19216
  }
19167
19217
  function opencodeConfigDir() {
19168
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".config", "opencode");
19218
+ return (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".config", "opencode");
19169
19219
  }
19170
19220
  function opencodeConfigPath() {
19171
- return (0, import_node_path22.join)(opencodeConfigDir(), "opencode.jsonc");
19221
+ return (0, import_node_path23.join)(opencodeConfigDir(), "opencode.jsonc");
19172
19222
  }
19173
19223
  function opencodeCommandsDir() {
19174
- return (0, import_node_path22.join)(opencodeConfigDir(), "commands");
19224
+ return (0, import_node_path23.join)(opencodeConfigDir(), "commands");
19175
19225
  }
19176
19226
  function opencodeSkillsPath() {
19177
- return (0, import_node_path22.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
19227
+ return (0, import_node_path23.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
19178
19228
  }
19179
19229
  function opencodeConfigSnapshot() {
19180
19230
  const path2 = opencodeConfigPath();
@@ -19203,7 +19253,7 @@ function writeOpencodeConfigPlugin(snapshot) {
19203
19253
  const plan2 = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
19204
19254
  if (plan2.action === "already") return true;
19205
19255
  if (!plan2.text || plan2.action === "unsafe") return false;
19206
- (0, import_node_fs26.mkdirSync)((0, import_node_path22.dirname)(path2), { recursive: true });
19256
+ (0, import_node_fs26.mkdirSync)((0, import_node_path23.dirname)(path2), { recursive: true });
19207
19257
  if (snapshot.hasConfig) (0, import_node_fs26.copyFileSync)(path2, `${path2}.bak`);
19208
19258
  (0, import_node_fs26.writeFileSync)(path2, plan2.text, "utf8");
19209
19259
  return true;
@@ -19220,7 +19270,7 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
19220
19270
  const normalized = skillsPath.replace(/\\/g, "/");
19221
19271
  if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
19222
19272
  parsed.skills = { ...skills, paths };
19223
- (0, import_node_fs26.mkdirSync)((0, import_node_path22.dirname)(snapshot.path), { recursive: true });
19273
+ (0, import_node_fs26.mkdirSync)((0, import_node_path23.dirname)(snapshot.path), { recursive: true });
19224
19274
  if (snapshot.hasConfig && (0, import_node_fs26.existsSync)(snapshot.path)) (0, import_node_fs26.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
19225
19275
  (0, import_node_fs26.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
19226
19276
  `, "utf8");
@@ -19241,7 +19291,7 @@ function writeOpencodeCommandFiles() {
19241
19291
  const dir = opencodeCommandsDir();
19242
19292
  (0, import_node_fs26.mkdirSync)(dir, { recursive: true });
19243
19293
  for (const command of OPENCODE_WORKFLOW_COMMANDS) {
19244
- (0, import_node_fs26.writeFileSync)((0, import_node_path22.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
19294
+ (0, import_node_fs26.writeFileSync)((0, import_node_path23.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
19245
19295
  }
19246
19296
  return true;
19247
19297
  } catch {
@@ -19250,8 +19300,8 @@ function writeOpencodeCommandFiles() {
19250
19300
  }
19251
19301
  function readOpencodeAdapterDiskVersion() {
19252
19302
  const candidates = [
19253
- (0, import_node_path22.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
19254
- (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
19303
+ (0, import_node_path23.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
19304
+ (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
19255
19305
  ];
19256
19306
  for (const path2 of candidates) {
19257
19307
  try {
@@ -19286,21 +19336,21 @@ function opencodePluginVersionsForReport() {
19286
19336
  }
19287
19337
  function opencodeDesktopLogsRoot() {
19288
19338
  if (process.platform === "win32") {
19289
- const base = process.env.APPDATA || (0, import_node_path22.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
19290
- return (0, import_node_path22.join)(base, "ai.opencode.desktop", "logs");
19339
+ const base = process.env.APPDATA || (0, import_node_path23.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
19340
+ return (0, import_node_path23.join)(base, "ai.opencode.desktop", "logs");
19291
19341
  }
19292
19342
  if (process.platform === "darwin") {
19293
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
19343
+ return (0, import_node_path23.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
19294
19344
  }
19295
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".config", "ai.opencode.desktop", "logs");
19345
+ return (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".config", "ai.opencode.desktop", "logs");
19296
19346
  }
19297
19347
  function opencodeDesktopBootstrapSnapshot() {
19298
19348
  const root = opencodeDesktopLogsRoot();
19299
19349
  try {
19300
- const sessionDirs = (0, import_node_fs26.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path22.join)(root, entry.name)).sort((a, b) => (0, import_node_fs26.statSync)(b).mtimeMs - (0, import_node_fs26.statSync)(a).mtimeMs);
19350
+ const sessionDirs = (0, import_node_fs26.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path23.join)(root, entry.name)).sort((a, b) => (0, import_node_fs26.statSync)(b).mtimeMs - (0, import_node_fs26.statSync)(a).mtimeMs);
19301
19351
  const newest = sessionDirs[0];
19302
19352
  if (!newest) return [];
19303
- const logPath = (0, import_node_path22.join)(newest, "renderer.log");
19353
+ const logPath = (0, import_node_path23.join)(newest, "renderer.log");
19304
19354
  const text = (0, import_node_fs26.readFileSync)(logPath, "utf8");
19305
19355
  return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs26.existsSync)(directory)).map((directory) => ({ directory, logPath }));
19306
19356
  } catch {
@@ -19308,7 +19358,7 @@ function opencodeDesktopBootstrapSnapshot() {
19308
19358
  }
19309
19359
  }
19310
19360
  function opencodeLegacyConfigSnapshot() {
19311
- const legacyPath = (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".opencode", "opencode.json");
19361
+ const legacyPath = (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".opencode", "opencode.json");
19312
19362
  if (!(0, import_node_fs26.existsSync)(legacyPath)) return {};
19313
19363
  const content = readTextFile(legacyPath);
19314
19364
  if (content == null) return {};
@@ -19329,16 +19379,16 @@ function quarantineOpencodeLegacyConfig(legacyPath) {
19329
19379
  }
19330
19380
  }
19331
19381
  function cursorPluginCacheRoot() {
19332
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
19382
+ return (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
19333
19383
  }
19334
19384
  function cursorPluginCachePinSnapshots() {
19335
19385
  const root = cursorPluginCacheRoot();
19336
19386
  try {
19337
19387
  return (0, import_node_fs26.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
19338
- const path2 = (0, import_node_path22.join)(root, entry.name);
19339
- const pluginJson = (0, import_node_path22.join)(path2, ".cursor-plugin", "plugin.json");
19340
- const hooksJson = (0, import_node_path22.join)(path2, "hooks", "hooks.json");
19341
- const cliBundle = (0, import_node_path22.join)(path2, "cli", "dist", "index.cjs");
19388
+ const path2 = (0, import_node_path23.join)(root, entry.name);
19389
+ const pluginJson = (0, import_node_path23.join)(path2, ".cursor-plugin", "plugin.json");
19390
+ const hooksJson = (0, import_node_path23.join)(path2, "hooks", "hooks.json");
19391
+ const cliBundle = (0, import_node_path23.join)(path2, "cli", "dist", "index.cjs");
19342
19392
  let version;
19343
19393
  try {
19344
19394
  const raw = JSON.parse((0, import_node_fs26.readFileSync)(pluginJson, "utf8"));
@@ -19367,19 +19417,19 @@ function cursorPluginCachePinSnapshots() {
19367
19417
  }
19368
19418
  }
19369
19419
  function hubCheckoutForCursorSeed() {
19370
- const manifest = (0, import_node_path22.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
19420
+ const manifest = (0, import_node_path23.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
19371
19421
  return (0, import_node_fs26.existsSync)(manifest) ? process.cwd() : void 0;
19372
19422
  }
19373
19423
  function mmiPluginCacheRootSnapshots() {
19374
19424
  const roots = [
19375
- { surface: "claude", root: (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
19376
- { surface: "codex", root: (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
19425
+ { surface: "claude", root: (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
19426
+ { surface: "codex", root: (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
19377
19427
  ];
19378
19428
  return roots.flatMap(({ surface, root }) => {
19379
19429
  try {
19380
19430
  const entries = (0, import_node_fs26.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
19381
19431
  name: entry.name,
19382
- path: (0, import_node_path22.join)(root, entry.name),
19432
+ path: (0, import_node_path23.join)(root, entry.name),
19383
19433
  isDirectory: entry.isDirectory()
19384
19434
  }));
19385
19435
  return [{ surface, root, entries }];
@@ -19390,7 +19440,7 @@ function mmiPluginCacheRootSnapshots() {
19390
19440
  }
19391
19441
  function hasNestedMmiChild(versionDir) {
19392
19442
  try {
19393
- return (0, import_node_fs26.statSync)((0, import_node_path22.join)(versionDir, "mmi")).isDirectory();
19443
+ return (0, import_node_fs26.statSync)((0, import_node_path23.join)(versionDir, "mmi")).isDirectory();
19394
19444
  } catch {
19395
19445
  return false;
19396
19446
  }
@@ -19415,7 +19465,7 @@ function quarantinePluginCacheDirs(plan2) {
19415
19465
  try {
19416
19466
  if (!(0, import_node_fs26.existsSync)(move.from)) continue;
19417
19467
  const target = uniqueQuarantineTarget(move.to);
19418
- (0, import_node_fs26.mkdirSync)((0, import_node_path22.dirname)(target), { recursive: true });
19468
+ (0, import_node_fs26.mkdirSync)((0, import_node_path23.dirname)(target), { recursive: true });
19419
19469
  (0, import_node_fs26.renameSync)(move.from, target);
19420
19470
  moved += 1;
19421
19471
  } catch {
@@ -19437,7 +19487,7 @@ async function clearNestedPluginTreeDir(targetPath) {
19437
19487
  try {
19438
19488
  if (!(0, import_node_fs26.existsSync)(targetPath)) return true;
19439
19489
  if (isWin) {
19440
- const emptyDir = (0, import_node_path22.join)((0, import_node_os5.tmpdir)(), `mmi-empty-${Date.now()}`);
19490
+ const emptyDir = (0, import_node_path23.join)((0, import_node_os5.tmpdir)(), `mmi-empty-${Date.now()}`);
19441
19491
  (0, import_node_fs26.mkdirSync)(emptyDir, { recursive: true });
19442
19492
  try {
19443
19493
  await robocopyMirrorEmpty(emptyDir, targetPath);
@@ -19464,7 +19514,7 @@ async function applyNestedPluginTreeCleanup(paths, log) {
19464
19514
  }
19465
19515
  return true;
19466
19516
  }
19467
- var gitignorePath = () => (0, import_node_path22.join)(process.cwd(), ".gitignore");
19517
+ var gitignorePath = () => (0, import_node_path23.join)(process.cwd(), ".gitignore");
19468
19518
  function readTextFile(path2) {
19469
19519
  try {
19470
19520
  if (!(0, import_node_fs26.existsSync)(path2)) return null;
@@ -19477,10 +19527,10 @@ function playwrightMcpConfigSnapshots() {
19477
19527
  const cwd = process.cwd();
19478
19528
  const home = (0, import_node_os5.homedir)();
19479
19529
  const candidates = [
19480
- (0, import_node_path22.join)(cwd, ".mcp.json"),
19481
- (0, import_node_path22.join)(cwd, ".cursor", "mcp.json"),
19482
- (0, import_node_path22.join)(home, ".cursor", "mcp.json"),
19483
- (0, import_node_path22.join)(home, ".codex", "config.toml")
19530
+ (0, import_node_path23.join)(cwd, ".mcp.json"),
19531
+ (0, import_node_path23.join)(cwd, ".cursor", "mcp.json"),
19532
+ (0, import_node_path23.join)(home, ".cursor", "mcp.json"),
19533
+ (0, import_node_path23.join)(home, ".codex", "config.toml")
19484
19534
  ];
19485
19535
  const out = [];
19486
19536
  for (const path2 of candidates) {
@@ -19493,7 +19543,7 @@ function strayBrowserArtifactPaths() {
19493
19543
  const cwd = process.cwd();
19494
19544
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
19495
19545
  try {
19496
- return (0, import_node_fs26.existsSync)((0, import_node_path22.join)(cwd, rel));
19546
+ return (0, import_node_fs26.existsSync)((0, import_node_path23.join)(cwd, rel));
19497
19547
  } catch {
19498
19548
  return false;
19499
19549
  }
@@ -19512,8 +19562,8 @@ function latestIso(values) {
19512
19562
  return best;
19513
19563
  }
19514
19564
  function latestNorthstarContinuityAt() {
19515
- const meta = parseMeta(readTextFile((0, import_node_path22.join)(process.cwd(), META_FILE)));
19516
- const queue = parseQueue(readTextFile((0, import_node_path22.join)(process.cwd(), QUEUE_FILE)));
19565
+ const meta = parseMeta(readTextFile((0, import_node_path23.join)(process.cwd(), META_FILE)));
19566
+ const queue = parseQueue(readTextFile((0, import_node_path23.join)(process.cwd(), QUEUE_FILE)));
19517
19567
  return latestIso([
19518
19568
  ...Object.values(meta).map((entry) => entry.syncedAt),
19519
19569
  ...queue.map((entry) => entry.queuedAt)
@@ -19570,6 +19620,10 @@ async function runDoctor(opts, io = consoleIo) {
19570
19620
  });
19571
19621
  const installedForHealPlan = readInstalledPlugins();
19572
19622
  const sourcesForHealPlan = installedPluginSources();
19623
+ const semverPrefix = /^\d+\.\d+\.\d+/;
19624
+ const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
19625
+ const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
19626
+ const cursorCacheStale = (0, import_node_fs26.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
19573
19627
  const healPlan = doctorHealPlan({
19574
19628
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19575
19629
  surface,
@@ -19584,7 +19638,9 @@ async function runDoctor(opts, io = consoleIo) {
19584
19638
  legacyPluginCheck: buildLegacyPluginInstallCheck({
19585
19639
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19586
19640
  sources: sourcesForHealPlan
19587
- })
19641
+ }),
19642
+ opencodeAdapterStale,
19643
+ cursorCacheStale
19588
19644
  });
19589
19645
  if (opts.preflight && !healPlan.needsEagerHeal) return;
19590
19646
  const eagerHeal = Boolean(opts.preflight) || Boolean(opts.banner) && healPlan.needsEagerHeal;
@@ -19615,9 +19671,19 @@ async function runDoctor(opts, io = consoleIo) {
19615
19671
  repoVersion: readRepoVersion(),
19616
19672
  releasedVersion
19617
19673
  });
19618
- if (repairFull) versionReport = await applyVersionAutoUpdate(versionReport, (m) => io.err(m));
19674
+ let selfUpdatedCli = false;
19675
+ if (repairFull) {
19676
+ const updated = await applyVersionAutoUpdate(versionReport, (m) => io.err(m));
19677
+ versionReport = updated.report;
19678
+ selfUpdatedCli = updated.applied === "npm";
19679
+ }
19619
19680
  if (!versionReport.ok) versionReport = { ...versionReport, fix: pluginRecoveryFix(surface) };
19620
19681
  checks.push(versionReport);
19682
+ if (selfUpdatedCli) {
19683
+ if (opts.json) io.log(JSON.stringify(buildSelfUpdateHaltPayload({ checks, updatedTo: versionReport.releasedVersion }), null, 2));
19684
+ else io.err(selfUpdateHaltLine(versionReport));
19685
+ return;
19686
+ }
19621
19687
  checks.push({ ok: Boolean(cfg.sagaApiUrl), label: "Hub API URL configured", fix: "set MMI_HUB_URL or use a current MMI CLI/plugin build" });
19622
19688
  const hubVersionInfo = await fetchHubVersionInfo(cfg.sagaApiUrl);
19623
19689
  const installedVersion = resolveClientVersion();
@@ -19732,14 +19798,12 @@ async function runDoctor(opts, io = consoleIo) {
19732
19798
  surface
19733
19799
  });
19734
19800
  if (!installedVersionCheck.ok && (repairFull || repairLocal)) {
19801
+ const crossSurfaceRepair = Boolean(opts.apply) || !opts.json && !opts.banner && !opts.preflight;
19802
+ const canDriveSurface = async (token) => surfaceToken(surface) === token || crossSurfaceRepair && await hostBinAvailable(token);
19803
+ const rereadInstalled = () => buildInstalledPluginVersionCheck({ isOrgRepo: Boolean(cfg.sagaApiUrl), sources: installedPluginSources(), releasedVersion, surface });
19735
19804
  const claudeStale = installedVersionCheck.staleSurfaces?.some((s) => s.surface === "claude") ?? false;
19736
- if (claudeStale && await applyPluginHeal("claude", surface, (m) => io.err(m))) {
19737
- const healed = buildInstalledPluginVersionCheck({
19738
- isOrgRepo: Boolean(cfg.sagaApiUrl),
19739
- sources: installedPluginSources(),
19740
- releasedVersion,
19741
- surface
19742
- });
19805
+ if (claudeStale && await canDriveSurface("claude") && await applyPluginHeal("claude", surface, (m) => io.err(m), { force: true })) {
19806
+ const healed = rereadInstalled();
19743
19807
  installedVersionCheck = healed;
19744
19808
  if (healed.ok) {
19745
19809
  markPluginReloadRequired();
@@ -19747,13 +19811,8 @@ async function runDoctor(opts, io = consoleIo) {
19747
19811
  }
19748
19812
  }
19749
19813
  const codexStale = installedVersionCheck.staleSurfaces?.some((s) => s.surface === "codex") ?? false;
19750
- if (!installedVersionCheck.ok && codexStale && await applyPluginHeal("codex", surface, (m) => io.err(m))) {
19751
- const healed = buildInstalledPluginVersionCheck({
19752
- isOrgRepo: Boolean(cfg.sagaApiUrl),
19753
- sources: installedPluginSources(),
19754
- releasedVersion,
19755
- surface
19756
- });
19814
+ if (!installedVersionCheck.ok && codexStale && await canDriveSurface("codex") && await applyPluginHeal("codex", surface, (m) => io.err(m), { force: true })) {
19815
+ const healed = rereadInstalled();
19757
19816
  installedVersionCheck = healed;
19758
19817
  if (healed.ok) {
19759
19818
  markPluginReloadRequired();
@@ -19937,7 +19996,7 @@ async function runDoctor(opts, io = consoleIo) {
19937
19996
  releasedVersion,
19938
19997
  hubCheckout: hubCheckoutForCursorSeed(),
19939
19998
  execFileP: execFileP2,
19940
- mkdtemp: (prefix) => (0, import_promises7.mkdtemp)((0, import_node_path22.join)((0, import_node_os5.tmpdir)(), prefix)),
19999
+ mkdtemp: (prefix) => (0, import_promises7.mkdtemp)((0, import_node_path23.join)((0, import_node_os5.tmpdir)(), prefix)),
19941
20000
  log: (m) => io.err(m)
19942
20001
  });
19943
20002
  if (seeded) {
@@ -20075,7 +20134,8 @@ async function runDoctor(opts, io = consoleIo) {
20075
20134
  }
20076
20135
  const gaps = checks.filter((c) => !c.ok);
20077
20136
  if (opts.preflight) {
20078
- if (healPlan.needsEagerHeal) io.err(doctorPreflightDoneLine(surface));
20137
+ const outcome = preflightOutcome({ gaps, needsEagerHeal: healPlan.needsEagerHeal, surface });
20138
+ if (outcome.line) io.err(outcome.line);
20079
20139
  if (pluginReloadRequired) io.err(pluginAutonomousHaltLine(reloadHint));
20080
20140
  return;
20081
20141
  }
@@ -20327,7 +20387,7 @@ rules.command("sync").option("--quiet", "stay silent unless something changed or
20327
20387
  if (!await runRulesSync(opts)) process.exitCode = 1;
20328
20388
  });
20329
20389
  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) => {
20330
- const path2 = (0, import_node_path23.join)(process.cwd(), ".gitignore");
20390
+ const path2 = (0, import_node_path24.join)(process.cwd(), ".gitignore");
20331
20391
  const current = (0, import_node_fs27.existsSync)(path2) ? (0, import_node_fs27.readFileSync)(path2, "utf8") : null;
20332
20392
  const plan2 = planManagedGitignore(current);
20333
20393
  const drift = [...plan2.added.map((l) => `+${l}`), ...plan2.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
@@ -20534,7 +20594,7 @@ function runWorktreeInstall(command, cwd, quiet) {
20534
20594
  async function primaryCheckoutRoot(worktreeRoot) {
20535
20595
  try {
20536
20596
  const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
20537
- return out ? (0, import_node_path23.dirname)(out) : void 0;
20597
+ return out ? (0, import_node_path24.dirname)(out) : void 0;
20538
20598
  } catch {
20539
20599
  return void 0;
20540
20600
  }
@@ -20547,7 +20607,7 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
20547
20607
  };
20548
20608
  }
20549
20609
  function acquireWorktreeSetupLock(worktreeRoot) {
20550
- const lockPath = (0, import_node_path23.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
20610
+ const lockPath = (0, import_node_path24.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
20551
20611
  const take = () => {
20552
20612
  const fd = (0, import_node_fs27.openSync)(lockPath, "wx");
20553
20613
  try {
@@ -20563,7 +20623,7 @@ function acquireWorktreeSetupLock(worktreeRoot) {
20563
20623
  };
20564
20624
  };
20565
20625
  try {
20566
- (0, import_node_fs27.mkdirSync)((0, import_node_path23.dirname)(lockPath), { recursive: true });
20626
+ (0, import_node_fs27.mkdirSync)((0, import_node_path24.dirname)(lockPath), { recursive: true });
20567
20627
  return take();
20568
20628
  } catch {
20569
20629
  try {
@@ -21569,7 +21629,7 @@ pr.command("create").description("create a PR and print {number,url} JSON").opti
21569
21629
  console.log(JSON.stringify(created));
21570
21630
  });
21571
21631
  async function listCiWorkflowPaths(cwd = process.cwd()) {
21572
- const wfDir = (0, import_node_path23.join)(cwd, ".github", "workflows");
21632
+ const wfDir = (0, import_node_path24.join)(cwd, ".github", "workflows");
21573
21633
  if (!(0, import_node_fs27.existsSync)(wfDir)) return [];
21574
21634
  return (0, import_node_fs27.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
21575
21635
  }
@@ -21743,7 +21803,7 @@ async function createDeferredWorktreeStore() {
21743
21803
  },
21744
21804
  write: async (entries) => {
21745
21805
  try {
21746
- await (0, import_promises8.mkdir)((0, import_node_path23.dirname)(registryPath), { recursive: true });
21806
+ await (0, import_promises8.mkdir)((0, import_node_path24.dirname)(registryPath), { recursive: true });
21747
21807
  await (0, import_promises8.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
21748
21808
  } catch {
21749
21809
  }
@@ -22101,8 +22161,8 @@ async function resolveStage() {
22101
22161
  local,
22102
22162
  shell: shellFor(),
22103
22163
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
22104
- hasCompose: (0, import_node_fs27.existsSync)((0, import_node_path23.join)(process.cwd(), "docker-compose.yml")),
22105
- hasEnvExample: (0, import_node_fs27.existsSync)((0, import_node_path23.join)(process.cwd(), ".env.example"))
22164
+ hasCompose: (0, import_node_fs27.existsSync)((0, import_node_path24.join)(process.cwd(), "docker-compose.yml")),
22165
+ hasEnvExample: (0, import_node_fs27.existsSync)((0, import_node_path24.join)(process.cwd(), ".env.example"))
22106
22166
  });
22107
22167
  }
22108
22168
  async function fetchStageVaultEnvMerge() {
@@ -22957,6 +23017,7 @@ program2.command("session-start").description("run the SessionStart verbs (rules
22957
23017
  console.error("[mmi-hook] session-start: cwd is a repository SUBDIRECTORY \u2014 skipping the SessionStart hook (spine/docs/plan/saga delivery); run it from the repo root.");
22958
23018
  return;
22959
23019
  }
23020
+ if (!isOrgRepoRoot(process.cwd())) return;
22960
23021
  try {
22961
23022
  const hook = parseHookInput(await readStdin());
22962
23023
  if (hook.session_id) persistSession(hook.session_id);