claude-mem-lite 3.35.0 → 3.35.1

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": "3.35.0",
13
+ "version": "3.35.1",
14
14
  "source": "./",
15
15
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark)."
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.35.0",
3
+ "version": "3.35.1",
4
4
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/cli.mjs CHANGED
@@ -33,8 +33,10 @@ if (cmd === '--version' || cmd === '-v') {
33
33
  const { existsSync } = await import('fs');
34
34
  const { join } = await import('path');
35
35
  // D#29: honor CLAUDE_MEM_DIR so the install-vs-CLI help routing is correct on
36
- // relocated installs (matches schema.mjs DB_DIR; HOME fallback when env unset).
37
- const dataDir = process.env.CLAUDE_MEM_DIR || join(process.env.HOME || '', '.claude-mem-lite');
36
+ // relocated installs (matches schema.mjs DB_DIR via the shared resolver, which
37
+ // also fixes the HOME-unset relative-path fallback this branch used to have).
38
+ const { resolveDataDir } = await import('./lib/resolve-data-dir.mjs');
39
+ const dataDir = resolveDataDir(process.env.CLAUDE_MEM_DIR);
38
40
  const dbPath = join(dataDir, 'claude-mem-lite.db');
39
41
  if (existsSync(dbPath)) {
40
42
  const { run } = await import('./mem-cli.mjs');
package/install.mjs CHANGED
@@ -7,6 +7,7 @@ import { join, resolve, dirname, isAbsolute } from 'path';
7
7
  import { homedir, tmpdir } from 'os';
8
8
  import { fileURLToPath, pathToFileURL } from 'url';
9
9
  import { createRequire } from 'node:module';
10
+ import { resolveDataDir } from './lib/resolve-data-dir.mjs';
10
11
 
11
12
  const PROJECT_DIR = resolve(import.meta.dirname ?? dirname(fileURLToPath(import.meta.url)));
12
13
  const SETTINGS_PATH = join(homedir(), '.claude', 'settings.json');
@@ -20,7 +21,7 @@ const DATA_DIR = join(homedir(), '.claude-mem-lite');
20
21
  // the runtime/data layer READS it (pre-fix: installer wrote homedir, runtime read
21
22
  // the relocated dir → preinstalled skills silently vanished, doctor read the wrong
22
23
  // DB). Equals DATA_DIR when CLAUDE_MEM_DIR is unset (the common case).
23
- const MEM_DATA_DIR = process.env.CLAUDE_MEM_DIR || DATA_DIR;
24
+ const MEM_DATA_DIR = resolveDataDir(process.env.CLAUDE_MEM_DIR);
24
25
  const DB_PATH = join(MEM_DATA_DIR, 'claude-mem-lite.db');
25
26
  const OLD_DATA_DIR = join(homedir(), '.claude-mem');
26
27
 
@@ -0,0 +1,35 @@
1
+ // Single source of truth for resolving the CLAUDE_MEM_DIR data directory.
2
+ // Zero runtime deps (node:path + node:os only) so hot-path hook scripts can
3
+ // import it without pulling in better-sqlite3.
4
+ //
5
+ // The env var is a RELOCATION knob: unset → ~/.claude-mem-lite (the common,
6
+ // non-relocated case); set → an absolute path on a larger/faster volume.
7
+ // Anything else is a mistake we must reject LOUDLY rather than turn into a
8
+ // stray directory:
9
+ // - The literal strings "undefined"/"null" arise when a caller interpolates
10
+ // a JS nullish value into a shell env string (e.g. `CLAUDE_MEM_DIR='${x}'`
11
+ // with x === undefined). They are truthy, so `env || default` accepts them
12
+ // and better-sqlite3 then creates a relative `undefined/` dir at cwd.
13
+ // - A relative path silently resolves against each process's cwd, scattering
14
+ // state across directories.
15
+ // Falsy (unset/empty) is the only non-absolute value we treat as "use default".
16
+ import { homedir } from 'node:os';
17
+ import { join, isAbsolute } from 'node:path';
18
+
19
+ /**
20
+ * @param {string|undefined|null} raw Typically process.env.CLAUDE_MEM_DIR.
21
+ * @returns {string} An absolute data directory.
22
+ * @throws if `raw` is a non-empty, non-absolute value (incl. "undefined"/"null").
23
+ */
24
+ export function resolveDataDir(raw) {
25
+ if (raw === undefined || raw === null || raw === '') {
26
+ return join(homedir(), '.claude-mem-lite');
27
+ }
28
+ if (typeof raw !== 'string' || raw === 'undefined' || raw === 'null' || !isAbsolute(raw)) {
29
+ throw new Error(
30
+ `CLAUDE_MEM_DIR must be an absolute path; got ${JSON.stringify(raw)}. ` +
31
+ `Leave it unset to use ~/.claude-mem-lite.`
32
+ );
33
+ }
34
+ return raw;
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.35.0",
3
+ "version": "3.35.1",
4
4
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",
@@ -67,6 +67,7 @@
67
67
  "lib/id-routing.mjs",
68
68
  "lib/err-sampler.mjs",
69
69
  "lib/hook-telemetry.mjs",
70
+ "lib/resolve-data-dir.mjs",
70
71
  "lib/file-intel.mjs",
71
72
  "lib/reread-guard.mjs",
72
73
  "lib/metrics.mjs",
@@ -5,7 +5,7 @@
5
5
  // invocations/recommend_count. Live injection is Phase 2. `off` skips all work.
6
6
  import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync, appendFileSync, readdirSync, unlinkSync } from 'fs';
7
7
  import { join } from 'path';
8
- import { homedir } from 'os';
8
+ import { resolveDataDir } from './lib/resolve-data-dir.mjs';
9
9
  import { searchResources, cjkIntentTokens } from './registry-retriever.mjs';
10
10
 
11
11
  const VALID_MODES = new Set(['shadow', 'live', 'off']);
@@ -26,7 +26,7 @@ const RECO_COOLDOWN_MS = 300_000; // 5 min, mirrors T4 SKILL_COOLDOWN_MS
26
26
  // DB_DIR formula) so tests sandbox via env without ESM-cache gymnastics, and prod reads
27
27
  // the same dir as the rest of the app.
28
28
  function recoRuntimeDir() {
29
- return join(process.env.CLAUDE_MEM_DIR || join(homedir(), '.claude-mem-lite'), 'runtime');
29
+ return join(resolveDataDir(process.env.CLAUDE_MEM_DIR), 'runtime');
30
30
  }
31
31
 
32
32
  const TOKEN_SPLIT = /[^a-z0-9一-鿿]+/;
package/schema.mjs CHANGED
@@ -7,10 +7,11 @@ import { homedir } from 'os';
7
7
  import { join } from 'path';
8
8
  import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, chmodSync } from 'fs';
9
9
  import { OBS_FTS_COLUMNS, debugCatch } from './utils.mjs';
10
+ import { resolveDataDir } from './lib/resolve-data-dir.mjs';
10
11
 
11
12
  // DATA location — DB, managed resources, registry DB, runtime/. Honors
12
13
  // CLAUDE_MEM_DIR so users can relocate state to a larger/faster volume.
13
- export const DB_DIR = process.env.CLAUDE_MEM_DIR || join(homedir(), '.claude-mem-lite');
14
+ export const DB_DIR = resolveDataDir(process.env.CLAUDE_MEM_DIR);
14
15
  export const DB_PATH = join(DB_DIR, 'claude-mem-lite.db');
15
16
  export const REGISTRY_DB_PATH = join(DB_DIR, 'resource-registry.db');
16
17
  // CODE / install location — server.mjs, hook.mjs, cli.mjs, package.json live
@@ -26,15 +26,20 @@
26
26
 
27
27
  import { existsSync, mkdirSync, writeFileSync, statSync, unlinkSync, readFileSync } from 'node:fs';
28
28
  import { spawnSync } from 'node:child_process';
29
- import { dirname, join } from 'node:path';
29
+ import { dirname, join, isAbsolute } from 'node:path';
30
30
  import { fileURLToPath, pathToFileURL } from 'node:url';
31
31
  import { homedir } from 'node:os';
32
32
 
33
33
  const __dirname = dirname(fileURLToPath(import.meta.url));
34
34
  const INSTALL_DIR = join(__dirname, '..');
35
- const RUNTIME_DIR = process.env.CLAUDE_MEM_DIR
36
- ? join(process.env.CLAUDE_MEM_DIR, 'runtime')
37
- : join(homedir(), '.claude-mem-lite', 'runtime');
35
+ // A bogus CLAUDE_MEM_DIR ("undefined"/"null"/relative from a mis-quoted env
36
+ // interpolation) must degrade to the default here, not become a stray dir. The
37
+ // launcher's pure-node charter (above) forbids importing lib/resolve-data-dir.mjs,
38
+ // and its fail-open duty forbids throwing, so mirror that guard inline + lenient:
39
+ // non-absolute → default. Data-writing paths import that module and throw instead.
40
+ const MEM_DIR = process.env.CLAUDE_MEM_DIR;
41
+ const DATA_DIR = MEM_DIR && isAbsolute(MEM_DIR) ? MEM_DIR : join(homedir(), '.claude-mem-lite');
42
+ const RUNTIME_DIR = join(DATA_DIR, 'runtime');
38
43
  const HEAL_MARKER = join(RUNTIME_DIR, 'hook-launcher-lastheal');
39
44
  const HEAL_COOLDOWN_MS = 6 * 60 * 60 * 1000;
40
45
  // Observable breakage state: written when the launcher degrades a broken install
@@ -15,11 +15,11 @@
15
15
 
16
16
  import { existsSync, readFileSync } from 'fs';
17
17
  import { basename, join } from 'path';
18
- import { homedir } from 'os';
18
+ import { resolveDataDir } from '../lib/resolve-data-dir.mjs';
19
19
 
20
20
  const SALIENCE_BIND = process.env.CLAUDE_MEM_SALIENCE === 'bind';
21
21
 
22
- const DATA_DIR = process.env.CLAUDE_MEM_DIR || join(homedir(), '.claude-mem-lite');
22
+ const DATA_DIR = resolveDataDir(process.env.CLAUDE_MEM_DIR);
23
23
  const RUNTIME_DIR = process.env.CLAUDE_MEM_RUNTIME_DIR || join(DATA_DIR, 'runtime');
24
24
  const LEGACY_COOLDOWN_PATH = join(RUNTIME_DIR, 'pre-recall-cooldown.json');
25
25
 
@@ -7,9 +7,10 @@ import { existsSync, readFileSync } from 'fs';
7
7
  import { join, resolve, sep } from 'path';
8
8
  import { homedir } from 'os';
9
9
  import { recordHookError } from '../lib/hook-telemetry.mjs';
10
+ import { resolveDataDir } from '../lib/resolve-data-dir.mjs';
10
11
 
11
12
  // CLAUDE_MEM_DIR mirrors pre-tool-recall.js — one env var sandboxes everything.
12
- const DATA_DIR = process.env.CLAUDE_MEM_DIR || join(homedir(), '.claude-mem-lite');
13
+ const DATA_DIR = resolveDataDir(process.env.CLAUDE_MEM_DIR);
13
14
  const RUNTIME_DIR = process.env.CLAUDE_MEM_RUNTIME_DIR || join(DATA_DIR, 'runtime');
14
15
  // D#29: all data artifacts follow DATA_DIR (CLAUDE_MEM_DIR-aware), not a hardcoded
15
16
  // homedir — previously REGISTRY_DB_PATH/MANAGED_BASE/MARKER pinned homedir while line 12
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
8
8
  import { basename, join } from 'path';
9
- import { homedir } from 'os';
9
+ import { resolveDataDir } from '../lib/resolve-data-dir.mjs';
10
10
  import { buildNotLowSignalSql } from '../lib/low-signal-patterns.mjs';
11
11
  import { recordHookError } from '../lib/hook-telemetry.mjs';
12
12
  import { citeFactorClause } from '../scoring-sql.mjs';
@@ -18,7 +18,7 @@ import { presentIdents } from '../lib/lesson-idents.mjs';
18
18
  // CLAUDE_MEM_DIR matches schema.mjs / main CLI — one env var sandboxes the
19
19
  // whole system. CLAUDE_MEM_DB_PATH / CLAUDE_MEM_RUNTIME_DIR remain as
20
20
  // per-component overrides for tests that mix isolated + real paths.
21
- const DATA_DIR = process.env.CLAUDE_MEM_DIR || join(homedir(), '.claude-mem-lite');
21
+ const DATA_DIR = resolveDataDir(process.env.CLAUDE_MEM_DIR);
22
22
  const DB_PATH = process.env.CLAUDE_MEM_DB_PATH || join(DATA_DIR, 'claude-mem-lite.db');
23
23
  const RUNTIME_DIR = process.env.CLAUDE_MEM_RUNTIME_DIR || join(DATA_DIR, 'runtime');
24
24
  // A3 (v2.83): cross-hook dedup window — must mirror DEDUP_STALE_MS in
package/source-files.mjs CHANGED
@@ -29,6 +29,12 @@ export const SOURCE_FILES = [
29
29
  'tier.mjs', 'tfidf.mjs',
30
30
  'nlp.mjs', 'synonyms.mjs', 'scoring-sql.mjs', 'stop-words.mjs', 'project-utils.mjs',
31
31
  'secret-scrub.mjs', 'format-utils.mjs', 'hash-utils.mjs', 'bash-utils.mjs',
32
+ // Single source of truth for the CLAUDE_MEM_DIR → data-dir resolver (rejects a
33
+ // stringified "undefined"/"null"/relative env instead of creating a stray dir).
34
+ // Statically imported by schema.mjs / cli.mjs / install.mjs / registry-recommend.mjs
35
+ // AND hook scripts (pre-tool-recall / post-tool-recall / pre-skill-bridge) — ship it
36
+ // or auto-update leaves schema + every hook with ERR_MODULE_NOT_FOUND on each fire.
37
+ 'lib/resolve-data-dir.mjs',
32
38
  // lib/ — statically imported by hook-llm.mjs (activity) + hook-handoff.mjs (git-state, task-reader);
33
39
  // dynamically imported by hook.mjs (startup-dashboard) + mem-cli.mjs (doctor-benchmark, plan-reader).
34
40
  'lib/activity.mjs',