claude-mem-lite 2.53.0 → 2.53.2

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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "2.53.0",
13
+ "version": "2.53.2",
14
14
  "source": "./",
15
15
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall"
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.53.0",
3
+ "version": "2.53.2",
4
4
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/install.mjs CHANGED
@@ -524,14 +524,19 @@ async function install() {
524
524
  try {
525
525
  const cacheBase = join(homedir(), '.claude', 'plugins', 'cache', MARKETPLACE_KEY, 'claude-mem-lite');
526
526
  if (existsSync(cacheBase)) {
527
- const srcLaunch = join(PROJECT_DIR, 'scripts', 'launch.mjs');
527
+ const launchSyncFiles = ['launch.mjs', 'launch-preflight.mjs'];
528
528
  let clearedHooks = 0;
529
529
  for (const ver of readdirSync(cacheBase)) {
530
530
  const verDir = join(cacheBase, ver);
531
531
 
532
- // Sync launch.mjs
532
+ // Sync launch.mjs + its preflight companion (issue #15)
533
533
  if (existsSync(join(verDir, 'scripts'))) {
534
- try { copyFileSync(srcLaunch, join(verDir, 'scripts', 'launch.mjs')); } catch {}
534
+ for (const f of launchSyncFiles) {
535
+ const src = join(PROJECT_DIR, 'scripts', f);
536
+ if (existsSync(src)) {
537
+ try { copyFileSync(src, join(verDir, 'scripts', f)); } catch { /* keep going */ }
538
+ }
539
+ }
535
540
  }
536
541
 
537
542
  // Clear cached hooks.json (runtime reads here, not marketplace source)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.53.0",
3
+ "version": "2.53.2",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "engines": {
@@ -98,6 +98,7 @@
98
98
  "commands/bug.md",
99
99
  "hooks/hooks.json",
100
100
  "scripts/launch.mjs",
101
+ "scripts/launch-preflight.mjs",
101
102
  "scripts/setup.sh",
102
103
  "scripts/post-tool-use.sh",
103
104
  "scripts/user-prompt-search.js",
@@ -0,0 +1,85 @@
1
+ // launch-preflight.mjs — Detect incomplete installs at MCP server launch.
2
+ //
3
+ // Why: issue #15 — published v2.53.0 npm tarball contains all files, but some
4
+ // users end up with a partial install (server.mjs present, search-engine.mjs
5
+ // missing) and the resulting ERR_MODULE_NOT_FOUND has no actionable message.
6
+ // We can't fix every cause (mirror lag / interrupted download / npm cache
7
+ // corruption / permission) on the user side, but we can detect the broken
8
+ // state and either fall back to ~/.claude-mem-lite/ (which hook-update.mjs
9
+ // keeps healthy) or print a clear repair command instead of a Node stack.
10
+ //
11
+ // Pure module — no I/O at import time, all deps injected. Tested in isolation;
12
+ // scripts/launch.mjs wires it to the actual filesystem + stderr.
13
+
14
+ import { existsSync, readFileSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+
17
+ // Match relative `.mjs` imports — covers all four ESM forms:
18
+ // from './x.mjs' (static named/default/namespace)
19
+ // import './x.mjs' (side-effect static)
20
+ // await import('./x.mjs') (dynamic)
21
+ // import('./x.mjs') without await (dynamic)
22
+ // Skips 'node:*' / package imports / non-mjs paths.
23
+ const FROM_RE = /\bfrom\s+['"](\.\/[^'"]+\.mjs)['"]/g;
24
+ const IMPORT_RE = /\bimport\s*\(?\s*['"](\.\/[^'"]+\.mjs)['"]/g;
25
+
26
+ export function detectMissingImports(installRoot) {
27
+ const serverPath = join(installRoot, 'server.mjs');
28
+ if (!existsSync(serverPath)) return ['server.mjs'];
29
+
30
+ let src;
31
+ try {
32
+ src = readFileSync(serverPath, 'utf8');
33
+ } catch {
34
+ return ['server.mjs'];
35
+ }
36
+
37
+ // Strip line + block comments so example strings in docblocks (e.g. the very
38
+ // patterns this regex enumerates) can't false-fire as "missing files".
39
+ src = src
40
+ .replace(/\/\*[\s\S]*?\*\//g, '')
41
+ .replace(/(^|\s)\/\/[^\n]*/g, '$1');
42
+
43
+ const missing = new Set();
44
+ for (const re of [FROM_RE, IMPORT_RE]) {
45
+ re.lastIndex = 0;
46
+ for (const m of src.matchAll(re)) {
47
+ const rel = m[1].replace(/^\.\//, '');
48
+ if (!existsSync(join(installRoot, rel))) missing.add(rel);
49
+ }
50
+ }
51
+ return [...missing];
52
+ }
53
+
54
+ export function resolveLaunchEntry({ primaryRoot, fallbackRoot, warn = () => {} }) {
55
+ const primaryMissing = detectMissingImports(primaryRoot);
56
+ if (primaryMissing.length === 0) {
57
+ return { path: join(primaryRoot, 'server.mjs'), source: 'primary' };
58
+ }
59
+
60
+ if (fallbackRoot && fallbackRoot !== primaryRoot) {
61
+ const fallbackMissing = detectMissingImports(fallbackRoot);
62
+ if (fallbackMissing.length === 0) {
63
+ warn(
64
+ `[claude-mem-lite] Primary install incomplete at ${primaryRoot} ` +
65
+ `(missing: ${primaryMissing.join(', ')}). Falling back to ${fallbackRoot}.`,
66
+ );
67
+ return {
68
+ path: join(fallbackRoot, 'server.mjs'),
69
+ source: 'fallback',
70
+ missingFromPrimary: primaryMissing,
71
+ };
72
+ }
73
+ }
74
+
75
+ const repairCmd = 'npm install -g claude-mem-lite@latest --force';
76
+ const err = new Error(
77
+ `[claude-mem-lite] Install incomplete at ${primaryRoot}\n` +
78
+ `[claude-mem-lite] Missing: ${primaryMissing.join(', ')}\n` +
79
+ `[claude-mem-lite] Repair: ${repairCmd}\n` +
80
+ `[claude-mem-lite] Or via Claude Code: /plugin uninstall claude-mem-lite && /plugin install claude-mem-lite@sdsrss`,
81
+ );
82
+ err.code = 'INSTALL_INCOMPLETE';
83
+ err.missing = primaryMissing;
84
+ throw err;
85
+ }
@@ -12,12 +12,22 @@ const ROOT = process.env.CLAUDE_PLUGIN_ROOT || join(__dirname, '..');
12
12
 
13
13
  if (!existsSync(join(ROOT, 'node_modules', 'better-sqlite3'))) {
14
14
  process.stderr.write('[claude-mem-lite] Installing dependencies...\n');
15
- execSync('npm install --omit=dev', {
16
- cwd: ROOT,
17
- stdio: ['ignore', 'pipe', 'inherit'], // stdout piped (discard), stderr inherit
18
- timeout: 120_000,
19
- });
20
- process.stderr.write('[claude-mem-lite] Dependencies installed\n');
15
+ try {
16
+ execSync('npm install --omit=dev', {
17
+ cwd: ROOT,
18
+ stdio: ['ignore', 'pipe', 'inherit'], // stdout piped (discard), stderr inherit
19
+ timeout: 120_000,
20
+ });
21
+ process.stderr.write('[claude-mem-lite] Dependencies installed\n');
22
+ } catch (e) {
23
+ // Plugin-cache / multi-user / disk-full installs can fail here. Without this
24
+ // catch the user sees a Node stack trace; with it they get an actionable line.
25
+ const detail = e.message?.split('\n')[0] || e.code || 'unknown error';
26
+ process.stderr.write(`[claude-mem-lite] npm install failed in ${ROOT} — ${detail}\n`);
27
+ process.stderr.write(`[claude-mem-lite] Likely cause: read-only directory, disk full, or network blocked.\n`);
28
+ process.stderr.write(`[claude-mem-lite] Repair: cd "${ROOT}" && npm install --omit=dev\n`);
29
+ process.exit(1);
30
+ }
21
31
  }
22
32
 
23
33
  // Verify MCP SDK is importable (exports mapping intact).
@@ -45,12 +55,31 @@ try {
45
55
  // Dev mode: prefer ~/.claude-mem-lite/server.mjs (symlinked to source) over
46
56
  // CLAUDE_PLUGIN_ROOT (potentially stale plugin cache). This ensures the MCP
47
57
  // server always runs the latest code when installed with `install --dev`.
48
- const devServer = join(homedir(), '.claude-mem-lite', 'server.mjs');
58
+ const dataDir = join(homedir(), '.claude-mem-lite');
59
+ const devServer = join(dataDir, 'server.mjs');
49
60
  let useDevServer = false;
50
61
  try { useDevServer = existsSync(devServer) && lstatSync(devServer).isSymbolicLink(); } catch {}
51
62
 
52
63
  if (useDevServer) {
53
64
  await import(pathToFileURL(devServer).href);
54
65
  } else {
55
- await import(new URL('../server.mjs', import.meta.url).href);
66
+ // Preflight: detect incomplete primary install (issue #15) — if relative
67
+ // imports referenced by server.mjs are missing on disk, fall back to the
68
+ // hook-update.mjs-maintained ~/.claude-mem-lite/ copy when healthy, or exit
69
+ // with a clear repair command instead of a Node ERR_MODULE_NOT_FOUND stack.
70
+ const { resolveLaunchEntry } = await import('./launch-preflight.mjs');
71
+ try {
72
+ const entry = resolveLaunchEntry({
73
+ primaryRoot: ROOT,
74
+ fallbackRoot: dataDir,
75
+ warn: (msg) => process.stderr.write(msg + '\n'),
76
+ });
77
+ await import(pathToFileURL(entry.path).href);
78
+ } catch (e) {
79
+ if (e.code === 'INSTALL_INCOMPLETE') {
80
+ process.stderr.write(e.message + '\n');
81
+ process.exit(1);
82
+ }
83
+ throw e;
84
+ }
56
85
  }