mneme-ai 2.19.70-lite → 2.19.71-lite

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.
@@ -1,128 +1,311 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * v2.19.69MNEME_LITE postinstall pruner (Gap 3 workaround #2 — npm 10
4
- * global `--omit=optional` bug).
3
+ * v2.19.71MNEME postinstall: TWO independent jobs in one script.
5
4
  *
6
- * npm 10.x silently ignores `--omit=optional` for global installs:
5
+ * JOB 1 MNEME_LITE prune (Gap 3 workaround #2).
6
+ * When the user sets MNEME_LITE=1 in the install environment,
7
+ * remove the optional-dep tree (@huggingface/transformers, sharp,
8
+ * @img, onnxruntime-*) AFTER npm has extracted it. Net result:
9
+ * ~462MB downloaded, ~5MB on-disk. See v2.19.69 docs/wild_workarounds/02.
7
10
  *
8
- * npm install -g mneme-ai --omit=optional # 467MB on disk
9
- * NPM_CONFIG_OMIT=optional npm install -g mneme-ai # 467MB on disk
11
+ * JOB 2 Dual-install detection (N5 fix, v2.19.71).
12
+ * Some users run two parallel Node version managers (nvm4w +
13
+ * nvm-windows + Volta + fnm). Each has its own npm global prefix.
14
+ * `npm install -g mneme-ai` writes to the ACTIVE prefix; PATH may
15
+ * resolve `mneme` from a DIFFERENT prefix that has an older copy.
16
+ * Result: user installs v2.19.70, but `mneme --version` reports
17
+ * v2.19.69 because PATH wins. The user perceives "I upgraded but
18
+ * version didn't change" — invisible distribution failure.
10
19
  *
11
- * This script gives the user a working alternative: set `MNEME_LITE=1` in
12
- * the environment when installing. npm still downloads + extracts the
13
- * optional `@huggingface/transformers` tree (we cannot influence that
14
- * from a package.json), but THIS postinstall hook then removes it from
15
- * disk before npm hands control back. Net result:
20
+ * This job runs on every install, scans known npm prefixes for
21
+ * other mneme-ai installs, and prints a LOUD warning + actionable
22
+ * remediation if PATH points to a different install than the one
23
+ * we just landed. Writes a marker file at
24
+ * ~/.mneme-global/dual-install-warning.json so the doctor organ
25
+ * can surface the same diagnosis later.
16
26
  *
17
- * MNEME_LITE=1 npm install -g mneme-ai
18
- * -> 467MB downloaded + extracted
19
- * -> ~462MB removed from disk by this hook (transformers + sharp + libvips)
20
- * -> ~5MB final on-disk footprint
21
- * -> 0 native DLL handles → EBUSY structurally extinct post-install
27
+ * Both jobs are NON-FATAL — they swallow all errors and always exit
28
+ * 0 so a bug here can never abort an npm install. Verified by the
29
+ * source-grep test in packages/cli/tests/postinstall_mneme_lite.test.ts
30
+ * ("every process.exit is exit 0").
22
31
  *
23
- * The mneme runtime gracefully falls back through the embedder chain
24
- * (OpenAI Ollama → bundled WASM SKIPPED → hash) every Mneme tool
25
- * continues to work, just at deterministic hash quality (★★) until the
26
- * user installs Ollama or adds an OpenAI key.
27
- *
28
- * Idempotent + non-fatal:
29
- * - Re-running after prune is a no-op (paths gone, fs.rmSync swallows
30
- * ENOENT with { force: true }).
31
- * - Any prune failure is logged but never breaks the install: the
32
- * fallback is "user keeps the 467MB tree", not "install aborts".
33
- * - Silent on the happy path (no env var set) so the default install
34
- * UX is unchanged.
35
- *
36
- * Logs a one-line summary on the prune path so AI agents inspecting
37
- * the install output can confirm the lite mode took effect.
32
+ * Skip with MNEME_SKIP_DUAL_CHECK=1 (job 2 only) or just don't set
33
+ * MNEME_LITE (job 1). CI environments (CI=true) silence job 2 to
34
+ * avoid spamming build logs that already pin versions exactly.
38
35
  */
39
36
 
40
37
  "use strict";
41
38
 
42
39
  const fs = require("node:fs");
43
40
  const path = require("node:path");
41
+ const os = require("node:os");
42
+ const { spawnSync } = require("node:child_process");
44
43
 
45
- const LITE = String(process.env.MNEME_LITE || "").trim();
46
- if (LITE !== "1" && LITE.toLowerCase() !== "true" && LITE.toLowerCase() !== "yes") {
47
- // No-op for the default install path — keeps behaviour pristine for the
48
- // 99% of users who want the bundled WASM embedder.
49
- process.exit(0);
50
- }
51
-
52
- // We're inside `<install-prefix>/lib/node_modules/mneme-ai/bin/` on POSIX
53
- // or `<install-prefix>/node_modules/mneme-ai/bin/` on Windows. The optional
54
- // tree lives at `<install-prefix>/.../mneme-ai/node_modules/<target>`.
55
44
  const here = __dirname;
56
45
  const mnemeRoot = path.resolve(here, "..");
57
- const sub = (p) => path.join(mnemeRoot, "node_modules", p);
58
-
59
- // Targetsevery directory that pulls in native binaries through the
60
- // `@huggingface/transformers` optional dep tree. Kept conservative on
61
- // purpose: anything outside this list might be a transitive dep some
62
- // other Mneme module actually uses, so we only delete the known-safe set.
63
- const PRUNE_TARGETS = [
64
- "@huggingface",
65
- "@img",
66
- "sharp",
67
- // onnxruntime-* drag a ~80MB native runtime even on Linux/macOS when
68
- // transformers is present. Safe to remove in lite mode because
69
- // transformers itself is gone — nothing else in Mneme uses ORT.
70
- "onnxruntime-common",
71
- "onnxruntime-node",
72
- "onnxruntime-web",
73
- ];
74
-
75
- function dirSizeBytes(dirPath) {
76
- if (!fs.existsSync(dirPath)) return 0;
77
- let total = 0;
78
- try {
79
- const entries = fs.readdirSync(dirPath, { withFileTypes: true });
80
- for (const ent of entries) {
81
- const full = path.join(dirPath, ent.name);
82
- try {
83
- if (ent.isDirectory()) total += dirSizeBytes(full);
84
- else if (ent.isFile()) total += fs.statSync(full).size;
85
- } catch { /* skip permission errors */ }
46
+
47
+ // ──────────────────────────────────────────────────────────────────────
48
+ // JOB 1 MNEME_LITE prune
49
+ // ──────────────────────────────────────────────────────────────────────
50
+
51
+ function runLitePrune() {
52
+ const LITE = String(process.env.MNEME_LITE || "").trim();
53
+ if (LITE !== "1" && LITE.toLowerCase() !== "true" && LITE.toLowerCase() !== "yes") {
54
+ return; // No-op for the default install path.
55
+ }
56
+
57
+ const sub = (p) => path.join(mnemeRoot, "node_modules", p);
58
+
59
+ const PRUNE_TARGETS = [
60
+ "@huggingface",
61
+ "@img",
62
+ "sharp",
63
+ "onnxruntime-common",
64
+ "onnxruntime-node",
65
+ "onnxruntime-web",
66
+ ];
67
+
68
+ function dirSizeBytes(dirPath) {
69
+ if (!fs.existsSync(dirPath)) return 0;
70
+ let total = 0;
71
+ try {
72
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
73
+ for (const ent of entries) {
74
+ const full = path.join(dirPath, ent.name);
75
+ try {
76
+ if (ent.isDirectory()) total += dirSizeBytes(full);
77
+ else if (ent.isFile()) total += fs.statSync(full).size;
78
+ } catch { /* skip */ }
79
+ }
80
+ } catch { /* */ }
81
+ return total;
82
+ }
83
+
84
+ const fmtMB = (b) => (b / (1024 * 1024)).toFixed(1) + "MB";
85
+
86
+ let prunedBytes = 0;
87
+ const prunedNames = [];
88
+ const skippedNames = [];
89
+
90
+ for (const name of PRUNE_TARGETS) {
91
+ const target = sub(name);
92
+ if (!fs.existsSync(target)) {
93
+ skippedNames.push(name);
94
+ continue;
86
95
  }
87
- } catch { /* skip unreadable dirs */ }
88
- return total;
89
- }
96
+ const sizeBefore = dirSizeBytes(target);
97
+ try {
98
+ fs.rmSync(target, { recursive: true, force: true });
99
+ prunedBytes += sizeBefore;
100
+ prunedNames.push(`${name} (${fmtMB(sizeBefore)})`);
101
+ } catch (err) {
102
+ process.stderr.write(
103
+ `[mneme-lite] could not prune ${name}: ${err && err.message ? err.message : err}\n`,
104
+ );
105
+ skippedNames.push(`${name} (locked)`);
106
+ }
107
+ }
90
108
 
91
- function fmtMB(bytes) {
92
- return (bytes / (1024 * 1024)).toFixed(1) + "MB";
109
+ const summary =
110
+ `[mneme-lite] MNEME_LITE=${LITE} pruned ${prunedNames.length}/${PRUNE_TARGETS.length} ` +
111
+ `optional-dep directories, ${fmtMB(prunedBytes)} freed.\n` +
112
+ (prunedNames.length > 0 ? `[mneme-lite] removed: ${prunedNames.join(", ")}\n` : "") +
113
+ (skippedNames.length > 0 ? `[mneme-lite] skipped: ${skippedNames.join(", ")}\n` : "") +
114
+ `[mneme-lite] mneme runtime will fall back through OpenAI → Ollama → hash embedder (bundled WASM skipped).\n`;
115
+ process.stdout.write(summary);
93
116
  }
94
117
 
95
- let prunedBytes = 0;
96
- const prunedNames = [];
97
- const skippedNames = [];
118
+ // ──────────────────────────────────────────────────────────────────────
119
+ // JOB 2 — Dual-install detection (N5 fix)
120
+ // ──────────────────────────────────────────────────────────────────────
121
+
122
+ function runDualInstallCheck() {
123
+ // Skip in CI / explicit opt-out — these envs have pinned versions
124
+ // and don't care about PATH resolution drift.
125
+ if (process.env.CI === "true" || process.env.MNEME_SKIP_DUAL_CHECK === "1") return;
126
+
127
+ let myVersion;
128
+ try {
129
+ myVersion = JSON.parse(fs.readFileSync(path.join(mnemeRoot, "package.json"), "utf8")).version;
130
+ } catch {
131
+ return; // can't read own version, bail silently
132
+ }
133
+
134
+ const home = os.homedir();
135
+ const w = process.platform === "win32";
136
+ const candidatePrefixes = w ? [
137
+ path.join(home, "AppData", "Roaming", "npm"),
138
+ "C:\\nvm4w\\nodejs",
139
+ path.join(home, "AppData", "Local", "nvm"), // nvm-windows
140
+ path.join(home, "AppData", "Local", "pnpm"),
141
+ path.join(home, "AppData", "Local", "Volta"),
142
+ path.join(home, "scoop", "apps", "nodejs", "current"),
143
+ ] : [
144
+ "/usr/local/lib",
145
+ "/usr/lib",
146
+ "/opt/homebrew/lib", // Apple Silicon Homebrew
147
+ path.join(home, ".npm-global"),
148
+ path.join(home, ".nvm", "versions", "node"), // nvm-bash
149
+ path.join(home, ".volta"),
150
+ path.join(home, ".pnpm-global"),
151
+ path.join(home, ".local", "share", "pnpm"),
152
+ ];
98
153
 
99
- for (const name of PRUNE_TARGETS) {
100
- const target = sub(name);
101
- if (!fs.existsSync(target)) {
102
- skippedNames.push(name);
103
- continue;
154
+ const otherInstalls = [];
155
+ function tryNodeModules(base) {
156
+ const pkg = path.join(base, "node_modules", "mneme-ai", "package.json");
157
+ if (!fs.existsSync(pkg)) return;
158
+ try {
159
+ const v = JSON.parse(fs.readFileSync(pkg, "utf8")).version;
160
+ const installRoot = path.join(base, "node_modules", "mneme-ai");
161
+ if (path.resolve(installRoot) === path.resolve(mnemeRoot)) return; // ourselves
162
+ otherInstalls.push({
163
+ version: v,
164
+ root: installRoot,
165
+ bin: path.join(installRoot, "bin", "mneme.js"),
166
+ });
167
+ } catch { /* */ }
168
+ }
169
+ function scan(dir) {
170
+ if (!fs.existsSync(dir)) return;
171
+ tryNodeModules(dir);
172
+ try {
173
+ for (const entry of fs.readdirSync(dir)) {
174
+ const sub = path.join(dir, entry);
175
+ tryNodeModules(sub);
176
+ tryNodeModules(path.join(sub, "nodejs"));
177
+ }
178
+ } catch { /* */ }
104
179
  }
105
- const sizeBefore = dirSizeBytes(target);
180
+ for (const pfx of candidatePrefixes) scan(pfx);
181
+
182
+ // Resolve which install PATH points to (which `mneme` runs by default)
183
+ let pathInstall = null;
184
+ try {
185
+ const r = w
186
+ ? spawnSync("where", ["mneme"], { encoding: "utf8", shell: true, timeout: 5000, windowsHide: true })
187
+ : spawnSync("which", ["mneme"], { encoding: "utf8", timeout: 5000 });
188
+ if (r.status === 0 && r.stdout) {
189
+ const firstLine = r.stdout.split(/\r?\n/)[0].trim();
190
+ if (firstLine) pathInstall = firstLine;
191
+ }
192
+ } catch { /* */ }
193
+
194
+ // Persist a marker file even if no warning prints — doctor organ
195
+ // and bin/mneme.js may surface it later.
106
196
  try {
107
- fs.rmSync(target, { recursive: true, force: true });
108
- prunedBytes += sizeBefore;
109
- prunedNames.push(`${name} (${fmtMB(sizeBefore)})`);
110
- } catch (err) {
111
- // Don't fail the install over a single stuck dir; just report.
112
- process.stderr.write(
113
- `[mneme-lite] could not prune ${name}: ${err && err.message ? err.message : err}\n`,
197
+ const dir = path.join(home, ".mneme-global");
198
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
199
+ const marker = {
200
+ v: 1,
201
+ ts: new Date().toISOString(),
202
+ myInstall: { root: mnemeRoot, version: myVersion },
203
+ otherInstalls,
204
+ pathResolvesTo: pathInstall,
205
+ };
206
+ fs.writeFileSync(
207
+ path.join(dir, "dual-install-warning.json"),
208
+ JSON.stringify(marker, null, 2),
209
+ { encoding: "utf8", mode: 0o600 },
114
210
  );
115
- skippedNames.push(`${name} (locked)`);
211
+ } catch { /* */ }
212
+
213
+ if (otherInstalls.length === 0) return; // single install — all good
214
+
215
+ // Compute whether PATH points to our install or an other one.
216
+ const myBinCandidates = [
217
+ path.resolve(path.join(mnemeRoot, "bin", "mneme.js")),
218
+ path.resolve(path.join(mnemeRoot, "bin", "mneme.cmd")),
219
+ path.resolve(path.join(path.dirname(mnemeRoot), ".bin", "mneme")),
220
+ path.resolve(path.join(path.dirname(mnemeRoot), ".bin", "mneme.cmd")),
221
+ path.resolve(path.join(path.dirname(mnemeRoot), ".bin", "mneme.ps1")),
222
+ ];
223
+ const normPath = pathInstall ? path.resolve(pathInstall) : null;
224
+ const pathPointsHere = !!normPath && myBinCandidates.includes(normPath);
225
+
226
+ // Headline — always tell the user.
227
+ const sep = "\n" + "─".repeat(70);
228
+ const Y = process.stdout.isTTY ? "\x1b[33m" : "";
229
+ const R = process.stdout.isTTY ? "\x1b[31m" : "";
230
+ const B = process.stdout.isTTY ? "\x1b[1m" : "";
231
+ const X = process.stdout.isTTY ? "\x1b[0m" : "";
232
+
233
+ const lines = [];
234
+ lines.push(sep);
235
+ lines.push(`${Y}${B}⚠ Mneme: dual-install detected on this machine${X}`);
236
+ lines.push(` This install: ${mnemeRoot} (v${myVersion})`);
237
+ for (const o of otherInstalls) {
238
+ const mark = o.version === myVersion ? " (same version)"
239
+ : compareSemver(o.version, myVersion) > 0 ? " (NEWER!)"
240
+ : " (older)";
241
+ lines.push(` Also found: ${o.root} (v${o.version})${mark}`);
242
+ }
243
+ if (pathInstall) {
244
+ lines.push(` 'mneme' on PATH: ${pathInstall}`);
245
+ } else {
246
+ lines.push(` 'mneme' on PATH: (not found via \`${w ? "where" : "which"} mneme\`)`);
116
247
  }
248
+
249
+ if (!pathPointsHere && pathInstall) {
250
+ // CRITICAL CASE — user's `mneme` command will NOT run the version
251
+ // they just installed. Loud + actionable remediation.
252
+ lines.push(``);
253
+ lines.push(`${R}${B} → \`mneme\` will NOT run v${myVersion} you just installed.${X}`);
254
+ lines.push(` PATH resolves to a DIFFERENT install.`);
255
+ lines.push(``);
256
+ lines.push(` ${B}To fix, pick ONE:${X}`);
257
+ lines.push(` ${B}# Option 1: uninstall the stale path${X}`);
258
+ lines.push(` npm uninstall -g mneme-ai # from a shell where 'which mneme' shows the stale one`);
259
+ lines.push(` npm install -g mneme-ai@${myVersion}`);
260
+ lines.push(``);
261
+ lines.push(` ${B}# Option 2: invoke this install directly${X}`);
262
+ if (w) {
263
+ lines.push(` node "${path.join(mnemeRoot, "bin", "mneme.js")}" <args>`);
264
+ } else {
265
+ lines.push(` node "${path.join(mnemeRoot, "bin", "mneme.js")}" <args>`);
266
+ }
267
+ lines.push(``);
268
+ lines.push(` ${B}# Option 3: run \`mneme doctor scan\` for a full diagnosis${X}`);
269
+ lines.push(` node "${path.join(mnemeRoot, "bin", "mneme.js")}" doctor scan --pretty`);
270
+ } else if (pathPointsHere) {
271
+ lines.push(` ${B}PATH points to this install — you're using v${myVersion} ✓${X}`);
272
+ lines.push(` (The other installs above are inactive copies; safe to remove with \`npm uninstall -g mneme-ai\` from their respective shells.)`);
273
+ }
274
+ lines.push(sep);
275
+
276
+ process.stdout.write(lines.join("\n") + "\n");
117
277
  }
118
278
 
119
- const summary =
120
- `[mneme-lite] MNEME_LITE=${LITE} pruned ${prunedNames.length}/${PRUNE_TARGETS.length} ` +
121
- `optional-dep directories, ${fmtMB(prunedBytes)} freed.\n` +
122
- (prunedNames.length > 0 ? `[mneme-lite] removed: ${prunedNames.join(", ")}\n` : "") +
123
- (skippedNames.length > 0 ? `[mneme-lite] skipped: ${skippedNames.join(", ")}\n` : "") +
124
- `[mneme-lite] mneme runtime will fall back through OpenAI → Ollama → hash embedder (bundled WASM skipped).\n`;
125
- process.stdout.write(summary);
279
+ /** Lexicographic semver-ish compare. Returns positive if a > b,
280
+ * negative if a < b, 0 if equal. Handles "X.Y.Z" + "X.Y.Z-suffix". */
281
+ function compareSemver(a, b) {
282
+ const parse = (s) => {
283
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/.exec(String(s));
284
+ if (!m) return [0, 0, 0, ""];
285
+ return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10), m[4] || ""];
286
+ };
287
+ const pa = parse(a);
288
+ const pb = parse(b);
289
+ for (let i = 0; i < 3; i++) {
290
+ if (pa[i] !== pb[i]) return pa[i] - pb[i];
291
+ }
292
+ // Prerelease loses to release (per semver), so "lite" suffix ranks LOWER.
293
+ if (pa[3] === pb[3]) return 0;
294
+ if (pa[3] === "") return 1;
295
+ if (pb[3] === "") return -1;
296
+ return pa[3] < pb[3] ? -1 : 1;
297
+ }
298
+
299
+ // ──────────────────────────────────────────────────────────────────────
300
+ // Master invariant: NEVER abort the install. Both jobs are wrapped.
301
+ // ──────────────────────────────────────────────────────────────────────
302
+
303
+ try { runLitePrune(); } catch (e) {
304
+ try { process.stderr.write(`[mneme-postinstall] lite-prune error (non-fatal): ${e && e.message ? e.message : e}\n`); } catch { /* */ }
305
+ }
306
+
307
+ try { runDualInstallCheck(); } catch (e) {
308
+ try { process.stderr.write(`[mneme-postinstall] dual-install-check error (non-fatal): ${e && e.message ? e.message : e}\n`); } catch { /* */ }
309
+ }
126
310
 
127
- // Always exit 0 — we never want this script to abort the install.
128
311
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mneme-ai",
3
- "version": "2.19.70-lite",
3
+ "version": "2.19.71-lite",
4
4
  "mcpName": "io.github.patsa2561-art/mneme-ai",
5
5
  "description": "Mneme เน‚โ‚ฌโ€ the memory layer for your codebase. Knows the WHY, the WHAT, the WHERE-IT-BREAKS. — LITE variant (no bundled WASM embedder, no native deps).",
6
6
  "type": "module",
@@ -32,10 +32,10 @@
32
32
  "preinstall": "node -e \"try{const fs=require('node:fs');const path=require('node:path');const os=require('node:os');const{spawnSync}=require('node:child_process');const crypto=require('node:crypto');const w=process.platform==='win32';const home=os.homedir();const organ=path.join(home,'.mneme-global');const trailPath=path.join(organ,'preinstall-trail.jsonl');const trailSecret=process.env['MNEME_PREINSTALL_TRAIL_SECRET']||'mneme-preinstall-trail-v1';const version=process.env['npm_package_version']||'unknown';try{if(!fs.existsSync(organ))fs.mkdirSync(organ,{recursive:true,mode:0o700})}catch(e){}const lastSig=()=>{try{if(!fs.existsSync(trailPath))return'genesis';const lines=fs.readFileSync(trailPath,'utf8').trim().split('\\\\n').filter(Boolean);if(lines.length===0)return'genesis';const last=JSON.parse(lines[lines.length-1]);return typeof last?.sig==='string'?last.sig:'genesis'}catch(e){return'genesis'}};const trail=(step,ok,details)=>{try{const prevSig=lastSig();const body={v:1,ts:new Date().toISOString(),version,step,ok,...(details?{details}:{}),pid:process.pid,prevSig};const sig=crypto.createHmac('sha256',trailSecret).update(prevSig+'::'+JSON.stringify(body)).digest('hex');fs.appendFileSync(trailPath,JSON.stringify({...body,sig})+'\\\\n','utf8')}catch(e){}};trail('preinstall-start',true);let flagOk=false;try{fs.writeFileSync(path.join(organ,'install-incoming.flag'),JSON.stringify({v:1,announcedAt:new Date().toISOString(),announcerPid:process.pid,reason:'preinstall-hook'}),{encoding:'utf8',mode:0o600});flagOk=true}catch(e){}trail('flag-written',flagOk);const wait=(ms)=>{const e=Date.now()+ms;while(Date.now()<e){}};wait(300);if(w){const r=spawnSync('taskkill',['/F','/IM','mneme.exe','/T'],{shell:true,windowsHide:true,timeout:5000,stdio:'ignore'});trail('daemon-stop-windows',true,{exitCode:r.status});let reaped=0;try{const beatDir=path.join(organ,'heartbeats');if(fs.existsSync(beatDir)){for(const f of fs.readdirSync(beatDir)){const m=f.match(/^(\\\\d+)\\\\.beat$/);if(m){const pid=parseInt(m[1]);if(pid>0&&pid!==process.pid){spawnSync('taskkill',['/F','/PID',pid.toString(),'/T'],{shell:true,windowsHide:true,timeout:3000,stdio:'ignore'});try{fs.unlinkSync(path.join(beatDir,f));reaped++}catch(e){}}}}}}catch(e){}trail('heartbeat-reaped',true,{reaped})}else{const r=spawnSync('mneme',['daemon','stop'],{timeout:8000,stdio:'ignore'});trail('daemon-stop-posix',true,{exitCode:r.status});let reaped=0;try{const beatDir=path.join(organ,'heartbeats');if(fs.existsSync(beatDir)){for(const f of fs.readdirSync(beatDir)){const m=f.match(/^(\\\\d+)\\\\.beat$/);if(m){const pid=parseInt(m[1]);if(pid>0&&pid!==process.pid){try{process.kill(pid,'SIGTERM')}catch(e){}wait(100);try{process.kill(pid,'SIGKILL')}catch(e){}try{fs.unlinkSync(path.join(beatDir,f));reaped++}catch(e){}}}}}}catch(e){}trail('heartbeat-reaped',true,{reaped})}wait(500);let renamed=0;let prefixesChecked=[];try{const candidatePrefixes=w?[path.join(home,'AppData','Roaming','npm'),path.dirname(process.execPath),'C:\\\\\\\\nvm4w\\\\\\\\nodejs',path.join(home,'AppData','Local','nvm')]:['/usr/local/lib','/usr/lib',path.join(home,'.npm-global'),path.join(home,'.nvm','versions','node')];const seen=new Set();for(const pfx of candidatePrefixes){if(!fs.existsSync(pfx))continue;let nodeModulesBases=[];if(fs.existsSync(path.join(pfx,'node_modules')))nodeModulesBases.push(path.join(pfx,'node_modules'));try{for(const entry of fs.readdirSync(pfx)){const sub=path.join(pfx,entry,'node_modules');if(fs.existsSync(sub))nodeModulesBases.push(sub);const sub2=path.join(pfx,entry,'nodejs','node_modules');if(fs.existsSync(sub2))nodeModulesBases.push(sub2)}}catch(e){}for(const nm of nodeModulesBases){if(seen.has(nm))continue;seen.add(nm);prefixesChecked.push(nm);const npmGlobal=path.join(nm,'mneme-ai');if(!fs.existsSync(npmGlobal))continue;const dllPaths=w?[path.join(npmGlobal,'node_modules','@img','sharp-libvips-win32-x64','lib','libvips-42.dll'),path.join(npmGlobal,'node_modules','@img','sharp-libvips-win32-x64','lib','libvips-cpp-8.17.3.dll'),path.join(npmGlobal,'node_modules','sharp','build','Release','sharp-win32-x64.node')]:[];for(const dll of dllPaths){if(!fs.existsSync(dll))continue;try{const target=dll+'.locked-'+Date.now()+'-'+process.pid;fs.renameSync(dll,target);renamed++}catch(e){for(let i=0;i<20;i++){try{const fd=fs.openSync(dll,'r+');fs.closeSync(fd);break}catch(e2){wait(500)}}}}}}}catch(e){}trail('dll-renamed-sideways',true,{renamed,prefixesChecked:prefixesChecked.length});let swept=0;try{const candidates=w?[path.join(home,'AppData','Roaming','npm','node_modules'),path.join(path.dirname(process.execPath),'node_modules')]:['/usr/local/lib/node_modules',path.join(home,'.npm-global','node_modules')];for(const npmParent of candidates){if(!fs.existsSync(npmParent))continue;try{for(const entry of fs.readdirSync(npmParent)){if(entry.startsWith('.mneme-ai-')){try{fs.rmSync(path.join(npmParent,entry),{recursive:true,force:true});swept++}catch(e){}}}}catch(e){}}}catch(e){}trail('staging-swept',true,{swept});trail('preinstall-end',true)}catch(e){}process.exit(0)\""
33
33
  },
34
34
  "dependencies": {
35
- "@mneme-ai/core": "2.19.70-lite",
36
- "@mneme-ai/correlator": "2.19.70-lite",
37
- "@mneme-ai/embeddings": "2.19.70-lite",
38
- "@mneme-ai/mcp": "2.19.70-lite",
35
+ "@mneme-ai/core": "2.19.71-lite",
36
+ "@mneme-ai/correlator": "2.19.71-lite",
37
+ "@mneme-ai/embeddings": "2.19.71-lite",
38
+ "@mneme-ai/mcp": "2.19.71-lite",
39
39
  "commander": "^14.0.3",
40
40
  "kleur": "^4.1.5"
41
41
  },