@mutmutco/cli 3.105.8 → 3.105.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.cjs +292 -246
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -8291,7 +8291,105 @@ function appendHookActivity(cwd, entry) {
8291
8291
 
8292
8292
  // src/worktree.ts
8293
8293
  var import_node_fs12 = require("node:fs");
8294
+ var import_node_path12 = require("node:path");
8295
+
8296
+ // src/file-lock.ts
8297
+ var import_promises2 = require("node:fs/promises");
8294
8298
  var import_node_path11 = require("node:path");
8299
+ var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
8300
+ var IMMEDIATE_RETRY_BUDGET = 3;
8301
+ var FileLockBusyError = class extends Error {
8302
+ lockPath;
8303
+ constructor(label, lockPath, maxWaitMs) {
8304
+ super(`${label} busy: ${lockPath} held longer than ${maxWaitMs}ms`);
8305
+ this.name = "FileLockBusyError";
8306
+ this.lockPath = lockPath;
8307
+ }
8308
+ };
8309
+ function resolveFileLockOpts(opts = {}) {
8310
+ return {
8311
+ staleMs: opts.staleMs ?? 3e4,
8312
+ retryMs: opts.retryMs ?? 50,
8313
+ maxWaitMs: opts.maxWaitMs ?? 5e3,
8314
+ label: opts.label ?? "file lock"
8315
+ };
8316
+ }
8317
+ async function acquireFileLock(lockPath, opts, deadline) {
8318
+ let immediateRetries = 0;
8319
+ for (; ; ) {
8320
+ let handle;
8321
+ try {
8322
+ handle = await (0, import_promises2.open)(lockPath, "wx");
8323
+ } catch (e) {
8324
+ const code = e.code;
8325
+ if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
8326
+ const retryNow = async () => {
8327
+ if (Date.now() >= deadline) throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
8328
+ if (++immediateRetries > IMMEDIATE_RETRY_BUDGET) await sleep(opts.retryMs);
8329
+ };
8330
+ try {
8331
+ const age = Date.now() - (await (0, import_promises2.stat)(lockPath)).mtimeMs;
8332
+ if (age > opts.staleMs) {
8333
+ try {
8334
+ await (0, import_promises2.unlink)(lockPath);
8335
+ await retryNow();
8336
+ continue;
8337
+ } catch (unlinkError) {
8338
+ if (unlinkError instanceof FileLockBusyError) throw unlinkError;
8339
+ const unlinkCode = unlinkError.code;
8340
+ if (unlinkCode === "ENOENT") {
8341
+ await retryNow();
8342
+ continue;
8343
+ }
8344
+ }
8345
+ }
8346
+ } catch (statError) {
8347
+ if (statError instanceof FileLockBusyError) throw statError;
8348
+ if (statError.code !== "ENOENT") throw statError;
8349
+ await retryNow();
8350
+ continue;
8351
+ }
8352
+ if (Date.now() >= deadline) {
8353
+ throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
8354
+ }
8355
+ await sleep(opts.retryMs);
8356
+ continue;
8357
+ }
8358
+ const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8359
+ try {
8360
+ await handle.writeFile(token);
8361
+ } catch (e) {
8362
+ await handle.close().catch(() => void 0);
8363
+ throw e;
8364
+ }
8365
+ return { token, handle };
8366
+ }
8367
+ }
8368
+ async function fileLockHeldBy(lockPath, token) {
8369
+ try {
8370
+ return await (0, import_promises2.readFile)(lockPath, "utf8") === token;
8371
+ } catch {
8372
+ return false;
8373
+ }
8374
+ }
8375
+ async function releaseFileLock(lockPath, guard) {
8376
+ await guard.handle.close().catch(() => void 0);
8377
+ if (await fileLockHeldBy(lockPath, guard.token)) {
8378
+ await (0, import_promises2.unlink)(lockPath).catch(() => void 0);
8379
+ }
8380
+ }
8381
+ async function withFileLock(lockPath, opts, fn) {
8382
+ const resolved = resolveFileLockOpts(opts);
8383
+ await (0, import_promises2.mkdir)((0, import_node_path11.dirname)(lockPath), { recursive: true }).catch(() => void 0);
8384
+ const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
8385
+ try {
8386
+ return await fn();
8387
+ } finally {
8388
+ await releaseFileLock(lockPath, guard);
8389
+ }
8390
+ }
8391
+
8392
+ // src/worktree.ts
8295
8393
  var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
8296
8394
  var PKG = "package.json";
8297
8395
  var LOCKFILE = "package-lock.json";
@@ -8337,7 +8435,7 @@ var realFsProbe = {
8337
8435
  }
8338
8436
  };
8339
8437
  function declaredProvision(fs2, abs) {
8340
- const raw = fs2.readFile?.((0, import_node_path11.join)(abs, PKG));
8438
+ const raw = fs2.readFile?.((0, import_node_path12.join)(abs, PKG));
8341
8439
  if (raw === void 0) return void 0;
8342
8440
  try {
8343
8441
  const scripts = JSON.parse(raw).scripts;
@@ -8349,13 +8447,13 @@ function declaredProvision(fs2, abs) {
8349
8447
  }
8350
8448
  function scanInstallDirs(root, fs2 = realFsProbe) {
8351
8449
  const factsFor = (dir) => {
8352
- const abs = dir ? (0, import_node_path11.join)(root, dir) : root;
8353
- const match = LOCKFILE_INSTALLS.find((c) => fs2.isFile((0, import_node_path11.join)(abs, c.lockfile)));
8354
- const hasPackageJson = fs2.isFile((0, import_node_path11.join)(abs, PKG));
8450
+ const abs = dir ? (0, import_node_path12.join)(root, dir) : root;
8451
+ const match = LOCKFILE_INSTALLS.find((c) => fs2.isFile((0, import_node_path12.join)(abs, c.lockfile)));
8452
+ const hasPackageJson = fs2.isFile((0, import_node_path12.join)(abs, PKG));
8355
8453
  return {
8356
8454
  dir,
8357
8455
  hasPackageJson,
8358
- hasNodeModules: fs2.isDir((0, import_node_path11.join)(abs, NODE_MODULES)),
8456
+ hasNodeModules: fs2.isDir((0, import_node_path12.join)(abs, NODE_MODULES)),
8359
8457
  install: match?.command,
8360
8458
  provision: hasPackageJson ? declaredProvision(fs2, abs) : void 0
8361
8459
  };
@@ -8371,7 +8469,7 @@ function npmInstallTargets(dirs) {
8371
8469
  }));
8372
8470
  }
8373
8471
  function isLinkedWorktree(root, fs2 = realFsProbe) {
8374
- return fs2.isFile((0, import_node_path11.join)(root, ".git"));
8472
+ return fs2.isFile((0, import_node_path12.join)(root, ".git"));
8375
8473
  }
8376
8474
  function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
8377
8475
  if (!isLinkedWorktree(root, fs2)) return null;
@@ -8381,7 +8479,7 @@ function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
8381
8479
  return `[worktree] provisioning tooling in the background (deps in ${where} + local config) \u2014 \`mmi-cli worktree setup\` to redo`;
8382
8480
  }
8383
8481
  function defaultCopyFile(from, to) {
8384
- (0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(to), { recursive: true });
8482
+ (0, import_node_fs12.mkdirSync)((0, import_node_path12.dirname)(to), { recursive: true });
8385
8483
  (0, import_node_fs12.copyFileSync)(from, to);
8386
8484
  }
8387
8485
  async function runDeclaredProvision(target, cwd, runInstall) {
@@ -8412,7 +8510,7 @@ async function provisionWorktree(worktreeRoot, deps) {
8412
8510
  const targets = npmInstallTargets(allDirs);
8413
8511
  if (deps.validateInstall) {
8414
8512
  for (const dir of allDirs.filter((d) => d.hasPackageJson && (d.provision ?? d.install) && d.hasNodeModules)) {
8415
- const cwd = dir.dir ? (0, import_node_path11.join)(worktreeRoot, dir.dir) : worktreeRoot;
8513
+ const cwd = dir.dir ? (0, import_node_path12.join)(worktreeRoot, dir.dir) : worktreeRoot;
8416
8514
  if (!await deps.validateInstall(cwd)) {
8417
8515
  targets.push({
8418
8516
  dir: dir.dir,
@@ -8426,7 +8524,7 @@ async function provisionWorktree(worktreeRoot, deps) {
8426
8524
  const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules && !targetDirs.has(d.dir)).map((d) => d.dir);
8427
8525
  const installed = [];
8428
8526
  for (const target of targets) {
8429
- const cwd = target.dir ? (0, import_node_path11.join)(worktreeRoot, target.dir) : worktreeRoot;
8527
+ const cwd = target.dir ? (0, import_node_path12.join)(worktreeRoot, target.dir) : worktreeRoot;
8430
8528
  log(`installing deps: ${target.command} in ${target.dir || "."}`);
8431
8529
  if (target.declared) await runDeclaredProvision(target, cwd, deps.runInstall);
8432
8530
  else await deps.runInstall(target.command, cwd);
@@ -8436,7 +8534,7 @@ async function provisionWorktree(worktreeRoot, deps) {
8436
8534
  const copySkipped = [];
8437
8535
  const primary = await deps.primaryCheckout();
8438
8536
  for (const rel of LOCAL_ONLY_FILES) {
8439
- const dest = (0, import_node_path11.join)(worktreeRoot, rel);
8537
+ const dest = (0, import_node_path12.join)(worktreeRoot, rel);
8440
8538
  if (fs2.isFile(dest)) {
8441
8539
  copySkipped.push({ file: rel, reason: "already-present" });
8442
8540
  continue;
@@ -8445,11 +8543,11 @@ async function provisionWorktree(worktreeRoot, deps) {
8445
8543
  copySkipped.push({ file: rel, reason: "no-primary" });
8446
8544
  continue;
8447
8545
  }
8448
- if (!fs2.isFile((0, import_node_path11.join)(primary, rel))) {
8546
+ if (!fs2.isFile((0, import_node_path12.join)(primary, rel))) {
8449
8547
  copySkipped.push({ file: rel, reason: "absent-in-primary" });
8450
8548
  continue;
8451
8549
  }
8452
- copyFile((0, import_node_path11.join)(primary, rel), dest);
8550
+ copyFile((0, import_node_path12.join)(primary, rel), dest);
8453
8551
  copied.push(rel);
8454
8552
  log(`copied local config: ${rel}`);
8455
8553
  }
@@ -8463,12 +8561,12 @@ function capWorktreeDirName(name, max = 40) {
8463
8561
  }
8464
8562
  function defaultWorktreePath(repoRoot2, branch) {
8465
8563
  const safe = capWorktreeDirName(branch.replace(/[/\\]+/g, "-"));
8466
- return (0, import_node_path11.join)((0, import_node_path11.dirname)(repoRoot2), "mmi-worktrees", (0, import_node_path11.basename)(repoRoot2), safe);
8564
+ return (0, import_node_path12.join)((0, import_node_path12.dirname)(repoRoot2), "mmi-worktrees", (0, import_node_path12.basename)(repoRoot2), safe);
8467
8565
  }
8468
8566
  async function primaryCheckoutRootOf(git2) {
8469
8567
  try {
8470
8568
  const out = (await git2(["rev-parse", "--path-format=absolute", "--git-common-dir"])).trim();
8471
- return out ? (0, import_node_path11.dirname)(out) : void 0;
8569
+ return out ? (0, import_node_path12.dirname)(out) : void 0;
8472
8570
  } catch {
8473
8571
  return void 0;
8474
8572
  }
@@ -8480,10 +8578,26 @@ function resolveWorktreeBase(from, remote) {
8480
8578
  if (SHA_LIKE_RE.test(from)) return { base: from };
8481
8579
  return { base: from, fetchBranch: from, preferRemote: `${remotePrefix}${from}` };
8482
8580
  }
8483
- var GIT_CONFIG_LOCK_RE = /could not lock config file|unable to write upstream branch configuration/i;
8581
+ var GIT_CONFIG_LOCK_RE = /could not lock config file|unable to write upstream branch configuration|unable to access ['"]?\.git\/config['"]?: Permission denied|unknown error occurred while reading the configuration files/i;
8484
8582
  function isGitConfigLockError(error) {
8485
8583
  return GIT_CONFIG_LOCK_RE.test(error instanceof Error ? error.message : String(error));
8486
8584
  }
8585
+ function worktreeAddLockPath(repoRoot2) {
8586
+ return repoRuntimeStatePath(repoRoot2, "worktree-add.lock");
8587
+ }
8588
+ var WORKTREE_ADD_LOCK_STALE_MS = 6e4;
8589
+ var WORKTREE_ADD_LOCK_MAX_WAIT_MS = 12e4;
8590
+ async function withWorktreeAddLock(repoRoot2, fn) {
8591
+ return withFileLock(
8592
+ worktreeAddLockPath(repoRoot2),
8593
+ {
8594
+ staleMs: WORKTREE_ADD_LOCK_STALE_MS,
8595
+ maxWaitMs: WORKTREE_ADD_LOCK_MAX_WAIT_MS,
8596
+ label: "worktree-add lock"
8597
+ },
8598
+ fn
8599
+ );
8600
+ }
8487
8601
  var ADD_WORKTREE_BACKOFF_MS = [100, 250, 500, 750, 1e3];
8488
8602
  var ADD_WORKTREE_MAX_ATTEMPTS = 6;
8489
8603
  async function cleanupOrphanBranch(branch, base, preExistingOid, deps) {
@@ -9251,12 +9365,12 @@ function planManagedGitignore(current) {
9251
9365
 
9252
9366
  // src/docs-index-command.ts
9253
9367
  var import_node_fs14 = require("node:fs");
9254
- var import_node_path13 = require("node:path");
9368
+ var import_node_path14 = require("node:path");
9255
9369
 
9256
9370
  // src/doc-refs-core.ts
9257
9371
  var import_node_child_process5 = require("node:child_process");
9258
9372
  var import_node_fs13 = require("node:fs");
9259
- var import_node_path12 = require("node:path");
9373
+ var import_node_path13 = require("node:path");
9260
9374
  var PIN_RE = /<!--\s*pinned by\s+([^:]+?)\s*:\s*"([^"]+)"[^"]*-->/;
9261
9375
  var PIN_MENTION_RE = /<!--\s*pinned by\b/;
9262
9376
  var FWD_RE = /<!--\s*forward-ref:\s*(\S+?)\s*-->/;
@@ -9315,7 +9429,7 @@ function checkPins(root, readFile9, docs2) {
9315
9429
  findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
9316
9430
  continue;
9317
9431
  }
9318
- const source = readFile9((0, import_node_path12.join)(root, pin.file));
9432
+ const source = readFile9((0, import_node_path13.join)(root, pin.file));
9319
9433
  if (source == null) {
9320
9434
  findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
9321
9435
  continue;
@@ -9377,11 +9491,11 @@ function checkRefs(root, deps, docs2) {
9377
9491
  for (const { ref } of extractRefs(markdown)) allFirstSegments.add(refFirstSegment(ref));
9378
9492
  }
9379
9493
  const tracked = allFirstSegments.size ? deps.trackedFirstSegments?.([...allFirstSegments]) ?? null : null;
9380
- const firstVerifiable = (first) => tracked ? tracked.has(first) : exists((0, import_node_path12.join)(root, first));
9494
+ const firstVerifiable = (first) => tracked ? tracked.has(first) : exists((0, import_node_path13.join)(root, first));
9381
9495
  const candidates = [];
9382
9496
  const direct = [];
9383
9497
  for (const [doc, markdown] of Object.entries(docs2)) {
9384
- const docDir = import_node_path12.posix.dirname(doc);
9498
+ const docDir = import_node_path13.posix.dirname(doc);
9385
9499
  const base = docDir === "." ? "" : docDir;
9386
9500
  const covered = /* @__PURE__ */ new Set();
9387
9501
  const markers = [];
@@ -9390,21 +9504,21 @@ function checkRefs(root, deps, docs2) {
9390
9504
  direct.push({ kind: "malformed-forward-ref", doc, line: fwd.line, detail: fwd.text });
9391
9505
  continue;
9392
9506
  }
9393
- const docRel = import_node_path12.posix.normalize(import_node_path12.posix.join(base, fwd.target));
9394
- const rootRel = import_node_path12.posix.normalize(fwd.target.replace(/^\/+/, ""));
9507
+ const docRel = import_node_path13.posix.normalize(import_node_path13.posix.join(base, fwd.target));
9508
+ const rootRel = import_node_path13.posix.normalize(fwd.target.replace(/^\/+/, ""));
9395
9509
  markers.push({ target: fwd.target, line: fwd.line, docRel, rootRel });
9396
9510
  covered.add(docRel);
9397
9511
  covered.add(rootRel);
9398
9512
  }
9399
9513
  const links = extractLinks(markdown).map(({ target, line }) => {
9400
- const resolved = import_node_path12.posix.normalize(import_node_path12.posix.join(base, target));
9401
- return { target, line, resolved, missing: !exists((0, import_node_path12.join)(root, resolved)) };
9514
+ const resolved = import_node_path13.posix.normalize(import_node_path13.posix.join(base, target));
9515
+ return { target, line, resolved, missing: !exists((0, import_node_path13.join)(root, resolved)) };
9402
9516
  });
9403
9517
  for (const marker of markers) {
9404
9518
  const coversMissing = links.some(
9405
9519
  (l) => l.missing && (l.resolved === marker.docRel || l.resolved === marker.rootRel)
9406
9520
  );
9407
- if (!coversMissing && (exists((0, import_node_path12.join)(root, marker.docRel)) || exists((0, import_node_path12.join)(root, marker.rootRel)))) {
9521
+ if (!coversMissing && (exists((0, import_node_path13.join)(root, marker.docRel)) || exists((0, import_node_path13.join)(root, marker.rootRel)))) {
9408
9522
  direct.push({
9409
9523
  kind: "stale-forward-ref",
9410
9524
  doc,
@@ -9415,7 +9529,7 @@ function checkRefs(root, deps, docs2) {
9415
9529
  }
9416
9530
  for (const { ref, line } of extractRefs(markdown)) {
9417
9531
  if (!firstVerifiable(refFirstSegment(ref))) continue;
9418
- if (!exists((0, import_node_path12.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
9532
+ if (!exists((0, import_node_path13.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
9419
9533
  }
9420
9534
  for (const { target, line, resolved, missing } of links) {
9421
9535
  if (resolved.startsWith("..")) {
@@ -9467,18 +9581,18 @@ function readFileOrNull(path2) {
9467
9581
  }
9468
9582
  function walk(dir, root, out) {
9469
9583
  for (const entry of (0, import_node_fs13.readdirSync)(dir)) {
9470
- const full = (0, import_node_path12.join)(dir, entry);
9584
+ const full = (0, import_node_path13.join)(dir, entry);
9471
9585
  if ((0, import_node_fs13.statSync)(full).isDirectory()) walk(full, root, out);
9472
9586
  else if (entry.endsWith(".md")) out.push(full.slice(root.length + 1).replaceAll("\\", "/"));
9473
9587
  }
9474
9588
  return out;
9475
9589
  }
9476
9590
  function defaultListDocs(root) {
9477
- const docsDir = (0, import_node_path12.join)(root, "docs");
9591
+ const docsDir = (0, import_node_path13.join)(root, "docs");
9478
9592
  const docs2 = ((0, import_node_fs13.existsSync)(docsDir) ? walk(docsDir, root, []) : []).filter(
9479
9593
  (rel) => !SKIP_WALK.some((skip) => rel.startsWith(skip))
9480
9594
  );
9481
- return [...ROOT_DOCS.filter((rel) => (0, import_node_fs13.existsSync)((0, import_node_path12.join)(root, rel))), ...docs2];
9595
+ return [...ROOT_DOCS.filter((rel) => (0, import_node_fs13.existsSync)((0, import_node_path13.join)(root, rel))), ...docs2];
9482
9596
  }
9483
9597
  var CHECK_IGNORE_MAX_BUFFER = 32 * 1024 * 1024;
9484
9598
  function defaultIsIgnored(root, relPaths, exec = import_node_child_process5.execFileSync) {
@@ -9538,7 +9652,7 @@ function runDocRefs(root, deps = {}) {
9538
9652
  const walked = listDocs(root);
9539
9653
  const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
9540
9654
  const docs2 = Object.fromEntries(
9541
- walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path12.join)(root, rel))]).filter(([, body]) => body != null)
9655
+ walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path13.join)(root, rel))]).filter(([, body]) => body != null)
9542
9656
  );
9543
9657
  const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs2);
9544
9658
  const findings = [
@@ -9646,19 +9760,19 @@ function walkMarkdown(dir) {
9646
9760
  while (stack.length) {
9647
9761
  const current = stack.pop();
9648
9762
  for (const entry of (0, import_node_fs14.readdirSync)(current, { withFileTypes: true })) {
9649
- const full = (0, import_node_path13.join)(current, entry.name);
9763
+ const full = (0, import_node_path14.join)(current, entry.name);
9650
9764
  if (entry.isDirectory()) {
9651
9765
  stack.push(full);
9652
9766
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
9653
- out.push((0, import_node_path13.relative)(dir, full).split(import_node_path13.sep).join("/"));
9767
+ out.push((0, import_node_path14.relative)(dir, full).split(import_node_path14.sep).join("/"));
9654
9768
  }
9655
9769
  }
9656
9770
  }
9657
9771
  return out;
9658
9772
  }
9659
9773
  function createDocsIndexDeps(repoRoot2) {
9660
- const docsDir = (0, import_node_path13.join)(repoRoot2, "docs");
9661
- const indexPath = (0, import_node_path13.join)(repoRoot2, DOCS_INDEX_PATH);
9774
+ const docsDir = (0, import_node_path14.join)(repoRoot2, "docs");
9775
+ const indexPath = (0, import_node_path14.join)(repoRoot2, DOCS_INDEX_PATH);
9662
9776
  return {
9663
9777
  listDocs: () => {
9664
9778
  if (!(0, import_node_fs14.existsSync)(docsDir)) return [];
@@ -9667,7 +9781,7 @@ function createDocsIndexDeps(repoRoot2) {
9667
9781
  const ignored = defaultIsIgnored(repoRoot2, walked.map((rel) => `docs/${rel}`));
9668
9782
  return walked.filter((rel) => !ignored.has(`docs/${rel}`)).sort();
9669
9783
  },
9670
- readDoc: (relPath) => (0, import_node_fs14.readFileSync)((0, import_node_path13.join)(docsDir, relPath), "utf8"),
9784
+ readDoc: (relPath) => (0, import_node_fs14.readFileSync)((0, import_node_path14.join)(docsDir, relPath), "utf8"),
9671
9785
  readIndex: () => (0, import_node_fs14.existsSync)(indexPath) ? (0, import_node_fs14.readFileSync)(indexPath, "utf8") : null,
9672
9786
  writeIndex: (content) => (0, import_node_fs14.writeFileSync)(indexPath, content, "utf8")
9673
9787
  };
@@ -10317,13 +10431,13 @@ function parseVerifyBroker(stdout) {
10317
10431
 
10318
10432
  // src/train-apply.ts
10319
10433
  var import_node_fs16 = require("node:fs");
10320
- var import_promises2 = require("node:fs/promises");
10321
- var import_node_path15 = require("node:path");
10434
+ var import_promises3 = require("node:fs/promises");
10435
+ var import_node_path16 = require("node:path");
10322
10436
 
10323
10437
  // src/plugin-guard-io.ts
10324
10438
  var import_node_fs15 = require("node:fs");
10325
10439
  var import_node_child_process6 = require("node:child_process");
10326
- var import_node_path14 = require("node:path");
10440
+ var import_node_path15 = require("node:path");
10327
10441
  var import_node_os4 = require("node:os");
10328
10442
  var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
10329
10443
 
@@ -10614,15 +10728,15 @@ function runHostBin(bin, args, opts) {
10614
10728
  return step ? execFileHard(file, argv, { ...shared, step }) : execFileP2(file, argv, shared);
10615
10729
  }
10616
10730
  function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os4.homedir)()) {
10617
- if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path14.join)(home, ".codex");
10618
- if (surface === "kimi") return env.KIMI_CODE_HOME?.trim() || (0, import_node_path14.join)(home, ".kimi-code");
10619
- if (surface === "kilo") return env.KILO_CONFIG_DIR?.trim() || (0, import_node_path14.join)(home, ".config", "kilo");
10620
- if (surface === "cursor") return (0, import_node_path14.join)(home, ".cursor");
10621
- if (surface === "jervcode") return env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path14.join)(home, ".pi", "agent");
10622
- return (0, import_node_path14.join)(home, ".claude");
10731
+ if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path15.join)(home, ".codex");
10732
+ if (surface === "kimi") return env.KIMI_CODE_HOME?.trim() || (0, import_node_path15.join)(home, ".kimi-code");
10733
+ if (surface === "kilo") return env.KILO_CONFIG_DIR?.trim() || (0, import_node_path15.join)(home, ".config", "kilo");
10734
+ if (surface === "cursor") return (0, import_node_path15.join)(home, ".cursor");
10735
+ if (surface === "jervcode") return env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path15.join)(home, ".pi", "agent");
10736
+ return (0, import_node_path15.join)(home, ".claude");
10623
10737
  }
10624
10738
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
10625
- return (0, import_node_path14.join)(surfaceConfigRoot(surface), "plugins", "installed_plugins.json");
10739
+ return (0, import_node_path15.join)(surfaceConfigRoot(surface), "plugins", "installed_plugins.json");
10626
10740
  };
10627
10741
  function readInstalledPlugins(surface = detectSurface(process.env)) {
10628
10742
  try {
@@ -10635,15 +10749,15 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
10635
10749
  if (surface === "codex") {
10636
10750
  const root = surfaceConfigRoot(surface, env, home);
10637
10751
  return [
10638
- (0, import_node_path14.join)(root, ".tmp", "marketplaces", CODEX_MARKETPLACE),
10639
- (0, import_node_path14.join)(root, "plugins", "marketplaces", CODEX_MARKETPLACE)
10752
+ (0, import_node_path15.join)(root, ".tmp", "marketplaces", CODEX_MARKETPLACE),
10753
+ (0, import_node_path15.join)(root, "plugins", "marketplaces", CODEX_MARKETPLACE)
10640
10754
  ];
10641
10755
  }
10642
10756
  if (surface === "kimi") return [];
10643
10757
  if (surface === "kilo") return [];
10644
10758
  if (surface === "cursor") return [];
10645
10759
  if (surface === "jervcode") return [];
10646
- return [(0, import_node_path14.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
10760
+ return [(0, import_node_path15.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
10647
10761
  }
10648
10762
  function marketplaceClonePresent(surface, home, exists = import_node_fs15.existsSync, env = process.env) {
10649
10763
  return marketplaceCloneCandidates(surface, home, env).some(exists);
@@ -10694,11 +10808,11 @@ function codexHookTrustState(status = codexPluginStatus()) {
10694
10808
  return { applicable: false, trusted: false, trustedCount: 0, requiredCount: 0 };
10695
10809
  }
10696
10810
  const root = surfaceConfigRoot("codex");
10697
- const hooksPath = (0, import_node_path14.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", status.version, "hooks", "codex-hooks.json");
10811
+ const hooksPath = (0, import_node_path15.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", status.version, "hooks", "codex-hooks.json");
10698
10812
  const requiredCount = countCodexHookCommands(hooksPath);
10699
10813
  let config = "";
10700
10814
  try {
10701
- config = (0, import_node_fs15.readFileSync)((0, import_node_path14.join)(root, "config.toml"), "utf8");
10815
+ config = (0, import_node_fs15.readFileSync)((0, import_node_path15.join)(root, "config.toml"), "utf8");
10702
10816
  } catch {
10703
10817
  return { applicable: true, trusted: false, trustedCount: 0, requiredCount };
10704
10818
  }
@@ -10734,9 +10848,9 @@ async function npmSelfUpdateCli(target, onStep) {
10734
10848
  }
10735
10849
  function kiloConfigListsPlugin(configRoot, home = (0, import_node_os4.homedir)(), read = (p) => (0, import_node_fs15.readFileSync)(p, "utf8"), exists = import_node_fs15.existsSync) {
10736
10850
  const candidates = ["kilo.json", "kilo.jsonc", "opencode.json", "opencode.jsonc", "config.json"];
10737
- for (const dir of [configRoot, (0, import_node_path14.join)(home, ".kilo")]) {
10851
+ for (const dir of [configRoot, (0, import_node_path15.join)(home, ".kilo")]) {
10738
10852
  for (const file of candidates) {
10739
- const path2 = (0, import_node_path14.join)(dir, file);
10853
+ const path2 = (0, import_node_path15.join)(dir, file);
10740
10854
  if (!exists(path2)) continue;
10741
10855
  try {
10742
10856
  const stripped = read(path2).replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
@@ -10753,7 +10867,7 @@ function kiloConfigListsPlugin(configRoot, home = (0, import_node_os4.homedir)()
10753
10867
  return false;
10754
10868
  }
10755
10869
  function cursorLocalPluginRoot(env = process.env, home = (0, import_node_os4.homedir)()) {
10756
- return (0, import_node_path14.join)(surfaceConfigRoot("cursor", env, home), "plugins", "local", "mmi");
10870
+ return (0, import_node_path15.join)(surfaceConfigRoot("cursor", env, home), "plugins", "local", "mmi");
10757
10871
  }
10758
10872
  function cursorPluginTreeHealthy(root, exists = import_node_fs15.existsSync) {
10759
10873
  return [
@@ -10762,14 +10876,14 @@ function cursorPluginTreeHealthy(root, exists = import_node_fs15.existsSync) {
10762
10876
  "hooks/cursor-hooks.json",
10763
10877
  "scripts/hook-run.mjs",
10764
10878
  "scripts/hook-policy.mjs"
10765
- ].every((path2) => exists((0, import_node_path14.join)(root, ...path2.split("/"))));
10879
+ ].every((path2) => exists((0, import_node_path15.join)(root, ...path2.split("/"))));
10766
10880
  }
10767
10881
  function kimiPluginTreeHealthy(root, exists = import_node_fs15.existsSync) {
10768
10882
  return [
10769
10883
  ".kimi-plugin/plugin.json",
10770
10884
  "skills/mmi/SKILL.md",
10771
10885
  "scripts/hook-run.mjs"
10772
- ].every((path2) => exists((0, import_node_path14.join)(root, ...path2.split("/"))));
10886
+ ].every((path2) => exists((0, import_node_path15.join)(root, ...path2.split("/"))));
10773
10887
  }
10774
10888
  var JERVCODE_WRAPPER_DIR = ".pi-plugin";
10775
10889
  function normalizePiEntry(value) {
@@ -10792,7 +10906,7 @@ function jervcodePackageFamily(entry) {
10792
10906
  function isMmiOwnedPiEntry(entry) {
10793
10907
  if (typeof entry !== "string") return false;
10794
10908
  try {
10795
- const pkg = JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(piEntryFsPath(entry), "package.json"), "utf8"));
10909
+ const pkg = JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path15.join)(piEntryFsPath(entry), "package.json"), "utf8"));
10796
10910
  return pkg.name === "mmi";
10797
10911
  } catch {
10798
10912
  return false;
@@ -10824,7 +10938,7 @@ function readPiSettings(path2) {
10824
10938
  }
10825
10939
  }
10826
10940
  function mmiPiWrapperEntry(env = process.env, home = (0, import_node_os4.homedir)()) {
10827
- const settings = readPiSettings((0, import_node_path14.join)(surfaceConfigRoot("jervcode", env, home), "settings.json"));
10941
+ const settings = readPiSettings((0, import_node_path15.join)(surfaceConfigRoot("jervcode", env, home), "settings.json"));
10828
10942
  const entries = Array.isArray(settings?.packages) ? settings.packages : [];
10829
10943
  for (const entry of entries) {
10830
10944
  if (typeof entry !== "string") continue;
@@ -10837,17 +10951,17 @@ function mmiPiWrapperEntry(env = process.env, home = (0, import_node_os4.homedir
10837
10951
  function mmiPiWrapperHealthy(entry) {
10838
10952
  if (!entry) return false;
10839
10953
  const wrapper = piEntryFsPath(entry);
10840
- return isMmiOwnedPiEntry(entry) && (0, import_node_fs15.existsSync)((0, import_node_path14.join)((0, import_node_path14.dirname)(wrapper), "skills", "mmi", "SKILL.md"));
10954
+ return isMmiOwnedPiEntry(entry) && (0, import_node_fs15.existsSync)((0, import_node_path15.join)((0, import_node_path15.dirname)(wrapper), "skills", "mmi", "SKILL.md"));
10841
10955
  }
10842
10956
  function findMmiPiSourceClone(home = (0, import_node_os4.homedir)()) {
10843
- const cacheRoot = (0, import_node_path14.join)(home, ".claude", "plugins", "cache", "mutmutco", "mmi");
10957
+ const cacheRoot = (0, import_node_path15.join)(home, ".claude", "plugins", "cache", "mutmutco", "mmi");
10844
10958
  let best = null;
10845
10959
  try {
10846
10960
  for (const entry of (0, import_node_fs15.readdirSync)(cacheRoot, { withFileTypes: true })) {
10847
10961
  if (!entry.isDirectory() || !/^\d+\.\d+\.\d+$/.test(entry.name)) continue;
10848
- if (!(0, import_node_fs15.existsSync)((0, import_node_path14.join)(cacheRoot, entry.name, JERVCODE_WRAPPER_DIR, "package.json"))) continue;
10962
+ if (!(0, import_node_fs15.existsSync)((0, import_node_path15.join)(cacheRoot, entry.name, JERVCODE_WRAPPER_DIR, "package.json"))) continue;
10849
10963
  if (!best || compareVersions(entry.name, best.version) > 0) {
10850
- best = { path: (0, import_node_path14.join)(cacheRoot, entry.name), version: entry.name };
10964
+ best = { path: (0, import_node_path15.join)(cacheRoot, entry.name), version: entry.name };
10851
10965
  }
10852
10966
  }
10853
10967
  } catch {
@@ -10885,7 +10999,7 @@ function healJervCodePackageRegistration(opts = {}) {
10885
10999
  return { available: true, ok: true, changed: false, version: null, detail: "skipped \u2014 no installed MMI clone carries the pi wrapper (install the Claude plugin first)" };
10886
11000
  }
10887
11001
  const packagePath = `${clone.path.replace(/\\/g, "/").replace(/\/+$/, "")}/${JERVCODE_WRAPPER_DIR}`;
10888
- const settingsPath2 = (0, import_node_path14.join)(agentDir, "settings.json");
11002
+ const settingsPath2 = (0, import_node_path15.join)(agentDir, "settings.json");
10889
11003
  const release = (0, import_node_fs15.existsSync)(settingsPath2) ? acquirePiSettingsLock(settingsPath2) : void 0;
10890
11004
  if ((0, import_node_fs15.existsSync)(settingsPath2) && !release) {
10891
11005
  return {
@@ -10915,7 +11029,7 @@ function healJervCodePackageRegistration(opts = {}) {
10915
11029
  }
10916
11030
  current.packages = merged.next;
10917
11031
  try {
10918
- (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(settingsPath2), { recursive: true });
11032
+ (0, import_node_fs15.mkdirSync)((0, import_node_path15.dirname)(settingsPath2), { recursive: true });
10919
11033
  const tmp = `${settingsPath2}.tmp-${process.pid}`;
10920
11034
  (0, import_node_fs15.writeFileSync)(tmp, `${JSON.stringify(current, null, 2)}
10921
11035
  `, "utf8");
@@ -10948,7 +11062,7 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
10948
11062
  return {
10949
11063
  isOrgRepo,
10950
11064
  installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()) || // Kimi's managed plugin directory is its native install record; it has no Claude-style ledger.
10951
- surface === "kimi" && (0, import_node_fs15.existsSync)((0, import_node_path14.join)(root, "plugins", "managed", "mmi")) || // kilo-p1: the install record is the config file itself.
11065
+ surface === "kimi" && (0, import_node_fs15.existsSync)((0, import_node_path15.join)(root, "plugins", "managed", "mmi")) || // kilo-p1: the install record is the config file itself.
10952
11066
  surface === "kilo" && kiloConfigListsPlugin(root) || // #4188: jervcode's install record is the settings-file packages[] entry itself.
10953
11067
  surface === "jervcode" && piEntry !== null || surface === "cursor" && (0, import_node_fs15.existsSync)(cursorLocalPluginRoot()),
10954
11068
  // Kilo has no marketplace to clone — the config file IS the install record, so this dimension of
@@ -10957,9 +11071,9 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
10957
11071
  // Kimi keeps no plugin cache dir — installs are copied to plugins/managed/<id> and run from there.
10958
11072
  // Kilo (kilo-p1) keeps no cache dir either: the plugin's server() provisions ~/.kilo behind the
10959
11073
  // version stamp, so the stamp's presence is the cache signal.
10960
- pluginCachePresent: surface === "jervcode" ? mmiPiWrapperHealthy(piEntry) : surface === "kilo" ? (0, import_node_fs15.existsSync)((0, import_node_path14.join)((0, import_node_os4.homedir)(), ".kilo", ".mmi-kilo-version")) : surface === "kimi" ? kimiPluginTreeHealthy((0, import_node_path14.join)(root, "plugins", "managed", "mmi")) : surface === "cursor" ? cursorPluginTreeHealthy(cursorLocalPluginRoot()) : surface === "codex" ? Boolean(
10961
- codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs15.existsSync)((0, import_node_path14.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
10962
- ) : (0, import_node_fs15.existsSync)((0, import_node_path14.join)(root, "plugins", "cache", "mutmutco", "mmi"))
11074
+ pluginCachePresent: surface === "jervcode" ? mmiPiWrapperHealthy(piEntry) : surface === "kilo" ? (0, import_node_fs15.existsSync)((0, import_node_path15.join)((0, import_node_os4.homedir)(), ".kilo", ".mmi-kilo-version")) : surface === "kimi" ? kimiPluginTreeHealthy((0, import_node_path15.join)(root, "plugins", "managed", "mmi")) : surface === "cursor" ? cursorPluginTreeHealthy(cursorLocalPluginRoot()) : surface === "codex" ? Boolean(
11075
+ codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs15.existsSync)((0, import_node_path15.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
11076
+ ) : (0, import_node_fs15.existsSync)((0, import_node_path15.join)(root, "plugins", "cache", "mutmutco", "mmi"))
10963
11077
  };
10964
11078
  }
10965
11079
  async function runHostBinLogged(bin, args, opts) {
@@ -10979,9 +11093,9 @@ async function runPluginCli(bin, args, log) {
10979
11093
  function captureCodexHookLauncher() {
10980
11094
  const status = codexPluginStatus();
10981
11095
  if (!status.installed || !status.enabled || !status.version) return void 0;
10982
- const root = (0, import_node_path14.join)(surfaceConfigRoot("codex"), "plugins", "cache", CODEX_MARKETPLACE, "mmi", status.version);
11096
+ const root = (0, import_node_path15.join)(surfaceConfigRoot("codex"), "plugins", "cache", CODEX_MARKETPLACE, "mmi", status.version);
10983
11097
  const files = ["mmi-hook", "mmi-hook.exe"].flatMap((name) => {
10984
- const path2 = (0, import_node_path14.join)(root, "bin", name);
11098
+ const path2 = (0, import_node_path15.join)(root, "bin", name);
10985
11099
  try {
10986
11100
  return [{ name, content: (0, import_node_fs15.readFileSync)(path2) }];
10987
11101
  } catch {
@@ -10991,11 +11105,11 @@ function captureCodexHookLauncher() {
10991
11105
  return files.length === 2 ? { root, files } : void 0;
10992
11106
  }
10993
11107
  function restoreCodexHookLauncher(snapshot) {
10994
- if (!snapshot || (0, import_node_fs15.existsSync)((0, import_node_path14.join)(snapshot.root, "scripts", "hook-run.mjs"))) return false;
10995
- const bin = (0, import_node_path14.join)(snapshot.root, "bin");
11108
+ if (!snapshot || (0, import_node_fs15.existsSync)((0, import_node_path15.join)(snapshot.root, "scripts", "hook-run.mjs"))) return false;
11109
+ const bin = (0, import_node_path15.join)(snapshot.root, "bin");
10996
11110
  (0, import_node_fs15.mkdirSync)(bin, { recursive: true });
10997
11111
  for (const file of snapshot.files) {
10998
- const path2 = (0, import_node_path14.join)(bin, file.name);
11112
+ const path2 = (0, import_node_path15.join)(bin, file.name);
10999
11113
  (0, import_node_fs15.writeFileSync)(path2, file.content);
11000
11114
  if (file.name === "mmi-hook") (0, import_node_fs15.chmodSync)(path2, 493);
11001
11115
  }
@@ -11006,8 +11120,8 @@ function canonicalCursorRemote(remote) {
11006
11120
  }
11007
11121
  async function installCursorPluginCheckout(env = process.env) {
11008
11122
  const configRoot = surfaceConfigRoot("cursor", env);
11009
- const pluginsRoot = (0, import_node_path14.join)(configRoot, "plugins");
11010
- const target = (0, import_node_path14.join)(pluginsRoot, "local", "mmi");
11123
+ const pluginsRoot = (0, import_node_path15.join)(configRoot, "plugins");
11124
+ const target = (0, import_node_path15.join)(pluginsRoot, "local", "mmi");
11011
11125
  const source = env.MMI_CURSOR_PLUGIN_SOURCE?.trim();
11012
11126
  if ((0, import_node_fs15.existsSync)(target) && !source) {
11013
11127
  try {
@@ -11019,12 +11133,12 @@ async function installCursorPluginCheckout(env = process.env) {
11019
11133
  return { ok: false, detail: `refused to replace unmanaged Cursor plugin directory at ${target}` };
11020
11134
  }
11021
11135
  }
11022
- (0, import_node_fs15.mkdirSync)((0, import_node_path14.join)(pluginsRoot, "local"), { recursive: true });
11023
- (0, import_node_fs15.mkdirSync)((0, import_node_path14.join)(pluginsRoot, "staging"), { recursive: true });
11024
- (0, import_node_fs15.mkdirSync)((0, import_node_path14.join)(pluginsRoot, "quarantine"), { recursive: true });
11136
+ (0, import_node_fs15.mkdirSync)((0, import_node_path15.join)(pluginsRoot, "local"), { recursive: true });
11137
+ (0, import_node_fs15.mkdirSync)((0, import_node_path15.join)(pluginsRoot, "staging"), { recursive: true });
11138
+ (0, import_node_fs15.mkdirSync)((0, import_node_path15.join)(pluginsRoot, "quarantine"), { recursive: true });
11025
11139
  const suffix = `${Date.now()}-${process.pid}`;
11026
- const staged = (0, import_node_path14.join)(pluginsRoot, "staging", `mmi-${suffix}`);
11027
- const quarantined = (0, import_node_path14.join)(pluginsRoot, "quarantine", `mmi-${suffix}`);
11140
+ const staged = (0, import_node_path15.join)(pluginsRoot, "staging", `mmi-${suffix}`);
11141
+ const quarantined = (0, import_node_path15.join)(pluginsRoot, "quarantine", `mmi-${suffix}`);
11028
11142
  try {
11029
11143
  if (source) {
11030
11144
  (0, import_node_fs15.cpSync)(source, staged, {
@@ -11107,7 +11221,7 @@ async function applyPluginHeal(surface, log, opts) {
11107
11221
  const refSupported = await marketplaceAddRefSupported(bin);
11108
11222
  const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
11109
11223
  log(healBannerLine(bin, token, refSupported));
11110
- const pinsPath = (0, import_node_path14.join)((0, import_node_os4.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
11224
+ const pinsPath = (0, import_node_path15.join)((0, import_node_os4.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
11111
11225
  const pins = token === "claude" ? captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]) : /* @__PURE__ */ new Map();
11112
11226
  try {
11113
11227
  for (const step of steps) {
@@ -11224,7 +11338,7 @@ function restoreMarketplacePinsOnDisk(path2, pins, hostIsRunning = claudeCodeIsR
11224
11338
  return writeMarketplacePinsOnDisk(
11225
11339
  path2,
11226
11340
  pins,
11227
- (restored) => hostIsRunning() ? `${restoredPinsLine(restored)}, but Claude Code is running and can rewrite this file from its own copy \u2014 restart it, then \`mmi-cli doctor --apply\` if the pins did not survive` : restoredPinsLine(restored),
11341
+ (restored) => hostIsRunning() ? `${restoredPinsLine(restored)}, but Claude Code is running and can rewrite this file from its own copy \u2014 restart it, then \`mmi-cli doctor\` if the pins did not survive` : restoredPinsLine(restored),
11228
11342
  "restore"
11229
11343
  );
11230
11344
  }
@@ -11238,13 +11352,13 @@ function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunni
11238
11352
  return `pinned ${[...pins.keys()].join(", ")} to ${ORG_MARKETPLACE_PINS.ref} with auto-update on`;
11239
11353
  },
11240
11354
  "pin",
11241
- () => hostIsRunning() ? "not pinned \u2014 Claude Code is running and rewrites this registration from its own copy; quit it, then run `mmi-cli doctor --apply`" : void 0
11355
+ () => hostIsRunning() ? "not pinned \u2014 Claude Code is running and rewrites this registration from its own copy; quit it, then run `mmi-cli doctor`" : void 0
11242
11356
  );
11243
11357
  return detail === void 0 ? void 0 : { detail, wrote: landed };
11244
11358
  }
11245
11359
  function writeMarketplacePinPending(path2, names, now = Date.now()) {
11246
11360
  try {
11247
- (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(path2), { recursive: true });
11361
+ (0, import_node_fs15.mkdirSync)((0, import_node_path15.dirname)(path2), { recursive: true });
11248
11362
  (0, import_node_fs15.writeFileSync)(path2, `${JSON.stringify({ v: 1, names: [...names], at: new Date(now).toISOString() })}
11249
11363
  `, "utf8");
11250
11364
  } catch {
@@ -11388,6 +11502,30 @@ async function resolveFoldPaths(deps, model) {
11388
11502
  }
11389
11503
  return ["package.json", "package-lock.json"];
11390
11504
  }
11505
+ async function alignExistingHotfixReleaseMarker(deps, version) {
11506
+ const script = [
11507
+ "const fs = require('fs');",
11508
+ "const p = 'package.json';",
11509
+ "if (!fs.existsSync(p)) { process.stdout.write('absent'); process.exit(0); }",
11510
+ "const j = JSON.parse(fs.readFileSync(p, 'utf8'));",
11511
+ "const marker = j.jervaiseRelease;",
11512
+ "const hasHotfixMarker = !!(marker && typeof marker === 'object' && marker.kind === 'hotfix');",
11513
+ "const requiresMarker = fs.existsSync('scripts/check-release-kind.mjs');",
11514
+ "if (!hasHotfixMarker && !requiresMarker) {",
11515
+ " process.stdout.write('skip');",
11516
+ " process.exit(0);",
11517
+ "}",
11518
+ `const version = ${JSON.stringify(version)};`,
11519
+ "if (hasHotfixMarker && marker.version === version) { process.stdout.write('ok'); process.exit(0); }",
11520
+ "j.jervaiseRelease = { kind: 'hotfix', version };",
11521
+ "fs.writeFileSync(p, JSON.stringify(j, null, 2) + '\\n');",
11522
+ "process.stdout.write(hasHotfixMarker ? 'updated' : 'stamped');"
11523
+ ].join("");
11524
+ const out = (await deps.run("node", ["-e", script])).trim();
11525
+ if (out === "updated") return `jervaiseRelease hotfix marker aligned to ${version}`;
11526
+ if (out === "stamped") return `jervaiseRelease hotfix marker stamped for ${version}`;
11527
+ return null;
11528
+ }
11391
11529
  async function installAppFoldDeps(deps) {
11392
11530
  const hasLock = await deps.run("git", ["cat-file", "-e", "HEAD:package-lock.json"]).then(() => true).catch(() => false);
11393
11531
  await deps.run("npm", hasLock ? ["ci"] : ["install"]);
@@ -11400,6 +11538,10 @@ async function foldReleaseVersion(deps, model, tag, foldPaths, sourceCommit = "H
11400
11538
  } else {
11401
11539
  await installAppFoldDeps(deps);
11402
11540
  await deps.run("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"]);
11541
+ const patch = Number(version.split(".")[2] ?? "0");
11542
+ if (model === "registry-publish" && Number.isFinite(patch) && patch > 0) {
11543
+ await alignExistingHotfixReleaseMarker(deps, version);
11544
+ }
11403
11545
  }
11404
11546
  for (const path2 of foldPaths) {
11405
11547
  await deps.run("git", ["add", "--", path2]).catch(() => void 0);
@@ -12356,7 +12498,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
12356
12498
  return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
12357
12499
  }
12358
12500
  function readLocalGateWorkflows() {
12359
- const dir = (0, import_node_path15.join)(".github", "workflows");
12501
+ const dir = (0, import_node_path16.join)(".github", "workflows");
12360
12502
  let names;
12361
12503
  try {
12362
12504
  names = (0, import_node_fs16.readdirSync)(dir);
@@ -12366,7 +12508,7 @@ function readLocalGateWorkflows() {
12366
12508
  const files = [];
12367
12509
  for (const name of names.filter(isGateWorkflowPath)) {
12368
12510
  try {
12369
- files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs16.readFileSync)((0, import_node_path15.join)(dir, name), "utf8") });
12511
+ files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs16.readFileSync)((0, import_node_path16.join)(dir, name), "utf8") });
12370
12512
  } catch {
12371
12513
  }
12372
12514
  }
@@ -12491,7 +12633,7 @@ async function waitForFoldIndexLock(deps, startBranch, resumeCommand) {
12491
12633
  const deadline = now() + GIT_INDEX_LOCK_WAIT_MS;
12492
12634
  for (; ; ) {
12493
12635
  try {
12494
- await (0, import_promises2.stat)(lockPath);
12636
+ await (0, import_promises3.stat)(lockPath);
12495
12637
  } catch (e) {
12496
12638
  if (e.code === "ENOENT") return;
12497
12639
  throw foldFailureGuidance(
@@ -14473,7 +14615,7 @@ var import_node_fs18 = require("node:fs");
14473
14615
  // src/stage-runner.ts
14474
14616
  var import_node_child_process7 = require("node:child_process");
14475
14617
  var import_node_fs17 = require("node:fs");
14476
- var import_node_path16 = require("node:path");
14618
+ var import_node_path17 = require("node:path");
14477
14619
  var import_node_net = require("node:net");
14478
14620
  var import_node_util5 = require("node:util");
14479
14621
 
@@ -14612,11 +14754,11 @@ function appendForceRecreate(up) {
14612
14754
  return `${up.trimEnd()} --force-recreate`;
14613
14755
  }
14614
14756
  function stageStatePath(cwd = process.cwd()) {
14615
- return (0, import_node_path16.join)(cwd, "tmp", "stage", "state.json");
14757
+ return (0, import_node_path17.join)(cwd, "tmp", "stage", "state.json");
14616
14758
  }
14617
14759
  function stageGlobalStatePath(cwd = process.cwd(), gitCommonDir = ".git") {
14618
- const dir = (0, import_node_path16.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path16.resolve)(cwd, gitCommonDir);
14619
- return (0, import_node_path16.join)(dir, "mmi", "stage", "state.json");
14760
+ const dir = (0, import_node_path17.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path17.resolve)(cwd, gitCommonDir);
14761
+ return (0, import_node_path17.join)(dir, "mmi", "stage", "state.json");
14620
14762
  }
14621
14763
  function normPath2(path2) {
14622
14764
  return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -14840,8 +14982,8 @@ function stageProcessEnv(stagePort, extraEnv) {
14840
14982
  }
14841
14983
  async function ensureStageRuntimeEnv(config, opts, cwd) {
14842
14984
  if (!config.ensureEnv) return;
14843
- const target = (0, import_node_path16.join)(cwd, config.ensureEnv.target);
14844
- const example = (0, import_node_path16.join)(cwd, config.ensureEnv.example);
14985
+ const target = (0, import_node_path17.join)(cwd, config.ensureEnv.target);
14986
+ const example = (0, import_node_path17.join)(cwd, config.ensureEnv.example);
14845
14987
  if (!(0, import_node_fs17.existsSync)(target) && (0, import_node_fs17.existsSync)(example)) {
14846
14988
  (0, import_node_fs17.copyFileSync)(example, target);
14847
14989
  } else if ((0, import_node_fs17.existsSync)(target) && (0, import_node_fs17.existsSync)(example)) {
@@ -15212,7 +15354,7 @@ var import_node_os17 = require("node:os");
15212
15354
  var import_node_child_process9 = require("node:child_process");
15213
15355
  var import_node_fs21 = require("node:fs");
15214
15356
  var import_node_os7 = require("node:os");
15215
- var import_node_path19 = require("node:path");
15357
+ var import_node_path20 = require("node:path");
15216
15358
  var import_node_util6 = require("node:util");
15217
15359
 
15218
15360
  // src/board-priority.ts
@@ -15567,7 +15709,7 @@ function boardConfigFromProject(meta, floor = {}) {
15567
15709
 
15568
15710
  // src/cli-doctor-shared.ts
15569
15711
  var import_node_fs19 = require("node:fs");
15570
- var import_node_path18 = require("node:path");
15712
+ var import_node_path19 = require("node:path");
15571
15713
  var import_node_fs20 = require("node:fs");
15572
15714
 
15573
15715
  // src/readiness-audit.ts
@@ -15688,9 +15830,9 @@ var import_node_child_process8 = require("node:child_process");
15688
15830
  var import_node_os6 = require("node:os");
15689
15831
 
15690
15832
  // src/gh-create.ts
15691
- var import_promises3 = require("node:fs/promises");
15833
+ var import_promises4 = require("node:fs/promises");
15692
15834
  var import_node_os5 = require("node:os");
15693
- var import_node_path17 = require("node:path");
15835
+ var import_node_path18 = require("node:path");
15694
15836
  var import_node_crypto3 = require("node:crypto");
15695
15837
  var ISSUE_TYPES = ["bug", "feature", "task"];
15696
15838
  var GH_MUTATION_TIMEOUT_MS = 12e4;
@@ -15740,12 +15882,12 @@ async function bodyArgsViaFile(args, deps = {}) {
15740
15882
  const i = args.indexOf("--body");
15741
15883
  if (i === -1 || i + 1 >= args.length) return { args, cleanup: async () => {
15742
15884
  } };
15743
- const write = deps.write ?? import_promises3.writeFile;
15744
- const remove2 = deps.remove ?? import_promises3.unlink;
15745
- const ensureDir = deps.ensureDir ?? import_promises3.mkdir;
15885
+ const write = deps.write ?? import_promises4.writeFile;
15886
+ const remove2 = deps.remove ?? import_promises4.unlink;
15887
+ const ensureDir = deps.ensureDir ?? import_promises4.mkdir;
15746
15888
  const dir = deps.dir ?? (0, import_node_os5.tmpdir)();
15747
- const file = (0, import_node_path17.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
15748
- await ensureDir((0, import_node_path17.dirname)(file), { recursive: true }).catch(() => {
15889
+ const file = (0, import_node_path18.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
15890
+ await ensureDir((0, import_node_path18.dirname)(file), { recursive: true }).catch(() => {
15749
15891
  });
15750
15892
  await write(file, args[i + 1], "utf8");
15751
15893
  return {
@@ -17269,7 +17411,7 @@ async function localBranchHeads() {
17269
17411
  }
17270
17412
  async function currentRepoWorktreeGitRoot(repoRoot2) {
17271
17413
  const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
17272
- return gitCommonDir ? (0, import_node_path18.resolve)(repoRoot2, gitCommonDir, "worktrees") : "";
17414
+ return gitCommonDir ? (0, import_node_path19.resolve)(repoRoot2, gitCommonDir, "worktrees") : "";
17273
17415
  }
17274
17416
  async function worktreeBranches() {
17275
17417
  const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
@@ -17289,7 +17431,7 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
17289
17431
  const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
17290
17432
  if (!match?.[1]) return void 0;
17291
17433
  const raw = match[1].trim();
17292
- return (0, import_node_path18.isAbsolute)(raw) ? raw : (0, import_node_path18.resolve)(worktreePath, raw);
17434
+ return (0, import_node_path19.isAbsolute)(raw) ? raw : (0, import_node_path19.resolve)(worktreePath, raw);
17293
17435
  }
17294
17436
  function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
17295
17437
  if (!worktreeGitRoot) return false;
@@ -17298,9 +17440,9 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
17298
17440
  for (const ent of entries) {
17299
17441
  if (!ent.isDirectory()) continue;
17300
17442
  try {
17301
- const gitdirPath = (0, import_node_fs19.readFileSync)((0, import_node_path18.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
17302
- const resolvedGitdir = (0, import_node_path18.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path18.resolve)(worktreeGitRoot, ent.name, gitdirPath);
17303
- if (sameWorktreeMetadataPath((0, import_node_path18.dirname)(resolvedGitdir), worktreePath)) return true;
17443
+ const gitdirPath = (0, import_node_fs19.readFileSync)((0, import_node_path19.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
17444
+ const resolvedGitdir = (0, import_node_path19.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path19.resolve)(worktreeGitRoot, ent.name, gitdirPath);
17445
+ if (sameWorktreeMetadataPath((0, import_node_path19.dirname)(resolvedGitdir), worktreePath)) return true;
17304
17446
  } catch {
17305
17447
  }
17306
17448
  }
@@ -17319,7 +17461,7 @@ function pathExistsKnown(path2) {
17319
17461
  }
17320
17462
  }
17321
17463
  function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
17322
- const gitPath = (0, import_node_path18.join)(path2, ".git");
17464
+ const gitPath = (0, import_node_path19.join)(path2, ".git");
17323
17465
  let st;
17324
17466
  try {
17325
17467
  st = (0, import_node_fs20.lstatSync)(gitPath);
@@ -17373,7 +17515,7 @@ async function preservedBranches() {
17373
17515
  async function siblingWorktreeDirs(explicitRoot) {
17374
17516
  const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
17375
17517
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
17376
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path18.dirname)((0, import_node_path18.dirname)(worktreeGitRoot)) : repoRoot2;
17518
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path19.dirname)((0, import_node_path19.dirname)(worktreeGitRoot)) : repoRoot2;
17377
17519
  try {
17378
17520
  const dirs = explicitRoot ? listDirsIn(resolveExplicitScanRoot(explicitRoot, primaryRepoRoot)) : worktreeScanDirs(siblingMmiWorktreesRoot(primaryRepoRoot), primaryRepoRoot, listDirsIn, isRepoCheckoutDir);
17379
17521
  return dirs.map((dir) => inspectSiblingWorktreeDir(dir, worktreeGitRoot)).filter((entry) => Boolean(entry));
@@ -17383,13 +17525,13 @@ async function siblingWorktreeDirs(explicitRoot) {
17383
17525
  }
17384
17526
  function listDirsIn(dir) {
17385
17527
  try {
17386
- return (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => (0, import_node_path18.join)(dir, ent.name));
17528
+ return (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => (0, import_node_path19.join)(dir, ent.name));
17387
17529
  } catch {
17388
17530
  return [];
17389
17531
  }
17390
17532
  }
17391
17533
  function isRepoCheckoutDir(dir) {
17392
- return (0, import_node_fs20.existsSync)((0, import_node_path18.join)(dir, ".git"));
17534
+ return (0, import_node_fs20.existsSync)((0, import_node_path19.join)(dir, ".git"));
17393
17535
  }
17394
17536
  function resolveExplicitScanRoot(explicitRoot, repoRoot2) {
17395
17537
  let rootDirs;
@@ -18751,14 +18893,14 @@ function probeLocalClaimSession(marker, now = Date.now()) {
18751
18893
  claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
18752
18894
  return state;
18753
18895
  };
18754
- const root = (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".claude", "projects");
18896
+ const root = (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".claude", "projects");
18755
18897
  try {
18756
18898
  const wanted = `${marker.session}.jsonl`.toLowerCase();
18757
18899
  const pending = [root];
18758
18900
  while (pending.length) {
18759
18901
  const dir = pending.pop();
18760
18902
  for (const entry of (0, import_node_fs21.readdirSync)(dir, { withFileTypes: true })) {
18761
- const path2 = (0, import_node_path19.join)(dir, entry.name);
18903
+ const path2 = (0, import_node_path20.join)(dir, entry.name);
18762
18904
  if (entry.isDirectory()) pending.push(path2);
18763
18905
  else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
18764
18906
  return remember(now - (0, import_node_fs21.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
@@ -19578,7 +19720,7 @@ function consolidateCommandNamespaces(program3) {
19578
19720
 
19579
19721
  // src/pi-plugin-registration.ts
19580
19722
  var import_node_fs22 = require("node:fs");
19581
- var import_node_path20 = require("node:path");
19723
+ var import_node_path21 = require("node:path");
19582
19724
 
19583
19725
  // src/plugin-cache-prune.ts
19584
19726
  var PLUGIN_CACHE_KEEP = 2;
@@ -19837,26 +19979,26 @@ function newestExistingPiPlugin(home) {
19837
19979
  return void 0;
19838
19980
  }
19839
19981
  for (const version of names.filter(isVersionDirName).sort((a, b) => compareVersions(b, a))) {
19840
- const candidate = (0, import_node_path20.join)(cacheRoot, version, ".pi-plugin");
19982
+ const candidate = (0, import_node_path21.join)(cacheRoot, version, ".pi-plugin");
19841
19983
  if ((0, import_node_fs22.existsSync)(candidate)) return candidate;
19842
19984
  }
19843
19985
  return void 0;
19844
19986
  }
19845
19987
  function expectedPiPluginPath(home, env, installedVersion) {
19846
19988
  const root = env.CLAUDE_PLUGIN_ROOT?.trim();
19847
- if (root && /[\\/]mutmutco[\\/]mmi[\\/]/.test(root)) return (0, import_node_path20.join)(root, ".pi-plugin");
19989
+ if (root && /[\\/]mutmutco[\\/]mmi[\\/]/.test(root)) return (0, import_node_path21.join)(root, ".pi-plugin");
19848
19990
  const version = installedVersion ?? runningPluginVersion(env);
19849
19991
  if (version) {
19850
- const pinned = (0, import_node_path20.join)(home, ".claude", "plugins", "cache", "mutmutco", "mmi", version, ".pi-plugin");
19992
+ const pinned = (0, import_node_path21.join)(home, ".claude", "plugins", "cache", "mutmutco", "mmi", version, ".pi-plugin");
19851
19993
  if ((0, import_node_fs22.existsSync)(pinned)) return pinned;
19852
19994
  }
19853
19995
  return newestExistingPiPlugin(home);
19854
19996
  }
19855
19997
  function settingsPath(home) {
19856
- return (0, import_node_path20.join)(home, ".pi", "agent", "settings.json");
19998
+ return (0, import_node_path21.join)(home, ".pi", "agent", "settings.json");
19857
19999
  }
19858
20000
  function readPiPluginState(home, env, installedVersion) {
19859
- if (!(0, import_node_fs22.existsSync)((0, import_node_path20.join)(home, ".pi", "agent"))) return void 0;
20001
+ if (!(0, import_node_fs22.existsSync)((0, import_node_path21.join)(home, ".pi", "agent"))) return void 0;
19860
20002
  const expectedPath = expectedPiPluginPath(home, env, installedVersion);
19861
20003
  if (!expectedPath) return void 0;
19862
20004
  const file = settingsPath(home);
@@ -21481,7 +21623,7 @@ function renderAccessReport(report) {
21481
21623
  var import_node_crypto4 = require("node:crypto");
21482
21624
  var import_node_child_process12 = require("node:child_process");
21483
21625
  var import_node_fs23 = require("node:fs");
21484
- var import_node_path21 = require("node:path");
21626
+ var import_node_path22 = require("node:path");
21485
21627
  var REPO_INDEX_SCHEMA = 1;
21486
21628
  var HARD_DENY = [
21487
21629
  /(^|\/)\.env(\.|$)/i,
@@ -21606,7 +21748,7 @@ function loadReadmeHints(cwd, candidatePaths) {
21606
21748
  }
21607
21749
  for (const rel of readmes) {
21608
21750
  if (isHardDeniedPath(rel)) continue;
21609
- const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
21751
+ const abs = (0, import_node_path22.join)(cwd, ...rel.split("/"));
21610
21752
  if (!(0, import_node_fs23.existsSync)(abs)) continue;
21611
21753
  let text;
21612
21754
  try {
@@ -21623,7 +21765,7 @@ function loadReadmeHints(cwd, candidatePaths) {
21623
21765
  return hints;
21624
21766
  }
21625
21767
  function toPosix(p) {
21626
- return p.split(import_node_path21.sep).join("/");
21768
+ return p.split(import_node_path22.sep).join("/");
21627
21769
  }
21628
21770
  function listCandidatePaths(cwd, exec = import_node_child_process12.execFileSync) {
21629
21771
  try {
@@ -21645,7 +21787,7 @@ function rebuildRepoIndex(cwd, repoSlug2) {
21645
21787
  for (const rel of candidates) {
21646
21788
  if (ignored.has(rel)) continue;
21647
21789
  if (isHardDeniedPath(rel)) continue;
21648
- const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
21790
+ const abs = (0, import_node_path22.join)(cwd, ...rel.split("/"));
21649
21791
  if (!(0, import_node_fs23.existsSync)(abs)) continue;
21650
21792
  let text;
21651
21793
  try {
@@ -21674,7 +21816,7 @@ function rebuildRepoIndex(cwd, repoSlug2) {
21674
21816
  entries
21675
21817
  };
21676
21818
  const store = repoIndexStorePath(cwd);
21677
- (0, import_node_fs23.mkdirSync)((0, import_node_path21.dirname)(store), { recursive: true });
21819
+ (0, import_node_fs23.mkdirSync)((0, import_node_path22.dirname)(store), { recursive: true });
21678
21820
  (0, import_node_fs23.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
21679
21821
  `, "utf8");
21680
21822
  return projection;
@@ -21755,7 +21897,7 @@ function inferRepoSlug(cwd, exec = import_node_child_process12.execFileSync) {
21755
21897
  if (m?.[1]) return m[1].toLowerCase();
21756
21898
  } catch {
21757
21899
  }
21758
- return ((0, import_node_path21.basename)(cwd) || "local").toLowerCase();
21900
+ return ((0, import_node_path22.basename)(cwd) || "local").toLowerCase();
21759
21901
  }
21760
21902
 
21761
21903
  // src/repo-index-cloud-client.ts
@@ -21897,7 +22039,7 @@ async function gcRepoIndexCloud(deps) {
21897
22039
  // src/repo-index-sync.ts
21898
22040
  var import_node_fs24 = require("node:fs");
21899
22041
  var import_node_os9 = require("node:os");
21900
- var import_node_path22 = require("node:path");
22042
+ var import_node_path23 = require("node:path");
21901
22043
  var import_node_child_process13 = require("node:child_process");
21902
22044
  var MAX_EMBED_BACKFILL_ROUNDS = 40;
21903
22045
  function normalizeRepo(raw) {
@@ -21941,7 +22083,7 @@ async function syncEstateRepoIndex(opts) {
21941
22083
  const failed = [];
21942
22084
  const skipped = [];
21943
22085
  for (const repo of repos) {
21944
- const dir = (0, import_node_fs24.mkdtempSync)((0, import_node_path22.join)((0, import_node_os9.tmpdir)(), "mmi-repo-index-"));
22086
+ const dir = (0, import_node_fs24.mkdtempSync)((0, import_node_path23.join)((0, import_node_os9.tmpdir)(), "mmi-repo-index-"));
21945
22087
  try {
21946
22088
  shallowClone(repo, dir, opts.githubToken);
21947
22089
  const built = rebuildRepoIndex(dir, repo);
@@ -22187,7 +22329,7 @@ async function runRepoIndexHealth(opts) {
22187
22329
  // src/spawn-policy-core.ts
22188
22330
  var import_node_child_process14 = require("node:child_process");
22189
22331
  var import_node_fs26 = require("node:fs");
22190
- var import_node_path23 = require("node:path");
22332
+ var import_node_path24 = require("node:path");
22191
22333
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
22192
22334
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
22193
22335
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -22273,7 +22415,7 @@ function runSpawnPolicy(root) {
22273
22415
  for (const file of files) {
22274
22416
  let raw;
22275
22417
  try {
22276
- raw = (0, import_node_fs26.readFileSync)((0, import_node_path23.join)(root, file), "utf8");
22418
+ raw = (0, import_node_fs26.readFileSync)((0, import_node_path24.join)(root, file), "utf8");
22277
22419
  } catch {
22278
22420
  continue;
22279
22421
  }
@@ -22292,7 +22434,7 @@ function runSpawnPolicy(root) {
22292
22434
  // src/test-policy-core.ts
22293
22435
  var import_node_child_process15 = require("node:child_process");
22294
22436
  var import_node_fs27 = require("node:fs");
22295
- var import_node_path24 = require("node:path");
22437
+ var import_node_path25 = require("node:path");
22296
22438
  var POLICY_FILE = "test-policy.json";
22297
22439
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
22298
22440
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
@@ -22345,7 +22487,7 @@ function isTestPath(path2) {
22345
22487
  return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
22346
22488
  }
22347
22489
  function loadPolicy(root, readFile9 = readFileOrNull2) {
22348
- const raw = readFile9((0, import_node_path24.join)(root, POLICY_FILE));
22490
+ const raw = readFile9((0, import_node_path25.join)(root, POLICY_FILE));
22349
22491
  if (raw == null) return { mandatory: [], declared: false };
22350
22492
  try {
22351
22493
  return { ...JSON.parse(raw), declared: true };
@@ -22383,11 +22525,11 @@ function classify(changed, policy, present = () => false) {
22383
22525
  return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
22384
22526
  }
22385
22527
  function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
22386
- return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path24.join)(root, p)));
22528
+ return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path25.join)(root, p)));
22387
22529
  }
22388
22530
  function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
22389
22531
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
22390
- return [...new Set(declared)].filter((p) => !exists((0, import_node_path24.join)(root, p)));
22532
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path25.join)(root, p)));
22391
22533
  }
22392
22534
  function evaluate(changed, policy, present = () => false) {
22393
22535
  const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
@@ -22575,7 +22717,7 @@ function runTestPolicy(root, deps = {}) {
22575
22717
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
22576
22718
  const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
22577
22719
  const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
22578
- const present = (path2) => exists((0, import_node_path24.join)(root, path2));
22720
+ const present = (path2) => exists((0, import_node_path25.join)(root, path2));
22579
22721
  const removedByThisDiff = removedPaths(changed);
22580
22722
  const staleFindings = [];
22581
22723
  const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
@@ -22614,7 +22756,7 @@ function runTestPolicy(root, deps = {}) {
22614
22756
 
22615
22757
  // src/project-info-sync.ts
22616
22758
  var import_node_fs28 = require("node:fs");
22617
- var import_node_path25 = require("node:path");
22759
+ var import_node_path26 = require("node:path");
22618
22760
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
22619
22761
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
22620
22762
  projectV2 { id }
@@ -22659,7 +22801,7 @@ function sharedName(entries, fallback) {
22659
22801
  }
22660
22802
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
22661
22803
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
22662
- const readmePath = (0, import_node_path25.join)(repoRoot2, "README.md");
22804
+ const readmePath = (0, import_node_path26.join)(repoRoot2, "README.md");
22663
22805
  if (!(0, import_node_fs28.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
22664
22806
  const entries = entriesFor(project2, projects);
22665
22807
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
@@ -22685,8 +22827,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
22685
22827
  const targetBase = `https://github.com/${targetRepo2}`;
22686
22828
  const targetBranch = branchFor(targetRepo2, projects);
22687
22829
  const orgDocs = [
22688
- (0, import_node_fs28.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
22689
- (0, import_node_fs28.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
22830
+ (0, import_node_fs28.existsSync)((0, import_node_path26.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
22831
+ (0, import_node_fs28.existsSync)((0, import_node_path26.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
22690
22832
  ].filter(Boolean);
22691
22833
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
22692
22834
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -23564,7 +23706,7 @@ function writeError(res) {
23564
23706
 
23565
23707
  // src/secrets-commands.ts
23566
23708
  var import_node_fs29 = require("node:fs");
23567
- var import_node_path26 = require("node:path");
23709
+ var import_node_path27 = require("node:path");
23568
23710
  var import_node_os10 = require("node:os");
23569
23711
 
23570
23712
  // src/project-runtime.ts
@@ -23688,11 +23830,11 @@ function collectMap(value, previous = []) {
23688
23830
  return [...previous, value];
23689
23831
  }
23690
23832
  async function decryptRailsCredentials(input) {
23691
- const appDir = (0, import_node_path26.resolve)(input.appDir ?? process.cwd());
23833
+ const appDir = (0, import_node_path27.resolve)(input.appDir ?? process.cwd());
23692
23834
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
23693
23835
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
23694
- const credentialsPath = (0, import_node_path26.resolve)(appDir, credentialsFile);
23695
- const masterKeyPath = (0, import_node_path26.resolve)(appDir, masterKeyFile);
23836
+ const credentialsPath = (0, import_node_path27.resolve)(appDir, credentialsFile);
23837
+ const masterKeyPath = (0, import_node_path27.resolve)(appDir, masterKeyFile);
23696
23838
  const env = {
23697
23839
  ...process.env,
23698
23840
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
@@ -23709,8 +23851,8 @@ async function decryptRailsCredentials(input) {
23709
23851
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
23710
23852
  "puts JSON.generate(config.config)"
23711
23853
  ].join("\n");
23712
- const scriptDir = (0, import_node_fs29.mkdtempSync)((0, import_node_path26.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
23713
- const scriptPath = (0, import_node_path26.join)(scriptDir, "decrypt.rb");
23854
+ const scriptDir = (0, import_node_fs29.mkdtempSync)((0, import_node_path27.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
23855
+ const scriptPath = (0, import_node_path27.join)(scriptDir, "decrypt.rb");
23714
23856
  (0, import_node_fs29.writeFileSync)(scriptPath, script, "utf8");
23715
23857
  try {
23716
23858
  const args = ["exec", "ruby", scriptPath];
@@ -23813,7 +23955,7 @@ function registerSecretsCommands(program3) {
23813
23955
  let body;
23814
23956
  if (o.file) {
23815
23957
  try {
23816
- body = (0, import_node_fs29.readFileSync)((0, import_node_path26.resolve)(o.file), "utf8");
23958
+ body = (0, import_node_fs29.readFileSync)((0, import_node_path27.resolve)(o.file), "utf8");
23817
23959
  } catch (e) {
23818
23960
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
23819
23961
  }
@@ -23918,7 +24060,7 @@ function registerSecretsCommands(program3) {
23918
24060
  {
23919
24061
  ...d,
23920
24062
  decryptRailsCredentials,
23921
- removeFile: (path2) => (0, import_node_fs29.unlinkSync)((0, import_node_path26.resolve)(o.appDir ?? process.cwd(), path2))
24063
+ removeFile: (path2) => (0, import_node_fs29.unlinkSync)((0, import_node_path27.resolve)(o.appDir ?? process.cwd(), path2))
23922
24064
  },
23923
24065
  {
23924
24066
  repo: o.repo,
@@ -24299,7 +24441,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
24299
24441
  }
24300
24442
 
24301
24443
  // src/schedules-commands.ts
24302
- var import_promises4 = require("node:fs/promises");
24444
+ var import_promises5 = require("node:fs/promises");
24303
24445
  var import_node_child_process16 = require("node:child_process");
24304
24446
  var import_node_util7 = require("node:util");
24305
24447
 
@@ -24964,9 +25106,9 @@ function registerSchedulesCommands(program3) {
24964
25106
  await failGraceful("org schedules --doc: refusing to regenerate the doc from an incomplete read (see warnings above).");
24965
25107
  return;
24966
25108
  }
24967
- const docText = await (0, import_promises4.readFile)(o.doc, "utf8");
25109
+ const docText = await (0, import_promises5.readFile)(o.doc, "utf8");
24968
25110
  const spliced = spliceDoc(docText, renderDocSection(entries, (/* @__PURE__ */ new Date()).toISOString()));
24969
- await (0, import_promises4.writeFile)(o.doc, spliced, "utf8");
25111
+ await (0, import_promises5.writeFile)(o.doc, spliced, "utf8");
24970
25112
  reportDrift(drift);
24971
25113
  console.log(`org schedules: wrote ${entries.length} entries into ${o.doc}`);
24972
25114
  return;
@@ -25049,102 +25191,6 @@ function registerSchedulesCommands(program3) {
25049
25191
  });
25050
25192
  }
25051
25193
 
25052
- // src/file-lock.ts
25053
- var import_promises5 = require("node:fs/promises");
25054
- var import_node_path27 = require("node:path");
25055
- var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
25056
- var IMMEDIATE_RETRY_BUDGET = 3;
25057
- var FileLockBusyError = class extends Error {
25058
- lockPath;
25059
- constructor(label, lockPath, maxWaitMs) {
25060
- super(`${label} busy: ${lockPath} held longer than ${maxWaitMs}ms`);
25061
- this.name = "FileLockBusyError";
25062
- this.lockPath = lockPath;
25063
- }
25064
- };
25065
- function resolveFileLockOpts(opts = {}) {
25066
- return {
25067
- staleMs: opts.staleMs ?? 3e4,
25068
- retryMs: opts.retryMs ?? 50,
25069
- maxWaitMs: opts.maxWaitMs ?? 5e3,
25070
- label: opts.label ?? "file lock"
25071
- };
25072
- }
25073
- async function acquireFileLock(lockPath, opts, deadline) {
25074
- let immediateRetries = 0;
25075
- for (; ; ) {
25076
- let handle;
25077
- try {
25078
- handle = await (0, import_promises5.open)(lockPath, "wx");
25079
- } catch (e) {
25080
- const code = e.code;
25081
- if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
25082
- const retryNow = async () => {
25083
- if (Date.now() >= deadline) throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
25084
- if (++immediateRetries > IMMEDIATE_RETRY_BUDGET) await sleep(opts.retryMs);
25085
- };
25086
- try {
25087
- const age = Date.now() - (await (0, import_promises5.stat)(lockPath)).mtimeMs;
25088
- if (age > opts.staleMs) {
25089
- try {
25090
- await (0, import_promises5.unlink)(lockPath);
25091
- await retryNow();
25092
- continue;
25093
- } catch (unlinkError) {
25094
- if (unlinkError instanceof FileLockBusyError) throw unlinkError;
25095
- const unlinkCode = unlinkError.code;
25096
- if (unlinkCode === "ENOENT") {
25097
- await retryNow();
25098
- continue;
25099
- }
25100
- }
25101
- }
25102
- } catch (statError) {
25103
- if (statError instanceof FileLockBusyError) throw statError;
25104
- if (statError.code !== "ENOENT") throw statError;
25105
- await retryNow();
25106
- continue;
25107
- }
25108
- if (Date.now() >= deadline) {
25109
- throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
25110
- }
25111
- await sleep(opts.retryMs);
25112
- continue;
25113
- }
25114
- const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
25115
- try {
25116
- await handle.writeFile(token);
25117
- } catch (e) {
25118
- await handle.close().catch(() => void 0);
25119
- throw e;
25120
- }
25121
- return { token, handle };
25122
- }
25123
- }
25124
- async function fileLockHeldBy(lockPath, token) {
25125
- try {
25126
- return await (0, import_promises5.readFile)(lockPath, "utf8") === token;
25127
- } catch {
25128
- return false;
25129
- }
25130
- }
25131
- async function releaseFileLock(lockPath, guard) {
25132
- await guard.handle.close().catch(() => void 0);
25133
- if (await fileLockHeldBy(lockPath, guard.token)) {
25134
- await (0, import_promises5.unlink)(lockPath).catch(() => void 0);
25135
- }
25136
- }
25137
- async function withFileLock(lockPath, opts, fn) {
25138
- const resolved = resolveFileLockOpts(opts);
25139
- await (0, import_promises5.mkdir)((0, import_node_path27.dirname)(lockPath), { recursive: true }).catch(() => void 0);
25140
- const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
25141
- try {
25142
- return await fn();
25143
- } finally {
25144
- await releaseFileLock(lockPath, guard);
25145
- }
25146
- }
25147
-
25148
25194
  // src/schedules-lift-command.ts
25149
25195
  var import_promises6 = require("node:fs/promises");
25150
25196
  var import_node_path28 = require("node:path");
@@ -35330,7 +35376,7 @@ withExamples(mutating(
35330
35376
  }
35331
35377
  if (!resumed) {
35332
35378
  step = `git worktree add ${wtPath}`;
35333
- await addWorktreeRobust(wtPath, branch, base, {
35379
+ await withWorktreeAddLock(repoRoot2, () => addWorktreeRobust(wtPath, branch, base, {
35334
35380
  git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
35335
35381
  revParse: async (ref) => {
35336
35382
  try {
@@ -35344,7 +35390,7 @@ withExamples(mutating(
35344
35390
  log: (m) => {
35345
35391
  if (!o.json) console.error(` ${m}`);
35346
35392
  }
35347
- });
35393
+ }));
35348
35394
  }
35349
35395
  step = "install deps + copy local-only config";
35350
35396
  const report = await provisionWorktree(wtPath, makeProvisionDeps(wtPath, Boolean(o.json), (m) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.105.8",
3
+ "version": "3.105.9",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",