@mutmutco/hub 4.0.14 → 4.0.16

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/index.cjs +465 -244
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/index.ts
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ buildWatcherCommand: () => buildWatcherCommand,
25
+ clearPending: () => clearPending,
24
26
  enumerateSurfaces: () => enumerateSurfaces,
25
27
  failConsequence: () => failConsequence,
26
28
  formatReconcileReport: () => formatReconcileReport,
@@ -31,24 +33,32 @@ __export(index_exports, {
31
33
  launcherSh: () => launcherSh,
32
34
  launcherVbs: () => launcherVbs,
33
35
  localStartBoundary: () => localStartBoundary,
36
+ lockShaped: () => lockShaped,
34
37
  main: () => main,
38
+ noteBusyDefer: () => noteBusyDefer,
35
39
  ownVersion: () => ownVersion,
40
+ pendingPath: () => pendingPath,
41
+ pidAlive: () => pidAlive,
36
42
  probeHermesInstallation: () => probeHermesInstallation,
37
43
  progressiveRow: () => progressiveRow,
44
+ readPending: () => readPending,
38
45
  reconcileCursorUserHooks: () => reconcileCursorUserHooks,
39
46
  reportFooterLines: () => reportFooterLines,
40
47
  retryHint: () => retryHint,
48
+ schedulePendingWatcher: () => schedulePendingWatcher,
41
49
  schedulerStatus: () => schedulerStatus,
42
50
  startSpinner: () => startSpinner,
43
51
  systemdServiceUnit: () => systemdServiceUnit,
44
52
  systemdTimerUnit: () => systemdTimerUnit,
45
53
  taskXml: () => taskXml,
46
54
  updateHeader: () => updateHeader,
47
- wrapWords: () => wrapWords
55
+ watcherLockPath: () => watcherLockPath,
56
+ wrapWords: () => wrapWords,
57
+ writePending: () => writePending
48
58
  });
49
59
  module.exports = __toCommonJS(index_exports);
50
- var import_node_fs16 = require("node:fs");
51
- var import_node_path13 = require("node:path");
60
+ var import_node_fs17 = require("node:fs");
61
+ var import_node_path14 = require("node:path");
52
62
 
53
63
  // src/state.ts
54
64
  var import_node_fs = require("node:fs");
@@ -382,7 +392,11 @@ function enumerateSurfaces(env = process.env) {
382
392
  return [
383
393
  { id: "claude", marker: (0, import_node_path3.join)(home, ".claude", "plugins", "installed_plugins.json") },
384
394
  { id: "codex", marker: (0, import_node_path3.join)(codexHome, "plugins", "installed_plugins.json") },
385
- { id: "cursor", marker: (0, import_node_path3.join)(home, ".cursor", "plugins", "local", "mmi") },
395
+ // The host, not the payload (#4951, #5356): this marker used to be plugins/local/mmi — the MMI
396
+ // plugin tree itself — so a machine running Cursor WITHOUT the plugin enumerated as "no Cursor"
397
+ // and the arm could never make the first install. The plugin's absence is a converge action, not
398
+ // a skip. The host root mirrors surfaceConfigRoot('cursor') in cli/src/plugin-guard-io.ts.
399
+ { id: "cursor", marker: (0, import_node_path3.join)(home, ".cursor") },
386
400
  { id: "kilo", marker: (0, import_node_path3.join)(home, ".config", "kilo") },
387
401
  // The host, not the payload (#4951): this marker used to be plugins/managed/mmi — the MMI plugin
388
402
  // itself — so a machine running Kimi WITHOUT the plugin enumerated as "no Kimi" and the arm
@@ -396,16 +410,160 @@ function enumerateSurfaces(env = process.env) {
396
410
  }
397
411
 
398
412
  // src/arm-cli.ts
399
- var import_node_fs5 = require("node:fs");
413
+ var import_node_fs6 = require("node:fs");
414
+ var import_node_child_process4 = require("node:child_process");
415
+ var import_node_path5 = require("node:path");
416
+
417
+ // src/pending.ts
400
418
  var import_node_child_process3 = require("node:child_process");
419
+ var import_node_fs5 = require("node:fs");
401
420
  var import_node_path4 = require("node:path");
421
+ function hubDistPath(env) {
422
+ const root = globalRoot(env);
423
+ return root ? (0, import_node_path4.join)(root, "@mutmutco", "hub", "dist", "index.cjs") : null;
424
+ }
425
+ var WATCHER_MAX_WAIT_MS = 4 * 60 * 60 * 1e3;
426
+ function pendingPath(paths) {
427
+ return (0, import_node_path4.join)(paths.root, "pending.json");
428
+ }
429
+ function watcherLockPath(paths) {
430
+ return (0, import_node_path4.join)(paths.root, "pending-watcher.lock.d");
431
+ }
432
+ function readPending(paths) {
433
+ try {
434
+ const parsed = JSON.parse((0, import_node_fs5.readFileSync)(pendingPath(paths), "utf8"));
435
+ if (!Array.isArray(parsed)) return [];
436
+ return parsed.filter((row) => Boolean(row) && typeof row === "object" && typeof row.surface === "string" && typeof row.target === "string" && Array.isArray(row.images) && Array.isArray(row.dirs));
437
+ } catch {
438
+ return [];
439
+ }
440
+ }
441
+ function writePending(paths, records) {
442
+ (0, import_node_fs5.mkdirSync)(paths.root, { recursive: true });
443
+ const path = pendingPath(paths);
444
+ const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
445
+ (0, import_node_fs5.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
446
+ `, "utf8");
447
+ (0, import_node_fs5.renameSync)(tmp, path);
448
+ }
449
+ function clearPending(paths, surface) {
450
+ const records = readPending(paths);
451
+ if (!records.some((row) => row.surface === surface)) return;
452
+ writePending(paths, records.filter((row) => row.surface !== surface));
453
+ }
454
+ function pidAlive(pid) {
455
+ try {
456
+ process.kill(pid, 0);
457
+ return true;
458
+ } catch (error) {
459
+ return error?.code === "EPERM";
460
+ }
461
+ }
462
+ function lockShaped(detail) {
463
+ return /EPERM|EBUSY|EACCES|locked|access is denied/i.test(detail);
464
+ }
465
+ function quotePs(value) {
466
+ return `'${value.replace(/'/gu, "''")}'`;
467
+ }
468
+ function quoteSh(value) {
469
+ return `'${value.replace(/'/gu, "'\\''")}'`;
470
+ }
471
+ function escapeEre(value) {
472
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
473
+ }
474
+ function buildWatcherCommand(opts) {
475
+ const platform = opts.platform ?? process.platform;
476
+ const maxWaitMs = opts.maxWaitMs ?? WATCHER_MAX_WAIT_MS;
477
+ const apply = [opts.node, opts.hubDist, "update"];
478
+ if (platform === "win32") {
479
+ const imageChecks2 = opts.images.map((image) => `if (Get-Process -Name ${quotePs(image)} -ErrorAction SilentlyContinue) { return $true }`);
480
+ const dirChecks2 = opts.dirs.map((dir) => `if (Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessId -ne $PID -and $_.CommandLine -and $_.CommandLine.IndexOf(${quotePs(dir)}, [System.StringComparison]::OrdinalIgnoreCase) -ge 0 }) { return $true }`);
481
+ const testBusy2 = `function Test-Busy { ${[...imageChecks2, ...dirChecks2].join("; ")}; return $false }`;
482
+ const wait2 = `$deadline = (Get-Date).AddMilliseconds(${maxWaitMs}); while ((Get-Date) -lt $deadline -and (Test-Busy)) { Start-Sleep -Seconds 5 }; if (Test-Busy) { exit 0 }`;
483
+ const lock2 = `$lock = ${quotePs(opts.lockPath)}; if (Test-Path $lock) { $owner = Get-Content "$lock\\pid" -ErrorAction SilentlyContinue; if ($owner -and -not (Get-Process -Id $owner -ErrorAction SilentlyContinue)) { Remove-Item $lock -Recurse -Force -ErrorAction SilentlyContinue } }`;
484
+ const run2 = `if (New-Item -ItemType Directory -Path $lock -ErrorAction SilentlyContinue) { $PID | Set-Content "$lock\\pid"; try { & ${apply.map(quotePs).join(" ")} *> ${quotePs(opts.logPath)}; Set-Content -Path ${quotePs(opts.codePath)} -Value $LASTEXITCODE } finally { Remove-Item $lock -Recurse -Force -ErrorAction SilentlyContinue } }`;
485
+ return {
486
+ file: "powershell",
487
+ args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", `${testBusy2}; ${wait2}; ${lock2}; ${run2}`]
488
+ };
489
+ }
490
+ const imageChecks = opts.images.map((image) => `pgrep -xi ${quoteSh(image)} >/dev/null 2>&1 && return 0`);
491
+ const dirChecks = opts.dirs.map((dir) => `[ -n "$(pgrep -f ${quoteSh(escapeEre(dir))} | grep -vx $$)" ] && return 0`);
492
+ const testBusy = `test_busy() { ${[...imageChecks, ...dirChecks].join("; ") || ":"}; return 1; }`;
493
+ const wait = `deadline=$(( $(date +%s) + ${Math.floor(maxWaitMs / 1e3)} )); while [ "$(date +%s)" -lt "$deadline" ] && test_busy; do sleep 5; done; test_busy && exit 0`;
494
+ const lock = `if [ -d ${quoteSh(opts.lockPath)} ]; then owner=$(cat ${quoteSh(`${opts.lockPath}/pid`)} 2>/dev/null); if [ -n "$owner" ] && ! kill -0 "$owner" 2>/dev/null; then rm -rf ${quoteSh(opts.lockPath)}; fi; fi`;
495
+ const run = `if mkdir ${quoteSh(opts.lockPath)} 2>/dev/null; then echo $$ > ${quoteSh(`${opts.lockPath}/pid`)}; trap "rm -rf ${quoteSh(opts.lockPath)}" EXIT; ${apply.map(quoteSh).join(" ")} > ${quoteSh(opts.logPath)} 2>&1; echo $? > ${quoteSh(opts.codePath)}; fi`;
496
+ return { file: "sh", args: ["-c", `${testBusy}; ${wait}; ${lock}; ${run}`] };
497
+ }
498
+ function defaultDetachSpawn(file, args) {
499
+ const child = (0, import_node_child_process3.spawn)(file, [...args], {
500
+ detached: process.platform !== "win32",
501
+ stdio: "ignore",
502
+ windowsHide: true,
503
+ shell: false
504
+ });
505
+ child.on("error", () => {
506
+ });
507
+ child.unref();
508
+ return child.pid ?? null;
509
+ }
510
+ function schedulePendingWatcher(paths, env = process.env, seam = {}) {
511
+ const records = readPending(paths);
512
+ if (!records.length) return { scheduled: false, reason: "no pending applies" };
513
+ const live = records.find((row) => row.watcherPid && pidAlive(row.watcherPid));
514
+ if (live) return { scheduled: false, reason: `watcher pid ${live.watcherPid} is already waiting` };
515
+ const hubDist = seam.hubDist ?? hubDistPath(env);
516
+ if (!hubDist || !(0, import_node_fs5.existsSync)(hubDist)) return { scheduled: false, reason: "the installed hub bundle is unresolvable \u2014 the hourly tick remains the retry" };
517
+ const command = buildWatcherCommand({
518
+ images: [...new Set(records.flatMap((row) => row.images))],
519
+ dirs: [...new Set(records.flatMap((row) => row.dirs))],
520
+ node: process.execPath,
521
+ hubDist,
522
+ codePath: (0, import_node_path4.join)(paths.root, "pending-result.code"),
523
+ logPath: (0, import_node_path4.join)(paths.root, "pending-result.log"),
524
+ lockPath: watcherLockPath(paths)
525
+ });
526
+ try {
527
+ const pid = (seam.detachSpawn ?? defaultDetachSpawn)(command.file, command.args);
528
+ if (!pid) return { scheduled: false, reason: "watcher spawn returned no pid" };
529
+ writePending(paths, records.map((row) => ({ ...row, watcherPid: pid })));
530
+ return { scheduled: true, pid };
531
+ } catch (error) {
532
+ return { scheduled: false, reason: String(error?.message ?? error) };
533
+ }
534
+ }
535
+ function noteBusyDefer(paths, env, entry, seam = {}) {
536
+ try {
537
+ const records = readPending(paths);
538
+ const existing = records.find((row) => row.surface === entry.surface);
539
+ const next = {
540
+ surface: entry.surface,
541
+ target: entry.target,
542
+ since: existing?.since ?? (/* @__PURE__ */ new Date()).toISOString(),
543
+ reason: entry.reason,
544
+ images: entry.images ?? [],
545
+ dirs: entry.dirs ?? [],
546
+ watcherPid: existing?.watcherPid
547
+ };
548
+ writePending(paths, [...records.filter((row) => row.surface !== entry.surface), next]);
549
+ const scheduled = schedulePendingWatcher(paths, env, seam);
550
+ return scheduled.scheduled ? `applies when the host exits (post-exit watcher pid ${scheduled.pid})` : `applies on a quiet tick (${scheduled.reason})`;
551
+ } catch (error) {
552
+ return `applies on a quiet tick (pending record failed: ${String(error?.message ?? error).slice(0, 80)})`;
553
+ }
554
+ }
555
+ function resumePendingWatcher(paths, env = process.env) {
556
+ return schedulePendingWatcher(paths, env);
557
+ }
558
+
559
+ // src/arm-cli.ts
402
560
  function globalCliDist(env) {
403
561
  const root = globalRoot(env);
404
- return root ? (0, import_node_path4.join)(root, "@mutmutco", "cli", "dist", "index.cjs") : null;
562
+ return root ? (0, import_node_path5.join)(root, "@mutmutco", "cli", "dist", "index.cjs") : null;
405
563
  }
406
564
  function probeCliVersion(distPath) {
407
565
  if (!fileExists(distPath)) return null;
408
- const probe = (0, import_node_child_process3.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
566
+ const probe = (0, import_node_child_process4.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
409
567
  const version = (probe.stdout || "").trim();
410
568
  return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
411
569
  }
@@ -432,11 +590,11 @@ function cliArm(options) {
432
590
  if (dryRun) {
433
591
  return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `dry-run: would converge ${installed ?? "absent"} -> ${target}` };
434
592
  }
435
- const stageRoot = (0, import_node_path4.join)(paths.staging, "cli", target);
436
- const stageDist = (0, import_node_path4.join)(stageRoot, "node_modules", "@mutmutco", "cli", "dist", "index.cjs");
593
+ const stageRoot = (0, import_node_path5.join)(paths.staging, "cli", target);
594
+ const stageDist = (0, import_node_path5.join)(stageRoot, "node_modules", "@mutmutco", "cli", "dist", "index.cjs");
437
595
  if (!fileExists(stageDist)) {
438
- (0, import_node_fs5.mkdirSync)(stageRoot, { recursive: true });
439
- (0, import_node_fs5.writeFileSync)((0, import_node_path4.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
596
+ (0, import_node_fs6.mkdirSync)(stageRoot, { recursive: true });
597
+ (0, import_node_fs6.writeFileSync)((0, import_node_path5.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
440
598
  const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `@mutmutco/cli@${target}`], env);
441
599
  if (install.status !== 0) {
442
600
  return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staging install failed: ${(install.stderr || install.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}` };
@@ -444,7 +602,7 @@ function cliArm(options) {
444
602
  }
445
603
  let stagedManifestVersion;
446
604
  try {
447
- stagedManifestVersion = JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path4.join)(stageRoot, "node_modules", "@mutmutco", "cli", "package.json"), "utf8")).version;
605
+ stagedManifestVersion = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path5.join)(stageRoot, "node_modules", "@mutmutco", "cli", "package.json"), "utf8")).version;
448
606
  } catch {
449
607
  }
450
608
  if (stagedManifestVersion !== target) {
@@ -456,7 +614,10 @@ function cliArm(options) {
456
614
  }
457
615
  const globalInstall = runNpm(["install", "-g", "--no-audit", "--no-fund", `@mutmutco/cli@${target}`], env);
458
616
  if (globalInstall.status !== 0) {
459
- return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `global install failed: ${(globalInstall.stderr || globalInstall.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}` };
617
+ const reason = `global install failed: ${(globalInstall.stderr || globalInstall.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}`;
618
+ const root = globalRoot(env);
619
+ const pending = lockShaped(reason) && root ? ` \u2014 ${noteBusyDefer(paths, env, { surface: "cli", target, reason, dirs: [root] })}` : "";
620
+ return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `${reason}${pending}` };
460
621
  }
461
622
  const switchedDist = globalCliDist(env);
462
623
  const switched = probeCliVersion(switchedDist ?? "");
@@ -467,16 +628,16 @@ function cliArm(options) {
467
628
  }
468
629
 
469
630
  // src/arm-npm-global.ts
470
- var import_node_fs6 = require("node:fs");
471
- var import_node_child_process4 = require("node:child_process");
472
- var import_node_path5 = require("node:path");
631
+ var import_node_fs7 = require("node:fs");
632
+ var import_node_child_process5 = require("node:child_process");
633
+ var import_node_path6 = require("node:path");
473
634
  function globalDist(env, packageName, distRel) {
474
635
  const root = globalRoot(env);
475
- return root ? (0, import_node_path5.join)(root, ...packageName.split("/"), ...distRel.split("/")) : null;
636
+ return root ? (0, import_node_path6.join)(root, ...packageName.split("/"), ...distRel.split("/")) : null;
476
637
  }
477
638
  function probeVersion(distPath) {
478
639
  if (!fileExists(distPath)) return null;
479
- const probe = (0, import_node_child_process4.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
640
+ const probe = (0, import_node_child_process5.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
480
641
  const version = (probe.stdout || "").trim();
481
642
  return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
482
643
  }
@@ -485,11 +646,11 @@ function tail(output) {
485
646
  }
486
647
  function stagePackage(options) {
487
648
  const { surface, packageName, target, witness, paths, env } = options;
488
- const stageRoot = (0, import_node_path5.join)(paths.staging, surface, target);
489
- const packageRoot = (0, import_node_path5.join)(stageRoot, "node_modules", ...packageName.split("/"));
490
- if (!fileExists((0, import_node_path5.join)(packageRoot, ...witness.split("/")))) {
491
- (0, import_node_fs6.mkdirSync)(stageRoot, { recursive: true });
492
- (0, import_node_fs6.writeFileSync)((0, import_node_path5.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
649
+ const stageRoot = (0, import_node_path6.join)(paths.staging, surface, target);
650
+ const packageRoot = (0, import_node_path6.join)(stageRoot, "node_modules", ...packageName.split("/"));
651
+ if (!fileExists((0, import_node_path6.join)(packageRoot, ...witness.split("/")))) {
652
+ (0, import_node_fs7.mkdirSync)(stageRoot, { recursive: true });
653
+ (0, import_node_fs7.writeFileSync)((0, import_node_path6.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
493
654
  const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${packageName}@${target}`], env);
494
655
  if (install.status !== 0) {
495
656
  return { defer: `staging install failed: ${tail(install.stderr || install.stdout)}` };
@@ -497,7 +658,7 @@ function stagePackage(options) {
497
658
  }
498
659
  let stagedManifestVersion;
499
660
  try {
500
- stagedManifestVersion = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path5.join)(packageRoot, "package.json"), "utf8")).version;
661
+ stagedManifestVersion = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(packageRoot, "package.json"), "utf8")).version;
501
662
  } catch {
502
663
  }
503
664
  if (stagedManifestVersion !== target) {
@@ -529,14 +690,17 @@ function npmGlobalArm(options) {
529
690
  if ("defer" in staged) {
530
691
  return result(installed, target, "defer", staged.defer);
531
692
  }
532
- const stageDist = (0, import_node_path5.join)(staged.packageRoot, ...distRel.split("/"));
693
+ const stageDist = (0, import_node_path6.join)(staged.packageRoot, ...distRel.split("/"));
533
694
  const stagedVersion = probeVersion(stageDist);
534
695
  if (stagedVersion !== target) {
535
696
  return result(installed, target, "defer", `staged binary probed ${stagedVersion ?? "dead"}, expected ${target}`);
536
697
  }
537
698
  const globalInstall = runNpm(["install", "-g", "--no-audit", "--no-fund", spec], env);
538
699
  if (globalInstall.status !== 0) {
539
- return result(installed, target, "defer", `global install failed: ${tail(globalInstall.stderr || globalInstall.stdout)}`);
700
+ const reason = `global install failed: ${tail(globalInstall.stderr || globalInstall.stdout)}`;
701
+ const root = globalRoot(env);
702
+ const pending = lockShaped(reason) && root ? ` \u2014 ${noteBusyDefer(paths, env, { surface, target, reason, dirs: [root] })}` : "";
703
+ return result(installed, target, "defer", `${reason}${pending}`);
540
704
  }
541
705
  const switchedDist = globalDist(env, packageName, distRel);
542
706
  const switched = probeVersion(switchedDist ?? "");
@@ -557,10 +721,10 @@ function selfArm(options) {
557
721
  }
558
722
 
559
723
  // src/arm-host-plugins.ts
560
- var import_node_child_process5 = require("node:child_process");
561
- var import_node_fs7 = require("node:fs");
724
+ var import_node_child_process6 = require("node:child_process");
725
+ var import_node_fs8 = require("node:fs");
562
726
  var import_node_os3 = require("node:os");
563
- var import_node_path6 = require("node:path");
727
+ var import_node_path7 = require("node:path");
564
728
  var REPO_URL_DEFAULT = "https://github.com/mutmutco/MMI-Hub.git";
565
729
  var MMI_MARKETPLACE_REPO = "mutmutco/MMI-Hub";
566
730
  function convergedAtOrAbove(installed, target, healthy) {
@@ -570,13 +734,13 @@ function cmdArg(value) {
570
734
  return /^[A-Za-z0-9_@./:\\=+-]+$/.test(value) ? value : `"${value.replaceAll('"', '""')}"`;
571
735
  }
572
736
  var runHostCommand = (command, args, env) => {
573
- const result = process.platform === "win32" ? (0, import_node_child_process5.spawnSync)("cmd.exe", ["/d", "/s", "/c", [command, ...args].map(cmdArg).join(" ")], {
737
+ const result = process.platform === "win32" ? (0, import_node_child_process6.spawnSync)("cmd.exe", ["/d", "/s", "/c", [command, ...args].map(cmdArg).join(" ")], {
574
738
  encoding: "utf8",
575
739
  env,
576
740
  input: "",
577
741
  timeout: 12e4,
578
742
  windowsHide: true
579
- }) : (0, import_node_child_process5.spawnSync)(command, args, {
743
+ }) : (0, import_node_child_process6.spawnSync)(command, args, {
580
744
  encoding: "utf8",
581
745
  env,
582
746
  input: "",
@@ -603,7 +767,7 @@ function claudeMmi(runner, env) {
603
767
  return { row: parsed.find((row) => row.id === "mmi@mutmutco") ?? null };
604
768
  }
605
769
  function claudeMarketplacesPath(env) {
606
- return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0, import_node_path6.join)((0, import_node_os3.homedir)(), ".claude", "plugins", "known_marketplaces.json");
770
+ return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".claude", "plugins", "known_marketplaces.json");
607
771
  }
608
772
  var CLAUDE_PROCESS_SEGMENT = /^claude(-code|-cli)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/;
609
773
  function claudeProcessSegment(line) {
@@ -617,7 +781,7 @@ function claudeProcessSegment(line) {
617
781
  function claudeHostRunning(env) {
618
782
  const marker = env.CLAUDE_CODE_SESSION_ID?.trim() ? "CLAUDE_CODE_SESSION_ID" : env.CLAUDECODE?.trim() ? "CLAUDECODE" : env.CLAUDE_PLUGIN_ROOT?.trim() ? "CLAUDE_PLUGIN_ROOT" : null;
619
783
  if (marker) return { running: true, evidence: `${marker} is set \u2014 this process is itself inside a Claude hook` };
620
- const result = process.platform === "win32" ? (0, import_node_child_process5.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process5.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
784
+ const result = process.platform === "win32" ? (0, import_node_child_process6.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process6.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
621
785
  if (result.status !== 0 || !(result.stdout ?? "").trim()) return { running: true, evidence: "the process list is unreadable \u2014 unsafe to write" };
622
786
  for (const line of result.stdout.split(/\r?\n/)) {
623
787
  const segment = claudeProcessSegment(line);
@@ -629,7 +793,7 @@ function ensureClaudeSingleWriter(env, dryRun) {
629
793
  const path = claudeMarketplacesPath(env);
630
794
  let body;
631
795
  try {
632
- body = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
796
+ body = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
633
797
  } catch (error) {
634
798
  if (error?.code === "ENOENT") return { ok: false, detail: `${path} does not exist \u2014 mutmutco marketplace is not registered`, unregistered: true };
635
799
  return { ok: false, detail: `cannot read ${path}` };
@@ -641,7 +805,10 @@ function ensureClaudeSingleWriter(env, dryRun) {
641
805
  if (registeredRepo !== MMI_MARKETPLACE_REPO) {
642
806
  return {
643
807
  ok: false,
644
- detail: `mutmutco marketplace is registered to ${registeredRepo ?? "a non-github source"}, not ${MMI_MARKETPLACE_REPO} \u2014 a retired or foreign repo is squatting the name; re-register it (claude plugin marketplace remove mutmutco && claude plugin marketplace add ${REPO_URL_DEFAULT}) before the updater can trust it`
808
+ // Remedy must use the github shorthand (#5369): `marketplace add <url>` registers
809
+ // source:'git', which fails this same github-provenance check forever; `add owner/repo`
810
+ // registers source:'github' and passes.
811
+ detail: `mutmutco marketplace is registered to ${registeredRepo ?? "a non-github source"}, not ${MMI_MARKETPLACE_REPO} \u2014 a retired or foreign repo is squatting the name; re-register it (claude plugin marketplace remove mutmutco && claude plugin marketplace add ${MMI_MARKETPLACE_REPO}) before the updater can trust it`
645
812
  };
646
813
  }
647
814
  if (registration.autoUpdate !== true) return { ok: true };
@@ -653,9 +820,9 @@ function ensureClaudeSingleWriter(env, dryRun) {
653
820
  registration.autoUpdate = false;
654
821
  const tmp = `${path}.tmp-${process.pid}`;
655
822
  try {
656
- (0, import_node_fs7.writeFileSync)(tmp, JSON.stringify(body, null, 2) + "\n", "utf8");
657
- (0, import_node_fs7.renameSync)(tmp, path);
658
- const reread = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
823
+ (0, import_node_fs8.writeFileSync)(tmp, JSON.stringify(body, null, 2) + "\n", "utf8");
824
+ (0, import_node_fs8.renameSync)(tmp, path);
825
+ const reread = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
659
826
  return reread?.mutmutco?.autoUpdate === false ? { ok: true, detail: "disabled Claude background autoUpdate (updater is the single writer)" } : { ok: false, detail: "Claude background autoUpdate write did not persist" };
660
827
  } catch (error) {
661
828
  return { ok: false, detail: `cannot disable Claude background autoUpdate: ${error?.message ?? error}` };
@@ -769,11 +936,11 @@ function codexArm(options) {
769
936
  }
770
937
  var KILO_PACKAGE = "@mutmutco/kilo-plugin";
771
938
  function kiloConfigPath(env) {
772
- return env.MMI_UPDATER_KILO_CONFIG || (0, import_node_path6.join)((0, import_node_os3.homedir)(), ".config", "kilo", "opencode.json");
939
+ return env.MMI_UPDATER_KILO_CONFIG || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".config", "kilo", "opencode.json");
773
940
  }
774
941
  function readKiloConfig(env) {
775
942
  try {
776
- const config = JSON.parse((0, import_node_fs7.readFileSync)(kiloConfigPath(env), "utf8"));
943
+ const config = JSON.parse((0, import_node_fs8.readFileSync)(kiloConfigPath(env), "utf8"));
777
944
  const specs = Array.isArray(config.plugin) && config.plugin.every((entry) => typeof entry === "string") ? config.plugin : [];
778
945
  return { config, specs };
779
946
  } catch {
@@ -793,8 +960,8 @@ function normalizeKiloConfig(env, exact) {
793
960
  const path = kiloConfigPath(env);
794
961
  const tmp = `${path}.tmp-${process.pid}`;
795
962
  try {
796
- (0, import_node_fs7.writeFileSync)(tmp, JSON.stringify({ ...current.config, plugin: next }, null, 2) + "\n", "utf8");
797
- (0, import_node_fs7.renameSync)(tmp, path);
963
+ (0, import_node_fs8.writeFileSync)(tmp, JSON.stringify({ ...current.config, plugin: next }, null, 2) + "\n", "utf8");
964
+ (0, import_node_fs8.renameSync)(tmp, path);
798
965
  return null;
799
966
  } catch (error) {
800
967
  return `cannot retire legacy Kilo registrations: ${error?.message ?? error}`;
@@ -857,15 +1024,15 @@ function kiloArm(options) {
857
1024
  }
858
1025
 
859
1026
  // src/arm-kimi.ts
860
- var import_node_child_process6 = require("node:child_process");
861
- var import_node_fs9 = require("node:fs");
862
- var import_node_path7 = require("node:path");
1027
+ var import_node_child_process7 = require("node:child_process");
1028
+ var import_node_fs10 = require("node:fs");
1029
+ var import_node_path8 = require("node:path");
863
1030
 
864
1031
  // src/compat.ts
865
- var import_node_fs8 = require("node:fs");
1032
+ var import_node_fs9 = require("node:fs");
866
1033
  function readManifestCompat(manifestPath) {
867
1034
  try {
868
- const parsed = JSON.parse((0, import_node_fs8.readFileSync)(manifestPath, "utf8"));
1035
+ const parsed = JSON.parse((0, import_node_fs9.readFileSync)(manifestPath, "utf8"));
869
1036
  return typeof parsed.mmiCompat === "string" ? parsed.mmiCompat : void 0;
870
1037
  } catch {
871
1038
  return void 0;
@@ -876,11 +1043,11 @@ function readManifestCompat(manifestPath) {
876
1043
  var KIMI_PACKAGE = "@mutmutco/kimi-plugin";
877
1044
  var TREE_MARKERS = [".kimi-plugin/plugin.json", "skills/mmi/SKILL.md", "scripts/hook-run.mjs"];
878
1045
  function treeHealthy(root) {
879
- return TREE_MARKERS.every((rel) => fileExists((0, import_node_path7.join)(root, ...rel.split("/"))));
1046
+ return TREE_MARKERS.every((rel) => fileExists((0, import_node_path8.join)(root, ...rel.split("/"))));
880
1047
  }
881
1048
  function markerVersion(root) {
882
1049
  try {
883
- const version = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path7.join)(root, ".kimi-plugin", "plugin.json"), "utf8")).version;
1050
+ const version = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(root, ".kimi-plugin", "plugin.json"), "utf8")).version;
884
1051
  return typeof version === "string" ? version : null;
885
1052
  } catch {
886
1053
  return null;
@@ -888,14 +1055,14 @@ function markerVersion(root) {
888
1055
  }
889
1056
  function packageVersion(root) {
890
1057
  try {
891
- const version = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path7.join)(root, "package.json"), "utf8")).version;
1058
+ const version = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(root, "package.json"), "utf8")).version;
892
1059
  return typeof version === "string" ? version : null;
893
1060
  } catch {
894
1061
  return null;
895
1062
  }
896
1063
  }
897
1064
  function probeKimiInstallation(env = process.env) {
898
- const location = (0, import_node_path7.join)(kimiHome(env), "plugins", "managed", "mmi");
1065
+ const location = (0, import_node_path8.join)(kimiHome(env), "plugins", "managed", "mmi");
899
1066
  const version = markerVersion(location);
900
1067
  return { version, location, detail: version ? "installed Kimi plugin manifest" : "installed Kimi plugin manifest was unreadable" };
901
1068
  }
@@ -903,14 +1070,14 @@ function tail2(output) {
903
1070
  return output.split("\n").filter(Boolean).slice(-3).join(" | ");
904
1071
  }
905
1072
  function scratchRoot(env) {
906
- return (0, import_node_path7.join)(kimiHome(env), ".mmi-updater");
1073
+ return (0, import_node_path8.join)(kimiHome(env), ".mmi-updater");
907
1074
  }
908
1075
  function pruneScratchDir(root, keep) {
909
1076
  try {
910
- for (const name of (0, import_node_fs9.readdirSync)(root)) {
1077
+ for (const name of (0, import_node_fs10.readdirSync)(root)) {
911
1078
  if (name === keep) continue;
912
1079
  try {
913
- (0, import_node_fs9.rmSync)((0, import_node_path7.join)(root, name), { recursive: true, force: true });
1080
+ (0, import_node_fs10.rmSync)((0, import_node_path8.join)(root, name), { recursive: true, force: true });
914
1081
  } catch {
915
1082
  }
916
1083
  }
@@ -926,7 +1093,7 @@ function kimiProcessToken(line) {
926
1093
  }
927
1094
  function kimiHostRunning(env) {
928
1095
  if (env.KIMI_PLUGIN_ROOT?.trim()) return { running: true, evidence: "KIMI_PLUGIN_ROOT is set \u2014 this process is itself inside a Kimi hook" };
929
- const result = process.platform === "win32" ? (0, import_node_child_process6.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process6.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
1096
+ const result = process.platform === "win32" ? (0, import_node_child_process7.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process7.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
930
1097
  if (result.status !== 0 || !(result.stdout ?? "").trim()) return { running: true, evidence: "the process list is unreadable \u2014 unsafe to write" };
931
1098
  for (const line of result.stdout.split(/\r?\n/)) {
932
1099
  const token = kimiProcessToken(line);
@@ -936,11 +1103,11 @@ function kimiHostRunning(env) {
936
1103
  }
937
1104
  function restoreQuarantine(quarantine, live) {
938
1105
  try {
939
- (0, import_node_fs9.renameSync)(quarantine, live);
1106
+ (0, import_node_fs10.renameSync)(quarantine, live);
940
1107
  } catch (error) {
941
1108
  return { restored: false, detail: `${error?.message ?? error}` };
942
1109
  }
943
- if (!(0, import_node_fs9.existsSync)(live)) return { restored: false, detail: `${live} is still absent after the restore rename` };
1110
+ if (!(0, import_node_fs10.existsSync)(live)) return { restored: false, detail: `${live} is still absent after the restore rename` };
944
1111
  return { restored: true, detail: `marker ${markerVersion(live) ?? "unreadable"}` };
945
1112
  }
946
1113
  function kimiArm(options) {
@@ -954,10 +1121,10 @@ function kimiArm(options) {
954
1121
  detail,
955
1122
  // C11: the constraint of the tree LEFT at `live` — read at result time, so ok carries the new
956
1123
  // manifest's declaration and defer/fail carry the previous tree's (#4973 reconsult ruling).
957
- compat: readManifestCompat((0, import_node_path7.join)(live, ".kimi-plugin", "plugin.json"))
1124
+ compat: readManifestCompat((0, import_node_path8.join)(live, ".kimi-plugin", "plugin.json"))
958
1125
  });
959
- const managedParent = (0, import_node_path7.join)(kimiHome(env), "plugins", "managed");
960
- const live = (0, import_node_path7.join)(managedParent, "mmi");
1126
+ const managedParent = (0, import_node_path8.join)(kimiHome(env), "plugins", "managed");
1127
+ const live = (0, import_node_path8.join)(managedParent, "mmi");
961
1128
  const installed = markerVersion(live);
962
1129
  if (installed && compareSemver(installed, target) > 0) {
963
1130
  return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
@@ -972,11 +1139,11 @@ function kimiArm(options) {
972
1139
  if (dryRun) {
973
1140
  return result(installed, target, "ok", `dry-run: would converge ${installed ?? "absent"} -> ${target} (tarball generation switch while Kimi is absent)`);
974
1141
  }
975
- const stageRoot = (0, import_node_path7.join)(paths.staging, "kimi", target);
976
- const staged = (0, import_node_path7.join)(stageRoot, "node_modules", ...KIMI_PACKAGE.split("/"));
1142
+ const stageRoot = (0, import_node_path8.join)(paths.staging, "kimi", target);
1143
+ const staged = (0, import_node_path8.join)(stageRoot, "node_modules", ...KIMI_PACKAGE.split("/"));
977
1144
  if (!treeHealthy(staged)) {
978
- (0, import_node_fs9.mkdirSync)(stageRoot, { recursive: true });
979
- (0, import_node_fs9.writeFileSync)((0, import_node_path7.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
1145
+ (0, import_node_fs10.mkdirSync)(stageRoot, { recursive: true });
1146
+ (0, import_node_fs10.writeFileSync)((0, import_node_path8.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
980
1147
  const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${KIMI_PACKAGE}@${target}`], env);
981
1148
  if (install.status !== 0) {
982
1149
  return result(installed, target, "defer", `staging install failed: ${tail2(install.stderr || install.stdout)}`);
@@ -995,75 +1162,79 @@ function kimiArm(options) {
995
1162
  }
996
1163
  const quiescence = kimiHostRunning(env);
997
1164
  if (quiescence.running) {
998
- return result(installed, target, "defer", `Kimi may be running \u2014 the managed tree is switched only while the host is absent: ${quiescence.evidence}`);
1165
+ const pending = options.dryRun ? "" : ` ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason: quiescence.evidence, dirs: ["kimi"] })}`;
1166
+ return result(installed, target, "defer", `Kimi may be running \u2014 the managed tree is switched only while the host is absent: ${quiescence.evidence}${pending ? ` \u2014${pending}` : ""}`);
999
1167
  }
1000
1168
  const scratch = scratchRoot(env);
1001
- const incomingRoot = (0, import_node_path7.join)(scratch, "incoming");
1002
- const incoming = (0, import_node_path7.join)(incomingRoot, target);
1003
- const quarantineRoot = (0, import_node_path7.join)(scratch, "quarantine");
1004
- const rejectedRoot = (0, import_node_path7.join)(scratch, "rejected");
1169
+ const incomingRoot = (0, import_node_path8.join)(scratch, "incoming");
1170
+ const incoming = (0, import_node_path8.join)(incomingRoot, target);
1171
+ const quarantineRoot = (0, import_node_path8.join)(scratch, "quarantine");
1172
+ const rejectedRoot = (0, import_node_path8.join)(scratch, "rejected");
1005
1173
  try {
1006
- (0, import_node_fs9.mkdirSync)(managedParent, { recursive: true });
1007
- (0, import_node_fs9.mkdirSync)(quarantineRoot, { recursive: true });
1008
- (0, import_node_fs9.rmSync)(incomingRoot, { recursive: true, force: true });
1009
- (0, import_node_fs9.cpSync)(staged, incoming, { recursive: true });
1174
+ (0, import_node_fs10.mkdirSync)(managedParent, { recursive: true });
1175
+ (0, import_node_fs10.mkdirSync)(quarantineRoot, { recursive: true });
1176
+ (0, import_node_fs10.rmSync)(incomingRoot, { recursive: true, force: true });
1177
+ (0, import_node_fs10.cpSync)(staged, incoming, { recursive: true });
1010
1178
  } catch (error) {
1011
1179
  return result(installed, target, "defer", `cannot place the incoming generation under ${scratch}: ${error?.message ?? error}`);
1012
1180
  }
1013
1181
  if (markerVersion(incoming) !== target || !treeHealthy(incoming)) {
1014
1182
  return result(installed, target, "defer", `incoming generation verifies as ${markerVersion(incoming) ?? "unreadable"}, expected ${target}`);
1015
1183
  }
1016
- const hadLive = (0, import_node_fs9.existsSync)(live);
1184
+ const hadLive = (0, import_node_fs10.existsSync)(live);
1017
1185
  const generation = `${installed ?? "unknown"}-${Date.now()}`;
1018
- const quarantine = (0, import_node_path7.join)(quarantineRoot, generation);
1186
+ const quarantine = (0, import_node_path8.join)(quarantineRoot, generation);
1019
1187
  if (hadLive) {
1020
1188
  try {
1021
- (0, import_node_fs9.renameSync)(live, quarantine);
1189
+ (0, import_node_fs10.renameSync)(live, quarantine);
1022
1190
  } catch (error) {
1023
- return result(installed, target, "defer", `cannot quarantine the live generation: ${error?.message ?? error}`);
1191
+ const reason = String(error?.message ?? error);
1192
+ const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason, dirs: ["kimi"] })}` : "";
1193
+ return result(installed, target, "defer", `cannot quarantine the live generation: ${reason}${pending}`);
1024
1194
  }
1025
1195
  }
1026
1196
  try {
1027
- (0, import_node_fs9.renameSync)(incoming, live);
1197
+ (0, import_node_fs10.renameSync)(incoming, live);
1028
1198
  } catch (error) {
1029
1199
  const failed = `cannot switch the new generation in: ${error?.message ?? error}`;
1030
1200
  if (!hadLive) {
1031
1201
  return result(installed, target, "defer", `${failed} \u2014 no generation was displaced; ${live} is still absent`);
1032
1202
  }
1033
1203
  const restore = restoreQuarantine(quarantine, live);
1034
- return restore.restored ? result(installed, installed ?? "unknown", "defer", `${failed} \u2014 the previous generation is back in place (${restore.detail})`) : result(installed, "none", "fail", `${failed}; ROLLBACK FAILED (${restore.detail}) \u2014 ${live} now has NO plugin and Kimi enforces nothing until this is repaired; the known-good generation is intact at ${quarantine}`);
1204
+ const pending = restore.restored && lockShaped(failed) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason: failed, dirs: ["kimi"] })}` : "";
1205
+ return restore.restored ? result(installed, installed ?? "unknown", "defer", `${failed} \u2014 the previous generation is back in place (${restore.detail})${pending}`) : result(installed, "none", "fail", `${failed}; ROLLBACK FAILED (${restore.detail}) \u2014 ${live} now has NO plugin and Kimi enforces nothing until this is repaired; the known-good generation is intact at ${quarantine}`);
1035
1206
  }
1036
1207
  const switched = markerVersion(live);
1037
1208
  if (switched !== target || !treeHealthy(live)) {
1038
1209
  const observed = `post-switch marker is ${switched ?? "unreadable"}, expected ${target}`;
1039
1210
  const rejectedName = `${target}-${Date.now()}`;
1040
1211
  try {
1041
- (0, import_node_fs9.mkdirSync)(rejectedRoot, { recursive: true });
1042
- (0, import_node_fs9.renameSync)(live, (0, import_node_path7.join)(rejectedRoot, rejectedName));
1212
+ (0, import_node_fs10.mkdirSync)(rejectedRoot, { recursive: true });
1213
+ (0, import_node_fs10.renameSync)(live, (0, import_node_path8.join)(rejectedRoot, rejectedName));
1043
1214
  } catch (error) {
1044
1215
  return result(installed, switched ?? "unknown", "fail", `${observed} \u2014 the unverified tree could NOT be moved aside (${error?.message ?? error}) and is STILL LIVE at ${live}${hadLive ? `; the previous generation is intact at ${quarantine}` : ""}`);
1045
1216
  }
1046
1217
  pruneScratchDir(rejectedRoot, rejectedName);
1047
1218
  if (!hadLive) {
1048
- return result(installed, "none", "fail", `${observed} \u2014 the unverified tree is parked at ${(0, import_node_path7.join)(rejectedRoot, rejectedName)}; ${live} is absent (there was no previous generation to restore)`);
1219
+ return result(installed, "none", "fail", `${observed} \u2014 the unverified tree is parked at ${(0, import_node_path8.join)(rejectedRoot, rejectedName)}; ${live} is absent (there was no previous generation to restore)`);
1049
1220
  }
1050
1221
  const restore = restoreQuarantine(quarantine, live);
1051
- return restore.restored ? result(installed, installed ?? "unknown", "fail", `${observed} \u2014 the unverified tree is parked at ${(0, import_node_path7.join)(rejectedRoot, rejectedName)}; rolled back to ${installed ?? "the previous generation"} and read back (${restore.detail})`) : result(installed, "none", "fail", `${observed}; ROLLBACK FAILED (${restore.detail}) \u2014 ${live} now has NO plugin and Kimi enforces nothing until this is repaired; the known-good generation is intact at ${quarantine}`);
1222
+ return restore.restored ? result(installed, installed ?? "unknown", "fail", `${observed} \u2014 the unverified tree is parked at ${(0, import_node_path8.join)(rejectedRoot, rejectedName)}; rolled back to ${installed ?? "the previous generation"} and read back (${restore.detail})`) : result(installed, "none", "fail", `${observed}; ROLLBACK FAILED (${restore.detail}) \u2014 ${live} now has NO plugin and Kimi enforces nothing until this is repaired; the known-good generation is intact at ${quarantine}`);
1052
1223
  }
1053
1224
  pruneScratchDir(quarantineRoot, hadLive ? generation : null);
1054
1225
  pruneScratchDir(rejectedRoot, null);
1055
1226
  try {
1056
- (0, import_node_fs9.rmSync)(incomingRoot, { recursive: true, force: true });
1227
+ (0, import_node_fs10.rmSync)(incomingRoot, { recursive: true, force: true });
1057
1228
  } catch {
1058
1229
  }
1059
1230
  return result(installed, target, "ok", `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched${hadLive ? `; previous generation quarantined at ${quarantine}` : ""})`);
1060
1231
  }
1061
1232
 
1062
1233
  // src/arm-cursor.ts
1063
- var import_node_fs10 = require("node:fs");
1064
- var import_node_child_process7 = require("node:child_process");
1234
+ var import_node_fs11 = require("node:fs");
1235
+ var import_node_child_process8 = require("node:child_process");
1065
1236
  var import_node_os4 = require("node:os");
1066
- var import_node_path8 = require("node:path");
1237
+ var import_node_path9 = require("node:path");
1067
1238
  var PACKAGE = "@mutmutco/cursor-plugin";
1068
1239
  var MANIFEST = ".cursor-plugin/plugin.json";
1069
1240
  var USER_HOOKS_FILE = "hooks.json";
@@ -1072,14 +1243,14 @@ var KEEP_GENERATIONS = 2;
1072
1243
  var STRANDED_TMP_AGE_MS = 24 * 60 * 6e4;
1073
1244
  var TREE_FILES = [MANIFEST, "skills/mmi/SKILL.md", "hooks/cursor-hooks.json", "scripts/hook-run.mjs", "scripts/hook-policy.mjs"];
1074
1245
  function cursorPluginsRoot() {
1075
- return (0, import_node_path8.join)((0, import_node_os4.homedir)(), ".cursor", "plugins");
1246
+ return (0, import_node_path9.join)((0, import_node_os4.homedir)(), ".cursor", "plugins");
1076
1247
  }
1077
1248
  function treeHealthy2(root) {
1078
- return TREE_FILES.every((rel) => (0, import_node_fs10.existsSync)((0, import_node_path8.join)(root, ...rel.split("/"))));
1249
+ return TREE_FILES.every((rel) => (0, import_node_fs11.existsSync)((0, import_node_path9.join)(root, ...rel.split("/"))));
1079
1250
  }
1080
1251
  function readJson(root, rel) {
1081
1252
  try {
1082
- return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(root, ...rel.split("/")), "utf8"));
1253
+ return JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path9.join)(root, ...rel.split("/")), "utf8"));
1083
1254
  } catch {
1084
1255
  return null;
1085
1256
  }
@@ -1088,7 +1259,7 @@ function manifest(root) {
1088
1259
  return readJson(root, MANIFEST);
1089
1260
  }
1090
1261
  function probeCursorInstallation() {
1091
- const location = (0, import_node_path8.join)(cursorPluginsRoot(), "local", "mmi");
1262
+ const location = (0, import_node_path9.join)(cursorPluginsRoot(), "local", "mmi");
1092
1263
  const version = manifest(location)?.version ?? null;
1093
1264
  return { version, location, detail: version ? "installed Cursor plugin manifest" : "installed Cursor plugin manifest was unreadable" };
1094
1265
  }
@@ -1103,7 +1274,7 @@ function message(error) {
1103
1274
  }
1104
1275
  function discard(path) {
1105
1276
  try {
1106
- (0, import_node_fs10.rmSync)(path, { recursive: true, force: true });
1277
+ (0, import_node_fs11.rmSync)(path, { recursive: true, force: true });
1107
1278
  } catch {
1108
1279
  }
1109
1280
  }
@@ -1122,7 +1293,7 @@ function cursorHostEvidence(env) {
1122
1293
  if (env[marker]?.trim()) return `env marker ${marker}`;
1123
1294
  }
1124
1295
  if (env.CURSOR_AGENT === "1") return "env marker CURSOR_AGENT=1";
1125
- const result = process.platform === "win32" ? (0, import_node_child_process7.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process7.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
1296
+ const result = process.platform === "win32" ? (0, import_node_child_process8.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process8.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
1126
1297
  if (result.status !== 0 || !(result.stdout ?? "").trim()) return "the process table is unreadable (unsafe to write)";
1127
1298
  for (const line of result.stdout.split(/\r?\n/)) {
1128
1299
  const executable = cursorExecutable(line);
@@ -1131,7 +1302,7 @@ function cursorHostEvidence(env) {
1131
1302
  return null;
1132
1303
  }
1133
1304
  function resolvedPluginRoot(root) {
1134
- return (0, import_node_path8.join)(root).replace(/\\/g, "/");
1305
+ return (0, import_node_path9.join)(root).replace(/\\/g, "/");
1135
1306
  }
1136
1307
  function isMmiEntry(entry, pluginRoot) {
1137
1308
  return typeof entry === "object" && entry !== null && typeof entry.command === "string" && entry.command.includes(resolvedPluginRoot(pluginRoot));
@@ -1141,7 +1312,7 @@ function resolveCommand(command, pluginRoot) {
1141
1312
  }
1142
1313
  function desiredUserHooks(pluginRoot) {
1143
1314
  try {
1144
- const doc = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(pluginRoot, ...PLUGIN_HOOKS_REL), "utf8"));
1315
+ const doc = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path9.join)(pluginRoot, ...PLUGIN_HOOKS_REL), "utf8"));
1145
1316
  const hooks = doc.hooks;
1146
1317
  if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
1147
1318
  const desired = /* @__PURE__ */ new Map();
@@ -1177,14 +1348,14 @@ function mergeUserHooks(existing, desired, pluginRoot) {
1177
1348
  function reconcileCursorUserHooks(pluginRoot, homeDir) {
1178
1349
  const desired = desiredUserHooks(pluginRoot);
1179
1350
  if (!desired) return "source-unreadable";
1180
- const path = (0, import_node_path8.join)(homeDir, ".cursor", USER_HOOKS_FILE);
1181
- if (!(0, import_node_fs10.existsSync)(path)) {
1182
- (0, import_node_fs10.writeFileSync)(path, JSON.stringify({ version: 1, hooks: Object.fromEntries(desired) }, null, 2) + "\n", "utf8");
1351
+ const path = (0, import_node_path9.join)(homeDir, ".cursor", USER_HOOKS_FILE);
1352
+ if (!(0, import_node_fs11.existsSync)(path)) {
1353
+ (0, import_node_fs11.writeFileSync)(path, JSON.stringify({ version: 1, hooks: Object.fromEntries(desired) }, null, 2) + "\n", "utf8");
1183
1354
  return "updated";
1184
1355
  }
1185
1356
  let existing;
1186
1357
  try {
1187
- existing = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
1358
+ existing = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
1188
1359
  } catch {
1189
1360
  return "user-conflict";
1190
1361
  }
@@ -1192,8 +1363,8 @@ function reconcileCursorUserHooks(pluginRoot, homeDir) {
1192
1363
  if (!merged) return "user-conflict";
1193
1364
  if (JSON.stringify(merged) === JSON.stringify(existing)) return "current";
1194
1365
  const tmp = `${path}.tmp-${process.pid}`;
1195
- (0, import_node_fs10.writeFileSync)(tmp, JSON.stringify(merged, null, 2) + "\n", "utf8");
1196
- (0, import_node_fs10.renameSync)(tmp, path);
1366
+ (0, import_node_fs11.writeFileSync)(tmp, JSON.stringify(merged, null, 2) + "\n", "utf8");
1367
+ (0, import_node_fs11.renameSync)(tmp, path);
1197
1368
  return "updated";
1198
1369
  }
1199
1370
  function userHooksDetail(pluginRoot, homeDir) {
@@ -1201,7 +1372,7 @@ function userHooksDetail(pluginRoot, homeDir) {
1201
1372
  case "updated":
1202
1373
  return "; user hooks bridged into ~/.cursor/hooks.json";
1203
1374
  case "user-conflict":
1204
- return `; USER HOOKS CONFLICT at ${(0, import_node_path8.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 Cursor gates are NOT bridged; fix or remove that file and reconcile`;
1375
+ return `; USER HOOKS CONFLICT at ${(0, import_node_path9.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 Cursor gates are NOT bridged; fix or remove that file and reconcile`;
1205
1376
  case "source-unreadable":
1206
1377
  return "; user hooks source unreadable \u2014 Cursor gates are NOT bridged";
1207
1378
  default:
@@ -1209,35 +1380,35 @@ function userHooksDetail(pluginRoot, homeDir) {
1209
1380
  }
1210
1381
  }
1211
1382
  function writeRollbackPointer(root, entry) {
1212
- const path = (0, import_node_path8.join)(root, "quarantine", "rollback.json");
1383
+ const path = (0, import_node_path9.join)(root, "quarantine", "rollback.json");
1213
1384
  const tmp = `${path}.tmp-${process.pid}`;
1214
1385
  try {
1215
- (0, import_node_fs10.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
1216
- (0, import_node_fs10.renameSync)(tmp, path);
1386
+ (0, import_node_fs11.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
1387
+ (0, import_node_fs11.renameSync)(tmp, path);
1217
1388
  } catch {
1218
1389
  discard(tmp);
1219
1390
  }
1220
1391
  }
1221
1392
  function mtimeMs(path) {
1222
1393
  try {
1223
- return (0, import_node_fs10.statSync)(path).mtimeMs;
1394
+ return (0, import_node_fs11.statSync)(path).mtimeMs;
1224
1395
  } catch {
1225
1396
  return 0;
1226
1397
  }
1227
1398
  }
1228
1399
  function pruneQuarantine(root, keep) {
1229
- const dir = (0, import_node_path8.join)(root, "quarantine");
1400
+ const dir = (0, import_node_path9.join)(root, "quarantine");
1230
1401
  let generations;
1231
1402
  try {
1232
- generations = (0, import_node_fs10.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("mmi-")).map((entry) => (0, import_node_path8.join)(dir, entry.name)).sort((a, b) => mtimeMs(b) - mtimeMs(a));
1403
+ generations = (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("mmi-")).map((entry) => (0, import_node_path9.join)(dir, entry.name)).sort((a, b) => mtimeMs(b) - mtimeMs(a));
1233
1404
  } catch {
1234
1405
  return 0;
1235
1406
  }
1236
1407
  let stranded = 0;
1237
1408
  try {
1238
- for (const entry of (0, import_node_fs10.readdirSync)(dir, { withFileTypes: true })) {
1409
+ for (const entry of (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true })) {
1239
1410
  if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
1240
- const path = (0, import_node_path8.join)(dir, entry.name);
1411
+ const path = (0, import_node_path9.join)(dir, entry.name);
1241
1412
  if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
1242
1413
  discard(path);
1243
1414
  stranded += 1;
@@ -1255,16 +1426,16 @@ function pruneQuarantine(root, keep) {
1255
1426
  }
1256
1427
  function restorePrevious(live, quarantined, broken) {
1257
1428
  try {
1258
- (0, import_node_fs10.renameSync)(live, broken);
1429
+ (0, import_node_fs11.renameSync)(live, broken);
1259
1430
  } catch {
1260
1431
  return "kept-broken";
1261
1432
  }
1262
1433
  try {
1263
- (0, import_node_fs10.renameSync)(quarantined, live);
1434
+ (0, import_node_fs11.renameSync)(quarantined, live);
1264
1435
  return "restored";
1265
1436
  } catch {
1266
1437
  try {
1267
- (0, import_node_fs10.renameSync)(broken, live);
1438
+ (0, import_node_fs11.renameSync)(broken, live);
1268
1439
  return "kept-broken";
1269
1440
  } catch {
1270
1441
  return "lost";
@@ -1275,7 +1446,7 @@ function cursorArm(options) {
1275
1446
  const env = options.env ?? process.env;
1276
1447
  const { target, dryRun, paths } = options;
1277
1448
  const root = cursorPluginsRoot();
1278
- const live = (0, import_node_path8.join)(root, "local", "mmi");
1449
+ const live = (0, import_node_path9.join)(root, "local", "mmi");
1279
1450
  const result = (from, to, verdict, detail) => ({
1280
1451
  surface: "cursor",
1281
1452
  from,
@@ -1284,7 +1455,7 @@ function cursorArm(options) {
1284
1455
  detail,
1285
1456
  // C11: the constraint of the generation LEFT at `live` — read at result time, so a failed
1286
1457
  // switch reports the restored generation's declaration, not the rejected one's (#4973).
1287
- compat: readManifestCompat((0, import_node_path8.join)(live, MANIFEST))
1458
+ compat: readManifestCompat((0, import_node_path9.join)(live, MANIFEST))
1288
1459
  });
1289
1460
  const current = manifest(live);
1290
1461
  const installed = current?.version ?? null;
@@ -1298,7 +1469,7 @@ function cursorArm(options) {
1298
1469
  if (installed === target && treeHealthy2(live)) {
1299
1470
  return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os4.homedir)())}`);
1300
1471
  }
1301
- if ((0, import_node_fs10.existsSync)(live) && !mmiOwned(live)) {
1472
+ if ((0, import_node_fs11.existsSync)(live) && !mmiOwned(live)) {
1302
1473
  return result(installed, target, "defer", `${live} carries no MMI ownership marker (plugin manifest name, or a ${PACKAGE} package.json) \u2014 refusing to displace an unmanaged directory (mmi-cli plugin heal adopts it)`);
1303
1474
  }
1304
1475
  if (dryRun) {
@@ -1314,15 +1485,16 @@ function cursorArm(options) {
1314
1485
  }
1315
1486
  const busy = cursorHostEvidence(env);
1316
1487
  if (busy) {
1317
- return result(installed, target, "defer", `Cursor is running (${busy}) \u2014 the plugin tree is only swapped while the host is absent`);
1488
+ const pending = dryRun ? "" : ` \u2014 ${noteBusyDefer(paths, env, { surface: "cursor", target, reason: busy, images: ["Cursor"] })}`;
1489
+ return result(installed, target, "defer", `Cursor is running (${busy}) \u2014 the plugin tree is only swapped while the host is absent${pending}`);
1318
1490
  }
1319
1491
  const stamp = Date.now();
1320
- const incoming = (0, import_node_path8.join)(root, "staging", `mmi-${safeSegment(target)}-${stamp}`);
1321
- const quarantined = (0, import_node_path8.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
1322
- for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs10.mkdirSync)((0, import_node_path8.join)(root, dir), { recursive: true });
1492
+ const incoming = (0, import_node_path9.join)(root, "staging", `mmi-${safeSegment(target)}-${stamp}`);
1493
+ const quarantined = (0, import_node_path9.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
1494
+ for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs11.mkdirSync)((0, import_node_path9.join)(root, dir), { recursive: true });
1323
1495
  try {
1324
1496
  discard(incoming);
1325
- (0, import_node_fs10.cpSync)(staged.packageRoot, incoming, { recursive: true });
1497
+ (0, import_node_fs11.cpSync)(staged.packageRoot, incoming, { recursive: true });
1326
1498
  } catch (error) {
1327
1499
  discard(incoming);
1328
1500
  return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
@@ -1334,28 +1506,31 @@ function cursorArm(options) {
1334
1506
  const started = cursorHostEvidence(env);
1335
1507
  if (started) {
1336
1508
  discard(incoming);
1337
- return result(installed, target, "defer", `Cursor started during staging (${started}) \u2014 the plugin tree is only swapped while the host is absent`);
1509
+ const pending = noteBusyDefer(paths, env, { surface: "cursor", target, reason: started, images: ["Cursor"] });
1510
+ return result(installed, target, "defer", `Cursor started during staging (${started}) \u2014 the plugin tree is only swapped while the host is absent \u2014 ${pending}`);
1338
1511
  }
1339
1512
  let displaced = false;
1340
1513
  try {
1341
- if ((0, import_node_fs10.existsSync)(live)) {
1342
- (0, import_node_fs10.renameSync)(live, quarantined);
1514
+ if ((0, import_node_fs11.existsSync)(live)) {
1515
+ (0, import_node_fs11.renameSync)(live, quarantined);
1343
1516
  displaced = true;
1344
1517
  }
1345
- (0, import_node_fs10.renameSync)(incoming, live);
1518
+ (0, import_node_fs11.renameSync)(incoming, live);
1346
1519
  } catch (error) {
1347
- if (displaced && !(0, import_node_fs10.existsSync)(live)) {
1520
+ if (displaced && !(0, import_node_fs11.existsSync)(live)) {
1348
1521
  try {
1349
- (0, import_node_fs10.renameSync)(quarantined, live);
1522
+ (0, import_node_fs11.renameSync)(quarantined, live);
1350
1523
  } catch {
1351
1524
  }
1352
1525
  }
1353
1526
  discard(incoming);
1354
- return result(installed, target, "defer", `generation switch failed, previous generation retained: ${message(error)}`);
1527
+ const reason = message(error);
1528
+ const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(paths, env, { surface: "cursor", target, reason, images: ["Cursor"] })}` : "";
1529
+ return result(installed, target, "defer", `generation switch failed, previous generation retained: ${reason}${pending}`);
1355
1530
  }
1356
1531
  const evidence = manifest(live)?.version;
1357
1532
  if (evidence !== target || !treeHealthy2(live)) {
1358
- const broken = `${(0, import_node_path8.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
1533
+ const broken = `${(0, import_node_path9.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
1359
1534
  const outcome = displaced ? restorePrevious(live, quarantined, broken) : "none";
1360
1535
  const detail = outcome === "restored" ? `restored the previous generation ${installed ?? "unknown"}, unverified tree kept at ${broken}` : outcome === "lost" ? `restore failed and ${live} is missing \u2014 the previous generation is at ${quarantined}` : displaced ? `restore failed, the unverified tree is still live and the previous generation is at ${quarantined}` : "nothing was displaced \u2014 there is no previous generation to restore";
1361
1536
  writeRollbackPointer(root, outcome === "restored" ? { installed: installed ?? "unknown", previous: null, generation: null } : { installed: evidence ?? "unknown", previous: installed, generation: displaced ? quarantined : null });
@@ -1368,8 +1543,8 @@ function cursorArm(options) {
1368
1543
  }
1369
1544
 
1370
1545
  // src/arm-jervcode.ts
1371
- var import_node_fs11 = require("node:fs");
1372
- var import_node_path9 = require("node:path");
1546
+ var import_node_fs12 = require("node:fs");
1547
+ var import_node_path10 = require("node:path");
1373
1548
  var PI_PACKAGE = "@mutmutco/pi-plugin";
1374
1549
  function hostEnv(env) {
1375
1550
  const dir = jervcodeAgentDir(env);
@@ -1380,7 +1555,7 @@ function hostCommands(_env) {
1380
1555
  }
1381
1556
  function readPackageSpecs(env) {
1382
1557
  try {
1383
- const settings = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path9.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
1558
+ const settings = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
1384
1559
  return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
1385
1560
  } catch {
1386
1561
  return null;
@@ -1388,14 +1563,14 @@ function readPackageSpecs(env) {
1388
1563
  }
1389
1564
  function installedVersion(env) {
1390
1565
  try {
1391
- const manifest2 = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
1566
+ const manifest2 = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
1392
1567
  return typeof manifest2.version === "string" ? manifest2.version : null;
1393
1568
  } catch {
1394
1569
  return null;
1395
1570
  }
1396
1571
  }
1397
1572
  function probeJervcodeInstallation(env = process.env) {
1398
- const location = (0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
1573
+ const location = (0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
1399
1574
  const version = installedVersion(env);
1400
1575
  return { version, location, detail: version ? "materialized pi package manifest" : "materialized pi package manifest was unreadable" };
1401
1576
  }
@@ -1417,32 +1592,32 @@ function jervcodeArm(options) {
1417
1592
  detail,
1418
1593
  // C11: the constraint of the package pi LEFT materialised — read at result time, so defer/fail
1419
1594
  // carry the previous payload's declaration (#4973 reconsult ruling).
1420
- compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
1595
+ compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
1421
1596
  });
1422
1597
  const before = readPackageSpecs(env);
1423
- if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path9.join)(jervcodeAgentDir(env), "settings.json")}`);
1598
+ if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json")}`);
1424
1599
  const from = installedVersion(env);
1425
1600
  const legacy = before.filter(isLegacyMmiPiPath);
1426
1601
  const highWater = journalHighWater(options.paths.journalPath, "jervcode");
1427
1602
  const floor = from && compareSemver(from, options.target) > 0 ? `installed ${from} is above candidate ${options.target} (monotonic)` : highWater && compareSemver(highWater, options.target) > 0 ? `journal high-water ${highWater} is above candidate ${options.target} (monotonic)` : null;
1428
1603
  if (options.dryRun) {
1429
1604
  const action = floor ? `hold ${from ?? "the install"} and skip the install (${floor})` : before.includes(exact) && from === options.target ? "keep exact pin" : `install ${exact}`;
1430
- return { surface: "jervcode", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacy.length ? ` and retire ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : ""}`, compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1605
+ return { surface: "jervcode", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacy.length ? ` and retire ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : ""}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1431
1606
  }
1432
1607
  const hosted = hostEnv(env);
1433
1608
  const commands = hostCommands(env);
1434
1609
  const command = commands.find((bin) => runner(bin, ["--version"], hosted).status === 0);
1435
- if (!command) return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: "neither `jervcode` nor `pi` answered --version \u2014 the host CLI is not on PATH", compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1610
+ if (!command) return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: "neither `jervcode` nor `pi` answered --version \u2014 the host CLI is not on PATH", compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1436
1611
  if (!floor && (!before.includes(exact) || from !== options.target)) {
1437
1612
  const install = runner(command, ["install", exact], hosted);
1438
1613
  if (install.status !== 0) {
1439
- return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1614
+ return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1440
1615
  }
1441
1616
  }
1442
1617
  for (const stale of legacy) {
1443
1618
  const removed = runner(command, ["remove", stale], hosted);
1444
1619
  if (removed.status !== 0) {
1445
- return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1620
+ return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1446
1621
  }
1447
1622
  }
1448
1623
  const after = readPackageSpecs(env);
@@ -1450,7 +1625,7 @@ function jervcodeArm(options) {
1450
1625
  const legacyAfter = after?.filter(isLegacyMmiPiPath) ?? [];
1451
1626
  const retired = legacy.length ? `; retired ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : "";
1452
1627
  if (floor && !legacyAfter.length) {
1453
- return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1628
+ return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1454
1629
  }
1455
1630
  if (!after?.includes(exact) || converged !== options.target || legacyAfter.length) {
1456
1631
  return {
@@ -1471,14 +1646,15 @@ function jervcodeArm(options) {
1471
1646
  }
1472
1647
 
1473
1648
  // src/arm-hermes.ts
1474
- var import_node_fs12 = require("node:fs");
1475
- var import_node_path10 = require("node:path");
1649
+ var import_node_fs13 = require("node:fs");
1650
+ var import_node_child_process9 = require("node:child_process");
1651
+ var import_node_path11 = require("node:path");
1476
1652
  var PACKAGE2 = "@mutmutco/hermes-plugin";
1477
1653
  var MANIFEST2 = "plugin.yaml";
1478
1654
  var TREE_MARKERS2 = [MANIFEST2, "__init__.py", "skills"];
1479
1655
  function yamlVersion(root) {
1480
1656
  try {
1481
- const text = (0, import_node_fs12.readFileSync)((0, import_node_path10.join)(root, MANIFEST2), "utf8");
1657
+ const text = (0, import_node_fs13.readFileSync)((0, import_node_path11.join)(root, MANIFEST2), "utf8");
1482
1658
  try {
1483
1659
  const version = JSON.parse(text).version;
1484
1660
  if (typeof version === "string" && version.trim()) return version.trim();
@@ -1491,9 +1667,9 @@ function yamlVersion(root) {
1491
1667
  }
1492
1668
  }
1493
1669
  function treeHealthy3(root) {
1494
- return TREE_MARKERS2.slice(0, 2).every((file) => (0, import_node_fs12.existsSync)((0, import_node_path10.join)(root, file))) && (() => {
1670
+ return TREE_MARKERS2.slice(0, 2).every((file) => (0, import_node_fs13.existsSync)((0, import_node_path11.join)(root, file))) && (() => {
1495
1671
  try {
1496
- return (0, import_node_fs12.statSync)((0, import_node_path10.join)(root, "skills")).isDirectory();
1672
+ return (0, import_node_fs13.statSync)((0, import_node_path11.join)(root, "skills")).isDirectory();
1497
1673
  } catch {
1498
1674
  return false;
1499
1675
  }
@@ -1501,13 +1677,13 @@ function treeHealthy3(root) {
1501
1677
  }
1502
1678
  function packageIdentity(root) {
1503
1679
  try {
1504
- return JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(root, "package.json"), "utf8")).name === PACKAGE2;
1680
+ return JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path11.join)(root, "package.json"), "utf8")).name === PACKAGE2;
1505
1681
  } catch {
1506
1682
  return false;
1507
1683
  }
1508
1684
  }
1509
1685
  function probeHermesInstallation(env = process.env) {
1510
- const location = (0, import_node_path10.join)(hermesHome(env), "plugins", "mmi");
1686
+ const location = (0, import_node_path11.join)(hermesHome(env), "plugins", "mmi");
1511
1687
  const version = yamlVersion(location);
1512
1688
  return { version, location, detail: version ? "installed Hermes plugin manifest" : "installed Hermes plugin manifest was unreadable" };
1513
1689
  }
@@ -1516,27 +1692,45 @@ function mmiOwned2(root) {
1516
1692
  }
1517
1693
  function discard2(path) {
1518
1694
  try {
1519
- (0, import_node_fs12.rmSync)(path, { recursive: true, force: true });
1695
+ (0, import_node_fs13.rmSync)(path, { recursive: true, force: true });
1520
1696
  } catch {
1521
1697
  }
1522
1698
  }
1523
1699
  function message2(error) {
1524
1700
  return String(error?.message ?? error).replace(/\s+/g, " ").slice(0, 200);
1525
1701
  }
1702
+ var HERMES_PROCESS_SEGMENT = /^hermes(-cli|-code)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/i;
1703
+ function hermesProcessToken(line, home) {
1704
+ const normalized = line.replace(/["']/g, " ");
1705
+ if (home && normalized.toLowerCase().includes(home.toLowerCase())) return home;
1706
+ for (const token of normalized.split(/\s+/)) {
1707
+ if (token && token.split(/[/\\]/).some((segment) => HERMES_PROCESS_SEGMENT.test(segment))) return token;
1708
+ }
1709
+ return null;
1710
+ }
1711
+ function hermesHostEvidence(env, home) {
1712
+ const result = process.platform === "win32" ? (0, import_node_child_process9.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process9.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
1713
+ if (result.status !== 0 || !(result.stdout ?? "").trim()) return "the process list is unreadable (unsafe to write)";
1714
+ for (const line of result.stdout.split(/\r?\n/)) {
1715
+ const token = hermesProcessToken(line, home);
1716
+ if (token) return `a live process names hermes (${token})`;
1717
+ }
1718
+ return null;
1719
+ }
1526
1720
  function materializeHermesSkills(home) {
1527
- const pluginSkills = (0, import_node_path10.join)(home, "plugins", "mmi", "skills");
1528
- const skillsRoot = (0, import_node_path10.join)(home, "skills");
1529
- const targetRoot = (0, import_node_path10.join)(skillsRoot, "mmi");
1721
+ const pluginSkills = (0, import_node_path11.join)(home, "plugins", "mmi", "skills");
1722
+ const skillsRoot = (0, import_node_path11.join)(home, "skills");
1723
+ const targetRoot = (0, import_node_path11.join)(skillsRoot, "mmi");
1530
1724
  try {
1531
- if (!(0, import_node_fs12.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
1532
- const names = (0, import_node_fs12.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
1725
+ if (!(0, import_node_fs13.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
1726
+ const names = (0, import_node_fs13.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
1533
1727
  if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
1534
- (0, import_node_fs12.mkdirSync)(skillsRoot, { recursive: true });
1535
- const incoming = (0, import_node_path10.join)(skillsRoot, ".mmi-incoming");
1728
+ (0, import_node_fs13.mkdirSync)(skillsRoot, { recursive: true });
1729
+ const incoming = (0, import_node_path11.join)(skillsRoot, ".mmi-incoming");
1536
1730
  discard2(incoming);
1537
- (0, import_node_fs12.cpSync)(pluginSkills, incoming, { recursive: true });
1731
+ (0, import_node_fs13.cpSync)(pluginSkills, incoming, { recursive: true });
1538
1732
  discard2(targetRoot);
1539
- (0, import_node_fs12.renameSync)(incoming, targetRoot);
1733
+ (0, import_node_fs13.renameSync)(incoming, targetRoot);
1540
1734
  return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
1541
1735
  } catch (error) {
1542
1736
  return { ok: false, detail: message2(error) };
@@ -1545,8 +1739,8 @@ function materializeHermesSkills(home) {
1545
1739
  function hermesArm(options) {
1546
1740
  const env = options.env ?? process.env;
1547
1741
  const home = hermesHome(env);
1548
- const parent = (0, import_node_path10.join)(home, "plugins");
1549
- const live = (0, import_node_path10.join)(parent, "mmi");
1742
+ const parent = (0, import_node_path11.join)(home, "plugins");
1743
+ const live = (0, import_node_path11.join)(parent, "mmi");
1550
1744
  const installed = yamlVersion(live);
1551
1745
  const result = (from, to, verdict, detail) => ({
1552
1746
  surface: "hermes",
@@ -1555,8 +1749,8 @@ function hermesArm(options) {
1555
1749
  verdict,
1556
1750
  detail
1557
1751
  });
1558
- if (!(0, import_node_fs12.existsSync)(home)) return result(null, options.target, "skip", `absent (${home}) \u2014 skipped without writes`);
1559
- if ((0, import_node_fs12.existsSync)(live) && !mmiOwned2(live)) {
1752
+ if (!(0, import_node_fs13.existsSync)(home)) return result(null, options.target, "skip", `absent (${home}) \u2014 skipped without writes`);
1753
+ if ((0, import_node_fs13.existsSync)(live) && !mmiOwned2(live)) {
1560
1754
  return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
1561
1755
  }
1562
1756
  if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
@@ -1567,21 +1761,21 @@ function hermesArm(options) {
1567
1761
  if (!skills2.ok) return result(installed, options.target, "fail", `plugin at target ${options.target}, but skills provisioning failed: ${skills2.detail}`);
1568
1762
  return result(installed, options.target, "ok", `already at target ${options.target}; ${skills2.detail}`);
1569
1763
  }
1570
- if (options.dryRun) return result(installed, options.target, "ok", `dry-run: would stage ${PACKAGE2}@${options.target}, atomically switch ${installed ?? "absent"} -> ${options.target}, then provision skills into ${(0, import_node_path10.join)(home, "skills", "mmi")}`);
1764
+ if (options.dryRun) return result(installed, options.target, "ok", `dry-run: would stage ${PACKAGE2}@${options.target}, atomically switch ${installed ?? "absent"} -> ${options.target}, then provision skills into ${(0, import_node_path11.join)(home, "skills", "mmi")}`);
1571
1765
  const staged = stagePackage({ surface: "hermes", packageName: PACKAGE2, target: options.target, witness: MANIFEST2, paths: options.paths, env });
1572
1766
  if ("defer" in staged) return result(installed, options.target, "defer", staged.defer);
1573
1767
  if (yamlVersion(staged.packageRoot) !== options.target || !treeHealthy3(staged.packageRoot)) {
1574
1768
  return result(installed, options.target, "defer", `staged Hermes manifest is ${yamlVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${options.target})`);
1575
1769
  }
1576
- const scratch = (0, import_node_path10.join)(home, ".mmi-updater");
1577
- const incomingRoot = (0, import_node_path10.join)(scratch, "incoming");
1578
- const incoming = (0, import_node_path10.join)(incomingRoot, options.target);
1579
- const quarantine = (0, import_node_path10.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
1770
+ const scratch = (0, import_node_path11.join)(home, ".mmi-updater");
1771
+ const incomingRoot = (0, import_node_path11.join)(scratch, "incoming");
1772
+ const incoming = (0, import_node_path11.join)(incomingRoot, options.target);
1773
+ const quarantine = (0, import_node_path11.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
1580
1774
  try {
1581
- (0, import_node_fs12.mkdirSync)(parent, { recursive: true });
1582
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.join)(scratch, "quarantine"), { recursive: true });
1775
+ (0, import_node_fs13.mkdirSync)(parent, { recursive: true });
1776
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.join)(scratch, "quarantine"), { recursive: true });
1583
1777
  discard2(incomingRoot);
1584
- (0, import_node_fs12.cpSync)(staged.packageRoot, incoming, { recursive: true });
1778
+ (0, import_node_fs13.cpSync)(staged.packageRoot, incoming, { recursive: true });
1585
1779
  } catch (error) {
1586
1780
  return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
1587
1781
  }
@@ -1589,24 +1783,32 @@ function hermesArm(options) {
1589
1783
  discard2(incomingRoot);
1590
1784
  return result(installed, options.target, "defer", "incoming Hermes generation did not reproduce the verified payload");
1591
1785
  }
1592
- const displaced = (0, import_node_fs12.existsSync)(live);
1786
+ const busy = hermesHostEvidence(env, home);
1787
+ if (busy) {
1788
+ discard2(incomingRoot);
1789
+ const pending = noteBusyDefer(options.paths, env, { surface: "hermes", target: options.target, reason: busy, images: ["hermes"], dirs: [home] });
1790
+ return result(installed, options.target, "defer", `Hermes may be running \u2014 the plugin tree is only swapped while the host is absent: ${busy} \u2014 ${pending}`);
1791
+ }
1792
+ const displaced = (0, import_node_fs13.existsSync)(live);
1593
1793
  try {
1594
- if (displaced) (0, import_node_fs12.renameSync)(live, quarantine);
1595
- (0, import_node_fs12.renameSync)(incoming, live);
1794
+ if (displaced) (0, import_node_fs13.renameSync)(live, quarantine);
1795
+ (0, import_node_fs13.renameSync)(incoming, live);
1596
1796
  } catch (error) {
1597
- if (displaced && !(0, import_node_fs12.existsSync)(live)) try {
1598
- (0, import_node_fs12.renameSync)(quarantine, live);
1797
+ if (displaced && !(0, import_node_fs13.existsSync)(live)) try {
1798
+ (0, import_node_fs13.renameSync)(quarantine, live);
1599
1799
  } catch {
1600
1800
  }
1601
1801
  discard2(incomingRoot);
1602
- return result(installed, options.target, "defer", `Hermes generation switch failed; previous generation retained: ${message2(error)}`);
1802
+ const reason = message2(error);
1803
+ const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "hermes", target: options.target, reason, images: ["hermes"], dirs: [home] })}` : "";
1804
+ return result(installed, options.target, "defer", `Hermes generation switch failed; previous generation retained: ${reason}${pending}`);
1603
1805
  }
1604
1806
  if (yamlVersion(live) !== options.target || !treeHealthy3(live)) {
1605
- const broken = (0, import_node_path10.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
1807
+ const broken = (0, import_node_path11.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
1606
1808
  try {
1607
- (0, import_node_fs12.mkdirSync)((0, import_node_path10.join)(scratch, "rejected"), { recursive: true });
1608
- (0, import_node_fs12.renameSync)(live, broken);
1609
- if (displaced) (0, import_node_fs12.renameSync)(quarantine, live);
1809
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.join)(scratch, "rejected"), { recursive: true });
1810
+ (0, import_node_fs13.renameSync)(live, broken);
1811
+ if (displaced) (0, import_node_fs13.renameSync)(quarantine, live);
1610
1812
  } catch (error) {
1611
1813
  return result(installed, yamlVersion(live) ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
1612
1814
  }
@@ -1619,9 +1821,9 @@ function hermesArm(options) {
1619
1821
  }
1620
1822
 
1621
1823
  // src/reap.ts
1622
- var import_node_child_process8 = require("node:child_process");
1623
- var import_node_fs13 = require("node:fs");
1624
- var import_node_path11 = require("node:path");
1824
+ var import_node_child_process10 = require("node:child_process");
1825
+ var import_node_fs14 = require("node:fs");
1826
+ var import_node_path12 = require("node:path");
1625
1827
  var KEEP_GENERATIONS2 = 2;
1626
1828
  var SCRATCH_AGE_MS = 24 * 60 * 6e4;
1627
1829
  var ORPHAN_MIN_AGE_MS = 10 * 6e4;
@@ -1629,7 +1831,7 @@ var SNAPSHOT_TIMEOUT_MS = 2e4;
1629
1831
  var MAX_UNWIND_ROUNDS = 4;
1630
1832
  function listDirs(path) {
1631
1833
  try {
1632
- return (0, import_node_fs13.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1834
+ return (0, import_node_fs14.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1633
1835
  } catch {
1634
1836
  return [];
1635
1837
  }
@@ -1637,7 +1839,7 @@ function listDirs(path) {
1637
1839
  function generationSets(root) {
1638
1840
  const children = listDirs(root);
1639
1841
  if (children.some((name) => parseSemver(name))) return [root];
1640
- return children.map((name) => (0, import_node_path11.join)(root, name)).filter((path) => listDirs(path).some((name) => parseSemver(name)));
1842
+ return children.map((name) => (0, import_node_path12.join)(root, name)).filter((path) => listDirs(path).some((name) => parseSemver(name)));
1641
1843
  }
1642
1844
  function pruneGenerations(root, category, dryRun) {
1643
1845
  let reaped = 0;
@@ -1650,7 +1852,7 @@ function pruneGenerations(root, category, dryRun) {
1650
1852
  continue;
1651
1853
  }
1652
1854
  try {
1653
- (0, import_node_fs13.rmSync)((0, import_node_path11.join)(set, version), { recursive: true, force: true });
1855
+ (0, import_node_fs14.rmSync)((0, import_node_path12.join)(set, version), { recursive: true, force: true });
1654
1856
  reaped += 1;
1655
1857
  } catch (error) {
1656
1858
  failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
@@ -1664,8 +1866,8 @@ function pruneGenerations(root, category, dryRun) {
1664
1866
  }
1665
1867
  var LEASE_STEAL_SCRATCH = /^lease\.lock\.stale-\d+$/;
1666
1868
  function atomicWriteScratch(path) {
1667
- const literal = (0, import_node_path11.basename)(path).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1668
- return { dir: (0, import_node_path11.dirname)(path), pattern: new RegExp(`^${literal}\\.tmp-\\d+$`) };
1869
+ const literal = (0, import_node_path12.basename)(path).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1870
+ return { dir: (0, import_node_path12.dirname)(path), pattern: new RegExp(`^${literal}\\.tmp-\\d+$`) };
1669
1871
  }
1670
1872
  function reapScratch(context, dryRun) {
1671
1873
  const category = "scratch";
@@ -1683,7 +1885,7 @@ function reapScratch(context, dryRun) {
1683
1885
  for (const [dir, patterns] of scanned) {
1684
1886
  let entries;
1685
1887
  try {
1686
- entries = (0, import_node_fs13.readdirSync)(dir, { withFileTypes: true });
1888
+ entries = (0, import_node_fs14.readdirSync)(dir, { withFileTypes: true });
1687
1889
  } catch {
1688
1890
  unreadable.push(dir);
1689
1891
  continue;
@@ -1691,10 +1893,10 @@ function reapScratch(context, dryRun) {
1691
1893
  for (const entry of entries) {
1692
1894
  if (!patterns.some((pattern) => pattern.test(entry.name))) continue;
1693
1895
  if (!entry.isFile()) continue;
1694
- const path = (0, import_node_path11.join)(dir, entry.name);
1896
+ const path = (0, import_node_path12.join)(dir, entry.name);
1695
1897
  let ageMs;
1696
1898
  try {
1697
- ageMs = now - (0, import_node_fs13.statSync)(path).mtimeMs;
1899
+ ageMs = now - (0, import_node_fs14.statSync)(path).mtimeMs;
1698
1900
  } catch {
1699
1901
  continue;
1700
1902
  }
@@ -1704,7 +1906,7 @@ function reapScratch(context, dryRun) {
1704
1906
  continue;
1705
1907
  }
1706
1908
  try {
1707
- (0, import_node_fs13.rmSync)(path, { force: true });
1909
+ (0, import_node_fs14.rmSync)(path, { force: true });
1708
1910
  reaped += 1;
1709
1911
  } catch (error) {
1710
1912
  failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
@@ -1820,7 +2022,7 @@ function stillTheProvenProcess(rows, kill) {
1820
2022
  }
1821
2023
  var SNAPSHOT_COMMAND = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine,@{n='Started';e={if ($_.CreationDate) { $_.CreationDate.ToUniversalTime().ToString('o') }}} | ConvertTo-Json -Compress";
1822
2024
  function processSnapshot() {
1823
- const result = (0, import_node_child_process8.spawnSync)("powershell.exe", ["-NoProfile", "-Command", SNAPSHOT_COMMAND], { encoding: "utf8", windowsHide: true, timeout: SNAPSHOT_TIMEOUT_MS });
2025
+ const result = (0, import_node_child_process10.spawnSync)("powershell.exe", ["-NoProfile", "-Command", SNAPSHOT_COMMAND], { encoding: "utf8", windowsHide: true, timeout: SNAPSHOT_TIMEOUT_MS });
1824
2026
  if (result.status !== 0 || !(result.stdout ?? "").trim()) return null;
1825
2027
  try {
1826
2028
  const parsed = JSON.parse(result.stdout);
@@ -1944,12 +2146,20 @@ function reconcile(options) {
1944
2146
  if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
1945
2147
  journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat });
1946
2148
  options.onArm?.(arm);
2149
+ if (arm.verdict === "ok" && !options.dryRun) clearPending(paths, arm.surface);
1947
2150
  if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
1948
2151
  if (arm.verdict === "fail") summary.exit = Math.max(summary.exit, 3);
1949
2152
  };
1950
2153
  try {
1951
2154
  say("acquiring single-writer lease");
1952
2155
  return withLease(paths.leasePath, () => {
2156
+ if (!options.dryRun) {
2157
+ const pendingBefore = readPending(paths);
2158
+ if (pendingBefore.length) {
2159
+ const resumed = resumePendingWatcher(paths, env);
2160
+ journal({ kind: "pending", verdict: "ok", count: pendingBefore.length, detail: resumed.scheduled ? `${pendingBefore.length} pending appl${pendingBefore.length === 1 ? "y" : "ies"}; post-exit watcher pid ${resumed.pid} waiting` : `${pendingBefore.length} pending appl${pendingBefore.length === 1 ? "y" : "ies"}; ${resumed.reason}` });
2161
+ }
2162
+ }
1953
2163
  summary.reap = reap({ paths, dryRun: options.dryRun, env });
1954
2164
  for (const row of summary.reap) journal({ kind: "reap", surface: row.category, verdict: row.verdict, count: row.reaped, detail: row.detail });
1955
2165
  for (const row of summary.reap.filter((entry) => entry.verdict !== "skip")) say(`reap ${row.category}: ${row.verdict}${row.detail ? " \u2014 " + row.detail : ""}`);
@@ -2043,10 +2253,10 @@ function reconcile(options) {
2043
2253
  }
2044
2254
 
2045
2255
  // src/scheduler.ts
2046
- var import_node_child_process9 = require("node:child_process");
2047
- var import_node_fs14 = require("node:fs");
2256
+ var import_node_child_process11 = require("node:child_process");
2257
+ var import_node_fs15 = require("node:fs");
2048
2258
  var import_node_os5 = require("node:os");
2049
- var import_node_path12 = require("node:path");
2259
+ var import_node_path13 = require("node:path");
2050
2260
  var TASK_NAME = "MMI Fleet Updater";
2051
2261
  var LAUNCHER_FILE = "reconcile-hidden.vbs";
2052
2262
  var RUN_BOUND = "PT15M";
@@ -2062,15 +2272,15 @@ var SYSTEMD_UNIT = "mmi-hub-updater";
2062
2272
  var SYSTEMD_SERVICE_NAME = `${SYSTEMD_UNIT}.service`;
2063
2273
  var SYSTEMD_TIMER_NAME = `${SYSTEMD_UNIT}.timer`;
2064
2274
  function schtasks(args) {
2065
- const result = (0, import_node_child_process9.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
2275
+ const result = (0, import_node_child_process11.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
2066
2276
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
2067
2277
  }
2068
2278
  function launchctl(args) {
2069
- const result = (0, import_node_child_process9.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
2279
+ const result = (0, import_node_child_process11.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
2070
2280
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
2071
2281
  }
2072
2282
  function systemctlUser(args) {
2073
- const result = (0, import_node_child_process9.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
2283
+ const result = (0, import_node_child_process11.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
2074
2284
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
2075
2285
  }
2076
2286
  function uid() {
@@ -2095,7 +2305,7 @@ WScript.Quit code
2095
2305
  `;
2096
2306
  }
2097
2307
  function loginPath(fallback = process.env.PATH ?? "") {
2098
- const probe = (0, import_node_child_process9.spawnSync)("/bin/sh", ["-lc", 'printf %s "$PATH"'], { encoding: "utf8", windowsHide: true, timeout: 1e4 });
2308
+ const probe = (0, import_node_child_process11.spawnSync)("/bin/sh", ["-lc", 'printf %s "$PATH"'], { encoding: "utf8", windowsHide: true, timeout: 1e4 });
2099
2309
  const captured = (probe.status === 0 ? probe.stdout ?? "" : "").trim();
2100
2310
  const source = captured || fallback;
2101
2311
  const durable = source.split(":").filter((entry) => entry && !/^\/(?:private\/)?(?:tmp|var\/folders)\//.test(entry));
@@ -2204,7 +2414,7 @@ WantedBy=timers.target
2204
2414
  }
2205
2415
  function readFileOrNull(path) {
2206
2416
  try {
2207
- return (0, import_node_fs14.readFileSync)(path, "utf8");
2417
+ return (0, import_node_fs15.readFileSync)(path, "utf8");
2208
2418
  } catch {
2209
2419
  return null;
2210
2420
  }
@@ -2216,7 +2426,7 @@ function elevatedRelaunch(mode, env) {
2216
2426
  if (env.MMI_UPDATER_ELEVATED_RETRY === "1") return false;
2217
2427
  const self = process.argv[1];
2218
2428
  const command = self ? `"${process.execPath}" "${self}" autoupdate ${mode}` : `mmi-hub autoupdate ${mode}`;
2219
- const relaunch = (0, import_node_child_process9.spawnSync)(
2429
+ const relaunch = (0, import_node_child_process11.spawnSync)(
2220
2430
  "powershell.exe",
2221
2431
  ["-NoProfile", "-Command", `Start-Process -Verb RunAs -Wait -WindowStyle Hidden -FilePath 'cmd.exe' -ArgumentList '/c',${psQuote(`set MMI_UPDATER_ELEVATED_RETRY=1&& ${command}`)}`],
2222
2432
  { encoding: "utf8", windowsHide: true }
@@ -2227,7 +2437,7 @@ var NEVER_RUN_RESULT = 267011;
2227
2437
  var NEVER_RUN_YEAR = 1999;
2228
2438
  var EMPTY_RUN_INFO = { lastRunTime: null, lastTaskResult: null, nextRunTime: null };
2229
2439
  function taskRunInfo() {
2230
- const result = (0, import_node_child_process9.spawnSync)(
2440
+ const result = (0, import_node_child_process11.spawnSync)(
2231
2441
  "powershell.exe",
2232
2442
  ["-NoProfile", "-Command", `$stamp = { param($t) if ($t) { $t.ToString('yyyy-MM-dd HH:mm:ss') } }; Get-ScheduledTaskInfo -TaskName '${TASK_NAME}' | Select-Object @{n='LastRunTime';e={& $stamp $_.LastRunTime}},LastTaskResult,@{n='NextRunTime';e={& $stamp $_.NextRunTime}} | ConvertTo-Json -Compress`],
2233
2443
  { encoding: "utf8", windowsHide: true, timeout: 3e4 }
@@ -2253,7 +2463,7 @@ function windowsSchedulerStatus(env) {
2253
2463
  return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${TASK_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
2254
2464
  }
2255
2465
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
2256
- const launcherPath = (0, import_node_path12.join)(statePaths(env).root, LAUNCHER_FILE);
2466
+ const launcherPath = (0, import_node_path13.join)(statePaths(env).root, LAUNCHER_FILE);
2257
2467
  const info = taskRunInfo();
2258
2468
  const runInfo = info ?? EMPTY_RUN_INFO;
2259
2469
  const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
@@ -2278,13 +2488,13 @@ function windowsSchedulerStatus(env) {
2278
2488
  return { ok: true, supported: true, enabled: true, ...runInfo, detail: `"${TASK_NAME}" registered ${CADENCE}; ${points}; ${ran}; next ${runInfo.nextRunTime ?? "unscheduled"}` };
2279
2489
  }
2280
2490
  function darwinSchedulerStatus(env) {
2281
- const plistPath = (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
2491
+ const plistPath = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
2282
2492
  const plist = readFileOrNull(plistPath);
2283
2493
  if (!plist) {
2284
2494
  return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
2285
2495
  }
2286
2496
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
2287
- const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
2497
+ const launcherPath = (0, import_node_path13.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
2288
2498
  const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
2289
2499
  if (!plist.includes(launcherPath)) {
2290
2500
  return fail(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
@@ -2304,15 +2514,15 @@ function darwinSchedulerStatus(env) {
2304
2514
  return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
2305
2515
  }
2306
2516
  function linuxSchedulerStatus(env) {
2307
- const unitDir = (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
2308
- const timerPath = (0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME);
2517
+ const unitDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
2518
+ const timerPath = (0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME);
2309
2519
  const timer = readFileOrNull(timerPath);
2310
- const service = readFileOrNull((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME));
2520
+ const service = readFileOrNull((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME));
2311
2521
  if (!timer || !service) {
2312
2522
  return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
2313
2523
  }
2314
2524
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
2315
- const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
2525
+ const launcherPath = (0, import_node_path13.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
2316
2526
  const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
2317
2527
  if (!service.includes(launcherPath)) {
2318
2528
  return fail(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
@@ -2351,10 +2561,10 @@ function installWindowsTask(env) {
2351
2561
  const paths = ensureState(env);
2352
2562
  const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
2353
2563
  if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
2354
- const launcherPath = (0, import_node_path12.join)(paths.root, LAUNCHER_FILE);
2355
- (0, import_node_fs14.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
2356
- const xmlPath = (0, import_node_path12.join)(paths.root, "task.xml");
2357
- (0, import_node_fs14.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
2564
+ const launcherPath = (0, import_node_path13.join)(paths.root, LAUNCHER_FILE);
2565
+ (0, import_node_fs15.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
2566
+ const xmlPath = (0, import_node_path13.join)(paths.root, "task.xml");
2567
+ (0, import_node_fs15.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
2358
2568
  const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
2359
2569
  if (create.status !== 0) {
2360
2570
  const denied = /access is denied/i.test(create.stderr + create.stdout);
@@ -2370,11 +2580,11 @@ function refreshPosixLauncher(env) {
2370
2580
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
2371
2581
  if (!hubDist) return null;
2372
2582
  const paths = ensureState(env);
2373
- const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
2583
+ const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
2374
2584
  const desired = launcherSh(process.execPath, hubDist);
2375
2585
  if (readFileOrNull(launcherPath) === desired) return null;
2376
- (0, import_node_fs14.writeFileSync)(launcherPath, desired, "utf8");
2377
- (0, import_node_fs14.chmodSync)(launcherPath, 493);
2586
+ (0, import_node_fs15.writeFileSync)(launcherPath, desired, "utf8");
2587
+ (0, import_node_fs15.chmodSync)(launcherPath, 493);
2378
2588
  return launcherPath;
2379
2589
  }
2380
2590
  function installLaunchd(env) {
@@ -2386,13 +2596,13 @@ function installLaunchd(env) {
2386
2596
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
2387
2597
  if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
2388
2598
  const paths = ensureState(env);
2389
- const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
2390
- (0, import_node_fs14.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
2391
- (0, import_node_fs14.chmodSync)(launcherPath, 493);
2392
- const agentsDir = (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents");
2393
- (0, import_node_fs14.mkdirSync)(agentsDir, { recursive: true });
2394
- const plistPath = (0, import_node_path12.join)(agentsDir, LAUNCHD_PLIST_NAME);
2395
- (0, import_node_fs14.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
2599
+ const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
2600
+ (0, import_node_fs15.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
2601
+ (0, import_node_fs15.chmodSync)(launcherPath, 493);
2602
+ const agentsDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents");
2603
+ (0, import_node_fs15.mkdirSync)(agentsDir, { recursive: true });
2604
+ const plistPath = (0, import_node_path13.join)(agentsDir, LAUNCHD_PLIST_NAME);
2605
+ (0, import_node_fs15.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
2396
2606
  launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
2397
2607
  const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
2398
2608
  if (load.status !== 0) {
@@ -2410,13 +2620,13 @@ function installSystemdTimer(env) {
2410
2620
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
2411
2621
  if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
2412
2622
  const paths = ensureState(env);
2413
- const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
2414
- (0, import_node_fs14.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
2415
- (0, import_node_fs14.chmodSync)(launcherPath, 493);
2416
- const unitDir = (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
2417
- (0, import_node_fs14.mkdirSync)(unitDir, { recursive: true });
2418
- (0, import_node_fs14.writeFileSync)((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
2419
- (0, import_node_fs14.writeFileSync)((0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
2623
+ const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
2624
+ (0, import_node_fs15.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
2625
+ (0, import_node_fs15.chmodSync)(launcherPath, 493);
2626
+ const unitDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
2627
+ (0, import_node_fs15.mkdirSync)(unitDir, { recursive: true });
2628
+ (0, import_node_fs15.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
2629
+ (0, import_node_fs15.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
2420
2630
  const reload = systemctlUser(["daemon-reload"]);
2421
2631
  if (reload.status !== 0) {
2422
2632
  return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
@@ -2454,28 +2664,28 @@ function uninstallWindowsTask(env) {
2454
2664
  return { ok: true, detail: `automatic updates off \u2014 removed "${TASK_NAME}"; installed tooling was kept` };
2455
2665
  }
2456
2666
  function uninstallLaunchd(env) {
2457
- const plistPath = (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
2667
+ const plistPath = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
2458
2668
  const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
2459
- if (boot.status !== 0 && (0, import_node_fs14.existsSync)(plistPath)) {
2669
+ if (boot.status !== 0 && (0, import_node_fs15.existsSync)(plistPath)) {
2460
2670
  return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
2461
2671
  }
2462
2672
  try {
2463
- (0, import_node_fs14.rmSync)(plistPath, { force: true });
2673
+ (0, import_node_fs15.rmSync)(plistPath, { force: true });
2464
2674
  } catch {
2465
2675
  }
2466
2676
  dropLauncher(env, POSIX_LAUNCHER_FILE);
2467
2677
  return { ok: true, detail: `automatic updates off \u2014 removed "${LAUNCHD_LABEL}"; installed tooling was kept` };
2468
2678
  }
2469
2679
  function uninstallSystemdTimer(env) {
2470
- const unitDir = (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
2471
- const timerPath = (0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME);
2680
+ const unitDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
2681
+ const timerPath = (0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME);
2472
2682
  const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
2473
- if (disable.status !== 0 && (0, import_node_fs14.existsSync)(timerPath)) {
2683
+ if (disable.status !== 0 && (0, import_node_fs15.existsSync)(timerPath)) {
2474
2684
  return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
2475
2685
  }
2476
2686
  try {
2477
- (0, import_node_fs14.rmSync)(timerPath, { force: true });
2478
- (0, import_node_fs14.rmSync)((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
2687
+ (0, import_node_fs15.rmSync)(timerPath, { force: true });
2688
+ (0, import_node_fs15.rmSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
2479
2689
  } catch {
2480
2690
  }
2481
2691
  systemctlUser(["daemon-reload"]);
@@ -2490,16 +2700,16 @@ function uninstallTask(env = process.env) {
2490
2700
  }
2491
2701
  function dropLauncher(env, file) {
2492
2702
  try {
2493
- (0, import_node_fs14.rmSync)((0, import_node_path12.join)(statePaths(env).root, file), { force: true });
2703
+ (0, import_node_fs15.rmSync)((0, import_node_path13.join)(statePaths(env).root, file), { force: true });
2494
2704
  } catch {
2495
2705
  }
2496
2706
  }
2497
2707
 
2498
2708
  // src/status.ts
2499
- var import_node_fs15 = require("node:fs");
2709
+ var import_node_fs16 = require("node:fs");
2500
2710
  function readJournal(path) {
2501
2711
  try {
2502
- const events = (0, import_node_fs15.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
2712
+ const events = (0, import_node_fs16.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
2503
2713
  try {
2504
2714
  return JSON.parse(line);
2505
2715
  } catch {
@@ -2618,6 +2828,7 @@ var YELLOW = "\x1B[33m";
2618
2828
  var RED = "\x1B[31m";
2619
2829
  var RESET = "\x1B[0m";
2620
2830
  var RETRY_HINTS = [
2831
+ [/applies when the host exits|applies on a quiet tick/, "installs when the app closes"],
2621
2832
  [/in flight|changed while/, "rerun update \u2014 host race"],
2622
2833
  [/is running|live process|inside a .* hook/, "close app, rerun update"],
2623
2834
  [/staging install failed|etarget|e404|network|fetch failed/, "check network/login, rerun"],
@@ -2876,7 +3087,7 @@ function startSpinner(enabled) {
2876
3087
  // src/index.ts
2877
3088
  function ownVersion() {
2878
3089
  try {
2879
- return JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path13.join)(__dirname, "..", "package.json"), "utf8")).version;
3090
+ return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path14.join)(__dirname, "..", "package.json"), "utf8")).version;
2880
3091
  } catch {
2881
3092
  return "0.0.0";
2882
3093
  }
@@ -3002,6 +3213,8 @@ if (typeof require !== "undefined" && require.main === module) {
3002
3213
  }
3003
3214
  // Annotate the CommonJS export names for ESM import in node:
3004
3215
  0 && (module.exports = {
3216
+ buildWatcherCommand,
3217
+ clearPending,
3005
3218
  enumerateSurfaces,
3006
3219
  failConsequence,
3007
3220
  formatReconcileReport,
@@ -3012,18 +3225,26 @@ if (typeof require !== "undefined" && require.main === module) {
3012
3225
  launcherSh,
3013
3226
  launcherVbs,
3014
3227
  localStartBoundary,
3228
+ lockShaped,
3015
3229
  main,
3230
+ noteBusyDefer,
3016
3231
  ownVersion,
3232
+ pendingPath,
3233
+ pidAlive,
3017
3234
  probeHermesInstallation,
3018
3235
  progressiveRow,
3236
+ readPending,
3019
3237
  reconcileCursorUserHooks,
3020
3238
  reportFooterLines,
3021
3239
  retryHint,
3240
+ schedulePendingWatcher,
3022
3241
  schedulerStatus,
3023
3242
  startSpinner,
3024
3243
  systemdServiceUnit,
3025
3244
  systemdTimerUnit,
3026
3245
  taskXml,
3027
3246
  updateHeader,
3028
- wrapWords
3247
+ watcherLockPath,
3248
+ wrapWords,
3249
+ writePending
3029
3250
  });