@mutmutco/cli 2.50.1 → 2.52.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 (3) hide show
  1. package/dist/main.cjs +451 -196
  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
  }
@@ -15749,6 +15756,22 @@ function stageRequiredSecrets(stage2, meta) {
15749
15756
  function stageKey(stage2, key) {
15750
15757
  return key.includes("/") ? key : `${stage2}/${key}`;
15751
15758
  }
15759
+ function materializedRuntimeSecretName(entry) {
15760
+ return entry.includes(":") ? entry.split(":").pop() : entry;
15761
+ }
15762
+ function runtimeSecretStreamGap(stage2, meta, presentSecrets) {
15763
+ const streamed = new Set(
15764
+ (contractByStage(meta.requiredRuntimeSecrets)[stage2] ?? []).map(materializedRuntimeSecretName)
15765
+ );
15766
+ const seen = /* @__PURE__ */ new Set();
15767
+ const gap = [];
15768
+ for (const name of stageRequiredSecrets(stage2, meta).map(materializedRuntimeSecretName)) {
15769
+ if (seen.has(name)) continue;
15770
+ seen.add(name);
15771
+ if (!streamed.has(name) && presentSecrets.has(stageKey(stage2, name))) gap.push(name);
15772
+ }
15773
+ return gap;
15774
+ }
15752
15775
  function hasRuntimeSecretContract(contract) {
15753
15776
  if (!contract || typeof contract !== "object" || Array.isArray(contract)) return false;
15754
15777
  return ["dev", "rc", "main"].some((stage2) => Array.isArray(contract[stage2]));
@@ -15990,6 +16013,11 @@ async function buildV2Doctor(repoOrSlug, deps) {
15990
16013
  const missing = required.filter((key) => !presentSecrets.has(key));
15991
16014
  return [stage2, { required, present, missing }];
15992
16015
  }));
16016
+ const runtimeSecretStreamWarnings = Object.fromEntries(STAGES.map((stage2) => [
16017
+ stage2,
16018
+ stageInTrack(meta, stage2) ? runtimeSecretStreamGap(stage2, meta, presentSecrets) : []
16019
+ ]));
16020
+ const runtimeSecretStreamWarningRows = STAGES.map((stage2) => ({ stage: stage2, names: runtimeSecretStreamWarnings[stage2] })).filter((row) => row.names.length > 0);
15993
16021
  const metaMissing = ["class", "projectType", "deployModel", "vaultPath", "kbPointer"].filter((key) => meta[key] === void 0).concat(boardRegistryGaps(meta));
15994
16022
  const ok = !secretsError && metaMissing.length === 0 && Object.values(deployCoords).every((v) => v.ok) && Object.values(secrets).every((v) => v.missing.length === 0);
15995
16023
  const edgeDomainWarnings = deps.resolveDns ? await probeEdgeDomains(meta, deps.resolveDns) : [];
@@ -16005,6 +16033,7 @@ async function buildV2Doctor(repoOrSlug, deps) {
16005
16033
  autoHealAvailable: Object.keys(autoHeal.patch),
16006
16034
  appOwnedGaps: autoHeal.appOwnedGaps,
16007
16035
  ...edgeDomainWarnings.length ? { edgeDomainWarnings } : {},
16036
+ ...runtimeSecretStreamWarningRows.length ? { runtimeSecretStreamWarnings: runtimeSecretStreamWarningRows } : {},
16008
16037
  appAttested: appAttestationOf(meta) ?? void 0
16009
16038
  };
16010
16039
  }
@@ -16035,6 +16064,9 @@ function renderReadinessIssueBody(existingBody, report, opts = {}) {
16035
16064
  ...(report.edgeDomainWarnings ?? []).map(
16036
16065
  (w) => `- \u26A0 edge domain does not resolve in DNS (advisory): ${w.stage} \u2192 ${w.host}; verify the registry edgeDomains value against the live public host`
16037
16066
  ),
16067
+ ...(report.runtimeSecretStreamWarnings ?? []).map(
16068
+ (w) => `- \u26A0 required secrets provisioned but not in requiredRuntimeSecrets (advisory): ${w.stage} \u2192 ${w.names.join(", ")}; add them to the registry stream list or they will not be materialized into tenant.env`
16069
+ ),
16038
16070
  "",
16039
16071
  "### Auto-heal applied / available",
16040
16072
  ...opts.healed?.length ? opts.healed.map((x) => `- ${x}`) : report.autoHealAvailable.map((x) => `- ${x}`),
@@ -17549,14 +17581,14 @@ function authorizeBodyHasMismatch(body) {
17549
17581
  // src/doctor-run.ts
17550
17582
  var import_node_fs26 = require("node:fs");
17551
17583
  var import_promises7 = require("node:fs/promises");
17552
- var import_node_path22 = require("node:path");
17584
+ var import_node_path23 = require("node:path");
17553
17585
  var import_node_os5 = require("node:os");
17554
17586
 
17555
17587
  // src/cursor-plugin-seed.ts
17556
17588
  var import_node_child_process12 = require("node:child_process");
17557
17589
  var import_node_fs22 = require("node:fs");
17558
17590
  var import_node_os4 = require("node:os");
17559
- var import_node_path19 = require("node:path");
17591
+ var import_node_path20 = require("node:path");
17560
17592
  var import_node_util7 = require("node:util");
17561
17593
  function isSemverVersion(v) {
17562
17594
  return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
@@ -17573,13 +17605,13 @@ function ghReleaseTarballApiArgs(tag) {
17573
17605
  }
17574
17606
  function cursorUserGlobalStatePath() {
17575
17607
  if (process.platform === "win32") {
17576
- const base = process.env.APPDATA || (0, import_node_path19.join)((0, import_node_os4.homedir)(), "AppData", "Roaming");
17577
- return (0, import_node_path19.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
17608
+ const base = process.env.APPDATA || (0, import_node_path20.join)((0, import_node_os4.homedir)(), "AppData", "Roaming");
17609
+ return (0, import_node_path20.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
17578
17610
  }
17579
17611
  if (process.platform === "darwin") {
17580
- return (0, import_node_path19.join)((0, import_node_os4.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
17612
+ return (0, import_node_path20.join)((0, import_node_os4.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
17581
17613
  }
17582
- return (0, import_node_path19.join)((0, import_node_os4.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
17614
+ return (0, import_node_path20.join)((0, import_node_os4.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
17583
17615
  }
17584
17616
  async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
17585
17617
  const dbPath = cursorUserGlobalStatePath();
@@ -17599,7 +17631,7 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
17599
17631
  function syncDirContents(src, dest) {
17600
17632
  (0, import_node_fs22.mkdirSync)(dest, { recursive: true });
17601
17633
  for (const name of (0, import_node_fs22.readdirSync)(dest)) {
17602
- (0, import_node_fs22.rmSync)((0, import_node_path19.join)(dest, name), { recursive: true, force: true });
17634
+ (0, import_node_fs22.rmSync)((0, import_node_path20.join)(dest, name), { recursive: true, force: true });
17603
17635
  }
17604
17636
  (0, import_node_fs22.cpSync)(src, dest, { recursive: true });
17605
17637
  }
@@ -17607,21 +17639,21 @@ function releaseTag(releasedVersion) {
17607
17639
  return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
17608
17640
  }
17609
17641
  async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
17610
- const tarFile = (0, import_node_path19.join)(tmpRoot, "archive.tar");
17642
+ const tarFile = (0, import_node_path20.join)(tmpRoot, "archive.tar");
17611
17643
  try {
17612
17644
  await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
17613
17645
  await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
17614
17646
  timeout: 6e4
17615
17647
  });
17616
17648
  await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
17617
- const pluginMmi = (0, import_node_path19.join)(tmpRoot, "plugins", "mmi");
17618
- return (0, import_node_fs22.existsSync)((0, import_node_path19.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
17649
+ const pluginMmi = (0, import_node_path20.join)(tmpRoot, "plugins", "mmi");
17650
+ return (0, import_node_fs22.existsSync)((0, import_node_path20.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
17619
17651
  } catch {
17620
17652
  return void 0;
17621
17653
  }
17622
17654
  }
17623
17655
  async function downloadPluginMmiViaGh(tag, tmpRoot) {
17624
- const tarPath = (0, import_node_path19.join)(tmpRoot, "repo.tgz");
17656
+ const tarPath = (0, import_node_path20.join)(tmpRoot, "repo.tgz");
17625
17657
  try {
17626
17658
  (0, import_node_fs22.mkdirSync)(tmpRoot, { recursive: true });
17627
17659
  const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
@@ -17634,8 +17666,8 @@ async function downloadPluginMmiViaGh(tag, tmpRoot) {
17634
17666
  await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
17635
17667
  const top = (0, import_node_fs22.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
17636
17668
  if (!top) return void 0;
17637
- const pluginMmi = (0, import_node_path19.join)(tmpRoot, top, "plugins", "mmi");
17638
- return (0, import_node_fs22.existsSync)((0, import_node_path19.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
17669
+ const pluginMmi = (0, import_node_path20.join)(tmpRoot, top, "plugins", "mmi");
17670
+ return (0, import_node_fs22.existsSync)((0, import_node_path20.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
17639
17671
  } catch {
17640
17672
  return void 0;
17641
17673
  }
@@ -17647,7 +17679,7 @@ async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, exe
17647
17679
  const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
17648
17680
  if (fromHub) return fromHub;
17649
17681
  }
17650
- return downloadPluginMmiViaGh(tag, (0, import_node_path19.join)(tmpRoot, "gh"));
17682
+ return downloadPluginMmiViaGh(tag, (0, import_node_path20.join)(tmpRoot, "gh"));
17651
17683
  }
17652
17684
  function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
17653
17685
  if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
@@ -18044,8 +18076,45 @@ var CLAUDE_RECOVERY = `claude plugin marketplace remove ${LEGACY_MMI_MARKETPLACE
18044
18076
  var CODEX_RECOVERY = `codex plugin marketplace remove ${LEGACY_MMI_MARKETPLACE} && codex plugin marketplace remove mutmutco && codex plugin marketplace add mutmutco/MMI-Hub --ref main && codex plugin add mmi@mutmutco`;
18045
18077
  var OPENCODE_PLUGIN_PACKAGE = "@mutmutco/opencode-mmi";
18046
18078
  var OPENCODE_PLUGIN_SPEC = `${OPENCODE_PLUGIN_PACKAGE}@latest`;
18047
- var OPENCODE_PLUGIN_INSTALL_COMMAND = `opencode plugin ${OPENCODE_PLUGIN_SPEC} --global --force`;
18079
+ var OPENCODE_PLUGIN_INSTALL_COMMAND = `mmi-cli doctor --apply`;
18048
18080
  var OPENCODE_RECOVERY = `${OPENCODE_PLUGIN_INSTALL_COMMAND} # then restart OpenCode to load MMI commands`;
18081
+ var OPENCODE_WORKFLOW_COMMANDS = [
18082
+ "mmi",
18083
+ "secrets",
18084
+ "stage",
18085
+ "rcand",
18086
+ "release",
18087
+ "hotfix",
18088
+ "bootstrap",
18089
+ "grind",
18090
+ "build",
18091
+ "handoff",
18092
+ "coop",
18093
+ "browser-automation"
18094
+ ];
18095
+ function opencodeCommandDescription(command) {
18096
+ if (command === "mmi") return "Run the MMI work-board workflow.";
18097
+ if (command === "browser-automation") return "Run the MMI browser automation workflow.";
18098
+ return `Run the MMI ${command} workflow.`;
18099
+ }
18100
+ function opencodeCommandTemplate(command) {
18101
+ return [
18102
+ `Use the \`${command}\` skill and follow it exactly.`,
18103
+ "",
18104
+ "$ARGUMENTS"
18105
+ ].join("\n");
18106
+ }
18107
+ function opencodeCommandMarkdown(command) {
18108
+ return [
18109
+ "---",
18110
+ `description: ${opencodeCommandDescription(command)}`,
18111
+ "agent: build",
18112
+ "---",
18113
+ "",
18114
+ opencodeCommandTemplate(command),
18115
+ ""
18116
+ ].join("\n");
18117
+ }
18049
18118
  var PLUGIN_SURFACE_HEAL = {
18050
18119
  claude: {
18051
18120
  delivery: "plugin-cli",
@@ -18239,6 +18308,7 @@ function buildOpencodeVersionCheck(input) {
18239
18308
  return { ...base, ok: false, installedVersion: input.installedVersion, releasedVersion: input.releasedVersion };
18240
18309
  }
18241
18310
  var OPENCODE_CONFIG_PLUGIN_LABEL = "OpenCode MMI adapter config wiring";
18311
+ var OPENCODE_SURFACE_ASSETS_LABEL = "OpenCode MMI commands and skills";
18242
18312
  function opencodePluginEntryMatches(entry) {
18243
18313
  return entry === OPENCODE_PLUGIN_PACKAGE || entry === OPENCODE_PLUGIN_SPEC || Array.isArray(entry) && (entry[0] === OPENCODE_PLUGIN_PACKAGE || entry[0] === OPENCODE_PLUGIN_SPEC);
18244
18314
  }
@@ -18326,6 +18396,23 @@ function buildOpencodeConfigPluginCheck(input) {
18326
18396
  }
18327
18397
  return { ...base, configPath: input.configPath };
18328
18398
  }
18399
+ function buildOpencodeSurfaceAssetsCheck(input) {
18400
+ const base = {
18401
+ ok: true,
18402
+ label: OPENCODE_SURFACE_ASSETS_LABEL,
18403
+ fix: OPENCODE_RECOVERY,
18404
+ configPath: input.configPath,
18405
+ commandsDir: input.commandsDir,
18406
+ skillsPath: input.skillsPath
18407
+ };
18408
+ if (!input.isOrgRepo) return base;
18409
+ const existing = new Set(input.existingCommands.map((c) => c.toLowerCase()));
18410
+ const missingCommands = OPENCODE_WORKFLOW_COMMANDS.filter((c) => !existing.has(c));
18411
+ const normalizedSkillsPath = input.skillsPath?.replace(/\\/g, "/");
18412
+ const hasSkillsPath = Boolean(normalizedSkillsPath && input.configuredSkillsPaths?.some((p) => p.replace(/\\/g, "/") === normalizedSkillsPath));
18413
+ if (!missingCommands.length && hasSkillsPath) return { ...base, hasSkillsPath };
18414
+ return { ...base, ok: false, missingCommands, hasSkillsPath };
18415
+ }
18329
18416
  var OPENCODE_DESKTOP_BOOTSTRAP_LABEL = "OpenCode Desktop stale project bootstrap";
18330
18417
  var OPENCODE_DESKTOP_BOOTSTRAP_FIX = "OpenCode Desktop is bootstrapping a deleted MMI worktree; open an existing checkout in OpenCode, remove/select away from the stale project entry, then restart OpenCode";
18331
18418
  function decodeLogUrlDirectory(value) {
@@ -18440,7 +18527,7 @@ function buildCursorPluginInstallCheck(input) {
18440
18527
  };
18441
18528
  }
18442
18529
  }
18443
- if (input.surface === "cursor" && isSemverVersion2(input.releasedVersion) && input.pins.some((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
18530
+ if (input.cacheRootExists && isSemverVersion2(input.releasedVersion) && input.pins.some((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
18444
18531
  const stale = input.pins.filter((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
18445
18532
  return {
18446
18533
  ...base,
@@ -18620,12 +18707,34 @@ function doctorHealPlan(input) {
18620
18707
  const versionUpdate = versionAutoUpdateAction(input.versionReport, input.hasPluginRoot);
18621
18708
  const pluginReinstall = input.isOrgRepo && !input.installedVersionCheck.ok && (input.surface === "claude-cli" || input.surface === "claude-vscode" || input.surface === "codex") && Boolean(input.installedVersionCheck.staleSurfaces?.length);
18622
18709
  const legacyPluginMigration = input.isOrgRepo && !input.legacyPluginCheck.ok;
18623
- const needsEagerHeal = versionUpdate !== "none" || pluginReinstall || legacyPluginMigration;
18624
- return { needsEagerHeal, versionUpdate, pluginReinstall, legacyPluginMigration };
18710
+ const opencodeReinstall = input.isOrgRepo && Boolean(input.opencodeAdapterStale);
18711
+ const cursorReseed = input.isOrgRepo && Boolean(input.cursorCacheStale);
18712
+ const needsEagerHeal = versionUpdate !== "none" || pluginReinstall || legacyPluginMigration || opencodeReinstall || cursorReseed;
18713
+ return { needsEagerHeal, versionUpdate, pluginReinstall, legacyPluginMigration, opencodeReinstall, cursorReseed };
18625
18714
  }
18626
18715
  function doctorPreflightDoneLine(surface) {
18627
18716
  return `\u21BB MMI tooling updated \u2014 ${reloadAction(surface)} to load changes`;
18628
18717
  }
18718
+ function selfUpdateHaltLine(report) {
18719
+ const to = report.releasedVersion ?? "latest";
18720
+ 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.`;
18721
+ }
18722
+ function buildSelfUpdateHaltPayload(input) {
18723
+ return {
18724
+ ok: false,
18725
+ rerunRequired: true,
18726
+ reason: "mmi-cli self-updated; rerun doctor so the remaining surfaces reconcile under the new CLI",
18727
+ updatedTo: input.updatedTo ?? null,
18728
+ checks: input.checks
18729
+ };
18730
+ }
18731
+ function preflightOutcome(input) {
18732
+ if (input.gaps.length) {
18733
+ return { healed: false, line: `\u26A0 MMI preflight: ${input.gaps.length} item(s) still need attention \u2014 ${input.gaps.map((g) => g.fix).join(" \xB7 ")}` };
18734
+ }
18735
+ if (input.needsEagerHeal) return { healed: true, line: doctorPreflightDoneLine(input.surface) };
18736
+ return { healed: true };
18737
+ }
18629
18738
  function pluginAutonomousHaltLine(reloadHint) {
18630
18739
  return `\u26A0 PLUGIN RELOAD REQUIRED \u2014 mmi:* skills and agent types are unavailable until you ${reloadHint}. Halt autonomous /grind and /build until then.`;
18631
18740
  }
@@ -18661,14 +18770,14 @@ function renderTerseDoctorReport(input) {
18661
18770
 
18662
18771
  // src/kb-drift-report.ts
18663
18772
  var import_node_fs23 = require("node:fs");
18664
- var import_node_path20 = require("node:path");
18773
+ var import_node_path21 = require("node:path");
18665
18774
  function yesterdayIso() {
18666
18775
  const d = /* @__PURE__ */ new Date();
18667
18776
  d.setUTCDate(d.getUTCDate() - 1);
18668
18777
  return d.toISOString().slice(0, 10);
18669
18778
  }
18670
18779
  async function fetchLatestKbDriftReport(execFileP5, repoRoot) {
18671
- const sagaIo = (0, import_node_path20.join)(repoRoot, "infra", "saga-io.mjs");
18780
+ const sagaIo = (0, import_node_path21.join)(repoRoot, "infra", "saga-io.mjs");
18672
18781
  if (!(0, import_node_fs23.existsSync)(sagaIo)) return null;
18673
18782
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
18674
18783
  for (const date of [today, yesterdayIso()]) {
@@ -18686,7 +18795,7 @@ async function fetchLatestKbDriftReport(execFileP5, repoRoot) {
18686
18795
 
18687
18796
  // src/cli-doctor-shared.ts
18688
18797
  var import_node_fs24 = require("node:fs");
18689
- var import_node_path21 = require("node:path");
18798
+ var import_node_path22 = require("node:path");
18690
18799
  var import_node_fs25 = require("node:fs");
18691
18800
  var GC_GH_TIMEOUT_MS = 2e4;
18692
18801
  async function awsCallerArn() {
@@ -18733,7 +18842,7 @@ async function localBranchHeads() {
18733
18842
  }
18734
18843
  async function currentRepoWorktreeGitRoot(repoRoot) {
18735
18844
  const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
18736
- return gitCommonDir ? (0, import_node_path21.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
18845
+ return gitCommonDir ? (0, import_node_path22.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
18737
18846
  }
18738
18847
  async function worktreeBranches() {
18739
18848
  const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
@@ -18753,7 +18862,7 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
18753
18862
  const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
18754
18863
  if (!match?.[1]) return void 0;
18755
18864
  const raw = match[1].trim();
18756
- return (0, import_node_path21.isAbsolute)(raw) ? raw : (0, import_node_path21.resolve)(worktreePath, raw);
18865
+ return (0, import_node_path22.isAbsolute)(raw) ? raw : (0, import_node_path22.resolve)(worktreePath, raw);
18757
18866
  }
18758
18867
  function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
18759
18868
  if (!worktreeGitRoot) return false;
@@ -18762,9 +18871,9 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
18762
18871
  for (const ent of entries) {
18763
18872
  if (!ent.isDirectory()) continue;
18764
18873
  try {
18765
- const gitdirPath = (0, import_node_fs24.readFileSync)((0, import_node_path21.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
18766
- const resolvedGitdir = (0, import_node_path21.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path21.resolve)(worktreeGitRoot, ent.name, gitdirPath);
18767
- if (sameWorktreeMetadataPath((0, import_node_path21.dirname)(resolvedGitdir), worktreePath)) return true;
18874
+ const gitdirPath = (0, import_node_fs24.readFileSync)((0, import_node_path22.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
18875
+ const resolvedGitdir = (0, import_node_path22.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path22.resolve)(worktreeGitRoot, ent.name, gitdirPath);
18876
+ if (sameWorktreeMetadataPath((0, import_node_path22.dirname)(resolvedGitdir), worktreePath)) return true;
18768
18877
  } catch {
18769
18878
  }
18770
18879
  }
@@ -18783,7 +18892,7 @@ function pathExistsKnown(path2) {
18783
18892
  }
18784
18893
  }
18785
18894
  function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
18786
- const gitPath = (0, import_node_path21.join)(path2, ".git");
18895
+ const gitPath = (0, import_node_path22.join)(path2, ".git");
18787
18896
  let st;
18788
18897
  try {
18789
18898
  st = (0, import_node_fs25.lstatSync)(gitPath);
@@ -18840,7 +18949,7 @@ async function siblingWorktreeDirs() {
18840
18949
  const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
18841
18950
  try {
18842
18951
  const entries = (0, import_node_fs25.readdirSync)(siblingRoot, { withFileTypes: true });
18843
- return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path21.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
18952
+ return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path22.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
18844
18953
  } catch {
18845
18954
  return [];
18846
18955
  }
@@ -18890,7 +18999,7 @@ async function fetchHubVersionInfo(baseUrl) {
18890
18999
  }
18891
19000
  function readRepoVersion() {
18892
19001
  try {
18893
- return JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path22.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
19002
+ return JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path23.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
18894
19003
  } catch {
18895
19004
  return void 0;
18896
19005
  }
@@ -18908,24 +19017,24 @@ var NPM_VIEW_TIMEOUT_MS = 15e3;
18908
19017
  var PLUGIN_PULL_TIMEOUT_MS = 3e4;
18909
19018
  async function applyVersionAutoUpdate(report, log) {
18910
19019
  const action = versionAutoUpdateAction(report, Boolean(process.env.CLAUDE_PLUGIN_ROOT));
18911
- if (action === "none") return report;
19020
+ if (action === "none") return { report, applied: "none" };
18912
19021
  const target = report.releasedVersion ?? "latest";
18913
19022
  if (action === "plugin-pull") {
18914
19023
  try {
18915
19024
  const root = (await execFileP2("git", ["-C", process.env.CLAUDE_PLUGIN_ROOT, "rev-parse", "--show-toplevel"], { timeout: PLUGIN_PULL_TIMEOUT_MS })).stdout.trim();
18916
19025
  log(` \u21BB refreshing MMI plugin ${report.currentVersion} \u2192 ${target} (effective next session)\u2026`);
18917
19026
  await execFileP2("git", ["-C", root, "pull", "--ff-only"], { timeout: PLUGIN_PULL_TIMEOUT_MS });
18918
- return { ...report, ok: true };
19027
+ return { report: { ...report, ok: true }, applied: "plugin-pull" };
18919
19028
  } catch {
18920
- return report;
19029
+ return { report, applied: "none" };
18921
19030
  }
18922
19031
  }
18923
19032
  try {
18924
19033
  log(` \u21BB updating mmi-cli ${report.currentVersion} \u2192 ${target}\u2026`);
18925
19034
  await runHostBin("npm", ["install", "-g", "@mutmutco/cli@latest"], { timeout: NPM_UPDATE_TIMEOUT_MS });
18926
- return { ...report, ok: true };
19035
+ return { report: { ...report, ok: true }, applied: "npm" };
18927
19036
  } catch {
18928
- return report;
19037
+ return { report, applied: "none" };
18929
19038
  }
18930
19039
  }
18931
19040
  async function fetchNpmReleasedVersion() {
@@ -18953,7 +19062,11 @@ async function applyDesignSystemUpdate(check, log) {
18953
19062
  if (check.latestVersion && installedVersion && compareVersions(installedVersion, check.latestVersion) >= 0) {
18954
19063
  return { ...check, ok: true, installedVersion };
18955
19064
  }
18956
- return { ...check, installedVersion };
19065
+ return {
19066
+ ...check,
19067
+ installedVersion,
19068
+ 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.`
19069
+ };
18957
19070
  } catch {
18958
19071
  return check;
18959
19072
  }
@@ -18987,6 +19100,9 @@ var CLAUDE_PLUGIN_TIMEOUT_MS = 12e4;
18987
19100
  function runHostBin(bin, args, opts) {
18988
19101
  return isWin ? execFileP2("cmd.exe", ["/c", bin, ...args], opts) : execFileP2(bin, args, opts);
18989
19102
  }
19103
+ function hostBinAvailable(bin) {
19104
+ return execFileP2(isWin ? "where" : "which", [bin]).then(() => true).catch(() => false);
19105
+ }
18990
19106
  async function runClaudePlugin(args) {
18991
19107
  try {
18992
19108
  await runHostBin("claude", args, { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
@@ -19019,7 +19135,7 @@ async function applyPluginHeal(token, surface, log, opts) {
19019
19135
  }
19020
19136
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
19021
19137
  const homeDir = surface === "codex" ? ".codex" : ".claude";
19022
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "installed_plugins.json");
19138
+ return (0, import_node_path23.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "installed_plugins.json");
19023
19139
  };
19024
19140
  function readInstalledPlugins() {
19025
19141
  try {
@@ -19030,7 +19146,7 @@ function readInstalledPlugins() {
19030
19146
  }
19031
19147
  function installedPluginSources() {
19032
19148
  return ["claude", "codex"].map((surface) => {
19033
- const recordPath = (0, import_node_path22.join)((0, import_node_os5.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
19149
+ const recordPath = (0, import_node_path23.join)((0, import_node_os5.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
19034
19150
  try {
19035
19151
  return { surface, installed: JSON.parse((0, import_node_fs26.readFileSync)(recordPath, "utf8")), recordPath };
19036
19152
  } catch {
@@ -19040,7 +19156,7 @@ function installedPluginSources() {
19040
19156
  }
19041
19157
  function readClaudeSettings() {
19042
19158
  try {
19043
- return JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path22.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
19159
+ return JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path23.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
19044
19160
  } catch {
19045
19161
  return null;
19046
19162
  }
@@ -19084,8 +19200,17 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
19084
19200
  return false;
19085
19201
  }
19086
19202
  }
19203
+ function opencodeConfigDir() {
19204
+ return (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".config", "opencode");
19205
+ }
19087
19206
  function opencodeConfigPath() {
19088
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".config", "opencode", "opencode.jsonc");
19207
+ return (0, import_node_path23.join)(opencodeConfigDir(), "opencode.jsonc");
19208
+ }
19209
+ function opencodeCommandsDir() {
19210
+ return (0, import_node_path23.join)(opencodeConfigDir(), "commands");
19211
+ }
19212
+ function opencodeSkillsPath() {
19213
+ return (0, import_node_path23.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
19089
19214
  }
19090
19215
  function opencodeConfigSnapshot() {
19091
19216
  const path2 = opencodeConfigPath();
@@ -19094,12 +19219,14 @@ function opencodeConfigSnapshot() {
19094
19219
  const raw = (0, import_node_fs26.readFileSync)(path2, "utf8");
19095
19220
  const parsed = JSON.parse(stripJsonc(raw));
19096
19221
  const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
19222
+ const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
19097
19223
  return {
19098
19224
  path: path2,
19099
19225
  hasConfig: true,
19100
19226
  hasPluginField,
19101
19227
  parseOk: true,
19102
19228
  raw,
19229
+ ...skillsPaths ? { skillsPaths } : {},
19103
19230
  ...Array.isArray(parsed.plugin) ? { pluginEntries: parsed.plugin } : parsed.plugin === void 0 ? {} : { pluginEntries: void 0 }
19104
19231
  };
19105
19232
  } catch {
@@ -19112,7 +19239,7 @@ function writeOpencodeConfigPlugin(snapshot) {
19112
19239
  const plan2 = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
19113
19240
  if (plan2.action === "already") return true;
19114
19241
  if (!plan2.text || plan2.action === "unsafe") return false;
19115
- (0, import_node_fs26.mkdirSync)((0, import_node_path22.dirname)(path2), { recursive: true });
19242
+ (0, import_node_fs26.mkdirSync)((0, import_node_path23.dirname)(path2), { recursive: true });
19116
19243
  if (snapshot.hasConfig) (0, import_node_fs26.copyFileSync)(path2, `${path2}.bak`);
19117
19244
  (0, import_node_fs26.writeFileSync)(path2, plan2.text, "utf8");
19118
19245
  return true;
@@ -19120,23 +19247,96 @@ function writeOpencodeConfigPlugin(snapshot) {
19120
19247
  return false;
19121
19248
  }
19122
19249
  }
19250
+ function writeOpencodeSkillsPath(snapshot, skillsPath) {
19251
+ try {
19252
+ const raw = snapshot.hasConfig ? snapshot.raw : void 0;
19253
+ const parsed = raw ? JSON.parse(stripJsonc(raw)) : { $schema: "https://opencode.ai/config.json" };
19254
+ const skills = parsed.skills && typeof parsed.skills === "object" && !Array.isArray(parsed.skills) ? parsed.skills : {};
19255
+ const paths = Array.isArray(skills.paths) ? skills.paths.filter((p) => typeof p === "string") : [];
19256
+ const normalized = skillsPath.replace(/\\/g, "/");
19257
+ if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
19258
+ parsed.skills = { ...skills, paths };
19259
+ (0, import_node_fs26.mkdirSync)((0, import_node_path23.dirname)(snapshot.path), { recursive: true });
19260
+ if (snapshot.hasConfig && (0, import_node_fs26.existsSync)(snapshot.path)) (0, import_node_fs26.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
19261
+ (0, import_node_fs26.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
19262
+ `, "utf8");
19263
+ return true;
19264
+ } catch {
19265
+ return false;
19266
+ }
19267
+ }
19268
+ function opencodeExistingCommands() {
19269
+ try {
19270
+ return (0, import_node_fs26.readdirSync)(opencodeCommandsDir(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3).toLowerCase());
19271
+ } catch {
19272
+ return [];
19273
+ }
19274
+ }
19275
+ function writeOpencodeCommandFiles() {
19276
+ try {
19277
+ const dir = opencodeCommandsDir();
19278
+ (0, import_node_fs26.mkdirSync)(dir, { recursive: true });
19279
+ for (const command of OPENCODE_WORKFLOW_COMMANDS) {
19280
+ (0, import_node_fs26.writeFileSync)((0, import_node_path23.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
19281
+ }
19282
+ return true;
19283
+ } catch {
19284
+ return false;
19285
+ }
19286
+ }
19287
+ function readOpencodeAdapterDiskVersion() {
19288
+ const candidates = [
19289
+ (0, import_node_path23.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
19290
+ (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
19291
+ ];
19292
+ for (const path2 of candidates) {
19293
+ try {
19294
+ const parsed = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
19295
+ if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
19296
+ } catch {
19297
+ continue;
19298
+ }
19299
+ }
19300
+ return void 0;
19301
+ }
19302
+ function opencodeMmiPluginSpecs(snapshot) {
19303
+ const specs = (snapshot.pluginEntries ?? []).map((entry) => Array.isArray(entry) ? entry[0] : entry).filter((entry) => typeof entry === "string").filter((entry) => entry === OPENCODE_PLUGIN_PACKAGE || entry === OPENCODE_PLUGIN_SPEC || entry.startsWith("@mutmutco/"));
19304
+ return Array.from(new Set(specs.length ? specs : [OPENCODE_PLUGIN_SPEC]));
19305
+ }
19306
+ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
19307
+ try {
19308
+ const specs = opencodeMmiPluginSpecs(snapshot);
19309
+ log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
19310
+ (0, import_node_fs26.mkdirSync)(opencodeConfigDir(), { recursive: true });
19311
+ await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
19312
+ return true;
19313
+ } catch {
19314
+ return false;
19315
+ }
19316
+ }
19317
+ function opencodeInstalledVersionForDoctor() {
19318
+ return process.env.MMI_OPENCODE_PLUGIN_VERSION || readOpencodeAdapterDiskVersion();
19319
+ }
19320
+ function opencodePluginVersionsForReport() {
19321
+ return [process.env.MMI_OPENCODE_PLUGIN_VERSION, readOpencodeAdapterDiskVersion()].filter((v) => Boolean(v));
19322
+ }
19123
19323
  function opencodeDesktopLogsRoot() {
19124
19324
  if (process.platform === "win32") {
19125
- const base = process.env.APPDATA || (0, import_node_path22.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
19126
- return (0, import_node_path22.join)(base, "ai.opencode.desktop", "logs");
19325
+ const base = process.env.APPDATA || (0, import_node_path23.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
19326
+ return (0, import_node_path23.join)(base, "ai.opencode.desktop", "logs");
19127
19327
  }
19128
19328
  if (process.platform === "darwin") {
19129
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
19329
+ return (0, import_node_path23.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
19130
19330
  }
19131
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".config", "ai.opencode.desktop", "logs");
19331
+ return (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".config", "ai.opencode.desktop", "logs");
19132
19332
  }
19133
19333
  function opencodeDesktopBootstrapSnapshot() {
19134
19334
  const root = opencodeDesktopLogsRoot();
19135
19335
  try {
19136
- 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);
19336
+ 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);
19137
19337
  const newest = sessionDirs[0];
19138
19338
  if (!newest) return [];
19139
- const logPath = (0, import_node_path22.join)(newest, "renderer.log");
19339
+ const logPath = (0, import_node_path23.join)(newest, "renderer.log");
19140
19340
  const text = (0, import_node_fs26.readFileSync)(logPath, "utf8");
19141
19341
  return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs26.existsSync)(directory)).map((directory) => ({ directory, logPath }));
19142
19342
  } catch {
@@ -19144,7 +19344,7 @@ function opencodeDesktopBootstrapSnapshot() {
19144
19344
  }
19145
19345
  }
19146
19346
  function opencodeLegacyConfigSnapshot() {
19147
- const legacyPath = (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".opencode", "opencode.json");
19347
+ const legacyPath = (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".opencode", "opencode.json");
19148
19348
  if (!(0, import_node_fs26.existsSync)(legacyPath)) return {};
19149
19349
  const content = readTextFile(legacyPath);
19150
19350
  if (content == null) return {};
@@ -19165,16 +19365,16 @@ function quarantineOpencodeLegacyConfig(legacyPath) {
19165
19365
  }
19166
19366
  }
19167
19367
  function cursorPluginCacheRoot() {
19168
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
19368
+ return (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
19169
19369
  }
19170
19370
  function cursorPluginCachePinSnapshots() {
19171
19371
  const root = cursorPluginCacheRoot();
19172
19372
  try {
19173
19373
  return (0, import_node_fs26.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
19174
- const path2 = (0, import_node_path22.join)(root, entry.name);
19175
- const pluginJson = (0, import_node_path22.join)(path2, ".cursor-plugin", "plugin.json");
19176
- const hooksJson = (0, import_node_path22.join)(path2, "hooks", "hooks.json");
19177
- const cliBundle = (0, import_node_path22.join)(path2, "cli", "dist", "index.cjs");
19374
+ const path2 = (0, import_node_path23.join)(root, entry.name);
19375
+ const pluginJson = (0, import_node_path23.join)(path2, ".cursor-plugin", "plugin.json");
19376
+ const hooksJson = (0, import_node_path23.join)(path2, "hooks", "hooks.json");
19377
+ const cliBundle = (0, import_node_path23.join)(path2, "cli", "dist", "index.cjs");
19178
19378
  let version;
19179
19379
  try {
19180
19380
  const raw = JSON.parse((0, import_node_fs26.readFileSync)(pluginJson, "utf8"));
@@ -19203,19 +19403,19 @@ function cursorPluginCachePinSnapshots() {
19203
19403
  }
19204
19404
  }
19205
19405
  function hubCheckoutForCursorSeed() {
19206
- const manifest = (0, import_node_path22.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
19406
+ const manifest = (0, import_node_path23.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
19207
19407
  return (0, import_node_fs26.existsSync)(manifest) ? process.cwd() : void 0;
19208
19408
  }
19209
19409
  function mmiPluginCacheRootSnapshots() {
19210
19410
  const roots = [
19211
- { surface: "claude", root: (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
19212
- { surface: "codex", root: (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
19411
+ { surface: "claude", root: (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
19412
+ { surface: "codex", root: (0, import_node_path23.join)((0, import_node_os5.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
19213
19413
  ];
19214
19414
  return roots.flatMap(({ surface, root }) => {
19215
19415
  try {
19216
19416
  const entries = (0, import_node_fs26.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
19217
19417
  name: entry.name,
19218
- path: (0, import_node_path22.join)(root, entry.name),
19418
+ path: (0, import_node_path23.join)(root, entry.name),
19219
19419
  isDirectory: entry.isDirectory()
19220
19420
  }));
19221
19421
  return [{ surface, root, entries }];
@@ -19226,7 +19426,7 @@ function mmiPluginCacheRootSnapshots() {
19226
19426
  }
19227
19427
  function hasNestedMmiChild(versionDir) {
19228
19428
  try {
19229
- return (0, import_node_fs26.statSync)((0, import_node_path22.join)(versionDir, "mmi")).isDirectory();
19429
+ return (0, import_node_fs26.statSync)((0, import_node_path23.join)(versionDir, "mmi")).isDirectory();
19230
19430
  } catch {
19231
19431
  return false;
19232
19432
  }
@@ -19251,7 +19451,7 @@ function quarantinePluginCacheDirs(plan2) {
19251
19451
  try {
19252
19452
  if (!(0, import_node_fs26.existsSync)(move.from)) continue;
19253
19453
  const target = uniqueQuarantineTarget(move.to);
19254
- (0, import_node_fs26.mkdirSync)((0, import_node_path22.dirname)(target), { recursive: true });
19454
+ (0, import_node_fs26.mkdirSync)((0, import_node_path23.dirname)(target), { recursive: true });
19255
19455
  (0, import_node_fs26.renameSync)(move.from, target);
19256
19456
  moved += 1;
19257
19457
  } catch {
@@ -19273,7 +19473,7 @@ async function clearNestedPluginTreeDir(targetPath) {
19273
19473
  try {
19274
19474
  if (!(0, import_node_fs26.existsSync)(targetPath)) return true;
19275
19475
  if (isWin) {
19276
- const emptyDir = (0, import_node_path22.join)((0, import_node_os5.tmpdir)(), `mmi-empty-${Date.now()}`);
19476
+ const emptyDir = (0, import_node_path23.join)((0, import_node_os5.tmpdir)(), `mmi-empty-${Date.now()}`);
19277
19477
  (0, import_node_fs26.mkdirSync)(emptyDir, { recursive: true });
19278
19478
  try {
19279
19479
  await robocopyMirrorEmpty(emptyDir, targetPath);
@@ -19300,7 +19500,7 @@ async function applyNestedPluginTreeCleanup(paths, log) {
19300
19500
  }
19301
19501
  return true;
19302
19502
  }
19303
- var gitignorePath = () => (0, import_node_path22.join)(process.cwd(), ".gitignore");
19503
+ var gitignorePath = () => (0, import_node_path23.join)(process.cwd(), ".gitignore");
19304
19504
  function readTextFile(path2) {
19305
19505
  try {
19306
19506
  if (!(0, import_node_fs26.existsSync)(path2)) return null;
@@ -19313,10 +19513,10 @@ function playwrightMcpConfigSnapshots() {
19313
19513
  const cwd = process.cwd();
19314
19514
  const home = (0, import_node_os5.homedir)();
19315
19515
  const candidates = [
19316
- (0, import_node_path22.join)(cwd, ".mcp.json"),
19317
- (0, import_node_path22.join)(cwd, ".cursor", "mcp.json"),
19318
- (0, import_node_path22.join)(home, ".cursor", "mcp.json"),
19319
- (0, import_node_path22.join)(home, ".codex", "config.toml")
19516
+ (0, import_node_path23.join)(cwd, ".mcp.json"),
19517
+ (0, import_node_path23.join)(cwd, ".cursor", "mcp.json"),
19518
+ (0, import_node_path23.join)(home, ".cursor", "mcp.json"),
19519
+ (0, import_node_path23.join)(home, ".codex", "config.toml")
19320
19520
  ];
19321
19521
  const out = [];
19322
19522
  for (const path2 of candidates) {
@@ -19329,7 +19529,7 @@ function strayBrowserArtifactPaths() {
19329
19529
  const cwd = process.cwd();
19330
19530
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
19331
19531
  try {
19332
- return (0, import_node_fs26.existsSync)((0, import_node_path22.join)(cwd, rel));
19532
+ return (0, import_node_fs26.existsSync)((0, import_node_path23.join)(cwd, rel));
19333
19533
  } catch {
19334
19534
  return false;
19335
19535
  }
@@ -19348,8 +19548,8 @@ function latestIso(values) {
19348
19548
  return best;
19349
19549
  }
19350
19550
  function latestNorthstarContinuityAt() {
19351
- const meta = parseMeta(readTextFile((0, import_node_path22.join)(process.cwd(), META_FILE)));
19352
- const queue = parseQueue(readTextFile((0, import_node_path22.join)(process.cwd(), QUEUE_FILE)));
19551
+ const meta = parseMeta(readTextFile((0, import_node_path23.join)(process.cwd(), META_FILE)));
19552
+ const queue = parseQueue(readTextFile((0, import_node_path23.join)(process.cwd(), QUEUE_FILE)));
19353
19553
  return latestIso([
19354
19554
  ...Object.values(meta).map((entry) => entry.syncedAt),
19355
19555
  ...queue.map((entry) => entry.queuedAt)
@@ -19406,6 +19606,10 @@ async function runDoctor(opts, io = consoleIo) {
19406
19606
  });
19407
19607
  const installedForHealPlan = readInstalledPlugins();
19408
19608
  const sourcesForHealPlan = installedPluginSources();
19609
+ const semverPrefix = /^\d+\.\d+\.\d+/;
19610
+ const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
19611
+ const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
19612
+ const cursorCacheStale = (0, import_node_fs26.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
19409
19613
  const healPlan = doctorHealPlan({
19410
19614
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19411
19615
  surface,
@@ -19420,7 +19624,9 @@ async function runDoctor(opts, io = consoleIo) {
19420
19624
  legacyPluginCheck: buildLegacyPluginInstallCheck({
19421
19625
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19422
19626
  sources: sourcesForHealPlan
19423
- })
19627
+ }),
19628
+ opencodeAdapterStale,
19629
+ cursorCacheStale
19424
19630
  });
19425
19631
  if (opts.preflight && !healPlan.needsEagerHeal) return;
19426
19632
  const eagerHeal = Boolean(opts.preflight) || Boolean(opts.banner) && healPlan.needsEagerHeal;
@@ -19451,9 +19657,19 @@ async function runDoctor(opts, io = consoleIo) {
19451
19657
  repoVersion: readRepoVersion(),
19452
19658
  releasedVersion
19453
19659
  });
19454
- if (repairFull) versionReport = await applyVersionAutoUpdate(versionReport, (m) => io.err(m));
19660
+ let selfUpdatedCli = false;
19661
+ if (repairFull) {
19662
+ const updated = await applyVersionAutoUpdate(versionReport, (m) => io.err(m));
19663
+ versionReport = updated.report;
19664
+ selfUpdatedCli = updated.applied === "npm";
19665
+ }
19455
19666
  if (!versionReport.ok) versionReport = { ...versionReport, fix: pluginRecoveryFix(surface) };
19456
19667
  checks.push(versionReport);
19668
+ if (selfUpdatedCli) {
19669
+ if (opts.json) io.log(JSON.stringify(buildSelfUpdateHaltPayload({ checks, updatedTo: versionReport.releasedVersion }), null, 2));
19670
+ else io.err(selfUpdateHaltLine(versionReport));
19671
+ return;
19672
+ }
19457
19673
  checks.push({ ok: Boolean(cfg.sagaApiUrl), label: "Hub API URL configured", fix: "set MMI_HUB_URL or use a current MMI CLI/plugin build" });
19458
19674
  const hubVersionInfo = await fetchHubVersionInfo(cfg.sagaApiUrl);
19459
19675
  const installedVersion = resolveClientVersion();
@@ -19568,14 +19784,12 @@ async function runDoctor(opts, io = consoleIo) {
19568
19784
  surface
19569
19785
  });
19570
19786
  if (!installedVersionCheck.ok && (repairFull || repairLocal)) {
19787
+ const crossSurfaceRepair = Boolean(opts.apply) || !opts.json && !opts.banner && !opts.preflight;
19788
+ const canDriveSurface = async (token) => surfaceToken(surface) === token || crossSurfaceRepair && await hostBinAvailable(token);
19789
+ const rereadInstalled = () => buildInstalledPluginVersionCheck({ isOrgRepo: Boolean(cfg.sagaApiUrl), sources: installedPluginSources(), releasedVersion, surface });
19571
19790
  const claudeStale = installedVersionCheck.staleSurfaces?.some((s) => s.surface === "claude") ?? false;
19572
- if (claudeStale && await applyPluginHeal("claude", surface, (m) => io.err(m))) {
19573
- const healed = buildInstalledPluginVersionCheck({
19574
- isOrgRepo: Boolean(cfg.sagaApiUrl),
19575
- sources: installedPluginSources(),
19576
- releasedVersion,
19577
- surface
19578
- });
19791
+ if (claudeStale && await canDriveSurface("claude") && await applyPluginHeal("claude", surface, (m) => io.err(m), { force: true })) {
19792
+ const healed = rereadInstalled();
19579
19793
  installedVersionCheck = healed;
19580
19794
  if (healed.ok) {
19581
19795
  markPluginReloadRequired();
@@ -19583,13 +19797,8 @@ async function runDoctor(opts, io = consoleIo) {
19583
19797
  }
19584
19798
  }
19585
19799
  const codexStale = installedVersionCheck.staleSurfaces?.some((s) => s.surface === "codex") ?? false;
19586
- if (!installedVersionCheck.ok && codexStale && await applyPluginHeal("codex", surface, (m) => io.err(m))) {
19587
- const healed = buildInstalledPluginVersionCheck({
19588
- isOrgRepo: Boolean(cfg.sagaApiUrl),
19589
- sources: installedPluginSources(),
19590
- releasedVersion,
19591
- surface
19592
- });
19800
+ if (!installedVersionCheck.ok && codexStale && await canDriveSurface("codex") && await applyPluginHeal("codex", surface, (m) => io.err(m), { force: true })) {
19801
+ const healed = rereadInstalled();
19593
19802
  installedVersionCheck = healed;
19594
19803
  if (healed.ok) {
19595
19804
  markPluginReloadRequired();
@@ -19598,7 +19807,7 @@ async function runDoctor(opts, io = consoleIo) {
19598
19807
  }
19599
19808
  }
19600
19809
  checks.push(installedVersionCheck);
19601
- const openCodeConfigSnapshot = opencodeConfigSnapshot();
19810
+ let openCodeConfigSnapshot = opencodeConfigSnapshot();
19602
19811
  const inspectOpenCode = surface === "opencode" || openCodeConfigSnapshot.hasConfig || runExtended;
19603
19812
  if (inspectOpenCode) {
19604
19813
  let opencodeConfigCheck = buildOpencodeConfigPluginCheck({
@@ -19611,14 +19820,14 @@ async function runDoctor(opts, io = consoleIo) {
19611
19820
  });
19612
19821
  if (!opencodeConfigCheck.ok && opencodeConfigCheck.reason !== "unreadable-config" && opencodeConfigCheck.reason !== "invalid-plugin-shape" && repairLocal) {
19613
19822
  if (writeOpencodeConfigPlugin(openCodeConfigSnapshot)) {
19614
- const refreshed = opencodeConfigSnapshot();
19823
+ openCodeConfigSnapshot = opencodeConfigSnapshot();
19615
19824
  opencodeConfigCheck = buildOpencodeConfigPluginCheck({
19616
19825
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19617
- configPath: refreshed.path,
19618
- hasConfig: refreshed.hasConfig,
19619
- hasPluginField: refreshed.hasPluginField,
19620
- pluginEntries: refreshed.pluginEntries,
19621
- parseOk: refreshed.parseOk
19826
+ configPath: openCodeConfigSnapshot.path,
19827
+ hasConfig: openCodeConfigSnapshot.hasConfig,
19828
+ hasPluginField: openCodeConfigSnapshot.hasPluginField,
19829
+ pluginEntries: openCodeConfigSnapshot.pluginEntries,
19830
+ parseOk: openCodeConfigSnapshot.parseOk
19622
19831
  });
19623
19832
  if (opencodeConfigCheck.ok) {
19624
19833
  markPluginReloadRequired();
@@ -19627,11 +19836,55 @@ async function runDoctor(opts, io = consoleIo) {
19627
19836
  }
19628
19837
  }
19629
19838
  checks.push(opencodeConfigCheck);
19630
- checks.push(buildOpencodeVersionCheck({
19839
+ let opencodeInstalledVersion = opencodeInstalledVersionForDoctor();
19840
+ let opencodeVersionCheck = buildOpencodeVersionCheck({
19631
19841
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19632
- installedVersion: process.env.MMI_OPENCODE_PLUGIN_VERSION,
19842
+ installedVersion: opencodeInstalledVersion,
19633
19843
  releasedVersion
19634
- }));
19844
+ });
19845
+ if (!opencodeVersionCheck.ok && repairFull) {
19846
+ if (await forceInstallOpencodeMmiPlugins(openCodeConfigSnapshot, (m) => io.err(m))) {
19847
+ opencodeInstalledVersion = readOpencodeAdapterDiskVersion() ?? opencodeInstalledVersion;
19848
+ opencodeVersionCheck = buildOpencodeVersionCheck({
19849
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
19850
+ installedVersion: opencodeInstalledVersion,
19851
+ releasedVersion
19852
+ });
19853
+ if (opencodeVersionCheck.ok) {
19854
+ markPluginReloadRequired();
19855
+ io.err(` \u21BB force-refreshed OpenCode MMI plugin \u2192 ${opencodeInstalledVersion ?? releasedVersion ?? "latest"} \u2014 ${reloadAction("opencode")} to load it`);
19856
+ }
19857
+ }
19858
+ }
19859
+ checks.push(opencodeVersionCheck);
19860
+ let surfaceAssetsCheck = buildOpencodeSurfaceAssetsCheck({
19861
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
19862
+ configPath: openCodeConfigSnapshot.path,
19863
+ commandsDir: opencodeCommandsDir(),
19864
+ existingCommands: opencodeExistingCommands(),
19865
+ skillsPath: opencodeSkillsPath(),
19866
+ configuredSkillsPaths: openCodeConfigSnapshot.skillsPaths
19867
+ });
19868
+ if (!surfaceAssetsCheck.ok && repairLocal) {
19869
+ const wroteCommands = surfaceAssetsCheck.missingCommands?.length ? writeOpencodeCommandFiles() : true;
19870
+ const wroteSkills = surfaceAssetsCheck.hasSkillsPath ? true : writeOpencodeSkillsPath(openCodeConfigSnapshot, opencodeSkillsPath());
19871
+ if (wroteCommands || wroteSkills) {
19872
+ openCodeConfigSnapshot = opencodeConfigSnapshot();
19873
+ surfaceAssetsCheck = buildOpencodeSurfaceAssetsCheck({
19874
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
19875
+ configPath: openCodeConfigSnapshot.path,
19876
+ commandsDir: opencodeCommandsDir(),
19877
+ existingCommands: opencodeExistingCommands(),
19878
+ skillsPath: opencodeSkillsPath(),
19879
+ configuredSkillsPaths: openCodeConfigSnapshot.skillsPaths
19880
+ });
19881
+ if (surfaceAssetsCheck.ok) {
19882
+ markPluginReloadRequired();
19883
+ io.err(` \u21BB materialized OpenCode MMI commands + skills path \u2014 ${reloadAction("opencode")} to load them`);
19884
+ }
19885
+ }
19886
+ }
19887
+ checks.push(surfaceAssetsCheck);
19635
19888
  checks.push(buildOpencodeDesktopBootstrapCheck({
19636
19889
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19637
19890
  surface,
@@ -19729,7 +19982,7 @@ async function runDoctor(opts, io = consoleIo) {
19729
19982
  releasedVersion,
19730
19983
  hubCheckout: hubCheckoutForCursorSeed(),
19731
19984
  execFileP: execFileP2,
19732
- mkdtemp: (prefix) => (0, import_promises7.mkdtemp)((0, import_node_path22.join)((0, import_node_os5.tmpdir)(), prefix)),
19985
+ mkdtemp: (prefix) => (0, import_promises7.mkdtemp)((0, import_node_path23.join)((0, import_node_os5.tmpdir)(), prefix)),
19733
19986
  log: (m) => io.err(m)
19734
19987
  });
19735
19988
  if (seeded) {
@@ -19867,7 +20120,8 @@ async function runDoctor(opts, io = consoleIo) {
19867
20120
  }
19868
20121
  const gaps = checks.filter((c) => !c.ok);
19869
20122
  if (opts.preflight) {
19870
- if (healPlan.needsEagerHeal) io.err(doctorPreflightDoneLine(surface));
20123
+ const outcome = preflightOutcome({ gaps, needsEagerHeal: healPlan.needsEagerHeal, surface });
20124
+ if (outcome.line) io.err(outcome.line);
19871
20125
  if (pluginReloadRequired) io.err(pluginAutonomousHaltLine(reloadHint));
19872
20126
  return;
19873
20127
  }
@@ -19885,7 +20139,7 @@ async function runDoctor(opts, io = consoleIo) {
19885
20139
  claudePluginVersions: sourceVersions("claude"),
19886
20140
  codexPluginVersions: sourceVersions("codex"),
19887
20141
  codexCacheVersions: cacheVersionsFor("codex"),
19888
- opencodePluginVersions: [process.env.MMI_OPENCODE_PLUGIN_VERSION],
20142
+ opencodePluginVersions: opencodePluginVersionsForReport(),
19889
20143
  releasedVersion
19890
20144
  });
19891
20145
  const resources = doctorResourcesForGaps(gaps);
@@ -20119,7 +20373,7 @@ rules.command("sync").option("--quiet", "stay silent unless something changed or
20119
20373
  if (!await runRulesSync(opts)) process.exitCode = 1;
20120
20374
  });
20121
20375
  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) => {
20122
- const path2 = (0, import_node_path23.join)(process.cwd(), ".gitignore");
20376
+ const path2 = (0, import_node_path24.join)(process.cwd(), ".gitignore");
20123
20377
  const current = (0, import_node_fs27.existsSync)(path2) ? (0, import_node_fs27.readFileSync)(path2, "utf8") : null;
20124
20378
  const plan2 = planManagedGitignore(current);
20125
20379
  const drift = [...plan2.added.map((l) => `+${l}`), ...plan2.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
@@ -20326,7 +20580,7 @@ function runWorktreeInstall(command, cwd, quiet) {
20326
20580
  async function primaryCheckoutRoot(worktreeRoot) {
20327
20581
  try {
20328
20582
  const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
20329
- return out ? (0, import_node_path23.dirname)(out) : void 0;
20583
+ return out ? (0, import_node_path24.dirname)(out) : void 0;
20330
20584
  } catch {
20331
20585
  return void 0;
20332
20586
  }
@@ -20339,7 +20593,7 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
20339
20593
  };
20340
20594
  }
20341
20595
  function acquireWorktreeSetupLock(worktreeRoot) {
20342
- const lockPath = (0, import_node_path23.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
20596
+ const lockPath = (0, import_node_path24.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
20343
20597
  const take = () => {
20344
20598
  const fd = (0, import_node_fs27.openSync)(lockPath, "wx");
20345
20599
  try {
@@ -20355,7 +20609,7 @@ function acquireWorktreeSetupLock(worktreeRoot) {
20355
20609
  };
20356
20610
  };
20357
20611
  try {
20358
- (0, import_node_fs27.mkdirSync)((0, import_node_path23.dirname)(lockPath), { recursive: true });
20612
+ (0, import_node_fs27.mkdirSync)((0, import_node_path24.dirname)(lockPath), { recursive: true });
20359
20613
  return take();
20360
20614
  } catch {
20361
20615
  try {
@@ -21361,7 +21615,7 @@ pr.command("create").description("create a PR and print {number,url} JSON").opti
21361
21615
  console.log(JSON.stringify(created));
21362
21616
  });
21363
21617
  async function listCiWorkflowPaths(cwd = process.cwd()) {
21364
- const wfDir = (0, import_node_path23.join)(cwd, ".github", "workflows");
21618
+ const wfDir = (0, import_node_path24.join)(cwd, ".github", "workflows");
21365
21619
  if (!(0, import_node_fs27.existsSync)(wfDir)) return [];
21366
21620
  return (0, import_node_fs27.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
21367
21621
  }
@@ -21535,7 +21789,7 @@ async function createDeferredWorktreeStore() {
21535
21789
  },
21536
21790
  write: async (entries) => {
21537
21791
  try {
21538
- await (0, import_promises8.mkdir)((0, import_node_path23.dirname)(registryPath), { recursive: true });
21792
+ await (0, import_promises8.mkdir)((0, import_node_path24.dirname)(registryPath), { recursive: true });
21539
21793
  await (0, import_promises8.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
21540
21794
  } catch {
21541
21795
  }
@@ -21893,8 +22147,8 @@ async function resolveStage() {
21893
22147
  local,
21894
22148
  shell: shellFor(),
21895
22149
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
21896
- hasCompose: (0, import_node_fs27.existsSync)((0, import_node_path23.join)(process.cwd(), "docker-compose.yml")),
21897
- hasEnvExample: (0, import_node_fs27.existsSync)((0, import_node_path23.join)(process.cwd(), ".env.example"))
22150
+ hasCompose: (0, import_node_fs27.existsSync)((0, import_node_path24.join)(process.cwd(), "docker-compose.yml")),
22151
+ hasEnvExample: (0, import_node_fs27.existsSync)((0, import_node_path24.join)(process.cwd(), ".env.example"))
21898
22152
  });
21899
22153
  }
21900
22154
  async function fetchStageVaultEnvMerge() {
@@ -22749,6 +23003,7 @@ program2.command("session-start").description("run the SessionStart verbs (rules
22749
23003
  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.");
22750
23004
  return;
22751
23005
  }
23006
+ if (!isOrgRepoRoot(process.cwd())) return;
22752
23007
  try {
22753
23008
  const hook = parseHookInput(await readStdin());
22754
23009
  if (hook.session_id) persistSession(hook.session_id);