claude-mem-lite 2.71.3 → 2.71.4

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.71.3",
13
+ "version": "2.71.4",
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.71.3",
3
+ "version": "2.71.4",
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/bash-utils.mjs CHANGED
@@ -15,9 +15,25 @@ export function detectBashSignificance(input, response) {
15
15
  // Skip error keyword matching when the command is a read/search operation
16
16
  // (grep output naturally contains matched keywords like "error")
17
17
  const isSearchCmd = /\b(grep|rg|ag|ack|cat|head|tail|less|more|find|locate|wc|file|which|type)\b/i.test(cmd);
18
- const isError = !isSearchCmd
18
+ const looksLikeError = !isSearchCmd
19
19
  && /\berror\b|\bERR!|fail(ed|ure)?|exception|panic|traceback|errno|enoent|command not found/i.test(response)
20
20
  && response.length > 15;
21
+ // Green test summary exemption — "0 fail/failed/failures" in test-runner
22
+ // output (bun/jest/pytest) gets matched by the broad `fail(ed|ure)?` token
23
+ // above, driving episode.isError=true for passing runs. A live cluster-merge
24
+ // audit found 5 noise observations with "Error: <test>.ts ... 0 fail" titles
25
+ // from this path. Flip back to non-error iff a "0 fail" marker is present
26
+ // AND no hard-error signal (panic / ENOENT / AssertionError / TypeError /
27
+ // explicit FAIL banner / npm ERR!) coexists in the output.
28
+ const hasGreenTestSummary = looksLikeError
29
+ && /\b0\s+(fail|failed|failures)\b/i.test(response);
30
+ // NOTE: do not add `\bFAIL\s` here — with /i flag it would re-match the
31
+ // very `0 fail\n` token green-summary is trying to exempt. A real test
32
+ // failure produces "N fail" (N≥1) which never triggers hasGreenTestSummary,
33
+ // so a uppercase-FAIL fingerprint isn't needed for correctness.
34
+ const hasHardErrorSignal = hasGreenTestSummary
35
+ && /\bERR!|panic|traceback|enoent|command not found|exception|AssertionError|TypeError:|SyntaxError:/i.test(response);
36
+ const isError = looksLikeError && !(hasGreenTestSummary && !hasHardErrorSignal);
21
37
  // Match actual test runner invocations, not commands that merely reference "test" as a keyword
22
38
  const isTest = /\b(npm\s+test|npm\s+run\s+test|yarn\s+test|pnpm\s+test|pnpm\s+run\s+test|bun\s+test|go\s+test|cargo\s+test)\b/i.test(cmd)
23
39
  || /\b(jest|pytest|vitest|mocha|cypress|playwright)\b/i.test(cmd);
package/install.mjs CHANGED
@@ -25,11 +25,10 @@ const MARKETPLACE_KEY = 'sdsrss';
25
25
  const PLUGIN_KEY = `claude-mem-lite@${MARKETPLACE_KEY}`;
26
26
  const NPM_INSTALL_CMD = 'npm install --omit=dev --no-audit --no-fund';
27
27
 
28
- import { createRequire } from 'module';
29
-
30
28
  import { RESOURCE_METADATA } from './install-metadata.mjs';
31
29
  import { scanPluginCacheHookPollution } from './plugin-cache-guard.mjs';
32
30
  import { SOURCE_FILES, HOOK_SCRIPT_FILES } from './source-files.mjs';
31
+ import { probeBetterSqlite3Binding, ensureBetterSqlite3Working } from './lib/binding-probe.mjs';
33
32
 
34
33
  // Re-export for backward compatibility — tests/install-hook-scripts.test.mjs
35
34
  // and any external consumers still import HOOK_SCRIPT_FILES from install.mjs.
@@ -37,6 +36,12 @@ import { SOURCE_FILES, HOOK_SCRIPT_FILES } from './source-files.mjs';
37
36
  // can share it without a static cycle.
38
37
  export { HOOK_SCRIPT_FILES };
39
38
 
39
+ // Re-export for backward compatibility — tests/install-bsqlite-probe.test.mjs
40
+ // imports these from install.mjs. The implementation moved to lib/binding-probe.mjs
41
+ // so scripts/launch.mjs can share the probe without importing install.mjs (which
42
+ // pulls heavy install-only deps).
43
+ export { probeBetterSqlite3Binding, ensureBetterSqlite3Working };
44
+
40
45
  export function copyHookScripts(srcDir, destDir) {
41
46
  for (const name of HOOK_SCRIPT_FILES) {
42
47
  const src = join(srcDir, name);
@@ -75,55 +80,6 @@ export function migrateLegacyClaudeMemData(oldDir, newDir, opts = {}) {
75
80
  return { action: 'backed-up', backupPath };
76
81
  }
77
82
 
78
- /**
79
- * Probe better-sqlite3's native binding by importing it from `installDir`'s
80
- * node_modules and opening an in-memory DB. Returns {ok, error?}. `npm install`
81
- * exits 0 even when the prebuilt .node binary mismatches the running Node ABI
82
- * (e.g. NODE_MODULE_VERSION 137 on Node v24), so install must verify before
83
- * declaring success — otherwise the next launch FATALs with "Could not locate
84
- * the bindings file".
85
- */
86
- export async function probeBetterSqlite3Binding(installDir) {
87
- try {
88
- const localRequire = createRequire(join(installDir, 'package.json'));
89
- const Database = localRequire('better-sqlite3');
90
- const db = new Database(':memory:');
91
- db.close();
92
- return { ok: true };
93
- } catch (e) {
94
- return { ok: false, error: e.message };
95
- }
96
- }
97
-
98
- /**
99
- * Verify better-sqlite3 binding works in `installDir`; if not, run
100
- * `npm rebuild better-sqlite3` and re-probe. Returns
101
- * { ok: true, action: 'verified' | 'rebuilt' } on success or
102
- * { ok: false, error } if rebuild can't fix it. The `probe` and `rebuild`
103
- * deps are injectable so this can be unit-tested without a real npm
104
- * subprocess.
105
- */
106
- export async function ensureBetterSqlite3Working(installDir, deps = {}) {
107
- const probe = deps.probe || (() => probeBetterSqlite3Binding(installDir));
108
- const rebuild = deps.rebuild || (async () => {
109
- execSync('npm rebuild better-sqlite3', { cwd: installDir, stdio: 'pipe' });
110
- });
111
-
112
- const first = await probe();
113
- if (first.ok) return { ok: true, action: 'verified' };
114
-
115
- try {
116
- await rebuild();
117
- } catch (e) {
118
- return { ok: false, error: `rebuild failed: ${e.message}` };
119
- }
120
-
121
- const second = await probe();
122
- if (second.ok) return { ok: true, action: 'rebuilt' };
123
-
124
- return { ok: false, error: second.error || first.error };
125
- }
126
-
127
83
  /**
128
84
  * Derive invocation_name from resource name when metadata doesn't provide one.
129
85
  * Rules:
@@ -421,7 +377,12 @@ async function install() {
421
377
  } else {
422
378
  log('Ensuring dependencies installed...');
423
379
  try {
424
- execSync(NPM_INSTALL_CMD, { cwd: INSTALL_DIR, stdio: 'pipe' });
380
+ // stderr inherited so users see real-time progress (network slowness,
381
+ // node-gyp compile spinner, prebuild-install fallback messages). With
382
+ // `stdio: 'pipe'` the install appeared to hang under the 5-min Bash
383
+ // timeout when better-sqlite3 had no Node v24 prebuild and had to
384
+ // compile from source — see bug audit 2026-05.
385
+ execSync(NPM_INSTALL_CMD, { cwd: INSTALL_DIR, stdio: ['ignore', 'pipe', 'inherit'] });
425
386
  ok('Dependencies installed');
426
387
  } catch (e) {
427
388
  fail('npm install failed: ' + e.message);
@@ -0,0 +1,63 @@
1
+ // lib/binding-probe.mjs — better-sqlite3 native binding probe + auto-rebuild.
2
+ //
3
+ // Shared by install.mjs (verify after `npm install`) and scripts/launch.mjs
4
+ // (verify before launching the MCP server). `npm install` exits 0 even when
5
+ // the prebuilt .node binary mismatches the running Node ABI (e.g. ABI v137 on
6
+ // Node v24), and the presence of node_modules/better-sqlite3/ on disk is not
7
+ // sufficient — the binding can be present-but-stale after a Node upgrade.
8
+
9
+ import { execSync } from 'node:child_process';
10
+ import { createRequire } from 'node:module';
11
+ import { join } from 'node:path';
12
+
13
+ /**
14
+ * Probe better-sqlite3's native binding by importing it from `installDir`'s
15
+ * node_modules and opening an in-memory DB. Returns {ok, error?}.
16
+ *
17
+ * @param {string} installDir Directory containing node_modules/better-sqlite3
18
+ * @returns {Promise<{ok: true} | {ok: false, error: string}>}
19
+ */
20
+ export async function probeBetterSqlite3Binding(installDir) {
21
+ try {
22
+ const localRequire = createRequire(join(installDir, 'package.json'));
23
+ const Database = localRequire('better-sqlite3');
24
+ const db = new Database(':memory:');
25
+ db.close();
26
+ return { ok: true };
27
+ } catch (e) {
28
+ return { ok: false, error: e.message };
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Verify better-sqlite3 binding works in `installDir`; if not, run
34
+ * `npm rebuild better-sqlite3` and re-probe. Returns
35
+ * { ok: true, action: 'verified' | 'rebuilt' } on success or
36
+ * { ok: false, error } if rebuild can't fix it. The `probe` and `rebuild`
37
+ * deps are injectable so this can be unit-tested without a real npm
38
+ * subprocess.
39
+ *
40
+ * @param {string} installDir Directory containing node_modules/better-sqlite3
41
+ * @param {{probe?: () => Promise<{ok: boolean, error?: string}>, rebuild?: () => Promise<void>}} [deps]
42
+ * @returns {Promise<{ok: true, action: 'verified' | 'rebuilt'} | {ok: false, error: string}>}
43
+ */
44
+ export async function ensureBetterSqlite3Working(installDir, deps = {}) {
45
+ const probe = deps.probe || (() => probeBetterSqlite3Binding(installDir));
46
+ const rebuild = deps.rebuild || (async () => {
47
+ execSync('npm rebuild better-sqlite3', { cwd: installDir, stdio: 'pipe' });
48
+ });
49
+
50
+ const first = await probe();
51
+ if (first.ok) return { ok: true, action: 'verified' };
52
+
53
+ try {
54
+ await rebuild();
55
+ } catch (e) {
56
+ return { ok: false, error: `rebuild failed: ${e.message}` };
57
+ }
58
+
59
+ const second = await probe();
60
+ if (second.ok) return { ok: true, action: 'rebuilt' };
61
+
62
+ return { ok: false, error: second.error || first.error };
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.71.3",
3
+ "version": "2.71.4",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",
@@ -61,6 +61,7 @@
61
61
  "lib/id-routing.mjs",
62
62
  "lib/err-sampler.mjs",
63
63
  "lib/metrics.mjs",
64
+ "lib/binding-probe.mjs",
64
65
  "lib/mem-override.mjs",
65
66
  "lib/save-observation.mjs",
66
67
  "lib/deferred-work.mjs",
@@ -30,6 +30,28 @@ if (!existsSync(join(ROOT, 'node_modules', 'better-sqlite3'))) {
30
30
  }
31
31
  }
32
32
 
33
+ // Verify better-sqlite3 native binding matches the current Node ABI. The
34
+ // directory-presence check above is necessary but not sufficient: a Node
35
+ // version change (e.g. v22 → v24, ABI v127 → v137) leaves node_modules
36
+ // intact but the .node binary stale → server FATALs with "Could not locate
37
+ // the bindings file" on first DB open. Probe + auto-rebuild before launching.
38
+ try {
39
+ const { ensureBetterSqlite3Working } = await import('../lib/binding-probe.mjs');
40
+ const verify = await ensureBetterSqlite3Working(ROOT);
41
+ if (!verify.ok) {
42
+ process.stderr.write(`[claude-mem-lite] better-sqlite3 binding unusable: ${verify.error}\n`);
43
+ process.stderr.write(`[claude-mem-lite] Repair: cd "${ROOT}" && npm rebuild better-sqlite3 --build-from-source\n`);
44
+ process.exit(1);
45
+ }
46
+ if (verify.action === 'rebuilt') {
47
+ process.stderr.write('[claude-mem-lite] Rebuilt better-sqlite3 binding for current Node ABI\n');
48
+ }
49
+ } catch (e) {
50
+ // Probe module itself failed to load — fall through to server import and let
51
+ // the native FATAL surface as before. Don't block launch on a probe regression.
52
+ process.stderr.write(`[claude-mem-lite] binding probe skipped: ${e.message}\n`);
53
+ }
54
+
33
55
  // Verify MCP SDK is importable (exports mapping intact).
34
56
  // Incomplete installs can leave the directory present but package.json missing,
35
57
  // causing Node.js to fail resolving subpath exports like /server/mcp.js.
package/source-files.mjs CHANGED
@@ -44,6 +44,11 @@ export const SOURCE_FILES = [
44
44
  'lib/id-routing.mjs',
45
45
  'lib/err-sampler.mjs',
46
46
  'lib/metrics.mjs',
47
+ // v2.71.x: better-sqlite3 ABI probe + auto-rebuild. Shared by install.mjs
48
+ // (post-`npm install` verify) and scripts/launch.mjs (pre-server-launch
49
+ // self-heal after Node ABI changes). Missing from manifest → auto-update
50
+ // ships a stale install that FATALs on first DB open after Node upgrade.
51
+ 'lib/binding-probe.mjs',
47
52
  // v2.41 god-module split — mem-cli.mjs router + per-cmd handlers under cli/
48
53
  'cli/common.mjs',
49
54
  'cli/fts-check.mjs',