@mutmutco/hub 4.2.7 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.cjs +291 -822
  2. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -29,6 +29,7 @@ __export(index_exports, {
29
29
  clearPending: () => clearPending,
30
30
  enumerateSurfaces: () => enumerateSurfaces,
31
31
  failConsequence: () => failConsequence,
32
+ fleetStamp: () => fleetStamp,
32
33
  formatReconcileReport: () => formatReconcileReport,
33
34
  hermesArm: () => hermesArm,
34
35
  hermesHome: () => hermesHome,
@@ -54,15 +55,14 @@ __export(index_exports, {
54
55
  probeHermesInstallation: () => probeHermesInstallation,
55
56
  probeHermesLaunchStaging: () => probeHermesLaunchStaging,
56
57
  probeJervcodeInstallation: () => probeJervcodeInstallation,
57
- probeKimiLaunchStaging: () => probeKimiLaunchStaging,
58
58
  probeLaunchTargetVersion: () => probeLaunchTargetVersion,
59
59
  progressiveRow: () => progressiveRow,
60
60
  readLaunchTarget: () => readLaunchTarget,
61
61
  readPending: () => readPending,
62
62
  reconcileCursorUserHooks: () => reconcileCursorUserHooks,
63
- reconcileKimiInstalledPlugin: () => reconcileKimiInstalledPlugin,
64
63
  releaseSharedDocLock: () => releaseSharedDocLock,
65
64
  reportFooterLines: () => reportFooterLines,
65
+ retiredSurfaces: () => retiredSurfaces,
66
66
  retryHint: () => retryHint,
67
67
  schedulePendingWatcher: () => schedulePendingWatcher,
68
68
  schedulerStatus: () => schedulerStatus,
@@ -71,11 +71,11 @@ __export(index_exports, {
71
71
  sharedDocLockTimeoutReceipt: () => sharedDocLockTimeoutReceipt,
72
72
  shouldHandOff: () => shouldHandOff,
73
73
  startSpinner: () => startSpinner,
74
+ surfaceArmPlan: () => surfaceArmPlan,
74
75
  systemdServiceUnit: () => systemdServiceUnit,
75
76
  systemdTimerUnit: () => systemdTimerUnit,
76
77
  taskXml: () => taskXml,
77
78
  updateHeader: () => updateHeader,
78
- validateKimiInstalledPlugin: () => validateKimiInstalledPlugin,
79
79
  watcherLockPath: () => watcherLockPath,
80
80
  withSharedDocLock: () => withSharedDocLock,
81
81
  wrapWords: () => wrapWords,
@@ -83,9 +83,9 @@ __export(index_exports, {
83
83
  writePending: () => writePending
84
84
  });
85
85
  module.exports = __toCommonJS(index_exports);
86
- var import_node_child_process12 = require("node:child_process");
86
+ var import_node_child_process10 = require("node:child_process");
87
87
  var import_node_fs19 = require("node:fs");
88
- var import_node_path15 = require("node:path");
88
+ var import_node_path14 = require("node:path");
89
89
 
90
90
  // src/state.ts
91
91
  var import_node_fs = require("node:fs");
@@ -208,6 +208,18 @@ function journalSurfaceRepeatedFail(path, surface, currentRun) {
208
208
  }
209
209
  return lastVerdict === "fail";
210
210
  }
211
+ function retiredSurfaces(events) {
212
+ const newest = /* @__PURE__ */ new Map();
213
+ for (const event of events) {
214
+ if (event.dryRun || !event.surface) continue;
215
+ if (event.kind === "arm" || event.kind === "surface") newest.set(event.surface, event);
216
+ }
217
+ const retired = /* @__PURE__ */ new Set();
218
+ for (const [surface, event] of newest) {
219
+ if (event.kind === "surface" && event.retired) retired.add(surface);
220
+ }
221
+ return retired;
222
+ }
211
223
  function parseSemver(version) {
212
224
  const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(version.trim());
213
225
  if (!match) return null;
@@ -770,7 +782,7 @@ function ensureClaudeMarketplace(env) {
770
782
  }
771
783
  var CLAUDE_PACKAGE = "@mutmutco/claude-plugin";
772
784
  var CLAUDE_MANIFEST = ".claude-plugin/plugin.json";
773
- var CLAUDE_TREE_MARKERS = [".claude-plugin/plugin.json", "hooks/hooks.json", "bin/mmi-hook"];
785
+ var CLAUDE_TREE_MARKERS = [".claude-plugin/plugin.json"];
774
786
  function claudePluginsRoot(env) {
775
787
  return (0, import_node_path6.join)(claudeHomeDir(env), "plugins");
776
788
  }
@@ -1003,99 +1015,24 @@ function codexArm(options) {
1003
1015
  }
1004
1016
  return { surface: "codex", from, to: options.target, verdict: "ok", detail: `marketplace upgraded; payload refresh and native evidence verified at ${options.target}` };
1005
1017
  }
1006
- var KILO_PACKAGE = "@mutmutco/kilo-plugin";
1007
- function kiloConfigPath(env) {
1008
- return env.MMI_UPDATER_KILO_CONFIG || (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".config", "kilo", "opencode.json");
1009
- }
1010
- function readKiloConfig(env) {
1011
- try {
1012
- const config = JSON.parse((0, import_node_fs7.readFileSync)(kiloConfigPath(env), "utf8"));
1013
- const specs = Array.isArray(config.plugin) && config.plugin.every((entry) => typeof entry === "string") ? config.plugin : [];
1014
- return { config, specs };
1015
- } catch {
1016
- return null;
1017
- }
1018
- }
1019
- function isLegacyMmiKiloPath(spec) {
1020
- const normalized = spec.replaceAll("\\", "/");
1021
- return /\/(?:MMI-Hub|@jervaise\/jerv-cli)\/\.kilo-plugin\/?$/i.test(normalized);
1022
- }
1023
- function normalizeKiloConfig(env, exact) {
1024
- const current = readKiloConfig(env);
1025
- if (!current) return `cannot read ${kiloConfigPath(env)}`;
1026
- const next = current.specs.filter((spec) => !isLegacyMmiKiloPath(spec));
1027
- if (!next.includes(exact)) next.push(exact);
1028
- if (JSON.stringify(next) === JSON.stringify(current.specs)) return null;
1029
- const path = kiloConfigPath(env);
1030
- const tmp = `${path}.tmp-${process.pid}`;
1031
- try {
1032
- (0, import_node_fs7.writeFileSync)(tmp, JSON.stringify({ ...current.config, plugin: next }, null, 2) + "\n", "utf8");
1033
- (0, import_node_fs7.renameSync)(tmp, path);
1034
- return null;
1035
- } catch (error) {
1036
- return `cannot retire legacy Kilo registrations: ${error?.message ?? error}`;
1037
- }
1038
- }
1039
- function kiloVersion(specs) {
1040
- const exact = specs?.find((entry) => entry.startsWith(`${KILO_PACKAGE}@`));
1041
- return exact?.slice(`${KILO_PACKAGE}@`.length) || null;
1042
- }
1043
1018
  function probeNativeHostInstallation(surface, env = process.env, runner = runHostCommand) {
1044
1019
  if (surface === "claude") {
1045
- const observed = claudeMmi(runner, env);
1020
+ const observed2 = claudeMmi(runner, env);
1046
1021
  return {
1047
- version: observed.row?.version ?? null,
1022
+ version: observed2.row?.version ?? null,
1048
1023
  location: "claude plugin list --json",
1049
- detail: observed.error ?? `native host reports enabled=${String(observed.row?.enabled)}`
1050
- };
1051
- }
1052
- if (surface === "codex") {
1053
- const observed = codexMmi(runner, env);
1054
- return {
1055
- version: observed.row?.version ?? null,
1056
- location: "codex plugin list --json",
1057
- detail: observed.error ?? `native host reports installed=${String(observed.row?.installed)}, enabled=${String(observed.row?.enabled)}`
1024
+ detail: observed2.error ?? `native host reports enabled=${String(observed2.row?.enabled)}`
1058
1025
  };
1059
1026
  }
1060
- const config = readKiloConfig(env);
1027
+ const observed = codexMmi(runner, env);
1061
1028
  return {
1062
- version: kiloVersion(config?.specs ?? null),
1063
- location: kiloConfigPath(env),
1064
- detail: config ? "exact package pin read from Kilo config" : "Kilo config was unreadable"
1029
+ version: observed.row?.version ?? null,
1030
+ location: "codex plugin list --json",
1031
+ detail: observed.error ?? `native host reports installed=${String(observed.row?.installed)}, enabled=${String(observed.row?.enabled)}`
1065
1032
  };
1066
1033
  }
1067
- function kiloArm(options) {
1068
- const env = options.env ?? process.env;
1069
- const runner = options.runner ?? runHostCommand;
1070
- const exact = `${KILO_PACKAGE}@${options.target}`;
1071
- const before = readKiloConfig(env);
1072
- if (!before) return { surface: "kilo", from: null, to: options.target, verdict: "defer", detail: `cannot read ${kiloConfigPath(env)}` };
1073
- const from = kiloVersion(before.specs);
1074
- const legacyCount = before.specs.filter(isLegacyMmiKiloPath).length;
1075
- if (options.dryRun) {
1076
- const action = before.specs.includes(exact) ? "keep exact pin" : `configure ${exact} globally`;
1077
- return { surface: "kilo", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacyCount ? ` and retire ${legacyCount} legacy MMI path${legacyCount === 1 ? "" : "s"}` : ""} (payload provisions next session)` };
1078
- }
1079
- if (!before.specs.includes(exact)) {
1080
- const install = runner("kilo", ["plugin", exact, "--global", "--force"], env);
1081
- if (install.status !== 0) {
1082
- return { surface: "kilo", from, to: options.target, verdict: "defer", detail: `kilo plugin failed: ${failure(install)}` };
1083
- }
1084
- }
1085
- const normalizeError = normalizeKiloConfig(env, exact);
1086
- if (normalizeError) return { surface: "kilo", from, to: options.target, verdict: "defer", detail: normalizeError };
1087
- const after = readKiloConfig(env);
1088
- const legacyAfter = after?.specs.filter(isLegacyMmiKiloPath) ?? [];
1089
- if (!after?.specs.includes(exact) || legacyAfter.length) {
1090
- return { surface: "kilo", from, to: kiloVersion(after?.specs ?? null) ?? "unknown", verdict: "fail", detail: `post-install config must contain ${exact} and no legacy MMI paths (legacy=${legacyAfter.length})` };
1091
- }
1092
- return { surface: "kilo", from, to: options.target, verdict: "ok", detail: `global config verified at ${exact}; ${legacyCount ? `retired ${legacyCount} legacy MMI path${legacyCount === 1 ? "" : "s"}; ` : ""}payload provisions at next session start` };
1093
- }
1094
1034
 
1095
1035
  // src/surfaces.ts
1096
- function kimiHome(env = process.env) {
1097
- return env.KIMI_CODE_HOME?.trim() || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".kimi-code");
1098
- }
1099
1036
  function jervcodeAgentDir(env = process.env) {
1100
1037
  return env.MMI_UPDATER_JERVCODE_AGENT_DIR?.trim() || env.JERVCODE_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".jerv", "agent");
1101
1038
  }
@@ -1110,6 +1047,10 @@ function hermesHostCommand(env = process.env) {
1110
1047
  function hermesHostPresent(env = process.env, runner = runHostCommand) {
1111
1048
  return runner(hermesHostCommand(env), ["--version"], env).status === 0;
1112
1049
  }
1050
+ function surfaceArmPlan(token, managedSurfaces) {
1051
+ if (!managedSurfaces) return "arm";
1052
+ return managedSurfaces.includes(token) ? "arm" : "retired";
1053
+ }
1113
1054
  function enumerateSurfaces(env = process.env) {
1114
1055
  const home = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os3.homedir)();
1115
1056
  const codexHome = env.CODEX_HOME || (0, import_node_path7.join)(home, ".codex");
@@ -1121,11 +1062,6 @@ function enumerateSurfaces(env = process.env) {
1121
1062
  // and the arm could never make the first install. The plugin's absence is a converge action, not
1122
1063
  // a skip. The host root mirrors surfaceConfigRoot('cursor') in cli/src/plugin-guard-io.ts.
1123
1064
  { id: "cursor", marker: (0, import_node_path7.join)(home, ".cursor") },
1124
- { id: "kilo", marker: (0, import_node_path7.join)(home, ".config", "kilo") },
1125
- // The host, not the payload (#4951): this marker used to be plugins/managed/mmi — the MMI plugin
1126
- // itself — so a machine running Kimi WITHOUT the plugin enumerated as "no Kimi" and the arm
1127
- // could never make the first install. The plugin's absence is a converge action, not a skip.
1128
- { id: "kimi", marker: kimiHome(env) },
1129
1065
  { id: "jervcode", marker: (0, import_node_path7.join)(jervcodeAgentDir(env), "settings.json") },
1130
1066
  // Host CLI, not HERMES_HOME (#5673): the config tree can survive an uninstall and still carry
1131
1067
  // leftover plugin manifests — that residue must not enumerate as an installed host.
@@ -1225,9 +1161,10 @@ function selfArm(options) {
1225
1161
  });
1226
1162
  }
1227
1163
 
1228
- // src/arm-kimi.ts
1229
- var import_node_child_process7 = require("node:child_process");
1164
+ // src/arm-cursor.ts
1230
1165
  var import_node_fs12 = require("node:fs");
1166
+ var import_node_child_process7 = require("node:child_process");
1167
+ var import_node_os5 = require("node:os");
1231
1168
  var import_node_path9 = require("node:path");
1232
1169
 
1233
1170
  // src/compat.ts
@@ -1241,21 +1178,6 @@ function readManifestCompat(manifestPath) {
1241
1178
  }
1242
1179
  }
1243
1180
 
1244
- // src/kimi-registration.ts
1245
- function validateKimiInstalledPlugin(document, pluginRoot, id) {
1246
- if (!document || typeof document !== "object" || Array.isArray(document)) return "malformed-document";
1247
- const plugins = document.plugins;
1248
- if (!Array.isArray(plugins)) return "malformed-document";
1249
- const entry = plugins.find(
1250
- (candidate) => candidate && typeof candidate === "object" && !Array.isArray(candidate) && candidate.id === id
1251
- );
1252
- if (!entry) return "absent-record";
1253
- const record = entry;
1254
- if (typeof record.root !== "string") return "malformed-record";
1255
- if (record.root !== pluginRoot) return "foreign-root";
1256
- return record.enabled ? "healthy" : "disabled";
1257
- }
1258
-
1259
1181
  // src/host-doc-lock.ts
1260
1182
  var import_node_fs11 = require("node:fs");
1261
1183
  var import_node_os4 = require("node:os");
@@ -1366,311 +1288,22 @@ function withSharedDocLock(documentPath, options, fn) {
1366
1288
  }
1367
1289
  }
1368
1290
 
1369
- // src/arm-kimi.ts
1370
- var KIMI_PACKAGE = "@mutmutco/kimi-plugin";
1371
- var TREE_MARKERS = [".kimi-plugin/plugin.json", "skills/mmi/SKILL.md", "scripts/hook-run.mjs"];
1372
- function treeHealthy(root) {
1373
- return TREE_MARKERS.every((rel) => fileExists((0, import_node_path9.join)(root, ...rel.split("/"))));
1374
- }
1375
- function markerVersion(root) {
1376
- try {
1377
- const version = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path9.join)(root, ".kimi-plugin", "plugin.json"), "utf8")).version;
1378
- return typeof version === "string" ? version : null;
1379
- } catch {
1380
- return null;
1381
- }
1382
- }
1383
- function packageVersion(root) {
1384
- try {
1385
- const version = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path9.join)(root, "package.json"), "utf8")).version;
1386
- return typeof version === "string" ? version : null;
1387
- } catch {
1388
- return null;
1389
- }
1390
- }
1391
- var KIMI_REGISTRATION_LOCK_OWNER = "mmi-hub:kimi-registration";
1392
- function reconcileKimiInstalledPlugin(kimiHomeDir, pluginRoot, id, lock = {}) {
1393
- const path = (0, import_node_path9.join)(kimiHomeDir, "plugins", "installed.json");
1394
- (0, import_node_fs12.mkdirSync)((0, import_node_path9.join)(kimiHomeDir, "plugins"), { recursive: true });
1395
- return withSharedDocLock(path, { owner: KIMI_REGISTRATION_LOCK_OWNER, ...lock }, () => mergeKimiInstalledPlugin(path, pluginRoot, id));
1396
- }
1397
- function mergeKimiInstalledPlugin(path, pluginRoot, id) {
1398
- const now = (/* @__PURE__ */ new Date()).toISOString();
1399
- const fresh = { id, root: pluginRoot, enabled: true, installedAt: now, updatedAt: now, originalSource: pluginRoot, source: "local-path" };
1400
- let document = { version: 1, plugins: [] };
1401
- if ((0, import_node_fs12.existsSync)(path)) {
1402
- let parsed;
1403
- try {
1404
- parsed = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
1405
- } catch {
1406
- return "conflict";
1407
- }
1408
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || !Array.isArray(parsed.plugins)) return "conflict";
1409
- document = parsed;
1410
- }
1411
- const plugins = [...document.plugins];
1412
- const registration = validateKimiInstalledPlugin(document, pluginRoot, id);
1413
- const index = plugins.findIndex((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) && entry.id === id);
1414
- if (registration === "malformed-record" || registration === "foreign-root") return "conflict";
1415
- if (index === -1) {
1416
- plugins.push(fresh);
1417
- } else {
1418
- const prior = plugins[index];
1419
- const next = {
1420
- ...prior,
1421
- root: pluginRoot,
1422
- // A user's explicit disable is their choice and survives; anything else converges to enabled.
1423
- enabled: prior.enabled === false ? false : true,
1424
- installedAt: typeof prior.installedAt === "string" ? prior.installedAt : now,
1425
- originalSource: pluginRoot,
1426
- source: "local-path"
1427
- };
1428
- if (JSON.stringify(next) === JSON.stringify(prior)) return "current";
1429
- plugins[index] = { ...next, updatedAt: now };
1430
- }
1431
- const tmp = `${path}.tmp-${process.pid}`;
1432
- (0, import_node_fs12.writeFileSync)(tmp, `${JSON.stringify({ ...document, version: 1, plugins }, null, 2)}
1433
- `, "utf8");
1434
- (0, import_node_fs12.renameSync)(tmp, path);
1435
- return "updated";
1436
- }
1437
- function probeKimiInstallation(env = process.env) {
1438
- const location = (0, import_node_path9.join)(kimiHome(env), "plugins", "managed", "mmi");
1439
- const version = markerVersion(location);
1440
- return { version, location, detail: version ? "installed Kimi plugin manifest" : "installed Kimi plugin manifest was unreadable" };
1441
- }
1442
- function tail2(output) {
1443
- return output.split("\n").filter(Boolean).slice(-3).join(" | ");
1444
- }
1445
- function scratchRoot(env) {
1446
- return (0, import_node_path9.join)(kimiHome(env), ".mmi-updater");
1447
- }
1448
- function pruneScratchDir(root, keep) {
1449
- try {
1450
- for (const name of (0, import_node_fs12.readdirSync)(root)) {
1451
- if (name === keep) continue;
1452
- try {
1453
- (0, import_node_fs12.rmSync)((0, import_node_path9.join)(root, name), { recursive: true, force: true });
1454
- } catch {
1455
- }
1456
- }
1457
- } catch {
1458
- }
1459
- }
1460
- var KIMI_PROCESS_SEGMENT = /^kimi(-cli|-code)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/i;
1461
- function kimiProcessToken(line) {
1462
- for (const token of line.replace(/["']/g, " ").split(/\s+/)) {
1463
- if (token && token.split(/[/\\]/).some((segment) => KIMI_PROCESS_SEGMENT.test(segment))) return token;
1464
- }
1465
- return null;
1466
- }
1467
- function kimiHostRunning(env) {
1468
- if (env.KIMI_PLUGIN_ROOT?.trim()) return { running: true, evidence: "KIMI_PLUGIN_ROOT is set \u2014 this process is itself inside a Kimi hook" };
1469
- 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 });
1470
- if (result.status !== 0 || !(result.stdout ?? "").trim()) return { running: true, evidence: "the process list is unreadable \u2014 unsafe to write" };
1471
- for (const line of result.stdout.split(/\r?\n/)) {
1472
- const token = kimiProcessToken(line);
1473
- if (token) return { running: true, evidence: `a live process names kimi (${token})` };
1474
- }
1475
- return { running: false, evidence: "no kimi-named process in the process list" };
1476
- }
1477
- function restoreQuarantine(quarantine, live) {
1478
- try {
1479
- (0, import_node_fs12.renameSync)(quarantine, live);
1480
- } catch (error) {
1481
- return { restored: false, detail: `${error?.message ?? error}` };
1482
- }
1483
- if (!(0, import_node_fs12.existsSync)(live)) return { restored: false, detail: `${live} is still absent after the restore rename` };
1484
- return { restored: true, detail: `marker ${markerVersion(live) ?? "unreadable"}` };
1485
- }
1486
- function kimiArm(options) {
1487
- const env = options.env ?? process.env;
1488
- const { target, dryRun, paths } = options;
1489
- const result = (from, to, verdict, detail, launchStaged = false) => ({
1490
- surface: "kimi",
1491
- from,
1492
- to,
1493
- verdict,
1494
- detail,
1495
- launchStaged: launchStaged || void 0,
1496
- // C11: the constraint of the tree LEFT at `live` — read at result time, so ok carries the new
1497
- // manifest's declaration and defer/fail carry the previous tree's (#4973 reconsult ruling).
1498
- compat: readManifestCompat((0, import_node_path9.join)(live, ".kimi-plugin", "plugin.json"))
1499
- });
1500
- const managedParent = (0, import_node_path9.join)(kimiHome(env), "plugins", "managed");
1501
- const live = (0, import_node_path9.join)(managedParent, "mmi");
1502
- const installed = markerVersion(live);
1503
- const ensureRegistered = (detail) => {
1504
- if (!(0, import_node_fs12.existsSync)(live) || !treeHealthy(live)) return { detail };
1505
- if (dryRun) return { detail: `${detail}; installed.json untouched (dry-run)` };
1506
- let registration;
1507
- try {
1508
- registration = reconcileKimiInstalledPlugin(kimiHome(env), live, "mmi");
1509
- } catch (error) {
1510
- return { fail: true, detail: `${detail}; installed.json registration FAILED: ${error?.message ?? error}` };
1511
- }
1512
- if (registration === "conflict") {
1513
- return { fail: true, detail: `${detail}; installed.json carries an unparsable or foreign mmi record \u2014 the tree is INERT until a human resolves it; the file was never touched` };
1514
- }
1515
- return { detail: `${detail}; installed.json record ${registration === "updated" ? "written" : "already current"}` };
1516
- };
1517
- if (installed && compareSemver(installed, target) > 0) {
1518
- const reg2 = ensureRegistered(`installed ${installed} is above candidate ${target} (monotonic)`);
1519
- return result(installed, installed, reg2.fail ? "fail" : "skip", reg2.detail);
1520
- }
1521
- const highWater = journalHighWater(paths.journalPath, "kimi");
1522
- if (highWater && compareSemver(highWater, target) > 0) {
1523
- const reg2 = ensureRegistered(`journal high-water ${highWater} is above candidate ${target} (monotonic)`);
1524
- return result(installed, highWater, reg2.fail ? "fail" : "skip", reg2.detail);
1525
- }
1526
- if (installed === target && treeHealthy(live)) {
1527
- const reg2 = ensureRegistered(`already at target ${target}`);
1528
- return result(installed, target, reg2.fail ? "fail" : "ok", reg2.detail);
1529
- }
1530
- if (dryRun) {
1531
- return result(installed, target, "ok", `dry-run: would converge ${installed ?? "absent"} -> ${target} (tarball generation switch while Kimi is absent)`);
1532
- }
1533
- const stageRoot = (0, import_node_path9.join)(paths.staging, "kimi", target);
1534
- const staged = (0, import_node_path9.join)(stageRoot, "node_modules", ...KIMI_PACKAGE.split("/"));
1535
- if (!treeHealthy(staged)) {
1536
- (0, import_node_fs12.mkdirSync)(stageRoot, { recursive: true });
1537
- (0, import_node_fs12.writeFileSync)((0, import_node_path9.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
1538
- const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${KIMI_PACKAGE}@${target}`], env);
1539
- if (install.status !== 0) {
1540
- return result(installed, target, "defer", `staging install failed: ${tail2(install.stderr || install.stdout)}`);
1541
- }
1542
- }
1543
- const stagedPackage = packageVersion(staged);
1544
- if (stagedPackage !== target) {
1545
- return result(installed, target, "defer", `staged package manifest is ${stagedPackage ?? "unreadable"}, expected ${target}`);
1546
- }
1547
- const stagedMarker = markerVersion(staged);
1548
- if (stagedMarker !== target) {
1549
- return result(installed, target, "defer", `staged plugin manifest is ${stagedMarker ?? "unreadable"}, expected ${target}`);
1550
- }
1551
- if (!treeHealthy(staged)) {
1552
- return result(installed, target, "defer", `staged tree is missing one of ${TREE_MARKERS.join(", ")}`);
1553
- }
1554
- const scratch = scratchRoot(env);
1555
- const launchTargetPath = (0, import_node_path9.join)(scratch, "launch-target.json");
1556
- const incomingRoot = (0, import_node_path9.join)(scratch, "incoming");
1557
- const incoming = (0, import_node_path9.join)(incomingRoot, target);
1558
- const quarantineRoot = (0, import_node_path9.join)(scratch, "quarantine");
1559
- const rejectedRoot = (0, import_node_path9.join)(scratch, "rejected");
1560
- const priorLaunch = readLaunchTarget(launchTargetPath);
1561
- const reuseIncoming = priorLaunch?.target === target && priorLaunch.incoming === incoming && markerVersion(incoming) === target && treeHealthy(incoming);
1562
- if (!reuseIncoming) {
1563
- try {
1564
- (0, import_node_fs12.mkdirSync)(managedParent, { recursive: true });
1565
- (0, import_node_fs12.mkdirSync)(quarantineRoot, { recursive: true });
1566
- (0, import_node_fs12.rmSync)(incomingRoot, { recursive: true, force: true });
1567
- (0, import_node_fs12.cpSync)(staged, incoming, { recursive: true });
1568
- } catch (error) {
1569
- return result(installed, target, "defer", `cannot place the incoming generation under ${scratch}: ${error?.message ?? error}`);
1570
- }
1571
- if (markerVersion(incoming) !== target || !treeHealthy(incoming)) {
1572
- return result(installed, target, "defer", `incoming generation verifies as ${markerVersion(incoming) ?? "unreadable"}, expected ${target}`);
1573
- }
1574
- }
1575
- const quiescence = kimiHostRunning(env);
1576
- if (quiescence.running) {
1577
- const pending = noteLaunchStaged(paths, env, {
1578
- surface: "kimi",
1579
- target,
1580
- reason: quiescence.evidence,
1581
- launchTargetPath,
1582
- incoming,
1583
- dirs: ["kimi"]
1584
- });
1585
- return result(installed, target, "ok", `staged ${target} for next launch while Kimi is running (${quiescence.evidence}) \u2014 ${pending}`, true);
1586
- }
1587
- const started = kimiHostRunning(env);
1588
- if (started.running) {
1589
- const pending = noteLaunchStaged(paths, env, {
1590
- surface: "kimi",
1591
- target,
1592
- reason: started.evidence,
1593
- launchTargetPath,
1594
- incoming,
1595
- dirs: ["kimi"]
1596
- });
1597
- return result(installed, target, "ok", `staged ${target} for next launch \u2014 Kimi started during staging (${started.evidence}) \u2014 ${pending}`, true);
1598
- }
1599
- const hadLive = (0, import_node_fs12.existsSync)(live);
1600
- const generation = `${installed ?? "unknown"}-${Date.now()}`;
1601
- const quarantine = (0, import_node_path9.join)(quarantineRoot, generation);
1602
- if (hadLive) {
1603
- try {
1604
- (0, import_node_fs12.renameSync)(live, quarantine);
1605
- } catch (error) {
1606
- const reason = String(error?.message ?? error);
1607
- const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason, dirs: ["kimi"] })}` : "";
1608
- return result(installed, target, "defer", `cannot quarantine the live generation: ${reason}${pending}`);
1609
- }
1610
- }
1611
- try {
1612
- (0, import_node_fs12.renameSync)(incoming, live);
1613
- } catch (error) {
1614
- const failed = `cannot switch the new generation in: ${error?.message ?? error}`;
1615
- if (!hadLive) {
1616
- return result(installed, target, "defer", `${failed} \u2014 no generation was displaced; ${live} is still absent`);
1617
- }
1618
- const restore = restoreQuarantine(quarantine, live);
1619
- const pending = restore.restored && lockShaped(failed) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason: failed, dirs: ["kimi"] })}` : "";
1620
- 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}`);
1621
- }
1622
- const switched = markerVersion(live);
1623
- if (switched !== target || !treeHealthy(live)) {
1624
- const observed = `post-switch marker is ${switched ?? "unreadable"}, expected ${target}`;
1625
- const rejectedName = `${target}-${Date.now()}`;
1626
- try {
1627
- (0, import_node_fs12.mkdirSync)(rejectedRoot, { recursive: true });
1628
- (0, import_node_fs12.renameSync)(live, (0, import_node_path9.join)(rejectedRoot, rejectedName));
1629
- } catch (error) {
1630
- 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}` : ""}`);
1631
- }
1632
- pruneScratchDir(rejectedRoot, rejectedName);
1633
- if (!hadLive) {
1634
- return result(installed, "none", "fail", `${observed} \u2014 the unverified tree is parked at ${(0, import_node_path9.join)(rejectedRoot, rejectedName)}; ${live} is absent (there was no previous generation to restore)`);
1635
- }
1636
- const restore = restoreQuarantine(quarantine, live);
1637
- return restore.restored ? result(installed, installed ?? "unknown", "fail", `${observed} \u2014 the unverified tree is parked at ${(0, import_node_path9.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}`);
1638
- }
1639
- pruneScratchDir(quarantineRoot, hadLive ? generation : null);
1640
- pruneScratchDir(rejectedRoot, null);
1641
- try {
1642
- (0, import_node_fs12.rmSync)(incomingRoot, { recursive: true, force: true });
1643
- } catch {
1644
- }
1645
- clearLaunchTarget(launchTargetPath);
1646
- const reg = ensureRegistered(`converged ${installed ?? "absent"} -> ${target} (staged, verified, switched${hadLive ? `; previous generation quarantined at ${quarantine}` : ""})`);
1647
- return result(installed, target, reg.fail ? "fail" : "ok", reg.detail);
1648
- }
1649
- function probeKimiLaunchStaging(env = process.env) {
1650
- return probeLaunchTargetVersion((0, import_node_path9.join)(scratchRoot(env), "launch-target.json"), markerVersion);
1651
- }
1652
-
1653
1291
  // src/arm-cursor.ts
1654
- var import_node_fs13 = require("node:fs");
1655
- var import_node_child_process8 = require("node:child_process");
1656
- var import_node_os5 = require("node:os");
1657
- var import_node_path10 = require("node:path");
1658
1292
  var PACKAGE = "@mutmutco/cursor-plugin";
1659
1293
  var MANIFEST = ".cursor-plugin/plugin.json";
1660
1294
  var USER_HOOKS_FILE = "hooks.json";
1661
- var PLUGIN_HOOKS_REL = ["hooks", "cursor-hooks.json"];
1662
1295
  var KEEP_GENERATIONS = 2;
1663
1296
  var STRANDED_TMP_AGE_MS = 24 * 60 * 6e4;
1664
- var TREE_FILES = [MANIFEST, "skills/mmi/SKILL.md", "hooks/cursor-hooks.json", "scripts/hook-run.mjs", "scripts/hook-policy.mjs"];
1297
+ var TREE_FILES = [MANIFEST, "skills/bootstrap/SKILL.md"];
1665
1298
  function cursorPluginsRoot() {
1666
- return (0, import_node_path10.join)((0, import_node_os5.homedir)(), ".cursor", "plugins");
1299
+ return (0, import_node_path9.join)((0, import_node_os5.homedir)(), ".cursor", "plugins");
1667
1300
  }
1668
- function treeHealthy2(root) {
1669
- return TREE_FILES.every((rel) => (0, import_node_fs13.existsSync)((0, import_node_path10.join)(root, ...rel.split("/"))));
1301
+ function treeHealthy(root) {
1302
+ return TREE_FILES.every((rel) => (0, import_node_fs12.existsSync)((0, import_node_path9.join)(root, ...rel.split("/"))));
1670
1303
  }
1671
1304
  function readJson(root, rel) {
1672
1305
  try {
1673
- return JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(root, ...rel.split("/")), "utf8"));
1306
+ return JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path9.join)(root, ...rel.split("/")), "utf8"));
1674
1307
  } catch {
1675
1308
  return null;
1676
1309
  }
@@ -1679,7 +1312,7 @@ function manifest(root) {
1679
1312
  return readJson(root, MANIFEST);
1680
1313
  }
1681
1314
  function probeCursorInstallation() {
1682
- const location = (0, import_node_path10.join)(cursorPluginsRoot(), "local", "mmi");
1315
+ const location = (0, import_node_path9.join)(cursorPluginsRoot(), "local", "mmi");
1683
1316
  const version = manifest(location)?.version ?? null;
1684
1317
  return { version, location, detail: version ? "installed Cursor plugin manifest" : "installed Cursor plugin manifest was unreadable" };
1685
1318
  }
@@ -1694,7 +1327,7 @@ function message(error) {
1694
1327
  }
1695
1328
  function discard(path) {
1696
1329
  try {
1697
- (0, import_node_fs13.rmSync)(path, { recursive: true, force: true });
1330
+ (0, import_node_fs12.rmSync)(path, { recursive: true, force: true });
1698
1331
  } catch {
1699
1332
  }
1700
1333
  }
@@ -1713,7 +1346,7 @@ function cursorHostEvidence(env) {
1713
1346
  if (env[marker]?.trim()) return `env marker ${marker}`;
1714
1347
  }
1715
1348
  if (env.CURSOR_AGENT === "1") return "env marker CURSOR_AGENT=1";
1716
- 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 });
1349
+ 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 });
1717
1350
  if (result.status !== 0 || !(result.stdout ?? "").trim()) return "the process table is unreadable (unsafe to write)";
1718
1351
  for (const line of result.stdout.split(/\r?\n/)) {
1719
1352
  const executable = cursorExecutable(line);
@@ -1722,73 +1355,45 @@ function cursorHostEvidence(env) {
1722
1355
  return null;
1723
1356
  }
1724
1357
  function resolvedPluginRoot(root) {
1725
- return (0, import_node_path10.join)(root).replace(/\\/g, "/");
1358
+ return (0, import_node_path9.join)(root).replace(/\\/g, "/");
1726
1359
  }
1727
1360
  function isMmiEntry(entry, pluginRoot) {
1728
- return typeof entry === "object" && entry !== null && typeof entry.command === "string" && entry.command.includes(resolvedPluginRoot(pluginRoot));
1729
- }
1730
- function resolveCommand(command, pluginRoot) {
1731
- return command.replaceAll("${CURSOR_PLUGIN_ROOT}", resolvedPluginRoot(pluginRoot));
1732
- }
1733
- function desiredUserHooks(pluginRoot) {
1734
- try {
1735
- const doc = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(pluginRoot, ...PLUGIN_HOOKS_REL), "utf8"));
1736
- const hooks = doc.hooks;
1737
- if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
1738
- const desired = /* @__PURE__ */ new Map();
1739
- for (const [event, entries] of Object.entries(hooks)) {
1740
- if (!Array.isArray(entries)) return null;
1741
- desired.set(event, entries.map((entry) => {
1742
- const command = typeof entry.command === "string" ? entry.command : "";
1743
- return { ...entry, command: resolveCommand(command, pluginRoot) };
1744
- }));
1745
- }
1746
- return desired;
1747
- } catch {
1748
- return null;
1749
- }
1361
+ if (typeof entry !== "object" || entry === null || typeof entry.command !== "string") return false;
1362
+ const command = entry.command;
1363
+ return command.includes(resolvedPluginRoot(pluginRoot)) || /(?:mmi-hook|hook-run\.mjs|hook-policy\.mjs)/i.test(command);
1750
1364
  }
1751
- function mergeUserHooks(existing, desired, pluginRoot) {
1365
+ function stripUserHooks(existing, pluginRoot) {
1752
1366
  if (typeof existing !== "object" || existing === null || Array.isArray(existing)) return null;
1753
- const merged = { ...existing };
1754
- const hooks = merged.hooks ?? {};
1367
+ const stripped = { ...existing };
1368
+ const hooks = stripped.hooks;
1755
1369
  if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
1756
- const nextHooks = {};
1370
+ const nextHooks = { ...hooks };
1757
1371
  for (const [event, entries] of Object.entries(hooks)) {
1758
1372
  if (!Array.isArray(entries)) return null;
1759
1373
  const kept = entries.filter((entry) => !isMmiEntry(entry, pluginRoot));
1760
- nextHooks[event] = [...kept, ...desired.get(event) ?? []];
1374
+ if (kept.length) nextHooks[event] = kept;
1375
+ else delete nextHooks[event];
1761
1376
  }
1762
- for (const [event, entries] of desired) {
1763
- if (!(event in hooks)) nextHooks[event] = entries;
1764
- }
1765
- merged.hooks = nextHooks;
1766
- return merged;
1377
+ stripped.hooks = nextHooks;
1378
+ return stripped;
1767
1379
  }
1768
1380
  var CURSOR_HOOKS_LOCK_OWNER = "mmi-hub:cursor-hooks";
1769
1381
  function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
1770
- const desired = desiredUserHooks(pluginRoot);
1771
- if (!desired) return "source-unreadable";
1772
- const path = (0, import_node_path10.join)(homeDir, ".cursor", USER_HOOKS_FILE);
1382
+ const path = (0, import_node_path9.join)(homeDir, ".cursor", USER_HOOKS_FILE);
1773
1383
  return withSharedDocLock(path, { owner: CURSOR_HOOKS_LOCK_OWNER, ...lock }, () => {
1774
- if (!(0, import_node_fs13.existsSync)(path)) {
1775
- const fresh = `${path}.tmp-${process.pid}`;
1776
- (0, import_node_fs13.writeFileSync)(fresh, JSON.stringify({ version: 1, hooks: Object.fromEntries(desired) }, null, 2) + "\n", "utf8");
1777
- (0, import_node_fs13.renameSync)(fresh, path);
1778
- return "updated";
1779
- }
1384
+ if (!(0, import_node_fs12.existsSync)(path)) return "current";
1780
1385
  let existing;
1781
1386
  try {
1782
- existing = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf8"));
1387
+ existing = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
1783
1388
  } catch {
1784
1389
  return "user-conflict";
1785
1390
  }
1786
- const merged = mergeUserHooks(existing, desired, pluginRoot);
1787
- if (!merged) return "user-conflict";
1788
- if (JSON.stringify(merged) === JSON.stringify(existing)) return "current";
1391
+ const stripped = stripUserHooks(existing, pluginRoot);
1392
+ if (!stripped) return "user-conflict";
1393
+ if (JSON.stringify(stripped) === JSON.stringify(existing)) return "current";
1789
1394
  const tmp = `${path}.tmp-${process.pid}`;
1790
- (0, import_node_fs13.writeFileSync)(tmp, JSON.stringify(merged, null, 2) + "\n", "utf8");
1791
- (0, import_node_fs13.renameSync)(tmp, path);
1395
+ (0, import_node_fs12.writeFileSync)(tmp, JSON.stringify(stripped, null, 2) + "\n", "utf8");
1396
+ (0, import_node_fs12.renameSync)(tmp, path);
1792
1397
  return "updated";
1793
1398
  });
1794
1399
  }
@@ -1801,45 +1406,43 @@ function userHooksDetail(pluginRoot, homeDir) {
1801
1406
  }
1802
1407
  switch (verdict) {
1803
1408
  case "updated":
1804
- return "; user hooks bridged into ~/.cursor/hooks.json";
1409
+ return "; user hooks legacy entries stripped";
1805
1410
  case "user-conflict":
1806
- return `; USER HOOKS CONFLICT at ${(0, import_node_path10.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 Cursor gates are NOT bridged; fix or remove that file and reconcile`;
1807
- case "source-unreadable":
1808
- return "; user hooks source unreadable \u2014 Cursor gates are NOT bridged";
1411
+ return `; USER HOOKS CONFLICT at ${(0, import_node_path9.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 legacy entries were not changed`;
1809
1412
  default:
1810
1413
  return "";
1811
1414
  }
1812
1415
  }
1813
1416
  function writeRollbackPointer(root, entry) {
1814
- const path = (0, import_node_path10.join)(root, "quarantine", "rollback.json");
1417
+ const path = (0, import_node_path9.join)(root, "quarantine", "rollback.json");
1815
1418
  const tmp = `${path}.tmp-${process.pid}`;
1816
1419
  try {
1817
- (0, import_node_fs13.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
1818
- (0, import_node_fs13.renameSync)(tmp, path);
1420
+ (0, import_node_fs12.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
1421
+ (0, import_node_fs12.renameSync)(tmp, path);
1819
1422
  } catch {
1820
1423
  discard(tmp);
1821
1424
  }
1822
1425
  }
1823
1426
  function mtimeMs(path) {
1824
1427
  try {
1825
- return (0, import_node_fs13.statSync)(path).mtimeMs;
1428
+ return (0, import_node_fs12.statSync)(path).mtimeMs;
1826
1429
  } catch {
1827
1430
  return 0;
1828
1431
  }
1829
1432
  }
1830
1433
  function pruneQuarantine(root, keep) {
1831
- const dir = (0, import_node_path10.join)(root, "quarantine");
1434
+ const dir = (0, import_node_path9.join)(root, "quarantine");
1832
1435
  let generations;
1833
1436
  try {
1834
- generations = (0, import_node_fs13.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("mmi-")).map((entry) => (0, import_node_path10.join)(dir, entry.name)).sort((a, b) => mtimeMs(b) - mtimeMs(a));
1437
+ generations = (0, import_node_fs12.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));
1835
1438
  } catch {
1836
1439
  return 0;
1837
1440
  }
1838
1441
  let stranded = 0;
1839
1442
  try {
1840
- for (const entry of (0, import_node_fs13.readdirSync)(dir, { withFileTypes: true })) {
1443
+ for (const entry of (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true })) {
1841
1444
  if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
1842
- const path = (0, import_node_path10.join)(dir, entry.name);
1445
+ const path = (0, import_node_path9.join)(dir, entry.name);
1843
1446
  if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
1844
1447
  discard(path);
1845
1448
  stranded += 1;
@@ -1857,16 +1460,16 @@ function pruneQuarantine(root, keep) {
1857
1460
  }
1858
1461
  function restorePrevious(live, quarantined, broken) {
1859
1462
  try {
1860
- (0, import_node_fs13.renameSync)(live, broken);
1463
+ (0, import_node_fs12.renameSync)(live, broken);
1861
1464
  } catch {
1862
1465
  return "kept-broken";
1863
1466
  }
1864
1467
  try {
1865
- (0, import_node_fs13.renameSync)(quarantined, live);
1468
+ (0, import_node_fs12.renameSync)(quarantined, live);
1866
1469
  return "restored";
1867
1470
  } catch {
1868
1471
  try {
1869
- (0, import_node_fs13.renameSync)(broken, live);
1472
+ (0, import_node_fs12.renameSync)(broken, live);
1870
1473
  return "kept-broken";
1871
1474
  } catch {
1872
1475
  return "lost";
@@ -1877,7 +1480,7 @@ function cursorArm(options) {
1877
1480
  const env = options.env ?? process.env;
1878
1481
  const { target, dryRun, paths } = options;
1879
1482
  const root = cursorPluginsRoot();
1880
- const live = (0, import_node_path10.join)(root, "local", "mmi");
1483
+ const live = (0, import_node_path9.join)(root, "local", "mmi");
1881
1484
  const result = (from, to, verdict, detail, launchStaged = false) => ({
1882
1485
  surface: "cursor",
1883
1486
  from,
@@ -1887,7 +1490,7 @@ function cursorArm(options) {
1887
1490
  launchStaged: launchStaged || void 0,
1888
1491
  // C11: the constraint of the generation LEFT at `live` — read at result time, so a failed
1889
1492
  // switch reports the restored generation's declaration, not the rejected one's (#4973).
1890
- compat: readManifestCompat((0, import_node_path10.join)(live, MANIFEST))
1493
+ compat: readManifestCompat((0, import_node_path9.join)(live, MANIFEST))
1891
1494
  });
1892
1495
  const current = manifest(live);
1893
1496
  const installed = current?.version ?? null;
@@ -1898,10 +1501,10 @@ function cursorArm(options) {
1898
1501
  if (highWater && compareSemver(highWater, target) > 0) {
1899
1502
  return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
1900
1503
  }
1901
- if (installed === target && treeHealthy2(live)) {
1504
+ if (installed === target && treeHealthy(live)) {
1902
1505
  return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
1903
1506
  }
1904
- if ((0, import_node_fs13.existsSync)(live) && !mmiOwned(live)) {
1507
+ if ((0, import_node_fs12.existsSync)(live) && !mmiOwned(live)) {
1905
1508
  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)`);
1906
1509
  }
1907
1510
  if (dryRun) {
@@ -1912,24 +1515,24 @@ function cursorArm(options) {
1912
1515
  return result(installed, target, "defer", staged.defer);
1913
1516
  }
1914
1517
  const stagedMarker = manifest(staged.packageRoot)?.version;
1915
- if (stagedMarker !== target || !treeHealthy2(staged.packageRoot)) {
1916
- return result(installed, target, "defer", `staged tree marker is ${stagedMarker ?? "unreadable"} and the tree is ${treeHealthy2(staged.packageRoot) ? "complete" : "incomplete"}, expected ${target} and complete`);
1518
+ if (stagedMarker !== target || !treeHealthy(staged.packageRoot)) {
1519
+ return result(installed, target, "defer", `staged tree marker is ${stagedMarker ?? "unreadable"} and the tree is ${treeHealthy(staged.packageRoot) ? "complete" : "incomplete"}, expected ${target} and complete`);
1917
1520
  }
1918
- const launchTargetPath = (0, import_node_path10.join)(root, "staging", "launch-target.json");
1521
+ const launchTargetPath = (0, import_node_path9.join)(root, "staging", "launch-target.json");
1919
1522
  const priorLaunch = readLaunchTarget(launchTargetPath);
1920
- let incoming = priorLaunch?.target === target && priorLaunch.incoming.startsWith((0, import_node_path10.join)(root, "staging")) && manifest(priorLaunch.incoming)?.version === target && treeHealthy2(priorLaunch.incoming) ? priorLaunch.incoming : (0, import_node_path10.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
1523
+ let incoming = priorLaunch?.target === target && priorLaunch.incoming.startsWith((0, import_node_path9.join)(root, "staging")) && manifest(priorLaunch.incoming)?.version === target && treeHealthy(priorLaunch.incoming) ? priorLaunch.incoming : (0, import_node_path9.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
1921
1524
  const reuseIncoming = incoming === priorLaunch?.incoming;
1922
- if (!reuseIncoming || !(0, import_node_fs13.existsSync)(incoming)) {
1923
- incoming = (0, import_node_path10.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
1924
- for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs13.mkdirSync)((0, import_node_path10.join)(root, dir), { recursive: true });
1525
+ if (!reuseIncoming || !(0, import_node_fs12.existsSync)(incoming)) {
1526
+ incoming = (0, import_node_path9.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
1527
+ for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs12.mkdirSync)((0, import_node_path9.join)(root, dir), { recursive: true });
1925
1528
  try {
1926
1529
  discard(incoming);
1927
- (0, import_node_fs13.cpSync)(staged.packageRoot, incoming, { recursive: true });
1530
+ (0, import_node_fs12.cpSync)(staged.packageRoot, incoming, { recursive: true });
1928
1531
  } catch (error) {
1929
1532
  discard(incoming);
1930
1533
  return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
1931
1534
  }
1932
- if (manifest(incoming)?.version !== target || !treeHealthy2(incoming)) {
1535
+ if (manifest(incoming)?.version !== target || !treeHealthy(incoming)) {
1933
1536
  discard(incoming);
1934
1537
  return result(installed, target, "defer", "the copy on the Cursor volume did not reproduce the verified tree");
1935
1538
  }
@@ -1947,8 +1550,8 @@ function cursorArm(options) {
1947
1550
  return result(installed, target, "ok", `staged ${target} for next launch while Cursor is running (${busy}) \u2014 ${pending}`, true);
1948
1551
  }
1949
1552
  const stamp = Date.now();
1950
- const quarantined = (0, import_node_path10.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
1951
- for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs13.mkdirSync)((0, import_node_path10.join)(root, dir), { recursive: true });
1553
+ const quarantined = (0, import_node_path9.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
1554
+ for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs12.mkdirSync)((0, import_node_path9.join)(root, dir), { recursive: true });
1952
1555
  const started = cursorHostEvidence(env);
1953
1556
  if (started) {
1954
1557
  const pending = noteLaunchStaged(paths, env, {
@@ -1963,15 +1566,15 @@ function cursorArm(options) {
1963
1566
  }
1964
1567
  let displaced = false;
1965
1568
  try {
1966
- if ((0, import_node_fs13.existsSync)(live)) {
1967
- (0, import_node_fs13.renameSync)(live, quarantined);
1569
+ if ((0, import_node_fs12.existsSync)(live)) {
1570
+ (0, import_node_fs12.renameSync)(live, quarantined);
1968
1571
  displaced = true;
1969
1572
  }
1970
- (0, import_node_fs13.renameSync)(incoming, live);
1573
+ (0, import_node_fs12.renameSync)(incoming, live);
1971
1574
  } catch (error) {
1972
- if (displaced && !(0, import_node_fs13.existsSync)(live)) {
1575
+ if (displaced && !(0, import_node_fs12.existsSync)(live)) {
1973
1576
  try {
1974
- (0, import_node_fs13.renameSync)(quarantined, live);
1577
+ (0, import_node_fs12.renameSync)(quarantined, live);
1975
1578
  } catch {
1976
1579
  }
1977
1580
  }
@@ -1981,8 +1584,8 @@ function cursorArm(options) {
1981
1584
  return result(installed, target, "defer", `generation switch failed, previous generation retained: ${reason}${pending}`);
1982
1585
  }
1983
1586
  const evidence = manifest(live)?.version;
1984
- if (evidence !== target || !treeHealthy2(live)) {
1985
- const broken = `${(0, import_node_path10.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
1587
+ if (evidence !== target || !treeHealthy(live)) {
1588
+ const broken = `${(0, import_node_path9.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
1986
1589
  const outcome = displaced ? restorePrevious(live, quarantined, broken) : "none";
1987
1590
  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";
1988
1591
  writeRollbackPointer(root, outcome === "restored" ? { installed: installed ?? "unknown", previous: null, generation: null } : { installed: evidence ?? "unknown", previous: installed, generation: displaced ? quarantined : null });
@@ -1995,12 +1598,12 @@ function cursorArm(options) {
1995
1598
  return result(installed, target, "ok", `switched ${installed ?? "absent"} -> ${target}; ${previous}${pruned ? `; pruned ${pruned} older generation${pruned === 1 ? "" : "s"}` : ""}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
1996
1599
  }
1997
1600
  function probeCursorLaunchStaging() {
1998
- return probeLaunchTargetVersion((0, import_node_path10.join)(cursorPluginsRoot(), "staging", "launch-target.json"), (root) => manifest(root)?.version ?? null);
1601
+ return probeLaunchTargetVersion((0, import_node_path9.join)(cursorPluginsRoot(), "staging", "launch-target.json"), (root) => manifest(root)?.version ?? null);
1999
1602
  }
2000
1603
 
2001
1604
  // src/arm-jervcode.ts
2002
- var import_node_fs14 = require("node:fs");
2003
- var import_node_path11 = require("node:path");
1605
+ var import_node_fs13 = require("node:fs");
1606
+ var import_node_path10 = require("node:path");
2004
1607
  var PI_PACKAGE = "@mutmutco/pi-plugin";
2005
1608
  function hostEnv(env) {
2006
1609
  const dir = jervcodeAgentDir(env);
@@ -2011,7 +1614,7 @@ function hostCommands(_env) {
2011
1614
  }
2012
1615
  function readPackageSpecs(env) {
2013
1616
  try {
2014
- const settings = JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
1617
+ const settings = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
2015
1618
  return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
2016
1619
  } catch {
2017
1620
  return null;
@@ -2019,14 +1622,14 @@ function readPackageSpecs(env) {
2019
1622
  }
2020
1623
  function installedVersion(env) {
2021
1624
  try {
2022
- const manifest2 = JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
1625
+ const manifest2 = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
2023
1626
  return typeof manifest2.version === "string" ? manifest2.version : null;
2024
1627
  } catch {
2025
1628
  return null;
2026
1629
  }
2027
1630
  }
2028
1631
  function materializedManifestLocation(env) {
2029
- return (0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
1632
+ return (0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
2030
1633
  }
2031
1634
  var PI_LIST_SPEC = new RegExp(`npm:${PI_PACKAGE.replace("/", "\\/")}@(\\S+)`);
2032
1635
  function parseListText(stdout) {
@@ -2085,32 +1688,32 @@ function jervcodeArm(options) {
2085
1688
  detail,
2086
1689
  // C11: the constraint of the package pi LEFT materialised — read at result time, so defer/fail
2087
1690
  // carry the previous payload's declaration (#4973 reconsult ruling).
2088
- compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
1691
+ compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
2089
1692
  });
2090
1693
  const before = readPackageSpecs(env);
2091
- if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path11.join)(jervcodeAgentDir(env), "settings.json")}`);
1694
+ if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json")}`);
2092
1695
  const from = installedVersion(env);
2093
1696
  const legacy = before.filter(isLegacyMmiPiPath);
2094
1697
  const highWater = journalHighWater(options.paths.journalPath, "jervcode");
2095
1698
  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;
2096
1699
  if (options.dryRun) {
2097
1700
  const action = floor ? `hold ${from ?? "the install"} and skip the install (${floor})` : before.includes(exact) && from === options.target ? "keep exact pin" : `install ${exact}`;
2098
- 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_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1701
+ 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")) };
2099
1702
  }
2100
1703
  const hosted = hostEnv(env);
2101
1704
  const commands = hostCommands(env);
2102
1705
  const command = commands.find((bin) => runner(bin, ["--version"], hosted).status === 0);
2103
- 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_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1706
+ 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")) };
2104
1707
  if (!floor && (!before.includes(exact) || from !== options.target)) {
2105
1708
  const install = runner(command, ["install", exact], hosted);
2106
1709
  if (install.status !== 0) {
2107
- return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1710
+ 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")) };
2108
1711
  }
2109
1712
  }
2110
1713
  for (const stale of legacy) {
2111
1714
  const removed = runner(command, ["remove", stale], hosted);
2112
1715
  if (removed.status !== 0) {
2113
- return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1716
+ 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")) };
2114
1717
  }
2115
1718
  }
2116
1719
  const after = readPackageSpecs(env);
@@ -2118,7 +1721,7 @@ function jervcodeArm(options) {
2118
1721
  const legacyAfter = after?.filter(isLegacyMmiPiPath) ?? [];
2119
1722
  const retired = legacy.length ? `; retired ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : "";
2120
1723
  if (floor && !legacyAfter.length) {
2121
- return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
1724
+ 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")) };
2122
1725
  }
2123
1726
  if (!after?.includes(exact) || converged !== options.target || legacyAfter.length) {
2124
1727
  return {
@@ -2139,15 +1742,15 @@ function jervcodeArm(options) {
2139
1742
  }
2140
1743
 
2141
1744
  // src/arm-hermes.ts
2142
- var import_node_fs15 = require("node:fs");
2143
- var import_node_child_process9 = require("node:child_process");
2144
- var import_node_path12 = require("node:path");
1745
+ var import_node_fs14 = require("node:fs");
1746
+ var import_node_child_process8 = require("node:child_process");
1747
+ var import_node_path11 = require("node:path");
2145
1748
  var PACKAGE2 = "@mutmutco/hermes-plugin";
2146
1749
  var MANIFEST2 = "plugin.yaml";
2147
1750
  var TREE_MARKERS_FALLBACK = [MANIFEST2, "__init__.py", "skills"];
2148
1751
  function yamlVersion(root) {
2149
1752
  try {
2150
- const text = (0, import_node_fs15.readFileSync)((0, import_node_path12.join)(root, MANIFEST2), "utf8");
1753
+ const text = (0, import_node_fs14.readFileSync)((0, import_node_path11.join)(root, MANIFEST2), "utf8");
2151
1754
  try {
2152
1755
  const version = JSON.parse(text).version;
2153
1756
  if (typeof version === "string" && version.trim()) return version.trim();
@@ -2161,7 +1764,7 @@ function yamlVersion(root) {
2161
1764
  }
2162
1765
  function packageTreeMarkers(root) {
2163
1766
  try {
2164
- const files = JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path12.join)(root, "package.json"), "utf8")).files;
1767
+ const files = JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(root, "package.json"), "utf8")).files;
2165
1768
  if (Array.isArray(files) && files.length && files.every((entry) => typeof entry === "string" && entry.trim())) {
2166
1769
  return files.map((entry) => entry.trim());
2167
1770
  }
@@ -2170,10 +1773,10 @@ function packageTreeMarkers(root) {
2170
1773
  return TREE_MARKERS_FALLBACK;
2171
1774
  }
2172
1775
  function markerPresent(root, rel) {
2173
- const path = (0, import_node_path12.join)(root, ...rel.split("/"));
2174
- if (!(0, import_node_fs15.existsSync)(path)) return false;
1776
+ const path = (0, import_node_path11.join)(root, ...rel.split("/"));
1777
+ if (!(0, import_node_fs14.existsSync)(path)) return false;
2175
1778
  try {
2176
- const stat = (0, import_node_fs15.statSync)(path);
1779
+ const stat = (0, import_node_fs14.statSync)(path);
2177
1780
  return stat.isFile() || stat.isDirectory();
2178
1781
  } catch {
2179
1782
  return false;
@@ -2183,7 +1786,7 @@ function treeHealth(root) {
2183
1786
  const missing = packageTreeMarkers(root).filter((rel) => !markerPresent(root, rel));
2184
1787
  return { healthy: missing.length === 0, missing };
2185
1788
  }
2186
- function treeHealthy3(root) {
1789
+ function treeHealthy2(root) {
2187
1790
  return treeHealth(root).healthy;
2188
1791
  }
2189
1792
  function treeIncompleteDetail(root, label) {
@@ -2192,13 +1795,13 @@ function treeIncompleteDetail(root, label) {
2192
1795
  }
2193
1796
  function packageIdentity(root) {
2194
1797
  try {
2195
- return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path12.join)(root, "package.json"), "utf8")).name === PACKAGE2;
1798
+ return JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(root, "package.json"), "utf8")).name === PACKAGE2;
2196
1799
  } catch {
2197
1800
  return false;
2198
1801
  }
2199
1802
  }
2200
1803
  function probeHermesInstallation(env = process.env) {
2201
- const location = (0, import_node_path12.join)(hermesHome(env), "plugins", "mmi");
1804
+ const location = (0, import_node_path11.join)(hermesHome(env), "plugins", "mmi");
2202
1805
  const version = yamlVersion(location);
2203
1806
  return { version, location, detail: version ? "installed Hermes plugin manifest" : "installed Hermes plugin manifest was unreadable" };
2204
1807
  }
@@ -2207,7 +1810,7 @@ function mmiOwned2(root) {
2207
1810
  }
2208
1811
  function discard2(path) {
2209
1812
  try {
2210
- (0, import_node_fs15.rmSync)(path, { recursive: true, force: true });
1813
+ (0, import_node_fs14.rmSync)(path, { recursive: true, force: true });
2211
1814
  } catch {
2212
1815
  }
2213
1816
  }
@@ -2226,7 +1829,7 @@ function hermesProcessToken(line, home) {
2226
1829
  return hermesToken(tokens) ?? (home && normalized.toLowerCase().includes(home.toLowerCase()) ? home : null);
2227
1830
  }
2228
1831
  function realProcessListing() {
2229
- 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 });
1832
+ const result = process.platform === "win32" ? (0, import_node_child_process8.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process8.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
2230
1833
  return result.status === 0 ? result.stdout ?? "" : null;
2231
1834
  }
2232
1835
  function hermesHostEvidence(env, home, processListProvider) {
@@ -2239,19 +1842,19 @@ function hermesHostEvidence(env, home, processListProvider) {
2239
1842
  return null;
2240
1843
  }
2241
1844
  function materializeHermesSkills(home) {
2242
- const pluginSkills = (0, import_node_path12.join)(home, "plugins", "mmi", "skills");
2243
- const skillsRoot = (0, import_node_path12.join)(home, "skills");
2244
- const targetRoot = (0, import_node_path12.join)(skillsRoot, "mmi");
1845
+ const pluginSkills = (0, import_node_path11.join)(home, "plugins", "mmi", "skills");
1846
+ const skillsRoot = (0, import_node_path11.join)(home, "skills");
1847
+ const targetRoot = (0, import_node_path11.join)(skillsRoot, "mmi");
2245
1848
  try {
2246
- if (!(0, import_node_fs15.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
2247
- const names = (0, import_node_fs15.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
1849
+ if (!(0, import_node_fs14.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
1850
+ const names = (0, import_node_fs14.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
2248
1851
  if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
2249
- (0, import_node_fs15.mkdirSync)(skillsRoot, { recursive: true });
2250
- const incoming = (0, import_node_path12.join)(skillsRoot, ".mmi-incoming");
1852
+ (0, import_node_fs14.mkdirSync)(skillsRoot, { recursive: true });
1853
+ const incoming = (0, import_node_path11.join)(skillsRoot, ".mmi-incoming");
2251
1854
  discard2(incoming);
2252
- (0, import_node_fs15.cpSync)(pluginSkills, incoming, { recursive: true });
1855
+ (0, import_node_fs14.cpSync)(pluginSkills, incoming, { recursive: true });
2253
1856
  discard2(targetRoot);
2254
- (0, import_node_fs15.renameSync)(incoming, targetRoot);
1857
+ (0, import_node_fs14.renameSync)(incoming, targetRoot);
2255
1858
  return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
2256
1859
  } catch (error) {
2257
1860
  return { ok: false, detail: message2(error) };
@@ -2260,8 +1863,8 @@ function materializeHermesSkills(home) {
2260
1863
  function hermesArm(options) {
2261
1864
  const env = options.env ?? process.env;
2262
1865
  const home = hermesHome(env);
2263
- const parent = (0, import_node_path12.join)(home, "plugins");
2264
- const live = (0, import_node_path12.join)(parent, "mmi");
1866
+ const parent = (0, import_node_path11.join)(home, "plugins");
1867
+ const live = (0, import_node_path11.join)(parent, "mmi");
2265
1868
  const installed = yamlVersion(live);
2266
1869
  const result = (from, to, verdict, detail, launchStaged = false) => ({
2267
1870
  surface: "hermes",
@@ -2274,18 +1877,18 @@ function hermesArm(options) {
2274
1877
  if (!hermesHostPresent(env, options.hostCommandRunner)) {
2275
1878
  return result(null, options.target, "skip", `host absent (${hermesHostCommand(env)} CLI not on PATH) \u2014 skipped without writes`);
2276
1879
  }
2277
- if ((0, import_node_fs15.existsSync)(live) && !mmiOwned2(live)) {
1880
+ if ((0, import_node_fs14.existsSync)(live) && !mmiOwned2(live)) {
2278
1881
  return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
2279
1882
  }
2280
1883
  if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
2281
1884
  const highWater = journalHighWater(options.paths.journalPath, "hermes");
2282
1885
  if (highWater && compareSemver(highWater, options.target) > 0) return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${options.target} (monotonic)`);
2283
- if (installed === options.target && treeHealthy3(live)) {
1886
+ if (installed === options.target && treeHealthy2(live)) {
2284
1887
  const skills2 = materializeHermesSkills(home);
2285
1888
  if (!skills2.ok) return result(installed, options.target, "fail", `plugin at target ${options.target}, but skills provisioning failed: ${skills2.detail}`);
2286
1889
  return result(installed, options.target, "ok", `already at target ${options.target}; ${skills2.detail}`);
2287
1890
  }
2288
- 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_path12.join)(home, "skills", "mmi")}`);
1891
+ 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")}`);
2289
1892
  const staged = stagePackage({ surface: "hermes", packageName: PACKAGE2, target: options.target, witness: MANIFEST2, paths: options.paths, env });
2290
1893
  if ("defer" in staged) return result(installed, options.target, "defer", staged.defer);
2291
1894
  const stagedVersion = yamlVersion(staged.packageRoot);
@@ -2294,19 +1897,19 @@ function hermesArm(options) {
2294
1897
  }
2295
1898
  const stagedTree = treeIncompleteDetail(staged.packageRoot, "staged Hermes");
2296
1899
  if (stagedTree) return result(installed, options.target, "defer", stagedTree);
2297
- const scratch = (0, import_node_path12.join)(home, ".mmi-updater");
2298
- const launchTargetPath = (0, import_node_path12.join)(scratch, "launch-target.json");
2299
- const incomingRoot = (0, import_node_path12.join)(scratch, "incoming");
2300
- const incoming = (0, import_node_path12.join)(incomingRoot, options.target);
2301
- const quarantine = (0, import_node_path12.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
1900
+ const scratch = (0, import_node_path11.join)(home, ".mmi-updater");
1901
+ const launchTargetPath = (0, import_node_path11.join)(scratch, "launch-target.json");
1902
+ const incomingRoot = (0, import_node_path11.join)(scratch, "incoming");
1903
+ const incoming = (0, import_node_path11.join)(incomingRoot, options.target);
1904
+ const quarantine = (0, import_node_path11.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
2302
1905
  const priorLaunch = readLaunchTarget(launchTargetPath);
2303
- const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target && treeHealthy3(incoming);
1906
+ const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target && treeHealthy2(incoming);
2304
1907
  if (!reuseIncoming) {
2305
1908
  try {
2306
- (0, import_node_fs15.mkdirSync)(parent, { recursive: true });
2307
- (0, import_node_fs15.mkdirSync)((0, import_node_path12.join)(scratch, "quarantine"), { recursive: true });
1909
+ (0, import_node_fs14.mkdirSync)(parent, { recursive: true });
1910
+ (0, import_node_fs14.mkdirSync)((0, import_node_path11.join)(scratch, "quarantine"), { recursive: true });
2308
1911
  discard2(incomingRoot);
2309
- (0, import_node_fs15.cpSync)(staged.packageRoot, incoming, { recursive: true });
1912
+ (0, import_node_fs14.cpSync)(staged.packageRoot, incoming, { recursive: true });
2310
1913
  } catch (error) {
2311
1914
  return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
2312
1915
  }
@@ -2334,13 +1937,13 @@ function hermesArm(options) {
2334
1937
  });
2335
1938
  return result(installed, options.target, "ok", `staged ${options.target} for next launch while Hermes is running (${busy}) \u2014 ${pending}`, true);
2336
1939
  }
2337
- const displaced = (0, import_node_fs15.existsSync)(live);
1940
+ const displaced = (0, import_node_fs14.existsSync)(live);
2338
1941
  try {
2339
- if (displaced) (0, import_node_fs15.renameSync)(live, quarantine);
2340
- (0, import_node_fs15.renameSync)(incoming, live);
1942
+ if (displaced) (0, import_node_fs14.renameSync)(live, quarantine);
1943
+ (0, import_node_fs14.renameSync)(incoming, live);
2341
1944
  } catch (error) {
2342
- if (displaced && !(0, import_node_fs15.existsSync)(live)) try {
2343
- (0, import_node_fs15.renameSync)(quarantine, live);
1945
+ if (displaced && !(0, import_node_fs14.existsSync)(live)) try {
1946
+ (0, import_node_fs14.renameSync)(quarantine, live);
2344
1947
  } catch {
2345
1948
  }
2346
1949
  discard2(incomingRoot);
@@ -2351,12 +1954,12 @@ function hermesArm(options) {
2351
1954
  const liveVersion = yamlVersion(live);
2352
1955
  const liveTree = treeIncompleteDetail(live, "live Hermes");
2353
1956
  if (liveVersion !== options.target || liveTree) {
2354
- const broken = (0, import_node_path12.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
1957
+ const broken = (0, import_node_path11.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
2355
1958
  const verifyDetail = liveVersion !== options.target ? `manifest is ${liveVersion ?? "unreadable"}, expected ${options.target}` : liveTree;
2356
1959
  try {
2357
- (0, import_node_fs15.mkdirSync)((0, import_node_path12.join)(scratch, "rejected"), { recursive: true });
2358
- (0, import_node_fs15.renameSync)(live, broken);
2359
- if (displaced) (0, import_node_fs15.renameSync)(quarantine, live);
1960
+ (0, import_node_fs14.mkdirSync)((0, import_node_path11.join)(scratch, "rejected"), { recursive: true });
1961
+ (0, import_node_fs14.renameSync)(live, broken);
1962
+ if (displaced) (0, import_node_fs14.renameSync)(quarantine, live);
2360
1963
  } catch (error) {
2361
1964
  return result(installed, liveVersion ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
2362
1965
  }
@@ -2369,21 +1972,17 @@ function hermesArm(options) {
2369
1972
  return result(installed, options.target, "ok", `converged ${installed ?? "absent"} -> ${options.target} (staged, verified, atomically switched${displaced ? `; previous generation quarantined at ${quarantine}` : ""}); ${skills.detail}`);
2370
1973
  }
2371
1974
  function probeHermesLaunchStaging(env = process.env) {
2372
- return probeLaunchTargetVersion((0, import_node_path12.join)(hermesHome(env), ".mmi-updater", "launch-target.json"), yamlVersion);
1975
+ return probeLaunchTargetVersion((0, import_node_path11.join)(hermesHome(env), ".mmi-updater", "launch-target.json"), yamlVersion);
2373
1976
  }
2374
1977
 
2375
1978
  // src/reap.ts
2376
- var import_node_child_process10 = require("node:child_process");
2377
- var import_node_fs16 = require("node:fs");
2378
- var import_node_path13 = require("node:path");
1979
+ var import_node_fs15 = require("node:fs");
1980
+ var import_node_path12 = require("node:path");
2379
1981
  var KEEP_GENERATIONS2 = 2;
2380
1982
  var SCRATCH_AGE_MS = 24 * 60 * 6e4;
2381
- var ORPHAN_MIN_AGE_MS = 10 * 6e4;
2382
- var SNAPSHOT_TIMEOUT_MS = 2e4;
2383
- var MAX_UNWIND_ROUNDS = 4;
2384
1983
  function listDirs(path) {
2385
1984
  try {
2386
- return (0, import_node_fs16.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1985
+ return (0, import_node_fs15.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
2387
1986
  } catch {
2388
1987
  return [];
2389
1988
  }
@@ -2391,7 +1990,7 @@ function listDirs(path) {
2391
1990
  function generationSets(root) {
2392
1991
  const children = listDirs(root);
2393
1992
  if (children.some((name) => parseSemver(name))) return [root];
2394
- return children.map((name) => (0, import_node_path13.join)(root, name)).filter((path) => listDirs(path).some((name) => parseSemver(name)));
1993
+ return children.map((name) => (0, import_node_path12.join)(root, name)).filter((path) => listDirs(path).some((name) => parseSemver(name)));
2395
1994
  }
2396
1995
  function pruneGenerations(root, category, dryRun) {
2397
1996
  let reaped = 0;
@@ -2404,7 +2003,7 @@ function pruneGenerations(root, category, dryRun) {
2404
2003
  continue;
2405
2004
  }
2406
2005
  try {
2407
- (0, import_node_fs16.rmSync)((0, import_node_path13.join)(set, version), { recursive: true, force: true });
2006
+ (0, import_node_fs15.rmSync)((0, import_node_path12.join)(set, version), { recursive: true, force: true });
2408
2007
  reaped += 1;
2409
2008
  } catch (error) {
2410
2009
  failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
@@ -2418,8 +2017,8 @@ function pruneGenerations(root, category, dryRun) {
2418
2017
  }
2419
2018
  var LEASE_STEAL_SCRATCH = /^lease\.lock\.stale-\d+$/;
2420
2019
  function atomicWriteScratch(path) {
2421
- const literal = (0, import_node_path13.basename)(path).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2422
- return { dir: (0, import_node_path13.dirname)(path), pattern: new RegExp(`^${literal}\\.tmp-\\d+$`) };
2020
+ const literal = (0, import_node_path12.basename)(path).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2021
+ return { dir: (0, import_node_path12.dirname)(path), pattern: new RegExp(`^${literal}\\.tmp-\\d+$`) };
2423
2022
  }
2424
2023
  function reapScratch(context, dryRun) {
2425
2024
  const category = "scratch";
@@ -2437,7 +2036,7 @@ function reapScratch(context, dryRun) {
2437
2036
  for (const [dir, patterns] of scanned) {
2438
2037
  let entries;
2439
2038
  try {
2440
- entries = (0, import_node_fs16.readdirSync)(dir, { withFileTypes: true });
2039
+ entries = (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true });
2441
2040
  } catch {
2442
2041
  unreadable.push(dir);
2443
2042
  continue;
@@ -2445,10 +2044,10 @@ function reapScratch(context, dryRun) {
2445
2044
  for (const entry of entries) {
2446
2045
  if (!patterns.some((pattern) => pattern.test(entry.name))) continue;
2447
2046
  if (!entry.isFile()) continue;
2448
- const path = (0, import_node_path13.join)(dir, entry.name);
2047
+ const path = (0, import_node_path12.join)(dir, entry.name);
2449
2048
  let ageMs;
2450
2049
  try {
2451
- ageMs = now - (0, import_node_fs16.statSync)(path).mtimeMs;
2050
+ ageMs = now - (0, import_node_fs15.statSync)(path).mtimeMs;
2452
2051
  } catch {
2453
2052
  continue;
2454
2053
  }
@@ -2458,7 +2057,7 @@ function reapScratch(context, dryRun) {
2458
2057
  continue;
2459
2058
  }
2460
2059
  try {
2461
- (0, import_node_fs16.rmSync)(path, { force: true });
2060
+ (0, import_node_fs15.rmSync)(path, { force: true });
2462
2061
  reaped += 1;
2463
2062
  } catch (error) {
2464
2063
  failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
@@ -2471,194 +2070,6 @@ function reapScratch(context, dryRun) {
2471
2070
  const stuck = failures.length ? `; ${failures.length} locked (${failures.join(", ")})` : "";
2472
2071
  return { category, reaped, verdict: failures.length ? "defer" : "ok", detail: `${verb} ${reaped} aged scratch entr(ies) under ${swept.join(", ")}${stuck}` };
2473
2072
  }
2474
- var HOOK_IMAGES = /* @__PURE__ */ new Set(["mmi-hook.exe", "node.exe", "cmd.exe", "powershell.exe", "pwsh.exe"]);
2475
- var HOOK_ARTIFACT = /(?:^|[\\/])(?:mmi-hook(?:-console)?(?:\.exe|\.cmd|\.bat)?|hook-run\.mjs)$/i;
2476
- var NODE_INTERPRETER = /(?:^|[\\/])node(?:\.exe)?$/i;
2477
- var NODE_VALUE_FLAGS = /* @__PURE__ */ new Set(["-e", "--eval", "-p", "--print", "-r", "--require", "--import", "--loader", "--experimental-loader", "-C", "--conditions"]);
2478
- var CMD_SHELL = /(?:^|[\\/])cmd(?:\.exe)?$/i;
2479
- var CMD_RUN_FLAGS = /* @__PURE__ */ new Set(["/c", "/k"]);
2480
- var PS_SHELL = /(?:^|[\\/])(?:powershell|pwsh)(?:\.exe)?$/i;
2481
- var PS_COMMAND_FLAGS = /* @__PURE__ */ new Set(["-command", "-c"]);
2482
- var SEPARATORS = ["&", "|"];
2483
- function tokenize(commandLine) {
2484
- const tokens = [];
2485
- let current = "";
2486
- let quoted = false;
2487
- let slashes = 0;
2488
- for (const char of commandLine) {
2489
- if (char === "\\") {
2490
- slashes += 1;
2491
- continue;
2492
- }
2493
- if (char === '"') {
2494
- current += "\\".repeat(slashes >> 1);
2495
- if (slashes % 2) current += '"';
2496
- else quoted = !quoted;
2497
- slashes = 0;
2498
- continue;
2499
- }
2500
- current += "\\".repeat(slashes);
2501
- slashes = 0;
2502
- if (!quoted && /\s/.test(char)) {
2503
- if (current) tokens.push(current);
2504
- current = "";
2505
- continue;
2506
- }
2507
- current += char;
2508
- }
2509
- current += "\\".repeat(slashes);
2510
- if (current) tokens.push(current);
2511
- return tokens;
2512
- }
2513
- function invocation(commandLine) {
2514
- let tokens = tokenize(commandLine);
2515
- if (tokens.length && CMD_SHELL.test(tokens[0])) {
2516
- const flag = tokens.findIndex((token) => CMD_RUN_FLAGS.has(token.toLowerCase()));
2517
- if (flag < 0) return [];
2518
- const rest = tokens.slice(flag + 1);
2519
- tokens = rest.length === 1 ? tokenize(rest[0]) : rest;
2520
- } else if (tokens.length && PS_SHELL.test(tokens[0])) {
2521
- const flag = tokens.findIndex((token) => PS_COMMAND_FLAGS.has(token.toLowerCase()));
2522
- if (flag < 0) return [];
2523
- const rest = tokens.slice(flag + 1);
2524
- tokens = rest.length === 1 ? tokenize(rest[0]) : rest;
2525
- if (tokens[0] === "&") tokens = tokens.slice(1);
2526
- }
2527
- const cut = tokens.findIndex((token) => SEPARATORS.some((separator) => token.includes(separator)));
2528
- return cut < 0 ? tokens : tokens.slice(0, cut);
2529
- }
2530
- function hookProgram(tokens) {
2531
- if (!tokens.length) return null;
2532
- if (HOOK_ARTIFACT.test(tokens[0])) return tokens[0];
2533
- if (!NODE_INTERPRETER.test(tokens[0])) return null;
2534
- let at = 1;
2535
- while (at < tokens.length && tokens[at].startsWith("-")) {
2536
- const flag = tokens[at].toLowerCase();
2537
- at += !flag.includes("=") && NODE_VALUE_FLAGS.has(flag) ? 2 : 1;
2538
- }
2539
- return at < tokens.length && HOOK_ARTIFACT.test(tokens[at]) ? tokens[at] : null;
2540
- }
2541
- function flagValue(tokens, flag) {
2542
- const at = tokens.indexOf(flag);
2543
- return at >= 0 && at + 1 < tokens.length ? tokens[at + 1] : null;
2544
- }
2545
- function selectHookOrphans(rows, livePids, options) {
2546
- const kills = [];
2547
- for (const row of rows) {
2548
- if (row.pid === options.selfPid) continue;
2549
- if (!HOOK_IMAGES.has(row.name.toLowerCase())) continue;
2550
- const tokens = invocation(row.commandLine);
2551
- const program = hookProgram(tokens);
2552
- if (!program) continue;
2553
- const surface = flagValue(tokens, "--surface");
2554
- const gate = flagValue(tokens, "--gate");
2555
- if (!surface || !gate) continue;
2556
- if (livePids.has(row.ppid)) continue;
2557
- if (row.startedMs === null) continue;
2558
- const ageMs = options.now - row.startedMs;
2559
- if (ageMs < options.minAgeMs) continue;
2560
- kills.push({
2561
- pid: row.pid,
2562
- name: row.name,
2563
- reason: `runs ${program} --surface ${surface} --gate ${gate}; parent ${row.ppid} absent from the snapshot; alive ${Math.round(ageMs / 6e4)}m`,
2564
- identity: row
2565
- });
2566
- }
2567
- return kills;
2568
- }
2569
- function stillTheProvenProcess(rows, kill) {
2570
- const live = rows.find((row) => row.pid === kill.pid);
2571
- if (!live) return false;
2572
- const same = live.name === kill.identity.name && live.commandLine === kill.identity.commandLine && live.startedMs === kill.identity.startedMs && live.ppid === kill.identity.ppid;
2573
- return same && !rows.some((row) => row.pid === kill.identity.ppid);
2574
- }
2575
- 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";
2576
- function processSnapshot() {
2577
- const result = (0, import_node_child_process10.spawnSync)("powershell.exe", ["-NoProfile", "-Command", SNAPSHOT_COMMAND], { encoding: "utf8", windowsHide: true, timeout: SNAPSHOT_TIMEOUT_MS });
2578
- if (result.status !== 0 || !(result.stdout ?? "").trim()) return null;
2579
- try {
2580
- const parsed = JSON.parse(result.stdout);
2581
- const rows = Array.isArray(parsed) ? parsed : [parsed];
2582
- return rows.filter((row) => typeof row?.ProcessId === "number").map((row) => ({
2583
- pid: row.ProcessId,
2584
- ppid: typeof row.ParentProcessId === "number" ? row.ParentProcessId : -1,
2585
- name: typeof row.Name === "string" ? row.Name : "",
2586
- commandLine: typeof row.CommandLine === "string" ? row.CommandLine : "",
2587
- startedMs: typeof row.Started === "string" ? Number.isNaN(Date.parse(row.Started)) ? null : Date.parse(row.Started) : null
2588
- }));
2589
- } catch {
2590
- return null;
2591
- }
2592
- }
2593
- function reapHookOrphans(dryRun) {
2594
- const category = "hook-orphans";
2595
- if (process.platform !== "win32") {
2596
- return { category, reaped: 0, verdict: "skip", detail: "dead-parent hook reap is Windows-only (#4943 class) \u2014 skipped" };
2597
- }
2598
- const snapshot = processSnapshot();
2599
- if (!snapshot) {
2600
- return { category, reaped: 0, verdict: "skip", detail: "process list unreadable \u2014 nothing is provably an orphan, so nothing was killed" };
2601
- }
2602
- const select = (rows) => selectHookOrphans(rows, new Set(rows.map((row) => row.pid)), { now: Date.now(), selfPid: process.pid, minAgeMs: ORPHAN_MIN_AGE_MS });
2603
- const first = select(snapshot);
2604
- if (!first.length) return { category, reaped: 0, verdict: "skip", detail: `no dead-parent MMI hook descendants among ${snapshot.length} processes` };
2605
- if (dryRun) {
2606
- const named2 = first.map((kill) => `${kill.name}#${kill.pid} (${kill.reason})`).join("; ");
2607
- return { category, reaped: first.length, verdict: "ok", detail: `dry-run: would kill ${first.length} dead-parent hook orphan(s) in the first unwind round: ${named2}` };
2608
- }
2609
- const reaped = [];
2610
- const survived = [];
2611
- const declined = [];
2612
- const attempted = /* @__PURE__ */ new Set();
2613
- let pending = first;
2614
- let rounds = 0;
2615
- let halted = "";
2616
- while (pending.length && rounds < MAX_UNWIND_ROUNDS) {
2617
- rounds += 1;
2618
- const confirm = processSnapshot();
2619
- if (!confirm) {
2620
- halted = "a re-validation snapshot was unreadable, so the remaining rows were not killed";
2621
- break;
2622
- }
2623
- const proven = pending.filter((kill) => stillTheProvenProcess(confirm, kill));
2624
- declined.push(...pending.filter((kill) => !proven.includes(kill)));
2625
- for (const kill of pending) attempted.add(kill.pid);
2626
- pending = [];
2627
- if (!proven.length) break;
2628
- for (const orphan of proven) {
2629
- try {
2630
- process.kill(orphan.pid);
2631
- } catch {
2632
- }
2633
- }
2634
- const after = processSnapshot();
2635
- if (!after) {
2636
- halted = `${proven.length} kill(s) were issued but the verification snapshot was unreadable \u2014 they are not claimed reaped`;
2637
- break;
2638
- }
2639
- const alive = new Set(after.map((row) => row.pid));
2640
- for (const orphan of proven) (alive.has(orphan.pid) ? survived : reaped).push(orphan);
2641
- pending = select(after).filter((kill) => !attempted.has(kill.pid));
2642
- }
2643
- const notes = [];
2644
- if (survived.length) notes.push(`${survived.length} survived the kill (${survived.map((kill) => kill.pid).join(", ")})`);
2645
- if (declined.length) notes.push(`${declined.length} declined at re-validation \u2014 gone on its own, or no longer the row that was proven (${declined.map((kill) => kill.pid).join(", ")})`);
2646
- if (pending.length) {
2647
- const why = rounds >= MAX_UNWIND_ROUNDS ? `the ${MAX_UNWIND_ROUNDS}-round unwind budget ran out` : "this tick stopped short";
2648
- notes.push(`${pending.length} hook orphan(s) still standing because ${why} (${pending.map((kill) => `${kill.name}#${kill.pid}`).join(", ")}) \u2014 next tick continues`);
2649
- }
2650
- if (halted) notes.push(halted);
2651
- const named = reaped.map((kill) => `${kill.name}#${kill.pid} (${kill.reason})`).join("; ");
2652
- if (!notes.length) {
2653
- return { category, reaped: reaped.length, verdict: "ok", detail: `reaped ${reaped.length} dead-parent hook orphan(s) over ${rounds} unwind round(s), verified gone: ${named}` };
2654
- }
2655
- return {
2656
- category,
2657
- reaped: reaped.length,
2658
- verdict: "defer",
2659
- detail: `reaped ${reaped.length} dead-parent hook orphan(s) over ${rounds} unwind round(s)${named ? ` (${named})` : ""}; ${notes.join("; ")}`
2660
- };
2661
- }
2662
2073
  function reap(options) {
2663
2074
  const { paths, dryRun } = options;
2664
2075
  const env = options.env ?? process.env;
@@ -2669,11 +2080,10 @@ function reap(options) {
2669
2080
  return { category, reaped: 0, verdict: "fail", detail: `reap failed: ${error?.message ?? error}` };
2670
2081
  }
2671
2082
  };
2672
- const atomicWritePaths = [claudeMarketplacesPath(env), kiloConfigPath(env)];
2083
+ const atomicWritePaths = [claudeMarketplacesPath(env)];
2673
2084
  return [
2674
2085
  guarded("staging-prune", () => pruneGenerations(paths.staging, "staging-prune", dryRun)),
2675
- guarded("scratch", () => reapScratch({ root: paths.root, atomicWritePaths }, dryRun)),
2676
- guarded("hook-orphans", () => reapHookOrphans(dryRun))
2086
+ guarded("scratch", () => reapScratch({ root: paths.root, atomicWritePaths }, dryRun))
2677
2087
  ];
2678
2088
  }
2679
2089
 
@@ -2698,7 +2108,7 @@ function reconcile(options) {
2698
2108
  const stamped = options.dryRun ? { ...event, dryRun: true } : event;
2699
2109
  if (!appendJournal(paths.journalPath, { ts: (/* @__PURE__ */ new Date()).toISOString(), run, ...stamped })) summary.journalOk = false;
2700
2110
  };
2701
- const summary = { run, target: null, gate: null, atomicity: null, cli: null, updater: null, plugins: [], reap: [], surfaces: [], journalOk: true, reexec: null, exit: 0 };
2111
+ const summary = { run, target: null, gate: null, managedSurfaces: null, atomicity: null, cli: null, updater: null, plugins: [], reap: [], surfaces: [], journalOk: true, reexec: null, exit: 0 };
2702
2112
  const recordArm = (arm) => {
2703
2113
  if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
2704
2114
  journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat });
@@ -2747,6 +2157,7 @@ function reconcile(options) {
2747
2157
  continue;
2748
2158
  }
2749
2159
  summary.target = candidate;
2160
+ summary.managedSurfaces = Array.isArray(gate.bom?.managedSurfaces) ? gate.bom.managedSurfaces.map(String) : null;
2750
2161
  summary.gate = `tag v${candidate} reachable from main, BOM stamp coherent`;
2751
2162
  summary.atomicity = atomicity;
2752
2163
  journal({ kind: "target", to: candidate, verdict: "ok", detail: summary.gate });
@@ -2786,8 +2197,14 @@ function reconcile(options) {
2786
2197
  journal({ kind: "surface", surface: probe.id, present: false, verdict: "skip", detail: `absent (${probe.marker}) \u2014 skipped` });
2787
2198
  continue;
2788
2199
  }
2200
+ if (surfaceArmPlan(probe.id, summary.managedSurfaces) === "retired") {
2201
+ summary.surfaces.push({ id: probe.id, present: true, action: "retired-at-source" });
2202
+ journal({ kind: "surface", surface: probe.id, present: true, verdict: "skip", retired: true, detail: `not in release v${summary.target} managedSurfaces \u2014 retired at source` });
2203
+ say(`${probe.id}: retired at source (not in release v${summary.target})`);
2204
+ continue;
2205
+ }
2789
2206
  say(`${probe.id}: converging to v${summary.target}`);
2790
- const arm = probe.id === "claude" ? claudeArm({ target: summary.target, dryRun: options.dryRun, paths, env, repoUrl }) : probe.id === "codex" ? codexArm({ target: summary.target, dryRun: options.dryRun, env, repoUrl }) : probe.id === "kilo" ? kiloArm({ target: summary.target, dryRun: options.dryRun, env }) : probe.id === "kimi" ? kimiArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "cursor" ? cursorArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "jervcode" ? jervcodeArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "hermes" ? hermesArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : null;
2207
+ const arm = probe.id === "claude" ? claudeArm({ target: summary.target, dryRun: options.dryRun, paths, env, repoUrl }) : probe.id === "codex" ? codexArm({ target: summary.target, dryRun: options.dryRun, env, repoUrl }) : probe.id === "cursor" ? cursorArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "jervcode" ? jervcodeArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "hermes" ? hermesArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : null;
2791
2208
  if (arm) {
2792
2209
  summary.plugins.push(arm);
2793
2210
  summary.surfaces.push({ id: probe.id, present: true, action: `arm-${arm.verdict}` });
@@ -2814,10 +2231,10 @@ function reconcile(options) {
2814
2231
  }
2815
2232
 
2816
2233
  // src/scheduler.ts
2817
- var import_node_child_process11 = require("node:child_process");
2818
- var import_node_fs17 = require("node:fs");
2234
+ var import_node_child_process9 = require("node:child_process");
2235
+ var import_node_fs16 = require("node:fs");
2819
2236
  var import_node_os6 = require("node:os");
2820
- var import_node_path14 = require("node:path");
2237
+ var import_node_path13 = require("node:path");
2821
2238
  var TASK_NAME = "MMI Fleet Updater";
2822
2239
  var LAUNCHER_FILE = "reconcile-hidden.vbs";
2823
2240
  var RUN_BOUND = "PT15M";
@@ -2833,15 +2250,15 @@ var SYSTEMD_UNIT = "mmi-hub-updater";
2833
2250
  var SYSTEMD_SERVICE_NAME = `${SYSTEMD_UNIT}.service`;
2834
2251
  var SYSTEMD_TIMER_NAME = `${SYSTEMD_UNIT}.timer`;
2835
2252
  function schtasks(args) {
2836
- const result = (0, import_node_child_process11.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
2253
+ const result = (0, import_node_child_process9.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
2837
2254
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
2838
2255
  }
2839
2256
  function launchctl(args) {
2840
- const result = (0, import_node_child_process11.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
2257
+ const result = (0, import_node_child_process9.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
2841
2258
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
2842
2259
  }
2843
2260
  function systemctlUser(args) {
2844
- const result = (0, import_node_child_process11.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
2261
+ const result = (0, import_node_child_process9.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
2845
2262
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
2846
2263
  }
2847
2264
  function uid() {
@@ -2866,7 +2283,7 @@ WScript.Quit code
2866
2283
  `;
2867
2284
  }
2868
2285
  function loginPath(fallback = process.env.PATH ?? "") {
2869
- const probe = (0, import_node_child_process11.spawnSync)("/bin/sh", ["-lc", 'printf %s "$PATH"'], { encoding: "utf8", windowsHide: true, timeout: 1e4 });
2286
+ const probe = (0, import_node_child_process9.spawnSync)("/bin/sh", ["-lc", 'printf %s "$PATH"'], { encoding: "utf8", windowsHide: true, timeout: 1e4 });
2870
2287
  const captured = (probe.status === 0 ? probe.stdout ?? "" : "").trim();
2871
2288
  const source = captured || fallback;
2872
2289
  const durable = source.split(":").filter((entry) => entry && !/^\/(?:private\/)?(?:tmp|var\/folders)\//.test(entry));
@@ -2975,7 +2392,7 @@ WantedBy=timers.target
2975
2392
  }
2976
2393
  function readFileOrNull(path) {
2977
2394
  try {
2978
- return (0, import_node_fs17.readFileSync)(path, "utf8");
2395
+ return (0, import_node_fs16.readFileSync)(path, "utf8");
2979
2396
  } catch {
2980
2397
  return null;
2981
2398
  }
@@ -2987,7 +2404,7 @@ function elevatedRelaunch(mode, env) {
2987
2404
  if (env.MMI_UPDATER_ELEVATED_RETRY === "1") return false;
2988
2405
  const self = process.argv[1];
2989
2406
  const command = self ? `"${process.execPath}" "${self}" autoupdate ${mode}` : `mmi-hub autoupdate ${mode}`;
2990
- const relaunch = (0, import_node_child_process11.spawnSync)(
2407
+ const relaunch = (0, import_node_child_process9.spawnSync)(
2991
2408
  "powershell.exe",
2992
2409
  ["-NoProfile", "-Command", `Start-Process -Verb RunAs -Wait -WindowStyle Hidden -FilePath 'cmd.exe' -ArgumentList '/c',${psQuote(`set MMI_UPDATER_ELEVATED_RETRY=1&& ${command}`)}`],
2993
2410
  { encoding: "utf8", windowsHide: true }
@@ -2998,7 +2415,7 @@ var NEVER_RUN_RESULT = 267011;
2998
2415
  var NEVER_RUN_YEAR = 1999;
2999
2416
  var EMPTY_RUN_INFO = { lastRunTime: null, lastTaskResult: null, nextRunTime: null };
3000
2417
  function taskRunInfo() {
3001
- const result = (0, import_node_child_process11.spawnSync)(
2418
+ const result = (0, import_node_child_process9.spawnSync)(
3002
2419
  "powershell.exe",
3003
2420
  ["-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`],
3004
2421
  { encoding: "utf8", windowsHide: true, timeout: 3e4 }
@@ -3024,7 +2441,7 @@ function windowsSchedulerStatus(env) {
3024
2441
  return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${TASK_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
3025
2442
  }
3026
2443
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3027
- const launcherPath = (0, import_node_path14.join)(statePaths(env).root, LAUNCHER_FILE);
2444
+ const launcherPath = (0, import_node_path13.join)(statePaths(env).root, LAUNCHER_FILE);
3028
2445
  const info = taskRunInfo();
3029
2446
  const runInfo = info ?? EMPTY_RUN_INFO;
3030
2447
  const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
@@ -3049,13 +2466,13 @@ function windowsSchedulerStatus(env) {
3049
2466
  return { ok: true, supported: true, enabled: true, ...runInfo, detail: `"${TASK_NAME}" registered ${CADENCE}; ${points}; ${ran}; next ${runInfo.nextRunTime ?? "unscheduled"}` };
3050
2467
  }
3051
2468
  function darwinSchedulerStatus(env) {
3052
- const plistPath = (0, import_node_path14.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
2469
+ const plistPath = (0, import_node_path13.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
3053
2470
  const plist = readFileOrNull(plistPath);
3054
2471
  if (!plist) {
3055
2472
  return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
3056
2473
  }
3057
2474
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3058
- const launcherPath = (0, import_node_path14.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
2475
+ const launcherPath = (0, import_node_path13.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
3059
2476
  const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
3060
2477
  if (!plist.includes(launcherPath)) {
3061
2478
  return fail(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
@@ -3075,15 +2492,15 @@ function darwinSchedulerStatus(env) {
3075
2492
  return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
3076
2493
  }
3077
2494
  function linuxSchedulerStatus(env) {
3078
- const unitDir = (0, import_node_path14.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
3079
- const timerPath = (0, import_node_path14.join)(unitDir, SYSTEMD_TIMER_NAME);
2495
+ const unitDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
2496
+ const timerPath = (0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME);
3080
2497
  const timer = readFileOrNull(timerPath);
3081
- const service = readFileOrNull((0, import_node_path14.join)(unitDir, SYSTEMD_SERVICE_NAME));
2498
+ const service = readFileOrNull((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME));
3082
2499
  if (!timer || !service) {
3083
2500
  return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
3084
2501
  }
3085
2502
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3086
- const launcherPath = (0, import_node_path14.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
2503
+ const launcherPath = (0, import_node_path13.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
3087
2504
  const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
3088
2505
  if (!service.includes(launcherPath)) {
3089
2506
  return fail(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
@@ -3122,10 +2539,10 @@ function installWindowsTask(env) {
3122
2539
  const paths = ensureState(env);
3123
2540
  const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
3124
2541
  if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
3125
- const launcherPath = (0, import_node_path14.join)(paths.root, LAUNCHER_FILE);
3126
- (0, import_node_fs17.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
3127
- const xmlPath = (0, import_node_path14.join)(paths.root, "task.xml");
3128
- (0, import_node_fs17.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
2542
+ const launcherPath = (0, import_node_path13.join)(paths.root, LAUNCHER_FILE);
2543
+ (0, import_node_fs16.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
2544
+ const xmlPath = (0, import_node_path13.join)(paths.root, "task.xml");
2545
+ (0, import_node_fs16.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
3129
2546
  const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
3130
2547
  if (create.status !== 0) {
3131
2548
  const denied = /access is denied/i.test(create.stderr + create.stdout);
@@ -3141,11 +2558,11 @@ function refreshPosixLauncher(env) {
3141
2558
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3142
2559
  if (!hubDist) return null;
3143
2560
  const paths = ensureState(env);
3144
- const launcherPath = (0, import_node_path14.join)(paths.root, POSIX_LAUNCHER_FILE);
2561
+ const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
3145
2562
  const desired = launcherSh(process.execPath, hubDist);
3146
2563
  if (readFileOrNull(launcherPath) === desired) return null;
3147
- (0, import_node_fs17.writeFileSync)(launcherPath, desired, "utf8");
3148
- (0, import_node_fs17.chmodSync)(launcherPath, 493);
2564
+ (0, import_node_fs16.writeFileSync)(launcherPath, desired, "utf8");
2565
+ (0, import_node_fs16.chmodSync)(launcherPath, 493);
3149
2566
  return launcherPath;
3150
2567
  }
3151
2568
  function installLaunchd(env) {
@@ -3157,13 +2574,13 @@ function installLaunchd(env) {
3157
2574
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3158
2575
  if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
3159
2576
  const paths = ensureState(env);
3160
- const launcherPath = (0, import_node_path14.join)(paths.root, POSIX_LAUNCHER_FILE);
3161
- (0, import_node_fs17.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
3162
- (0, import_node_fs17.chmodSync)(launcherPath, 493);
3163
- const agentsDir = (0, import_node_path14.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents");
3164
- (0, import_node_fs17.mkdirSync)(agentsDir, { recursive: true });
3165
- const plistPath = (0, import_node_path14.join)(agentsDir, LAUNCHD_PLIST_NAME);
3166
- (0, import_node_fs17.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
2577
+ const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
2578
+ (0, import_node_fs16.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
2579
+ (0, import_node_fs16.chmodSync)(launcherPath, 493);
2580
+ const agentsDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents");
2581
+ (0, import_node_fs16.mkdirSync)(agentsDir, { recursive: true });
2582
+ const plistPath = (0, import_node_path13.join)(agentsDir, LAUNCHD_PLIST_NAME);
2583
+ (0, import_node_fs16.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
3167
2584
  launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
3168
2585
  const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
3169
2586
  if (load.status !== 0) {
@@ -3181,13 +2598,13 @@ function installSystemdTimer(env) {
3181
2598
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3182
2599
  if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
3183
2600
  const paths = ensureState(env);
3184
- const launcherPath = (0, import_node_path14.join)(paths.root, POSIX_LAUNCHER_FILE);
3185
- (0, import_node_fs17.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
3186
- (0, import_node_fs17.chmodSync)(launcherPath, 493);
3187
- const unitDir = (0, import_node_path14.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
3188
- (0, import_node_fs17.mkdirSync)(unitDir, { recursive: true });
3189
- (0, import_node_fs17.writeFileSync)((0, import_node_path14.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
3190
- (0, import_node_fs17.writeFileSync)((0, import_node_path14.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
2601
+ const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
2602
+ (0, import_node_fs16.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
2603
+ (0, import_node_fs16.chmodSync)(launcherPath, 493);
2604
+ const unitDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
2605
+ (0, import_node_fs16.mkdirSync)(unitDir, { recursive: true });
2606
+ (0, import_node_fs16.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
2607
+ (0, import_node_fs16.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
3191
2608
  const reload = systemctlUser(["daemon-reload"]);
3192
2609
  if (reload.status !== 0) {
3193
2610
  return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
@@ -3225,28 +2642,28 @@ function uninstallWindowsTask(env) {
3225
2642
  return { ok: true, detail: `automatic updates off \u2014 removed "${TASK_NAME}"; installed tooling was kept` };
3226
2643
  }
3227
2644
  function uninstallLaunchd(env) {
3228
- const plistPath = (0, import_node_path14.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
2645
+ const plistPath = (0, import_node_path13.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
3229
2646
  const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
3230
- if (boot.status !== 0 && (0, import_node_fs17.existsSync)(plistPath)) {
2647
+ if (boot.status !== 0 && (0, import_node_fs16.existsSync)(plistPath)) {
3231
2648
  return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
3232
2649
  }
3233
2650
  try {
3234
- (0, import_node_fs17.rmSync)(plistPath, { force: true });
2651
+ (0, import_node_fs16.rmSync)(plistPath, { force: true });
3235
2652
  } catch {
3236
2653
  }
3237
2654
  dropLauncher(env, POSIX_LAUNCHER_FILE);
3238
2655
  return { ok: true, detail: `automatic updates off \u2014 removed "${LAUNCHD_LABEL}"; installed tooling was kept` };
3239
2656
  }
3240
2657
  function uninstallSystemdTimer(env) {
3241
- const unitDir = (0, import_node_path14.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
3242
- const timerPath = (0, import_node_path14.join)(unitDir, SYSTEMD_TIMER_NAME);
2658
+ const unitDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
2659
+ const timerPath = (0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME);
3243
2660
  const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
3244
- if (disable.status !== 0 && (0, import_node_fs17.existsSync)(timerPath)) {
2661
+ if (disable.status !== 0 && (0, import_node_fs16.existsSync)(timerPath)) {
3245
2662
  return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
3246
2663
  }
3247
2664
  try {
3248
- (0, import_node_fs17.rmSync)(timerPath, { force: true });
3249
- (0, import_node_fs17.rmSync)((0, import_node_path14.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
2665
+ (0, import_node_fs16.rmSync)(timerPath, { force: true });
2666
+ (0, import_node_fs16.rmSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
3250
2667
  } catch {
3251
2668
  }
3252
2669
  systemctlUser(["daemon-reload"]);
@@ -3261,16 +2678,16 @@ function uninstallTask(env = process.env) {
3261
2678
  }
3262
2679
  function dropLauncher(env, file) {
3263
2680
  try {
3264
- (0, import_node_fs17.rmSync)((0, import_node_path14.join)(statePaths(env).root, file), { force: true });
2681
+ (0, import_node_fs16.rmSync)((0, import_node_path13.join)(statePaths(env).root, file), { force: true });
3265
2682
  } catch {
3266
2683
  }
3267
2684
  }
3268
2685
 
3269
2686
  // src/status.ts
3270
- var import_node_fs18 = require("node:fs");
2687
+ var import_node_fs17 = require("node:fs");
3271
2688
  function readJournal(path) {
3272
2689
  try {
3273
- const events = (0, import_node_fs18.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
2690
+ const events = (0, import_node_fs17.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
3274
2691
  try {
3275
2692
  return JSON.parse(line);
3276
2693
  } catch {
@@ -3307,12 +2724,9 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
3307
2724
  if (surface.id === "claude") {
3308
2725
  const probe = probeNativeHostInstallation("claude", env);
3309
2726
  actual.set(surface.id, { version: probe.version, staged: probeClaudeLaunchStaging(env), location: probe.location, detail: probe.detail });
3310
- } else if (surface.id === "codex" || surface.id === "kilo") {
3311
- const probe = probeNativeHostInstallation(surface.id, env);
2727
+ } else if (surface.id === "codex") {
2728
+ const probe = probeNativeHostInstallation("codex", env);
3312
2729
  actual.set(surface.id, { version: probe.version, staged: null, location: probe.location, detail: probe.detail });
3313
- } else if (surface.id === "kimi") {
3314
- const probe = probeKimiInstallation(env);
3315
- actual.set(surface.id, { version: probe.version, staged: probeKimiLaunchStaging(env), location: probe.location, detail: probe.detail });
3316
2730
  } else if (surface.id === "cursor") {
3317
2731
  const probe = probeCursorInstallation();
3318
2732
  actual.set(surface.id, { version: probe.version, staged: probeCursorLaunchStaging(), location: probe.location, detail: probe.detail });
@@ -3325,6 +2739,7 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
3325
2739
  }
3326
2740
  }
3327
2741
  const installed = {};
2742
+ const retired = retiredSurfaces(realEvents);
3328
2743
  for (const [surface, probe] of actual) {
3329
2744
  const staged = probe.staged ?? null;
3330
2745
  installed[surface] = {
@@ -3332,7 +2747,7 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
3332
2747
  staged,
3333
2748
  expected: expectedVersion,
3334
2749
  location: probe.location,
3335
- state: versionState(probe.version, staged, expectedVersion),
2750
+ state: retired.has(surface) ? "retired" : versionState(probe.version, staged, expectedVersion),
3336
2751
  detail: probe.detail
3337
2752
  };
3338
2753
  }
@@ -3342,6 +2757,7 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
3342
2757
  if (!events.length && !error) failures.push("no maintenance journal yet \u2014 run `mmi-hub install`");
3343
2758
  if (!schedule.ok && schedule.supported) failures.push(schedule.detail);
3344
2759
  for (const [surface, row] of Object.entries(installed)) {
2760
+ if (row.state === "retired") continue;
3345
2761
  if (!row.installed) failures.push(`${surface}: installed version is unreadable at ${row.location ?? "an unknown location"} \u2014 run \`mmi-hub update\``);
3346
2762
  if (row.state === "behind") failures.push(`${surface}: installed ${row.installed} is behind expected ${row.expected} \u2014 run \`mmi-hub update\``);
3347
2763
  }
@@ -3395,10 +2811,8 @@ var MIN_NOTE_SPACE = 8;
3395
2811
  var ACTIVATION_NOTE = {
3396
2812
  claude: "next session",
3397
2813
  codex: "next session",
3398
- kimi: "next session",
3399
2814
  jervcode: "next launch",
3400
2815
  cursor: "on reload",
3401
- kilo: "after restart",
3402
2816
  hermes: "after restart",
3403
2817
  updater: "next run"
3404
2818
  };
@@ -3409,7 +2823,7 @@ var RESET = "\x1B[0m";
3409
2823
  var RETRY_HINTS = [
3410
2824
  [/applies when the host exits|applies on a quiet tick/, "installs when the app closes"],
3411
2825
  [/in flight|changed while/, "rerun update \u2014 host race"],
3412
- [/is running|live process|inside a .* hook/, "close app, rerun update"],
2826
+ [/is running|live process/, "close app, rerun update"],
3413
2827
  [/staging install failed|etarget|e404|network|fetch failed/, "check network/login, rerun"],
3414
2828
  [/not on npm|no green|registry not ready/, "release still publishing"],
3415
2829
  [/elevat|access is denied/, "run from elevated terminal"]
@@ -3672,10 +3086,65 @@ function startSpinner(enabled) {
3672
3086
  };
3673
3087
  }
3674
3088
 
3089
+ // src/stamp.ts
3090
+ var import_node_fs18 = require("node:fs");
3091
+ var ALWAYS_ARMED = ["cli", "updater"];
3092
+ var PLUGIN_ARMS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "cursor", "hermes"]);
3093
+ function fleetStamp(env = process.env) {
3094
+ const paths = statePaths(env);
3095
+ let events = [];
3096
+ try {
3097
+ events = (0, import_node_fs18.readFileSync)(paths.journalPath, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
3098
+ try {
3099
+ return JSON.parse(line);
3100
+ } catch {
3101
+ return null;
3102
+ }
3103
+ }).filter((event) => event !== null);
3104
+ } catch {
3105
+ return "fleet: no journal yet \u2014 MMI Hub maintenance has not run on this machine";
3106
+ }
3107
+ const target = [...events].reverse().find((event) => event.kind === "target" && event.verdict === "ok" && event.to)?.to ?? null;
3108
+ if (!target) {
3109
+ return "fleet: no gated release observed yet (registry not ready is a state, not a failure)";
3110
+ }
3111
+ const lastArm = /* @__PURE__ */ new Map();
3112
+ for (const event of events) {
3113
+ if (event.dryRun) continue;
3114
+ if (event.kind === "arm" && event.surface) lastArm.set(event.surface, event);
3115
+ }
3116
+ const probes = enumerateSurfaces(env);
3117
+ const retired = retiredSurfaces(events);
3118
+ const required = [
3119
+ ...ALWAYS_ARMED,
3120
+ ...probes.filter((probe) => probe.present && PLUGIN_ARMS.has(probe.id) && !retired.has(probe.id)).map((probe) => probe.id)
3121
+ ];
3122
+ const drift = [];
3123
+ const healthy = [];
3124
+ for (const surface of required) {
3125
+ const arm = lastArm.get(surface);
3126
+ if (!arm) {
3127
+ drift.push(`${surface} never converged`);
3128
+ } else if (arm.verdict === "ok" && (arm.to === target || (arm.detail ?? "").includes("already at target"))) {
3129
+ healthy.push(surface);
3130
+ } else {
3131
+ drift.push(`${surface} ${arm.from ?? "absent"} \u2192 ${arm.to ?? "?"} (${arm.verdict})`);
3132
+ }
3133
+ }
3134
+ const pending = probes.filter((probe) => probe.present && !PLUGIN_ARMS.has(probe.id)).map((probe) => probe.id);
3135
+ const retiredPresent = probes.filter((probe) => probe.present && retired.has(probe.id)).map((probe) => probe.id);
3136
+ if (drift.length) {
3137
+ return `fleet drift: target ${target} \u2014 ${drift.join("; ")}`;
3138
+ }
3139
+ const pendingNote = pending.length ? ` \xB7 ${pending.length} surface${pending.length === 1 ? "" : "s"} pending an arm: ${pending.join(" ")}` : "";
3140
+ const retiredNote = retiredPresent.length ? ` \xB7 retired at source: ${retiredPresent.join(" ")}` : "";
3141
+ return `fleet ${target} \u2713 ${healthy.join("\xB7")}${pendingNote}${retiredNote}`;
3142
+ }
3143
+
3675
3144
  // src/index.ts
3676
3145
  function ownVersion() {
3677
3146
  try {
3678
- return JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path15.join)(__dirname, "..", "package.json"), "utf8")).version;
3147
+ return JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path14.join)(__dirname, "..", "package.json"), "utf8")).version;
3679
3148
  } catch {
3680
3149
  return "0.0.0";
3681
3150
  }
@@ -3714,7 +3183,7 @@ function handOff(reexec, args, json) {
3714
3183
 
3715
3184
  `);
3716
3185
  }
3717
- const child = (0, import_node_child_process12.spawnSync)(process.execPath, [reexec.dist, ...args], {
3186
+ const child = (0, import_node_child_process10.spawnSync)(process.execPath, [reexec.dist, ...args], {
3718
3187
  stdio: "inherit",
3719
3188
  windowsHide: true,
3720
3189
  env: { ...process.env, MMI_UPDATER_REEXEC: "1" }
@@ -3840,6 +3309,7 @@ if (typeof require !== "undefined" && require.main === module) {
3840
3309
  clearPending,
3841
3310
  enumerateSurfaces,
3842
3311
  failConsequence,
3312
+ fleetStamp,
3843
3313
  formatReconcileReport,
3844
3314
  hermesArm,
3845
3315
  hermesHome,
@@ -3865,15 +3335,14 @@ if (typeof require !== "undefined" && require.main === module) {
3865
3335
  probeHermesInstallation,
3866
3336
  probeHermesLaunchStaging,
3867
3337
  probeJervcodeInstallation,
3868
- probeKimiLaunchStaging,
3869
3338
  probeLaunchTargetVersion,
3870
3339
  progressiveRow,
3871
3340
  readLaunchTarget,
3872
3341
  readPending,
3873
3342
  reconcileCursorUserHooks,
3874
- reconcileKimiInstalledPlugin,
3875
3343
  releaseSharedDocLock,
3876
3344
  reportFooterLines,
3345
+ retiredSurfaces,
3877
3346
  retryHint,
3878
3347
  schedulePendingWatcher,
3879
3348
  schedulerStatus,
@@ -3882,11 +3351,11 @@ if (typeof require !== "undefined" && require.main === module) {
3882
3351
  sharedDocLockTimeoutReceipt,
3883
3352
  shouldHandOff,
3884
3353
  startSpinner,
3354
+ surfaceArmPlan,
3885
3355
  systemdServiceUnit,
3886
3356
  systemdTimerUnit,
3887
3357
  taskXml,
3888
3358
  updateHeader,
3889
- validateKimiInstalledPlugin,
3890
3359
  watcherLockPath,
3891
3360
  withSharedDocLock,
3892
3361
  wrapWords,