claude-mem-lite 3.0.0 → 3.1.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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "3.0.0",
13
+ "version": "3.1.0",
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.0.0",
3
+ "version": "3.1.0",
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/README.md CHANGED
@@ -160,6 +160,8 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
160
160
 
161
161
  Plugin mode manages its own hooks/runtime. On session start it only **checks and reports** new claude-mem-lite versions; it does **not** self-overwrite plugin files in place. Update plugin-mode installs through Claude's plugin workflow.
162
162
 
163
+ > **The plugin install is complete on its own** — hooks, MCP tools, and the bundled slash commands (`/mem`, `/lesson`, `/bug`, `/adopt`) all run from the plugin with no second step. The slash commands invoke the bundled CLI by absolute path (`node ~/.claude-mem-lite/cli.mjs <cmd>`), so they work without anything on your `PATH`. A global `claude-mem-lite` **shell** command (for running queries yourself in a terminal) is **optional** — `npm i -g claude-mem-lite` — and is a *separate* npm install: the plugin's auto-update does **not** refresh it, so re-run `npm i -g claude-mem-lite@latest` if you want that shell command kept in sync. You do **not** need it for the plugin to be fully functional.
164
+
163
165
  > **Auto-adopt fires on the first SessionStart per project (v2.82.1+).** The plugin automatically writes the **invited-memory sentinel** (a system-authority pointer that boosts Claude's proactive use of `mem_recall` / `mem_save`) into the project's memdir — **no manual `/adopt` needed**, regardless of install path (npm, npx, `/plugin`, manual). Per-project opt-out: `claude-mem-lite adopt --disable` (`--enable` to re-arm). Global opt-out: `export MEM_NO_AUTO_ADOPT=1`. Manual `/adopt` remains available for re-applying after edits and for the `--all` batch path.
164
166
 
165
167
  ### Method 2: npx (one-liner)
package/adopt-content.mjs CHANGED
@@ -26,7 +26,7 @@ export function getIndexLine() {
26
26
  export function getDetailDoc() {
27
27
  return `# claude-mem-lite 插件契约
28
28
 
29
- > 由 \`claude-mem-lite adopt\` 生成;卸载用 \`claude-mem-lite unadopt\`。
29
+ > 由 \`node ~/.claude-mem-lite/cli.mjs adopt\` 生成;卸载用 \`node ~/.claude-mem-lite/cli.mjs unadopt\`。
30
30
  > 设计背景见 docs/plans/2026-04-16-invited-memory-pattern.md。
31
31
 
32
32
  ## 何时调用 MCP 工具
@@ -58,28 +58,28 @@ MCP 层,按名 \`tools/call\` 仍可命中,但对 Claude Code 这类只读 t
58
58
 
59
59
  | 场景 | CLI |
60
60
  |------|-----|
61
- | 清理过期记忆 | \`claude-mem-lite maintain --action scan\` → \`--action execute\` |
62
- | 深度优化(Haiku) | \`claude-mem-lite optimize --action preview\` |
63
- | 压缩旧条目 | \`claude-mem-lite compress --preview\` |
64
- | FTS5 索引检查 / 重建 | \`claude-mem-lite fts-check [--rebuild]\` |
65
- | tier 分组浏览 | \`claude-mem-lite browse [--tier active]\` |
66
- | 导出 JSON/JSONL | \`claude-mem-lite export [--format jsonl]\` |
67
- | 统计总量 / 健康 | \`claude-mem-lite stats [--days 30]\` |
68
- | 删除某条 | \`claude-mem-lite delete <id>[,<id>]\` |
69
- | 更新某条 | \`claude-mem-lite update <id> [--title ...]\` |
70
- | 列 / 搜索 / 导入 skill-agent registry | \`claude-mem-lite registry <list\\|search\\|import>\` |
61
+ | 清理过期记忆 | \`node ~/.claude-mem-lite/cli.mjs maintain scan --ops purge_stale\` → \`maintain execute --ops purge_stale\` |
62
+ | 深度优化(Haiku) | \`node ~/.claude-mem-lite/cli.mjs optimize\`(默认 preview;\`--run\` 执行,\`--task re-enrich,normalize,cluster-merge,smart-compress\` 选阶段) |
63
+ | 压缩旧条目 | \`node ~/.claude-mem-lite/cli.mjs compress\`(默认 preview;\`--execute\` 执行,\`--age-days N\` 改阈值) |
64
+ | FTS5 索引检查 / 重建 | \`node ~/.claude-mem-lite/cli.mjs fts-check [--rebuild]\` |
65
+ | tier 分组浏览 | \`node ~/.claude-mem-lite/cli.mjs browse [--tier active]\` |
66
+ | 导出 JSON/JSONL | \`node ~/.claude-mem-lite/cli.mjs export [--format jsonl]\` |
67
+ | 统计总量 / 健康 | \`node ~/.claude-mem-lite/cli.mjs stats [--days 30]\` |
68
+ | 删除某条 | \`node ~/.claude-mem-lite/cli.mjs delete <id>[,<id>]\` |
69
+ | 更新某条 | \`node ~/.claude-mem-lite/cli.mjs update <id> [--title ...]\` |
70
+ | 列 / 搜索 / 导入 skill-agent registry | \`node ~/.claude-mem-lite/cli.mjs registry <list\\|search\\|import>\` |
71
71
  | 按 registry 名载入 skill/agent | (MCP only:\`mem_use\`;由用户主动请求时才使用) |
72
72
 
73
73
  ## CLI 速查(常用检索)
74
74
 
75
75
  | 命令 | 用途 |
76
76
  |------|------|
77
- | \`claude-mem-lite search "query"\` | FTS5 全文搜索 |
78
- | \`claude-mem-lite search "err" --type bugfix\` | 按类型过滤 |
79
- | \`claude-mem-lite recall "file.mjs"\` | 文件相关记忆 |
80
- | \`claude-mem-lite recent 5\` | 最近 5 条 |
81
- | \`claude-mem-lite get 42,43\` | 按 ID 展开 |
82
- | \`claude-mem-lite timeline --anchor 42\` | 时间线上下文 |
77
+ | \`node ~/.claude-mem-lite/cli.mjs search "query"\` | FTS5 全文搜索 |
78
+ | \`node ~/.claude-mem-lite/cli.mjs search "err" --type bugfix\` | 按类型过滤 |
79
+ | \`node ~/.claude-mem-lite/cli.mjs recall "file.mjs"\` | 文件相关记忆 |
80
+ | \`node ~/.claude-mem-lite/cli.mjs recent 5\` | 最近 5 条 |
81
+ | \`node ~/.claude-mem-lite/cli.mjs get 42,43\` | 按 ID 展开 |
82
+ | \`node ~/.claude-mem-lite/cli.mjs timeline --anchor 42\` | 时间线上下文 |
83
83
 
84
84
  ## 质量门槛
85
85
 
@@ -89,6 +89,6 @@ MCP 层,按名 \`tools/call\` 仍可命中,但对 Claude Code 这类只读 t
89
89
 
90
90
  ## 卸载
91
91
 
92
- \`claude-mem-lite unadopt\` 精确移除 sentinel 段 + 本文件;其它 MEMORY.md 内容不动。
92
+ \`node ~/.claude-mem-lite/cli.mjs unadopt\` 精确移除 sentinel 段 + 本文件;其它 MEMORY.md 内容不动。
93
93
  `;
94
94
  }
package/commands/adopt.md CHANGED
@@ -57,4 +57,4 @@ session. After running `adopt`:
57
57
 
58
58
  Same caveat applies in reverse for `/unadopt`.
59
59
 
60
- !claude-mem-lite adopt $ARGUMENTS
60
+ !node ~/.claude-mem-lite/cli.mjs adopt $ARGUMENTS
package/commands/bug.md CHANGED
@@ -42,7 +42,7 @@ Build the body as `<description>\n\nRepro:\n<repro-steps>` (or just
42
42
 
43
43
  Run via Bash:
44
44
 
45
- claude-mem-lite activity save \
45
+ node ~/.claude-mem-lite/cli.mjs activity save \
46
46
  --type bug \
47
47
  --title "<first 60 chars of description>" \
48
48
  --body "<body>" \
@@ -37,7 +37,7 @@ Run via Bash — the CLI takes the lesson text as positional args and stores
37
37
  the full text as the title (the `activity save` command uses title-as-body
38
38
  when `--body` is absent):
39
39
 
40
- claude-mem-lite activity save \
40
+ node ~/.claude-mem-lite/cli.mjs activity save \
41
41
  --type lesson \
42
42
  --title "<first 60 chars of text>" \
43
43
  --body "<full text>" \
package/commands/mem.md CHANGED
@@ -23,16 +23,16 @@ Search and browse your project memory efficiently.
23
23
 
24
24
  When the user invokes `/mem`, parse their intent:
25
25
 
26
- - `/mem search <query>` → run `claude-mem-lite search <query>` via Bash
27
- - `/mem recent` or `/mem recent 20` → run `claude-mem-lite recent [N]` via Bash
28
- - `/mem recall <file>` → run `claude-mem-lite recall <file>` via Bash
29
- - `/mem timeline <id>` → run `claude-mem-lite timeline --anchor <id>` via Bash
26
+ - `/mem search <query>` → run `node ~/.claude-mem-lite/cli.mjs search <query>` via Bash
27
+ - `/mem recent` or `/mem recent 20` → run `node ~/.claude-mem-lite/cli.mjs recent [N]` via Bash
28
+ - `/mem recall <file>` → run `node ~/.claude-mem-lite/cli.mjs recall <file>` via Bash
29
+ - `/mem timeline <id>` → run `node ~/.claude-mem-lite/cli.mjs timeline --anchor <id>` via Bash
30
30
  - `/mem save <text>` → call `mem_save` MCP tool with the text as content
31
- - `/mem stats` → run `claude-mem-lite stats` via Bash
32
- - `/mem get <ids>` → run `claude-mem-lite get <ids>` via Bash
31
+ - `/mem stats` → run `node ~/.claude-mem-lite/cli.mjs stats` via Bash
32
+ - `/mem get <ids>` → run `node ~/.claude-mem-lite/cli.mjs get <ids>` via Bash
33
33
  - `/mem cleanup` → run `mem_maintain(action="scan")`, report pending purge count and stale items to user, ask for confirmation, then run `mem_maintain(action="execute", operations=["purge_stale"])` if confirmed
34
34
  - `/mem cleanup Nd` (e.g. `60d`) → same as above but use `retain_days=N` to only purge items older than N days
35
35
  - `/mem cleanup keep Nd` (e.g. `keep 14d`) → same as above with `retain_days=N`
36
- - `/mem <query>` (no subcommand) → treat as search, run `claude-mem-lite search <query>` via Bash
36
+ - `/mem <query>` (no subcommand) → treat as search, run `node ~/.claude-mem-lite/cli.mjs search <query>` via Bash
37
37
 
38
- Use Bash commands first. For detailed data, use `claude-mem-lite get <id>` via Bash.
38
+ Use Bash commands first. For detailed data, use `node ~/.claude-mem-lite/cli.mjs get <id>` via Bash.
@@ -27,4 +27,4 @@ Once unadopted, the conservative hook layer (SessionStart `File Lessons` /
27
27
  `Key Context`, MCP instructions `WHEN TO USE`) goes back to verbose mode —
28
28
  Claude Code will see the full injection again on the next session start.
29
29
 
30
- !claude-mem-lite unadopt $ARGUMENTS
30
+ !node ~/.claude-mem-lite/cli.mjs unadopt $ARGUMENTS
package/hook.mjs CHANGED
@@ -45,6 +45,7 @@ import {
45
45
  } from './hook-shared.mjs';
46
46
  import { handleLLMEpisode, handleLLMSummary, saveObservation, buildImmediateObservation } from './hook-llm.mjs';
47
47
  import { scrubRecord } from './lib/scrub-record.mjs';
48
+ import { formatHookError } from './lib/native-binding-hint.mjs';
48
49
  import { selectCompressionCandidates, groupByProjectWeek, compressGroup } from './lib/compress-core.mjs';
49
50
  import { cleanupBroken, decayAndMarkIdle, boostAccessed } from './lib/maintain-core.mjs';
50
51
  import {
@@ -1439,9 +1440,13 @@ try {
1439
1440
  case 'update-check': await checkForUpdate(); break;
1440
1441
  }
1441
1442
  } catch (err) {
1442
- // Always log fatal errors (ungated) with structured format
1443
- const ts = new Date().toISOString();
1444
- console.error(`[claude-mem-lite] [${ts}] [ERROR] ${event}: ${err.message}`);
1443
+ // Log fatal errors (ungated) with structured format. ERR_DLOPEN_FAILED (an
1444
+ // unloadable native DB binding, e.g. ABI-stale after a Node upgrade) is
1445
+ // collapsed to one short, rate-limited rebuild hint instead of the raw
1446
+ // multi-line NODE_MODULE_VERSION message on every fire — see
1447
+ // lib/native-binding-hint.mjs.
1448
+ const line = formatHookError(err, event, { runtimeDir: RUNTIME_DIR });
1449
+ if (line) console.error(line);
1445
1450
  }
1446
1451
 
1447
1452
  process.exit(0);
@@ -0,0 +1,71 @@
1
+ // lib/native-binding-hint.mjs — friendly, rate-limited hint for an unloadable
2
+ // native DB binding (better-sqlite3 ERR_DLOPEN_FAILED, e.g. a Node version
3
+ // upgrade leaves the prebuilt .node ABI-stale).
4
+ //
5
+ // This is the SIBLING of the missing-dependency case handled in
6
+ // scripts/hook-launcher.mjs. The two fail on different paths:
7
+ // • MISSING dependency (ERR_MODULE_NOT_FOUND) throws at IMPORT time, before
8
+ // hook.mjs runs — caught by the launcher.
9
+ // • UNLOADABLE binding (ERR_DLOPEN_FAILED) imports fine (better-sqlite3 loads
10
+ // its .node lazily at the first `new Database()`), then throws inside a hook
11
+ // handler — caught by hook.mjs's top-level dispatch try/catch. Pre-this,
12
+ // that catch logged the raw multi-line NODE_MODULE_VERSION message on EVERY
13
+ // hook fire. Here we collapse it to one short, actionable line, rate-limited
14
+ // per cooldown. The actual rebuild is the MCP server launch path's job
15
+ // (lib/binding-probe.mjs::ensureBetterSqlite3Working) — a hook must never
16
+ // run `npm rebuild` itself (2–5s timeout + concurrent-fire races).
17
+ //
18
+ // Pure node: imports + injectable now/runtimeDir so it unit-tests without the
19
+ // hook dependency graph (no schema.mjs / better-sqlite3 import).
20
+
21
+ import { join } from 'node:path';
22
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
23
+
24
+ export const NATIVE_BINDING_HINT_COOLDOWN_MS = 6 * 60 * 60 * 1000; // 6h
25
+ const MARKER_NAME = 'native-binding-hint-last';
26
+
27
+ /**
28
+ * True at most once per cooldown window. Persists the last-hint timestamp as
29
+ * file CONTENT (not mtime) under runtimeDir so callers can inject `now`
30
+ * deterministically in tests. Best-effort: any fs error returns true — showing
31
+ * the hint beats silently swallowing a real problem.
32
+ *
33
+ * @param {string} runtimeDir Directory for the marker file
34
+ * @param {number} [now] Current epoch ms (injectable)
35
+ * @param {number} [cooldownMs] Suppression window
36
+ * @returns {boolean}
37
+ */
38
+ export function nativeBindingHintDue(runtimeDir, now = Date.now(), cooldownMs = NATIVE_BINDING_HINT_COOLDOWN_MS) {
39
+ const marker = join(runtimeDir, MARKER_NAME);
40
+ try {
41
+ const last = Number(readFileSync(marker, 'utf8'));
42
+ if (Number.isFinite(last) && now - last < cooldownMs) return false;
43
+ } catch { /* no/invalid marker → due */ }
44
+ try {
45
+ if (!existsSync(runtimeDir)) mkdirSync(runtimeDir, { recursive: true });
46
+ writeFileSync(marker, String(now));
47
+ } catch { /* best-effort */ }
48
+ return true;
49
+ }
50
+
51
+ /**
52
+ * Single stderr line hook.mjs should log for a caught dispatch error, or null
53
+ * to stay silent (ERR_DLOPEN_FAILED still within cooldown). ERR_DLOPEN_FAILED →
54
+ * short rate-limited rebuild hint; everything else → the existing ungated
55
+ * structured ERROR line. Pass runtimeDir to enable rate-limiting (omit it to
56
+ * always format, e.g. in tests).
57
+ *
58
+ * @param {Error & {code?: string}} err
59
+ * @param {string} event Hook event name (stop / session-start / …)
60
+ * @param {{now?: number, runtimeDir?: string}} [opts]
61
+ * @returns {string | null}
62
+ */
63
+ export function formatHookError(err, event, { now = Date.now(), runtimeDir } = {}) {
64
+ const ts = new Date(now).toISOString();
65
+ if (err && err.code === 'ERR_DLOPEN_FAILED') {
66
+ if (runtimeDir && !nativeBindingHintDue(runtimeDir, now)) return null;
67
+ return `[claude-mem-lite] [${ts}] [WARN] ${event}: native DB binding can't load ` +
68
+ `(likely a Node version change) — auto-heals on next MCP server start, or run: claude-mem-lite repair`;
69
+ }
70
+ return `[claude-mem-lite] [${ts}] [ERROR] ${event}: ${err && err.message}`;
71
+ }
package/mem-cli.mjs CHANGED
@@ -39,6 +39,7 @@ import { resolveAnchorToken, formatAnchorError, resolveQueryAnchor, fetchRecentT
39
39
  import { buildSearchFtsQuery, parseDateBounds, computePerSourceWindow, effectiveObsFtsQuery, searchSessionsFts, searchPromptsFts, normalizeCrossSourceScores, applyUserSort, applyTierFilter } from './lib/search-core.mjs';
40
40
  import { AUTO_MERGE_THRESHOLD } from './lib/dedup-constants.mjs';
41
41
  import { countRecentHookErrors } from './lib/hook-telemetry.mjs';
42
+ import { aggregateMetrics } from './lib/metrics.mjs';
42
43
  import {
43
44
  insertDeferred, listOpenWithOrdinal, dropDeferred,
44
45
  resolveDeferredIds, closeDeferredItems,
@@ -1154,6 +1155,13 @@ async function cmdStats(db, args) {
1154
1155
  out(` Low-value (imp=1, never accessed, >30d): ${lowVal.c} (${(noiseRatio * 100).toFixed(1)}% noise)`);
1155
1156
  out(` Compressed: ${compressedCount.c}`);
1156
1157
  out(` Hook errors (last 24h): ${hookErrors24h}${hookErrors24h > 0 ? ` ← tail ${join(DB_DIR, 'runtime/hook-errors')}` : ''}`);
1158
+ // Tier-1 firing counters for ① file-intel + ② reread-guard (recorded by
1159
+ // pre-tool-recall.js via lib/metrics.mjs; CLAUDE_MEM_METRICS=1 to enable).
1160
+ const featAgg = aggregateMetrics(DB_DIR, 7);
1161
+ const fiN = featAgg.file_intel?.count ?? 0;
1162
+ const rrN = featAgg.reread_warn?.count ?? 0;
1163
+ const metricsOn = process.env.CLAUDE_MEM_METRICS === '1';
1164
+ out(` Feature injections (7d): 📄 file-intel ${fiN} · 🔁 reread-warn ${rrN}${(!metricsOn && fiN + rrN === 0) ? ' (set CLAUDE_MEM_METRICS=1 to record)' : ''}`);
1157
1165
  if (noiseRatio > 0.6) out(' ⚠️ High noise ratio — consider running mem compress');
1158
1166
  out('');
1159
1167
  // Tier counts only live (uncompressed, non-superseded) observations — surface the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
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",
@@ -79,6 +79,7 @@
79
79
  "lib/deferred-work.mjs",
80
80
  "lib/upgrade-banner.mjs",
81
81
  "lib/scrub-record.mjs",
82
+ "lib/native-binding-hint.mjs",
82
83
  "lib/import-jsonl.mjs",
83
84
  "cli/common.mjs",
84
85
  "cli/fts-check.mjs",
@@ -9,10 +9,16 @@
9
9
  // launcher is defense-in-depth for similar future drift (corrupt download,
10
10
  // half-applied install, manual file deletion).
11
11
  //
12
- // Behavior: try-import the target entry. On ERR_MODULE_NOT_FOUND whose URL
13
- // points under the install dir, run `install.mjs repair` (rate-limited via a
14
- // 6h marker file under runtime/) and retry the import once. On any other
15
- // exception, re-throw so Node's default error surface is preserved.
12
+ // Behavior: try-import the target entry. On ERR_MODULE_NOT_FOUND originating
13
+ // from our install either a missing relative module (e.url under the install
14
+ // dir) or a missing bare dependency like better-sqlite3 (e.url is undefined and
15
+ // the importer named in the message is under the install dir) — run
16
+ // `install.mjs repair` (rate-limited via a 6h marker file under runtime/) and
17
+ // retry the import once. If repair is unavailable or fails, degrade quietly:
18
+ // these are best-effort memory hooks, so a broken/missing dependency emits one
19
+ // clean recovery line and exits 0 rather than dumping a Node stack trace on
20
+ // every fire. On any other (foreign) exception, re-throw so Node's default
21
+ // error surface is preserved.
16
22
  //
17
23
  // HARD constraint: pure node: imports only. Importing anything from lib/ here
18
24
  // would defeat the entire purpose — the launcher must survive a broken
@@ -59,10 +65,33 @@ async function runEntry({ bustCache = false } = {}) {
59
65
  await import(url);
60
66
  }
61
67
 
68
+ // Two ERR_MODULE_NOT_FOUND shapes reach here (both verified against Node 22):
69
+ // • missing relative module → e.url = file://<missing-path> (under install)
70
+ // • missing bare dependency (e.g. a half-installed better-sqlite3) → e.url is
71
+ // UNDEFINED and the message is `Cannot find package '<name>' imported from
72
+ // <importer>`. This is the shape that bricked the hooks: the old
73
+ // file://INSTALL_DIR prefix test never matched it in a dev-dir install, so
74
+ // a missing dependency was misread as a foreign error and re-thrown as a
75
+ // Node stack trace on every hook fire.
76
+ // Anchor on any path the error exposes — the missing URL and/or the importer
77
+ // (present in both messages as "imported from <path>"). If it sits inside our
78
+ // install, a self-heal could fix it.
62
79
  function isLocalModuleErr(e) {
63
80
  if (!e || e.code !== 'ERR_MODULE_NOT_FOUND') return false;
64
- const where = String(e.url || e.message || '');
65
- return where.includes('.claude-mem-lite') || where.startsWith(`file://${INSTALL_DIR}`);
81
+ const importer = /imported from (.+?)\s*$/.exec(String(e.message || ''))?.[1];
82
+ const paths = [e.url, importer]
83
+ .filter(Boolean)
84
+ .map((p) => String(p).replace(/^file:\/\//, ''));
85
+ return paths.some((p) => p.startsWith(INSTALL_DIR) || p.includes('.claude-mem-lite'));
86
+ }
87
+
88
+ // Human-readable label for the "Detected broken install (<reason>)" line:
89
+ // prefer the missing dependency/module name over a raw path fragment.
90
+ function describeFailure(e) {
91
+ const pkg = /Cannot find package '([^']+)'/.exec(String(e.message || ''))?.[1];
92
+ if (pkg) return pkg;
93
+ if (e.url) return String(e.url).split('/').pop();
94
+ return String(e.message || 'unknown').split('/').slice(-2).join('/');
66
95
  }
67
96
 
68
97
  function recentHealAttempt() {
@@ -133,9 +162,14 @@ try {
133
162
  await runEntry();
134
163
  } catch (e) {
135
164
  if (!isLocalModuleErr(e)) throw e;
136
- const reason = String(e.url || e.message).split('/').slice(-2).join('/');
137
- const healed = await attemptHeal(reason);
138
- if (!healed) throw e;
165
+ const healed = await attemptHeal(describeFailure(e));
166
+ if (!healed) {
167
+ // Broken/missing dependency we can't repair right now (repair failed, or
168
+ // was skipped within the 6h cooldown). attemptHeal already wrote actionable
169
+ // guidance — degrade quietly instead of re-throwing the original import
170
+ // error, which would spew a Node stack trace on every hook fire.
171
+ process.exit(0);
172
+ }
139
173
  try {
140
174
  await runEntry({ bustCache: true });
141
175
  } catch (retryErr) {
@@ -143,6 +177,6 @@ try {
143
177
  `[claude-mem-lite] Hook still failing after self-heal: ${retryErr.message}\n` +
144
178
  `[claude-mem-lite] Manual recovery: ${TARBALL_FALLBACK}\n`,
145
179
  );
146
- process.exit(1);
180
+ process.exit(0);
147
181
  }
148
182
  }
@@ -12,6 +12,7 @@ import { recordHookError } from '../lib/hook-telemetry.mjs';
12
12
  import { citeFactorClause } from '../scoring-sql.mjs';
13
13
  import { fileIntelFor } from '../lib/file-intel.mjs';
14
14
  import { shouldWarnReread, buildRereadWarning, readFileMeta } from '../lib/reread-guard.mjs';
15
+ import { recordMetric } from '../lib/metrics.mjs';
15
16
 
16
17
  // CLAUDE_MEM_DIR matches schema.mjs / main CLI — one env var sandboxes the
17
18
  // whole system. CLAUDE_MEM_DB_PATH / CLAUDE_MEM_RUNTIME_DIR remain as
@@ -258,6 +259,7 @@ try {
258
259
  ].join('\n'),
259
260
  },
260
261
  }));
262
+ recordMetric(DATA_DIR, { event: 'reread_warn' }); // tier-1 firing counter (②)
261
263
  }
262
264
  }
263
265
  process.exit(0); // already recalled this file in-session
@@ -388,6 +390,9 @@ try {
388
390
  if (isRead && !FILE_INTEL_OFF) {
389
391
  try { fileIntelLine = fileIntelFor(filePath, { minTokens: FILE_INTEL_MIN_TOKENS }); } catch {}
390
392
  }
393
+ // Tier-1 firing counter (①). recordMetric no-ops unless CLAUDE_MEM_METRICS=1,
394
+ // so default users pay nothing; observers see counts in `doctor` / `stats`.
395
+ if (fileIntelLine) recordMetric(DATA_DIR, { event: 'file_intel' });
391
396
  const lines = [];
392
397
  // v2.34.6: Read mode uses 120-char truncation (Edit mode keeps the 240-char
393
398
  // cap from R3-UX). Rationale: Read is a one-shot nudge with 1 lesson max;
@@ -14,7 +14,7 @@ import { porterStem } from './tfidf.mjs';
14
14
  const INSTRUCTIONS_BASE = [
15
15
  'Long-term memory across sessions. Hooks auto-inject context; CLI preferred for explicit queries.',
16
16
  '',
17
- 'CLI (via Bash):',
17
+ 'CLI (via Bash) — invoke as `node ~/.claude-mem-lite/cli.mjs <cmd>` (shipped with the plugin), or bare `claude-mem-lite <cmd>` only if you ran the optional global `npm i -g claude-mem-lite`:',
18
18
  ' claude-mem-lite search "query" — FTS5 full-text search',
19
19
  ' claude-mem-lite search "err" --type bugfix — filter by type',
20
20
  ' claude-mem-lite recall "file.mjs" — file-related memories',
@@ -22,7 +22,7 @@ const INSTRUCTIONS_BASE = [
22
22
  ' claude-mem-lite get 42,43 — full details by ID',
23
23
  ' claude-mem-lite timeline --anchor 42 — chronological context',
24
24
  '',
25
- 'MCP tools: mem_search, mem_recent, mem_save, mem_get, mem_recall, mem_timeline for programmatic access.',
25
+ 'MCP tools: mem_search, mem_recent, mem_save, mem_get, mem_recall, mem_timeline for programmatic access (always available — no PATH/CLI install needed).',
26
26
  'mem_save: Save non-obvious insights (bugfix lessons, architecture decisions).',
27
27
  'Search tips: short keywords (2-3 words), filter with obs_type when relevant.',
28
28
  ];
package/source-files.mjs CHANGED
@@ -121,6 +121,10 @@ export const SOURCE_FILES = [
121
121
  // Statically imported by hook-llm, hook-handoff, hook-optimize, hook,
122
122
  // mem-cli; reached transitively from server.mjs and cli.mjs.
123
123
  'lib/scrub-record.mjs',
124
+ // Rate-limited friendly hint for an unloadable native DB binding
125
+ // (ERR_DLOPEN_FAILED). Statically imported by hook.mjs; ship it so the
126
+ // dispatch catch path resolves in installed/tarball runtimes.
127
+ 'lib/native-binding-hint.mjs',
124
128
  // Cold-start backfill: parses ~/.claude/projects/<encoded>/<uuid>.jsonl
125
129
  // transcripts into user_prompts + observations. Dynamic-imported by
126
130
  // mem-cli.mjs::cmdImportJsonl; listed here so source-files-sync.test.mjs
package/tool-schemas.mjs CHANGED
@@ -349,7 +349,7 @@ export const tools = [
349
349
  ' - Looking for prior art on a module/feature before refactoring\n' +
350
350
  ' - User asks "have we seen this before" or references something not in visible context\n' +
351
351
  '\n' +
352
- 'Equivalent CLI: claude-mem-lite search "<query>" [--type bugfix]',
352
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs search "<query>" [--type bugfix]',
353
353
  inputSchema: memSearchSchema,
354
354
  },
355
355
  {
@@ -367,7 +367,7 @@ export const tools = [
367
367
  ' - User asks "what did we do yesterday / last" with no topic keyword\n' +
368
368
  ' - Verifying that a just-made change was captured as an observation\n' +
369
369
  '\n' +
370
- 'Equivalent CLI: claude-mem-lite recent [N]',
370
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs recent [N]',
371
371
  inputSchema: memRecentSchema,
372
372
  },
373
373
  {
@@ -387,7 +387,7 @@ export const tools = [
387
387
  ' - A search hit is interesting and you want its chronological neighbours\n' +
388
388
  ' - Replaying a session narrative around a known observation ID\n' +
389
389
  '\n' +
390
- 'Equivalent CLI: claude-mem-lite timeline --anchor <ID> [--before N --after N]',
390
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs timeline --anchor <ID> [--before N --after N]',
391
391
  inputSchema: memTimelineSchema,
392
392
  },
393
393
  {
@@ -407,7 +407,7 @@ export const tools = [
407
407
  '\n' +
408
408
  'On miss, response includes "Try: …" hint listing other sources the ID lives in.\n' +
409
409
  '\n' +
410
- 'Equivalent CLI: claude-mem-lite get <id>[,<id>,...] — accepts P#/S#/# prefix.',
410
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs get <id>[,<id>,...] — accepts P#/S#/# prefix.',
411
411
  inputSchema: memGetSchema,
412
412
  },
413
413
  {
@@ -425,7 +425,7 @@ export const tools = [
425
425
  ' - Cleaning up an observation saved from a test run or incorrect save\n' +
426
426
  ' - Always run once with confirm=false, then again with confirm=true\n' +
427
427
  '\n' +
428
- 'Equivalent CLI: claude-mem-lite delete <id>[,<id>,...] [--confirm]',
428
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs delete <id>[,<id>,...] [--confirm]',
429
429
  inputSchema: memDeleteSchema,
430
430
  hidden: true,
431
431
  },
@@ -444,7 +444,7 @@ export const tools = [
444
444
  ' - After a non-obvious architecture/tradeoff decision — set type="decision", lesson_learned="<constraint + why>"\n' +
445
445
  ' - User explicitly asks "remember this" or "save a note that ..."\n' +
446
446
  '\n' +
447
- 'Equivalent CLI: claude-mem-lite save --type bugfix --lesson "..." "<content>"',
447
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs save --type bugfix --lesson "..." "<content>"',
448
448
  inputSchema: memSaveSchema,
449
449
  },
450
450
  {
@@ -462,7 +462,7 @@ export const tools = [
462
462
  ' - Diagnosing why search feels sparse or noisy at a macro level\n' +
463
463
  ' - Auditing a project before major compression/maintenance\n' +
464
464
  '\n' +
465
- 'Equivalent CLI: claude-mem-lite stats [--project X] [--days 30]',
465
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs stats [--project X] [--days 30]',
466
466
  inputSchema: memStatsSchema,
467
467
  hidden: true,
468
468
  },
@@ -481,7 +481,7 @@ export const tools = [
481
481
  ' - After a major project phase completes and old per-file observations are noise\n' +
482
482
  ' - Stats show thousands of low-importance rows dragging search quality\n' +
483
483
  '\n' +
484
- 'Equivalent CLI: claude-mem-lite compress [--execute] [--age-days 90] (preview is default)',
484
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs compress [--execute] [--age-days 90] (preview is default)',
485
485
  inputSchema: memCompressSchema,
486
486
  hidden: true,
487
487
  },
@@ -500,7 +500,7 @@ export const tools = [
500
500
  ' - After bulk imports or a long offline period\n' +
501
501
  ' - User asks for periodic maintenance / cleanup\n' +
502
502
  '\n' +
503
- 'Equivalent CLI: claude-mem-lite maintain scan --ops dedup,decay',
503
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs maintain scan --ops dedup,decay',
504
504
  inputSchema: memMaintainSchema,
505
505
  hidden: true,
506
506
  },
@@ -519,7 +519,7 @@ export const tools = [
519
519
  ' - stats show many degraded (title-only, no lesson) observations\n' +
520
520
  ' - Start with action="preview" to see candidates before spending tokens\n' +
521
521
  '\n' +
522
- 'Equivalent CLI: claude-mem-lite optimize [--run|--run-all] [--task re-enrich,normalize,cluster-merge,smart-compress] [--max N] (preview is default)',
522
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs optimize [--run|--run-all] [--task re-enrich,normalize,cluster-merge,smart-compress] [--max N] (preview is default)',
523
523
  inputSchema: memOptimizeSchema,
524
524
  hidden: true,
525
525
  },
@@ -538,7 +538,7 @@ export const tools = [
538
538
  ' - Looking for a tool by capability → action="search" with keywords\n' +
539
539
  ' - User explicitly asks to import a GitHub repo → action="import_url"\n' +
540
540
  '\n' +
541
- 'Equivalent CLI: claude-mem-lite registry <list|search|import|...> [args]',
541
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs registry <list|search|import|...> [args]',
542
542
  inputSchema: memRegistrySchema,
543
543
  hidden: true,
544
544
  },
@@ -576,7 +576,7 @@ export const tools = [
576
576
  ' - You later discover additional context worth appending to lesson_learned\n' +
577
577
  ' - Reclassifying an observation after its true type becomes clear\n' +
578
578
  '\n' +
579
- 'Equivalent CLI: claude-mem-lite update <id> [--title ...] [--lesson ...]',
579
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs update <id> [--title ...] [--lesson ...]',
580
580
  inputSchema: memUpdateSchema,
581
581
  hidden: true,
582
582
  },
@@ -595,7 +595,7 @@ export const tools = [
595
595
  ' - Moving observations between machines or projects\n' +
596
596
  ' - User asks for a JSON snapshot of a project\'s memories\n' +
597
597
  '\n' +
598
- 'Equivalent CLI: claude-mem-lite export [--format jsonl] [--project X] [--limit 500]',
598
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs export [--format jsonl] [--project X] [--limit 500]',
599
599
  inputSchema: memExportSchema,
600
600
  hidden: true,
601
601
  },
@@ -614,7 +614,7 @@ export const tools = [
614
614
  ' - User asks "what do we know about <file>"\n' +
615
615
  ' - Investigating a recurring issue in a file you have not touched recently\n' +
616
616
  '\n' +
617
- 'Equivalent CLI: claude-mem-lite recall "<file>" [--limit 10]',
617
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs recall "<file>" [--limit 10]',
618
618
  inputSchema: memRecallSchema,
619
619
  },
620
620
  {
@@ -632,7 +632,7 @@ export const tools = [
632
632
  ' - After a crash, power loss, or manual DB edit\n' +
633
633
  ' - doctor / stats flags FTS integrity problems\n' +
634
634
  '\n' +
635
- 'Equivalent CLI: claude-mem-lite fts-check [--rebuild]',
635
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs fts-check [--rebuild]',
636
636
  inputSchema: memFtsCheckSchema,
637
637
  hidden: true,
638
638
  },
@@ -651,7 +651,7 @@ export const tools = [
651
651
  ' - Triaging what to compress or clean up before running maintenance\n' +
652
652
  ' - Scanning for interesting anchors to follow up with mem_timeline\n' +
653
653
  '\n' +
654
- 'Equivalent CLI: claude-mem-lite browse [--tier active] [--project X]',
654
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs browse [--tier active] [--project X]',
655
655
  inputSchema: memBrowseSchema,
656
656
  hidden: true,
657
657
  },
@@ -670,7 +670,7 @@ export const tools = [
670
670
  ' - Wrap-up phase enumerates follow-up items for the next session\n' +
671
671
  ' - Bug surfaces but root cause is out of this session\'s scope\n' +
672
672
  '\n' +
673
- 'Equivalent CLI: claude-mem-lite defer add "<title>" [--priority 1|2|3] [--detail "..."] [--files a.mjs,b.mjs]',
673
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs defer add "<title>" [--priority 1|2|3] [--detail "..."] [--files a.mjs,b.mjs]',
674
674
  inputSchema: memDeferSchema,
675
675
  },
676
676
  {
@@ -687,7 +687,7 @@ export const tools = [
687
687
  ' - About to refer to "item N" and need to confirm what N points to\n' +
688
688
  ' - Auditing carry-forward state across multiple sessions\n' +
689
689
  '\n' +
690
- 'Equivalent CLI: claude-mem-lite defer list [--project X] [--limit 10]',
690
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs defer list [--project X] [--limit 10]',
691
691
  inputSchema: memDeferListSchema,
692
692
  },
693
693
  {
@@ -705,7 +705,7 @@ export const tools = [
705
705
  ' - Scope changed and the work is no longer needed\n' +
706
706
  ' - User explicitly says "drop the deferred X, never mind"\n' +
707
707
  '\n' +
708
- 'Equivalent CLI: claude-mem-lite defer drop <D#N|ordinal> --reason "..."',
708
+ 'Equivalent CLI: node ~/.claude-mem-lite/cli.mjs defer drop <D#N|ordinal> --reason "..."',
709
709
  inputSchema: memDeferDropSchema,
710
710
  },
711
711
  ];