claude-mem-lite 3.0.1 → 3.1.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -3
- package/README.zh-CN.md +5 -3
- package/adopt-content.mjs +21 -19
- package/cli-path.mjs +21 -0
- package/commands/adopt.md +1 -1
- package/commands/bug.md +1 -1
- package/commands/lesson.md +1 -1
- package/commands/mem.md +8 -8
- package/commands/unadopt.md +1 -1
- package/hook.mjs +8 -3
- package/install.mjs +17 -0
- package/lib/native-binding-hint.mjs +105 -0
- package/package.json +3 -1
- package/scripts/hook-launcher.mjs +123 -12
- package/server-internals.mjs +9 -8
- package/source-files.mjs +5 -1
- package/tool-schemas.mjs +20 -19
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.
|
|
13
|
+
"version": "3.1.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.
|
|
3
|
+
"version": "3.1.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/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 an absolute path resolved from the plugin directory (`${CLAUDE_PLUGIN_ROOT}/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)
|
|
@@ -261,9 +263,9 @@ surface — reach them through the CLI column in the second table.
|
|
|
261
263
|
| `mem_update` | `claude-mem-lite update <id>` | Edit an observation in place. |
|
|
262
264
|
| `mem_stats` | `claude-mem-lite stats` | Counts, type distribution, daily activity. |
|
|
263
265
|
| `mem_delete` | `claude-mem-lite delete <id>` | Preview / confirm workflow, FTS5 cleanup. |
|
|
264
|
-
| `mem_compress` | `claude-mem-lite compress
|
|
265
|
-
| `mem_maintain` | `claude-mem-lite maintain --
|
|
266
|
-
| `mem_optimize` | `claude-mem-lite optimize
|
|
266
|
+
| `mem_compress` | `claude-mem-lite compress` | Roll up old low-value observations (preview default; `--execute` to apply). |
|
|
267
|
+
| `mem_maintain` | `claude-mem-lite maintain scan --ops dedup,decay` | dedup / decay / cleanup / rebuild_vectors (`scan` previews, `execute` applies). |
|
|
268
|
+
| `mem_optimize` | `claude-mem-lite optimize` | LLM-powered re-enrich / normalize / cluster-merge (preview default; `--run` to apply). |
|
|
267
269
|
| `mem_export` | `claude-mem-lite export` | JSON / JSONL dump, filters by project, type, date. |
|
|
268
270
|
| `mem_fts_check` | `claude-mem-lite fts-check [--rebuild]` | FTS5 integrity + rebuild. |
|
|
269
271
|
| `mem_browse` | `claude-mem-lite browse` | Tier-grouped dashboard (working / active / archive). |
|
package/README.zh-CN.md
CHANGED
|
@@ -127,6 +127,8 @@
|
|
|
127
127
|
|
|
128
128
|
插件模式会管理自己的运行时与钩子。SessionStart 时它现在只会**检查并提示**新版本,不会直接覆盖插件目录中的文件。插件模式请通过 Claude 的插件更新流程完成升级。
|
|
129
129
|
|
|
130
|
+
> **插件安装本身即完整** —— hooks、MCP 工具、以及捆绑的 slash 命令(`/mem`、`/lesson`、`/bug`、`/adopt`)全部从插件内运行,无需第二步。slash 命令以从插件目录解析出的绝对路径调用捆绑 CLI(`${CLAUDE_PLUGIN_ROOT}/cli.mjs <cmd>`),因此不依赖 `PATH` 上的任何东西。全局 `claude-mem-lite` **shell** 命令(用于你自己在终端里跑查询)是**可选**的 —— `npm i -g claude-mem-lite` —— 且是**独立**的 npm 安装:插件的自动更新**不会**刷新它,想保持同步就重新跑 `npm i -g claude-mem-lite@latest`。插件要完整工作**并不需要**它。
|
|
131
|
+
|
|
130
132
|
### 方式二:npx(一行命令)
|
|
131
133
|
|
|
132
134
|
```bash
|
|
@@ -223,9 +225,9 @@ v2.34.0 起服务端注册 17 个工具,但 `tools/list` 只暴露 6 个 **核
|
|
|
223
225
|
| `mem_update` | `claude-mem-lite update <id>` | 原地更新某条观察。 |
|
|
224
226
|
| `mem_stats` | `claude-mem-lite stats` | 计数、类型分布、每日活动。 |
|
|
225
227
|
| `mem_delete` | `claude-mem-lite delete <id>` | 预览 / 确认流程,FTS5 自动清理。 |
|
|
226
|
-
| `mem_compress` | `claude-mem-lite compress
|
|
227
|
-
| `mem_maintain` | `claude-mem-lite maintain --
|
|
228
|
-
| `mem_optimize` | `claude-mem-lite optimize
|
|
228
|
+
| `mem_compress` | `claude-mem-lite compress` | 压缩旧的低价值观察(默认 preview;`--execute` 执行)。 |
|
|
229
|
+
| `mem_maintain` | `claude-mem-lite maintain scan --ops dedup,decay` | 去重 / decay / 清理 / 向量重建(`scan` 预览,`execute` 执行)。 |
|
|
230
|
+
| `mem_optimize` | `claude-mem-lite optimize` | LLM 深度优化:re-enrich / normalize / cluster-merge(默认 preview;`--run` 执行)。 |
|
|
229
231
|
| `mem_export` | `claude-mem-lite export` | JSON / JSONL 导出,支持项目/类型/日期过滤。 |
|
|
230
232
|
| `mem_fts_check` | `claude-mem-lite fts-check [--rebuild]` | FTS5 完整性检查与重建。 |
|
|
231
233
|
| `mem_browse` | `claude-mem-lite browse` | 分层仪表盘(working / active / archive)。 |
|
package/adopt-content.mjs
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// docs/plans/2026-04-16-invited-memory-pattern.md so installs do a version-
|
|
8
8
|
// bump replace instead of treating the old content as a user edit.
|
|
9
9
|
|
|
10
|
+
import { CLI_INVOKE } from './cli-path.mjs';
|
|
11
|
+
|
|
10
12
|
export const PLUGIN_SLUG = 'claude-mem-lite';
|
|
11
13
|
export const CURRENT_SENTINEL_VERSION = 'v1';
|
|
12
14
|
|
|
@@ -26,7 +28,7 @@ export function getIndexLine() {
|
|
|
26
28
|
export function getDetailDoc() {
|
|
27
29
|
return `# claude-mem-lite 插件契约
|
|
28
30
|
|
|
29
|
-
> 由
|
|
31
|
+
> 由 \`${CLI_INVOKE} adopt\` 生成;卸载用 \`${CLI_INVOKE} unadopt\`。
|
|
30
32
|
> 设计背景见 docs/plans/2026-04-16-invited-memory-pattern.md。
|
|
31
33
|
|
|
32
34
|
## 何时调用 MCP 工具
|
|
@@ -58,37 +60,37 @@ MCP 层,按名 \`tools/call\` 仍可命中,但对 Claude Code 这类只读 t
|
|
|
58
60
|
|
|
59
61
|
| 场景 | CLI |
|
|
60
62
|
|------|-----|
|
|
61
|
-
| 清理过期记忆 |
|
|
62
|
-
| 深度优化(Haiku) |
|
|
63
|
-
| 压缩旧条目 |
|
|
64
|
-
| FTS5 索引检查 / 重建 |
|
|
65
|
-
| tier 分组浏览 |
|
|
66
|
-
| 导出 JSON/JSONL |
|
|
67
|
-
| 统计总量 / 健康 |
|
|
68
|
-
| 删除某条 |
|
|
69
|
-
| 更新某条 |
|
|
70
|
-
| 列 / 搜索 / 导入 skill-agent registry |
|
|
63
|
+
| 清理过期记忆 | \`${CLI_INVOKE} maintain scan --ops purge_stale\` → \`maintain execute --ops purge_stale --confirm\`(execute 删行必须带 \`--confirm\`,否则只预览) |
|
|
64
|
+
| 深度优化(Haiku) | \`${CLI_INVOKE} optimize\`(默认 preview;\`--run\` 执行,\`--task re-enrich,normalize,cluster-merge,smart-compress\` 选阶段) |
|
|
65
|
+
| 压缩旧条目 | \`${CLI_INVOKE} compress\`(默认 preview;\`--execute\` 执行,\`--age-days N\` 改阈值) |
|
|
66
|
+
| FTS5 索引检查 / 重建 | \`${CLI_INVOKE} fts-check [--rebuild]\` |
|
|
67
|
+
| tier 分组浏览 | \`${CLI_INVOKE} browse [--tier active]\` |
|
|
68
|
+
| 导出 JSON/JSONL | \`${CLI_INVOKE} export [--format jsonl]\` |
|
|
69
|
+
| 统计总量 / 健康 | \`${CLI_INVOKE} stats [--days 30]\` |
|
|
70
|
+
| 删除某条 | \`${CLI_INVOKE} delete <id>[,<id>]\` |
|
|
71
|
+
| 更新某条 | \`${CLI_INVOKE} update <id> [--title ...]\` |
|
|
72
|
+
| 列 / 搜索 / 导入 skill-agent registry | \`${CLI_INVOKE} registry <list\\|search\\|import>\` |
|
|
71
73
|
| 按 registry 名载入 skill/agent | (MCP only:\`mem_use\`;由用户主动请求时才使用) |
|
|
72
74
|
|
|
73
75
|
## CLI 速查(常用检索)
|
|
74
76
|
|
|
75
77
|
| 命令 | 用途 |
|
|
76
78
|
|------|------|
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
79
|
+
| \`${CLI_INVOKE} search "query"\` | FTS5 全文搜索 |
|
|
80
|
+
| \`${CLI_INVOKE} search "err" --type bugfix\` | 按类型过滤 |
|
|
81
|
+
| \`${CLI_INVOKE} recall "file.mjs"\` | 文件相关记忆 |
|
|
82
|
+
| \`${CLI_INVOKE} recent 5\` | 最近 5 条 |
|
|
83
|
+
| \`${CLI_INVOKE} get 42,43\` | 按 ID 展开 |
|
|
84
|
+
| \`${CLI_INVOKE} timeline --anchor 42\` | 时间线上下文 |
|
|
83
85
|
|
|
84
86
|
## 质量门槛
|
|
85
87
|
|
|
86
88
|
- \`mem_save\` 的 \`lesson_learned\` 不要写 \`none\`——写不出教训就保持 NULL
|
|
87
|
-
- \`decision\`
|
|
89
|
+
- \`decision\` 的命中率高于 \`change\`(当前遥测约 3:1,数值会漂移——用 \`${CLI_INVOKE} stats\` 实测,别套固定倍数);方向稳健:一条好 decision 抵数条 change
|
|
88
90
|
- 一般搜索跳过 \`obs_type\` 让系统自动路由;特定意图再过滤
|
|
89
91
|
|
|
90
92
|
## 卸载
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
\`${CLI_INVOKE} unadopt\` 精确移除 sentinel 段 + 本文件;其它 MEMORY.md 内容不动。
|
|
93
95
|
`;
|
|
94
96
|
}
|
package/cli-path.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// cli-path.mjs — single source of truth for invoking the bundled CLI by an
|
|
2
|
+
// absolute, install-shape-independent path.
|
|
3
|
+
//
|
|
4
|
+
// cli.mjs is a sibling of this module at the package root, so import.meta.url
|
|
5
|
+
// resolves it correctly on EVERY install shape: plugin cache, `npm i -g`
|
|
6
|
+
// symlink farm, and manual/dev checkout. The pre-v3.1.1 hardcoded
|
|
7
|
+
// `~/.claude-mem-lite/cli.mjs` only existed on direct-install symlink farms —
|
|
8
|
+
// on a plugin-only install setup.sh provisions the data dir but never
|
|
9
|
+
// materializes source there, so that path is a module-not-found. See the
|
|
10
|
+
// 2026-06-20 code review, findings #1/#2/#3/#13.
|
|
11
|
+
//
|
|
12
|
+
// Use this for JS-emitted, runtime-resolved surfaces (MCP `instructions`, the
|
|
13
|
+
// per-tool "Equivalent CLI" hints, hook recovery lines, the generated adopt
|
|
14
|
+
// doc). Plugin MANIFEST files (commands/*.md, .mcp.json) must instead use the
|
|
15
|
+
// literal `${CLAUDE_PLUGIN_ROOT}` token, which Claude Code — not the shell —
|
|
16
|
+
// substitutes at execution time (the env var is absent from a plain Bash env).
|
|
17
|
+
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
export const CLI_PATH = fileURLToPath(new URL('./cli.mjs', import.meta.url));
|
|
21
|
+
export const CLI_INVOKE = `node ${CLI_PATH}`;
|
package/commands/adopt.md
CHANGED
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
|
-
|
|
45
|
+
node ${CLAUDE_PLUGIN_ROOT}/cli.mjs activity save \
|
|
46
46
|
--type bug \
|
|
47
47
|
--title "<first 60 chars of description>" \
|
|
48
48
|
--body "<body>" \
|
package/commands/lesson.md
CHANGED
|
@@ -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
|
-
|
|
40
|
+
node ${CLAUDE_PLUGIN_ROOT}/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 `
|
|
27
|
-
- `/mem recent` or `/mem recent 20` → run `
|
|
28
|
-
- `/mem recall <file>` → run `
|
|
29
|
-
- `/mem timeline <id>` → run `
|
|
26
|
+
- `/mem search <query>` → run `node ${CLAUDE_PLUGIN_ROOT}/cli.mjs search <query>` via Bash
|
|
27
|
+
- `/mem recent` or `/mem recent 20` → run `node ${CLAUDE_PLUGIN_ROOT}/cli.mjs recent [N]` via Bash
|
|
28
|
+
- `/mem recall <file>` → run `node ${CLAUDE_PLUGIN_ROOT}/cli.mjs recall <file>` via Bash
|
|
29
|
+
- `/mem timeline <id>` → run `node ${CLAUDE_PLUGIN_ROOT}/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 `
|
|
32
|
-
- `/mem get <ids>` → run `
|
|
31
|
+
- `/mem stats` → run `node ${CLAUDE_PLUGIN_ROOT}/cli.mjs stats` via Bash
|
|
32
|
+
- `/mem get <ids>` → run `node ${CLAUDE_PLUGIN_ROOT}/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 `
|
|
36
|
+
- `/mem <query>` (no subcommand) → treat as search, run `node ${CLAUDE_PLUGIN_ROOT}/cli.mjs search <query>` via Bash
|
|
37
37
|
|
|
38
|
-
Use Bash commands first. For detailed data, use `
|
|
38
|
+
Use Bash commands first. For detailed data, use `node ${CLAUDE_PLUGIN_ROOT}/cli.mjs get <id>` via Bash.
|
package/commands/unadopt.md
CHANGED
|
@@ -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
|
-
!
|
|
30
|
+
!node ${CLAUDE_PLUGIN_ROOT}/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
|
-
//
|
|
1443
|
-
|
|
1444
|
-
|
|
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);
|
package/install.mjs
CHANGED
|
@@ -1369,6 +1369,23 @@ async function doctor() {
|
|
|
1369
1369
|
issues++;
|
|
1370
1370
|
}
|
|
1371
1371
|
|
|
1372
|
+
// Hook self-heal runtime: the launcher (scripts/hook-launcher.mjs) degrades a
|
|
1373
|
+
// broken install to exit 0 so it never spams a Node stack trace on every hook
|
|
1374
|
+
// fire. That silence is intentional but hides failure — it drops a breakage
|
|
1375
|
+
// marker so this check can surface the otherwise-invisible degraded state.
|
|
1376
|
+
const brokenMarker = join(MEM_DATA_DIR, 'runtime', 'hook-launcher-broken');
|
|
1377
|
+
if (existsSync(brokenMarker)) {
|
|
1378
|
+
let detail = '';
|
|
1379
|
+
try {
|
|
1380
|
+
const b = JSON.parse(readFileSync(brokenMarker, 'utf8'));
|
|
1381
|
+
const ageH = Math.round((Date.now() - (b.ts || 0)) / 3600000);
|
|
1382
|
+
detail = ` (last: ${b.reason || 'unknown'}, ~${ageH}h ago)`;
|
|
1383
|
+
} catch { /* unreadable marker → bare warning */ }
|
|
1384
|
+
dwarn(`Hook self-heal: a recent hook fire degraded to exit-0${detail} — run \`node ${join(PROJECT_DIR, 'install.mjs')} repair\``);
|
|
1385
|
+
} else {
|
|
1386
|
+
ok('Hook self-heal: no recent silent hook breakage');
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1372
1389
|
// Plugin/hook lifecycle state
|
|
1373
1390
|
const settings = readSettings();
|
|
1374
1391
|
const hasHooks = hasMemHooksConfigured(settings);
|
|
@@ -0,0 +1,105 @@
|
|
|
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, renameSync } from 'node:fs';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
export const NATIVE_BINDING_HINT_COOLDOWN_MS = 6 * 60 * 60 * 1000; // 6h
|
|
26
|
+
const MARKER_NAME = 'native-binding-hint-last';
|
|
27
|
+
|
|
28
|
+
// Resolvable invocation of the bundled CLI's repair path. Absolute via
|
|
29
|
+
// import.meta.url (cli.mjs is one dir up from lib/) so it works on a plugin-only
|
|
30
|
+
// install, where bare `claude-mem-lite` is not on PATH. cli.mjs routes `repair`
|
|
31
|
+
// → install.mjs. (review #3)
|
|
32
|
+
const CLI_REPAIR = `node ${fileURLToPath(new URL('../cli.mjs', import.meta.url))} repair`;
|
|
33
|
+
|
|
34
|
+
// Stable-ish identity of a fault so DISTINCT failures get DISTINCT cooldown
|
|
35
|
+
// windows: the same fault → same key (suppressed within the window), a different
|
|
36
|
+
// fault → different key → surfaces even within the window. djb2 over the message
|
|
37
|
+
// keeps it dependency-free (no node:crypto). (review #8/#15)
|
|
38
|
+
function errKey(message = '') {
|
|
39
|
+
const m = String(message);
|
|
40
|
+
let h = 5381;
|
|
41
|
+
for (let i = 0; i < m.length; i++) h = ((h * 33) ^ m.charCodeAt(i)) >>> 0;
|
|
42
|
+
return h.toString(36);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* True at most once per cooldown window FOR A GIVEN `key`. Persists
|
|
47
|
+
* "<epochMs>\t<key>" as file CONTENT (not mtime) under runtimeDir so callers can
|
|
48
|
+
* inject `now` deterministically in tests. A different `key` (a distinct fault)
|
|
49
|
+
* resets the window, so a new problem is never silenced by an earlier, unrelated
|
|
50
|
+
* one. Best-effort: any fs error returns true — showing the hint beats silently
|
|
51
|
+
* swallowing a real problem.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} runtimeDir Directory for the marker file
|
|
54
|
+
* @param {number} [now] Current epoch ms (injectable)
|
|
55
|
+
* @param {number} [cooldownMs] Suppression window
|
|
56
|
+
* @param {string} [key] Fault identity; same key within the window → suppressed
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
export function nativeBindingHintDue(runtimeDir, now = Date.now(), cooldownMs = NATIVE_BINDING_HINT_COOLDOWN_MS, key = '') {
|
|
60
|
+
const marker = join(runtimeDir, MARKER_NAME);
|
|
61
|
+
try {
|
|
62
|
+
const raw = readFileSync(marker, 'utf8');
|
|
63
|
+
// Format "<epochMs>\t<key>"; a legacy bare "<epochMs>" parses with key ''.
|
|
64
|
+
const tab = raw.indexOf('\t');
|
|
65
|
+
const last = Number(tab === -1 ? raw : raw.slice(0, tab));
|
|
66
|
+
const lastKey = tab === -1 ? '' : raw.slice(tab + 1);
|
|
67
|
+
if (Number.isFinite(last) && lastKey === key && now - last < cooldownMs) return false;
|
|
68
|
+
} catch { /* no/invalid marker → due */ }
|
|
69
|
+
try {
|
|
70
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
71
|
+
// Atomic write (tmp + rename) so a concurrent reader sees the old or the new
|
|
72
|
+
// COMPLETE value, never a torn timestamp that parses NaN → spurious "due" →
|
|
73
|
+
// duplicate hint. The residual read-then-decide race can still emit twice, but
|
|
74
|
+
// the hint is cosmetic and 6h-rate-limited, so that is acceptable. (#7/#10)
|
|
75
|
+
const tmp = `${marker}.tmp-${process.pid}`;
|
|
76
|
+
writeFileSync(tmp, `${now}\t${key}`);
|
|
77
|
+
renameSync(tmp, marker);
|
|
78
|
+
} catch { /* best-effort */ }
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Single stderr line hook.mjs should log for a caught dispatch error, or null
|
|
84
|
+
* to stay silent (ERR_DLOPEN_FAILED still within cooldown). ERR_DLOPEN_FAILED →
|
|
85
|
+
* short rate-limited rebuild hint; everything else → the existing ungated
|
|
86
|
+
* structured ERROR line. Pass runtimeDir to enable rate-limiting (omit it to
|
|
87
|
+
* always format, e.g. in tests).
|
|
88
|
+
*
|
|
89
|
+
* @param {Error & {code?: string}} err
|
|
90
|
+
* @param {string} event Hook event name (stop / session-start / …)
|
|
91
|
+
* @param {{now?: number, runtimeDir?: string}} [opts]
|
|
92
|
+
* @returns {string | null}
|
|
93
|
+
*/
|
|
94
|
+
export function formatHookError(err, event, { now = Date.now(), runtimeDir } = {}) {
|
|
95
|
+
const ts = new Date(now).toISOString();
|
|
96
|
+
if (err && err.code === 'ERR_DLOPEN_FAILED') {
|
|
97
|
+
// Key the cooldown on the fault identity so a DISTINCT native failure within
|
|
98
|
+
// the window still surfaces (a second ABI mismatch after a partial rebuild, a
|
|
99
|
+
// corrupt .node) instead of being silenced by a prior, different DLOPEN. (#8/#15)
|
|
100
|
+
if (runtimeDir && !nativeBindingHintDue(runtimeDir, now, NATIVE_BINDING_HINT_COOLDOWN_MS, errKey(err.message))) return null;
|
|
101
|
+
return `[claude-mem-lite] [${ts}] [WARN] ${event}: native DB binding can't load ` +
|
|
102
|
+
`(likely a Node version change) — auto-heals on next MCP server start, or run: ${CLI_REPAIR}`;
|
|
103
|
+
}
|
|
104
|
+
return `[claude-mem-lite] [${ts}] [ERROR] ${event}: ${err && err.message}`;
|
|
105
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.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",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"cli.mjs",
|
|
28
|
+
"cli-path.mjs",
|
|
28
29
|
"mem-cli.mjs",
|
|
29
30
|
"server.mjs",
|
|
30
31
|
"server-internals.mjs",
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
"lib/deferred-work.mjs",
|
|
80
81
|
"lib/upgrade-banner.mjs",
|
|
81
82
|
"lib/scrub-record.mjs",
|
|
83
|
+
"lib/native-binding-hint.mjs",
|
|
82
84
|
"lib/import-jsonl.mjs",
|
|
83
85
|
"cli/common.mjs",
|
|
84
86
|
"cli/fts-check.mjs",
|
|
@@ -9,16 +9,22 @@
|
|
|
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
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
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
|
|
19
25
|
// install.
|
|
20
26
|
|
|
21
|
-
import { existsSync, mkdirSync, writeFileSync, statSync } from 'node:fs';
|
|
27
|
+
import { existsSync, mkdirSync, writeFileSync, statSync, unlinkSync, readFileSync } from 'node:fs';
|
|
22
28
|
import { spawnSync } from 'node:child_process';
|
|
23
29
|
import { dirname, join } from 'node:path';
|
|
24
30
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -31,8 +37,18 @@ const RUNTIME_DIR = process.env.CLAUDE_MEM_DIR
|
|
|
31
37
|
: join(homedir(), '.claude-mem-lite', 'runtime');
|
|
32
38
|
const HEAL_MARKER = join(RUNTIME_DIR, 'hook-launcher-lastheal');
|
|
33
39
|
const HEAL_COOLDOWN_MS = 6 * 60 * 60 * 1000;
|
|
40
|
+
// Observable breakage state: written when the launcher degrades a broken install
|
|
41
|
+
// to exit 0, cleared once the install is confirmed healthy. `doctor` reads it so
|
|
42
|
+
// the intentional silence (no stack trace per fire) stays detectable. (#4/#8)
|
|
43
|
+
const BROKEN_MARKER = join(RUNTIME_DIR, 'hook-launcher-broken');
|
|
34
44
|
|
|
35
|
-
//
|
|
45
|
+
// Resolvable invocation of the bundled CLI's repair path. Absolute via
|
|
46
|
+
// INSTALL_DIR (import.meta.url) so it works on a plugin-only install, where
|
|
47
|
+
// bare `claude-mem-lite` is not on PATH and ~/.claude-mem-lite/ holds no source.
|
|
48
|
+
// cli.mjs routes `repair` → install.mjs. (review #3)
|
|
49
|
+
const CLI_REPAIR = `node ${join(INSTALL_DIR, 'cli.mjs')} repair`;
|
|
50
|
+
|
|
51
|
+
// Last-resort recovery string for users whose `cli.mjs repair` path
|
|
36
52
|
// itself failed (install.mjs missing / repair errored / retry still drifting).
|
|
37
53
|
// Duplicated in install.mjs::repair() catch; both are reachable when local
|
|
38
54
|
// scripts are broken, so neither can import a shared constant.
|
|
@@ -59,10 +75,71 @@ async function runEntry({ bustCache = false } = {}) {
|
|
|
59
75
|
await import(url);
|
|
60
76
|
}
|
|
61
77
|
|
|
78
|
+
// Two ERR_MODULE_NOT_FOUND shapes reach here (both verified against Node 22):
|
|
79
|
+
// • missing relative module → e.url = file://<missing-path> (under install)
|
|
80
|
+
// • missing bare dependency (e.g. a half-installed better-sqlite3) → e.url is
|
|
81
|
+
// UNDEFINED and the message is `Cannot find package '<name>' imported from
|
|
82
|
+
// <importer>`. This is the shape that bricked the hooks: the old
|
|
83
|
+
// file://INSTALL_DIR prefix test never matched it in a dev-dir install, so
|
|
84
|
+
// a missing dependency was misread as a foreign error and re-thrown as a
|
|
85
|
+
// Node stack trace on every hook fire.
|
|
86
|
+
// Anchor on any path the error exposes — the missing URL and/or the importer
|
|
87
|
+
// (present in both messages as "imported from <path>"). If it sits inside our
|
|
88
|
+
// install, a self-heal could fix it.
|
|
89
|
+
// package.json dependency set of THIS install — read lazily, best-effort. Lets
|
|
90
|
+
// isLocalModuleErr tell a genuinely-ours missing bare dependency (better-sqlite3,
|
|
91
|
+
// zod, …) apart from a foreign/mistyped package name that merely happens to be
|
|
92
|
+
// imported from an install-dir file. The former is self-healable; the latter is
|
|
93
|
+
// a real packaging bug that must surface a Node stack trace rather than be
|
|
94
|
+
// swallowed by an exit-0 self-heal. (review #5/#7)
|
|
95
|
+
function ownDependencies() {
|
|
96
|
+
try {
|
|
97
|
+
const pkg = JSON.parse(readFileSync(join(INSTALL_DIR, 'package.json'), 'utf8'));
|
|
98
|
+
return new Set([
|
|
99
|
+
...Object.keys(pkg.dependencies || {}),
|
|
100
|
+
...Object.keys(pkg.optionalDependencies || {}),
|
|
101
|
+
]);
|
|
102
|
+
} catch {
|
|
103
|
+
return null; // unreadable package.json → caller stays permissive
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
62
107
|
function isLocalModuleErr(e) {
|
|
63
108
|
if (!e || e.code !== 'ERR_MODULE_NOT_FOUND') return false;
|
|
64
|
-
|
|
65
|
-
|
|
109
|
+
// Missing RELATIVE module: e.url is the missing file's URL. Ours iff it sits
|
|
110
|
+
// under our install dir (the `.claude-mem-lite` substring also covers the
|
|
111
|
+
// symlink-farm dev/direct-install case where INSTALL_DIR is the realpath).
|
|
112
|
+
if (e.url) {
|
|
113
|
+
const p = String(e.url).replace(/^file:\/\//, '');
|
|
114
|
+
return p.startsWith(INSTALL_DIR) || p.includes('.claude-mem-lite');
|
|
115
|
+
}
|
|
116
|
+
// Missing BARE dependency: e.url is UNDEFINED; message is
|
|
117
|
+
// `Cannot find package '<name>' imported from <importer>`. The (.+) capture
|
|
118
|
+
// needs no `m` flag (`.` stops at a newline) and so tolerates a multi-line
|
|
119
|
+
// message that appends a hint line after the importer path — the old
|
|
120
|
+
// `(.+?)\s*$` returned undefined there and misclassified the dep. (review #11/#15)
|
|
121
|
+
const msg = String(e.message || '');
|
|
122
|
+
const importer = /imported from (.+)/.exec(msg)?.[1]?.trim();
|
|
123
|
+
if (!importer) return false;
|
|
124
|
+
const importerPath = importer.replace(/^file:\/\//, '');
|
|
125
|
+
if (!(importerPath.startsWith(INSTALL_DIR) || importerPath.includes('.claude-mem-lite'))) return false;
|
|
126
|
+
// Importer is ours — but only self-heal if the missing package is one we
|
|
127
|
+
// actually declare. A foreign/typo'd name re-throws so the bug is visible.
|
|
128
|
+
const pkgName = /Cannot find package '([^']+)'/.exec(msg)?.[1];
|
|
129
|
+
const deps = ownDependencies();
|
|
130
|
+
if (!deps || !pkgName) return true; // best-effort: can't verify → stay permissive
|
|
131
|
+
// Normalize sub-path / scoped imports to the package root (better-sqlite3/x → better-sqlite3).
|
|
132
|
+
const root = pkgName.startsWith('@') ? pkgName.split('/').slice(0, 2).join('/') : pkgName.split('/')[0];
|
|
133
|
+
return deps.has(root);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Human-readable label for the "Detected broken install (<reason>)" line:
|
|
137
|
+
// prefer the missing dependency/module name over a raw path fragment.
|
|
138
|
+
function describeFailure(e) {
|
|
139
|
+
const pkg = /Cannot find package '([^']+)'/.exec(String(e.message || ''))?.[1];
|
|
140
|
+
if (pkg) return pkg;
|
|
141
|
+
if (e.url) return String(e.url).split('/').pop();
|
|
142
|
+
return String(e.message || 'unknown').split('/').slice(-2).join('/');
|
|
66
143
|
}
|
|
67
144
|
|
|
68
145
|
function recentHealAttempt() {
|
|
@@ -78,11 +155,29 @@ function recordHealAttempt() {
|
|
|
78
155
|
} catch { /* best-effort */ }
|
|
79
156
|
}
|
|
80
157
|
|
|
158
|
+
// Drop the 6h cooldown once a heal fully resolves. The marker is written BEFORE
|
|
159
|
+
// spawn (rate-limits concurrent fires), but a SUCCESSFUL heal must not keep
|
|
160
|
+
// blocking an unrelated later breakage that happens within the window. (#6/#9)
|
|
161
|
+
function clearHealMarker() {
|
|
162
|
+
try { unlinkSync(HEAL_MARKER); } catch { /* already gone — fine */ }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function recordBreakage(reason) {
|
|
166
|
+
try {
|
|
167
|
+
mkdirSync(RUNTIME_DIR, { recursive: true });
|
|
168
|
+
writeFileSync(BROKEN_MARKER, JSON.stringify({ reason, ts: Date.now() }));
|
|
169
|
+
} catch { /* best-effort */ }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function clearBreakage() {
|
|
173
|
+
try { if (existsSync(BROKEN_MARKER)) unlinkSync(BROKEN_MARKER); } catch { /* best-effort */ }
|
|
174
|
+
}
|
|
175
|
+
|
|
81
176
|
async function attemptHeal(reason) {
|
|
82
177
|
if (recentHealAttempt()) {
|
|
83
178
|
process.stderr.write(
|
|
84
179
|
`[claude-mem-lite] Self-heal skipped (last attempt < 6h ago).\n` +
|
|
85
|
-
`[claude-mem-lite] Manual recovery:
|
|
180
|
+
`[claude-mem-lite] Manual recovery: ${CLI_REPAIR}\n` +
|
|
86
181
|
`[claude-mem-lite] If that fails, run: ${TARBALL_FALLBACK}\n`,
|
|
87
182
|
);
|
|
88
183
|
return false;
|
|
@@ -131,18 +226,34 @@ if (rest.includes('session-start')) {
|
|
|
131
226
|
|
|
132
227
|
try {
|
|
133
228
|
await runEntry();
|
|
229
|
+
// A clean session-start fire confirms the install is healthy → clear any stale
|
|
230
|
+
// breakage marker. Gated to session-start so the per-tool hot path pays nothing.
|
|
231
|
+
if (rest.includes('session-start')) clearBreakage();
|
|
134
232
|
} catch (e) {
|
|
135
233
|
if (!isLocalModuleErr(e)) throw e;
|
|
136
|
-
const reason =
|
|
234
|
+
const reason = describeFailure(e);
|
|
137
235
|
const healed = await attemptHeal(reason);
|
|
138
|
-
if (!healed)
|
|
236
|
+
if (!healed) {
|
|
237
|
+
// Broken/missing dependency we can't repair right now (repair failed, or
|
|
238
|
+
// was skipped within the 6h cooldown). attemptHeal already wrote actionable
|
|
239
|
+
// guidance — degrade quietly instead of re-throwing the original import
|
|
240
|
+
// error, which would spew a Node stack trace on every hook fire. Record the
|
|
241
|
+
// breakage so the exit-0 silence stays observable to `doctor`. (#4/#8)
|
|
242
|
+
recordBreakage(reason);
|
|
243
|
+
process.exit(0);
|
|
244
|
+
}
|
|
139
245
|
try {
|
|
140
246
|
await runEntry({ bustCache: true });
|
|
247
|
+
// Fully healed: drop the cooldown so an UNRELATED later break can heal
|
|
248
|
+
// immediately (#6/#9), and clear the breakage marker.
|
|
249
|
+
clearHealMarker();
|
|
250
|
+
clearBreakage();
|
|
141
251
|
} catch (retryErr) {
|
|
252
|
+
recordBreakage(`retry-failed: ${retryErr.message}`);
|
|
142
253
|
process.stderr.write(
|
|
143
254
|
`[claude-mem-lite] Hook still failing after self-heal: ${retryErr.message}\n` +
|
|
144
255
|
`[claude-mem-lite] Manual recovery: ${TARBALL_FALLBACK}\n`,
|
|
145
256
|
);
|
|
146
|
-
process.exit(
|
|
257
|
+
process.exit(0);
|
|
147
258
|
}
|
|
148
259
|
}
|
package/server-internals.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { debugCatch, COMPRESSED_AUTO, COMPRESSED_PENDING_PURGE, OBS_BM25 } from './utils.mjs';
|
|
5
5
|
import { BASE_STOP_WORDS } from './stop-words.mjs';
|
|
6
6
|
import { porterStem } from './tfidf.mjs';
|
|
7
|
+
import { CLI_INVOKE } from './cli-path.mjs';
|
|
7
8
|
|
|
8
9
|
// ─── MCP Server Instructions Builder ───────────────────────────────────────
|
|
9
10
|
// Phase A (v2.31.3+): when quiet=true, drops WHEN-TO-USE proactive-trigger and
|
|
@@ -14,15 +15,15 @@ import { porterStem } from './tfidf.mjs';
|
|
|
14
15
|
const INSTRUCTIONS_BASE = [
|
|
15
16
|
'Long-term memory across sessions. Hooks auto-inject context; CLI preferred for explicit queries.',
|
|
16
17
|
'',
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
`CLI (via Bash) — invoke as \`${CLI_INVOKE} <cmd>\` (resolves on any install shape; the bare \`claude-mem-lite\` shorthand works only after an optional global \`npm i -g claude-mem-lite\`):`,
|
|
19
|
+
` ${CLI_INVOKE} search "query" — FTS5 full-text search`,
|
|
20
|
+
` ${CLI_INVOKE} search "err" --type bugfix — filter by type`,
|
|
21
|
+
` ${CLI_INVOKE} recall "file.mjs" — file-related memories`,
|
|
22
|
+
` ${CLI_INVOKE} recent 5 — latest observations`,
|
|
23
|
+
` ${CLI_INVOKE} get 42,43 — full details by ID`,
|
|
24
|
+
` ${CLI_INVOKE} timeline --anchor 42 — chronological context`,
|
|
24
25
|
'',
|
|
25
|
-
'MCP tools: mem_search, mem_recent, mem_save, mem_get, mem_recall, mem_timeline for programmatic access.',
|
|
26
|
+
'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
27
|
'mem_save: Save non-obvious insights (bugfix lessons, architecture decisions).',
|
|
27
28
|
'Search tips: short keywords (2-3 words), filter with obs_type when relevant.',
|
|
28
29
|
];
|
package/source-files.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
export const SOURCE_FILES = [
|
|
8
8
|
// Entry points and top-level modules
|
|
9
|
-
'cli.mjs', 'server.mjs', 'server-internals.mjs', 'search-engine.mjs', 'tool-schemas.mjs',
|
|
9
|
+
'cli.mjs', 'cli-path.mjs', 'server.mjs', 'server-internals.mjs', 'search-engine.mjs', 'tool-schemas.mjs',
|
|
10
10
|
'hook.mjs', 'hook-shared.mjs', 'hook-llm.mjs', 'hook-memory.mjs', 'skip-tools.mjs',
|
|
11
11
|
'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs',
|
|
12
12
|
'hook-update.mjs', 'hook-optimize.mjs', 'hook-precompact.mjs',
|
|
@@ -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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Single source of truth — used by server.mjs (runtime) and contract.test.mjs (validation tests)
|
|
3
3
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
+
import { CLI_INVOKE } from './cli-path.mjs';
|
|
5
6
|
|
|
6
7
|
export const OBS_TYPE_ENUM = z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']);
|
|
7
8
|
|
|
@@ -349,7 +350,7 @@ export const tools = [
|
|
|
349
350
|
' - Looking for prior art on a module/feature before refactoring\n' +
|
|
350
351
|
' - User asks "have we seen this before" or references something not in visible context\n' +
|
|
351
352
|
'\n' +
|
|
352
|
-
'Equivalent CLI:
|
|
353
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' search "<query>" [--type bugfix]',
|
|
353
354
|
inputSchema: memSearchSchema,
|
|
354
355
|
},
|
|
355
356
|
{
|
|
@@ -367,7 +368,7 @@ export const tools = [
|
|
|
367
368
|
' - User asks "what did we do yesterday / last" with no topic keyword\n' +
|
|
368
369
|
' - Verifying that a just-made change was captured as an observation\n' +
|
|
369
370
|
'\n' +
|
|
370
|
-
'Equivalent CLI:
|
|
371
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' recent [N]',
|
|
371
372
|
inputSchema: memRecentSchema,
|
|
372
373
|
},
|
|
373
374
|
{
|
|
@@ -387,7 +388,7 @@ export const tools = [
|
|
|
387
388
|
' - A search hit is interesting and you want its chronological neighbours\n' +
|
|
388
389
|
' - Replaying a session narrative around a known observation ID\n' +
|
|
389
390
|
'\n' +
|
|
390
|
-
'Equivalent CLI:
|
|
391
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' timeline --anchor <ID> [--before N --after N]',
|
|
391
392
|
inputSchema: memTimelineSchema,
|
|
392
393
|
},
|
|
393
394
|
{
|
|
@@ -407,7 +408,7 @@ export const tools = [
|
|
|
407
408
|
'\n' +
|
|
408
409
|
'On miss, response includes "Try: …" hint listing other sources the ID lives in.\n' +
|
|
409
410
|
'\n' +
|
|
410
|
-
'Equivalent CLI:
|
|
411
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' get <id>[,<id>,...] — accepts P#/S#/# prefix.',
|
|
411
412
|
inputSchema: memGetSchema,
|
|
412
413
|
},
|
|
413
414
|
{
|
|
@@ -425,7 +426,7 @@ export const tools = [
|
|
|
425
426
|
' - Cleaning up an observation saved from a test run or incorrect save\n' +
|
|
426
427
|
' - Always run once with confirm=false, then again with confirm=true\n' +
|
|
427
428
|
'\n' +
|
|
428
|
-
'Equivalent CLI:
|
|
429
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' delete <id>[,<id>,...] [--confirm]',
|
|
429
430
|
inputSchema: memDeleteSchema,
|
|
430
431
|
hidden: true,
|
|
431
432
|
},
|
|
@@ -444,7 +445,7 @@ export const tools = [
|
|
|
444
445
|
' - After a non-obvious architecture/tradeoff decision — set type="decision", lesson_learned="<constraint + why>"\n' +
|
|
445
446
|
' - User explicitly asks "remember this" or "save a note that ..."\n' +
|
|
446
447
|
'\n' +
|
|
447
|
-
'Equivalent CLI:
|
|
448
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' save --type bugfix --lesson "..." "<content>"',
|
|
448
449
|
inputSchema: memSaveSchema,
|
|
449
450
|
},
|
|
450
451
|
{
|
|
@@ -462,7 +463,7 @@ export const tools = [
|
|
|
462
463
|
' - Diagnosing why search feels sparse or noisy at a macro level\n' +
|
|
463
464
|
' - Auditing a project before major compression/maintenance\n' +
|
|
464
465
|
'\n' +
|
|
465
|
-
'Equivalent CLI:
|
|
466
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' stats [--project X] [--days 30]',
|
|
466
467
|
inputSchema: memStatsSchema,
|
|
467
468
|
hidden: true,
|
|
468
469
|
},
|
|
@@ -481,7 +482,7 @@ export const tools = [
|
|
|
481
482
|
' - After a major project phase completes and old per-file observations are noise\n' +
|
|
482
483
|
' - Stats show thousands of low-importance rows dragging search quality\n' +
|
|
483
484
|
'\n' +
|
|
484
|
-
'Equivalent CLI:
|
|
485
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' compress [--execute] [--age-days 90] (preview is default)',
|
|
485
486
|
inputSchema: memCompressSchema,
|
|
486
487
|
hidden: true,
|
|
487
488
|
},
|
|
@@ -500,7 +501,7 @@ export const tools = [
|
|
|
500
501
|
' - After bulk imports or a long offline period\n' +
|
|
501
502
|
' - User asks for periodic maintenance / cleanup\n' +
|
|
502
503
|
'\n' +
|
|
503
|
-
'Equivalent CLI:
|
|
504
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' maintain scan --ops dedup,decay',
|
|
504
505
|
inputSchema: memMaintainSchema,
|
|
505
506
|
hidden: true,
|
|
506
507
|
},
|
|
@@ -519,7 +520,7 @@ export const tools = [
|
|
|
519
520
|
' - stats show many degraded (title-only, no lesson) observations\n' +
|
|
520
521
|
' - Start with action="preview" to see candidates before spending tokens\n' +
|
|
521
522
|
'\n' +
|
|
522
|
-
'Equivalent CLI:
|
|
523
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' optimize [--run|--run-all] [--task re-enrich,normalize,cluster-merge,smart-compress] [--max N] (preview is default)',
|
|
523
524
|
inputSchema: memOptimizeSchema,
|
|
524
525
|
hidden: true,
|
|
525
526
|
},
|
|
@@ -538,7 +539,7 @@ export const tools = [
|
|
|
538
539
|
' - Looking for a tool by capability → action="search" with keywords\n' +
|
|
539
540
|
' - User explicitly asks to import a GitHub repo → action="import_url"\n' +
|
|
540
541
|
'\n' +
|
|
541
|
-
'Equivalent CLI:
|
|
542
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' registry <list|search|import|...> [args]',
|
|
542
543
|
inputSchema: memRegistrySchema,
|
|
543
544
|
hidden: true,
|
|
544
545
|
},
|
|
@@ -576,7 +577,7 @@ export const tools = [
|
|
|
576
577
|
' - You later discover additional context worth appending to lesson_learned\n' +
|
|
577
578
|
' - Reclassifying an observation after its true type becomes clear\n' +
|
|
578
579
|
'\n' +
|
|
579
|
-
'Equivalent CLI:
|
|
580
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' update <id> [--title ...] [--lesson ...]',
|
|
580
581
|
inputSchema: memUpdateSchema,
|
|
581
582
|
hidden: true,
|
|
582
583
|
},
|
|
@@ -595,7 +596,7 @@ export const tools = [
|
|
|
595
596
|
' - Moving observations between machines or projects\n' +
|
|
596
597
|
' - User asks for a JSON snapshot of a project\'s memories\n' +
|
|
597
598
|
'\n' +
|
|
598
|
-
'Equivalent CLI:
|
|
599
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' export [--format jsonl] [--project X] [--limit 500]',
|
|
599
600
|
inputSchema: memExportSchema,
|
|
600
601
|
hidden: true,
|
|
601
602
|
},
|
|
@@ -614,7 +615,7 @@ export const tools = [
|
|
|
614
615
|
' - User asks "what do we know about <file>"\n' +
|
|
615
616
|
' - Investigating a recurring issue in a file you have not touched recently\n' +
|
|
616
617
|
'\n' +
|
|
617
|
-
'Equivalent CLI:
|
|
618
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' recall "<file>" [--limit 10]',
|
|
618
619
|
inputSchema: memRecallSchema,
|
|
619
620
|
},
|
|
620
621
|
{
|
|
@@ -632,7 +633,7 @@ export const tools = [
|
|
|
632
633
|
' - After a crash, power loss, or manual DB edit\n' +
|
|
633
634
|
' - doctor / stats flags FTS integrity problems\n' +
|
|
634
635
|
'\n' +
|
|
635
|
-
'Equivalent CLI:
|
|
636
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' fts-check [--rebuild]',
|
|
636
637
|
inputSchema: memFtsCheckSchema,
|
|
637
638
|
hidden: true,
|
|
638
639
|
},
|
|
@@ -651,7 +652,7 @@ export const tools = [
|
|
|
651
652
|
' - Triaging what to compress or clean up before running maintenance\n' +
|
|
652
653
|
' - Scanning for interesting anchors to follow up with mem_timeline\n' +
|
|
653
654
|
'\n' +
|
|
654
|
-
'Equivalent CLI:
|
|
655
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' browse [--tier active] [--project X]',
|
|
655
656
|
inputSchema: memBrowseSchema,
|
|
656
657
|
hidden: true,
|
|
657
658
|
},
|
|
@@ -670,7 +671,7 @@ export const tools = [
|
|
|
670
671
|
' - Wrap-up phase enumerates follow-up items for the next session\n' +
|
|
671
672
|
' - Bug surfaces but root cause is out of this session\'s scope\n' +
|
|
672
673
|
'\n' +
|
|
673
|
-
'Equivalent CLI:
|
|
674
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' defer add "<title>" [--priority 1|2|3] [--detail "..."] [--files a.mjs,b.mjs]',
|
|
674
675
|
inputSchema: memDeferSchema,
|
|
675
676
|
},
|
|
676
677
|
{
|
|
@@ -687,7 +688,7 @@ export const tools = [
|
|
|
687
688
|
' - About to refer to "item N" and need to confirm what N points to\n' +
|
|
688
689
|
' - Auditing carry-forward state across multiple sessions\n' +
|
|
689
690
|
'\n' +
|
|
690
|
-
'Equivalent CLI:
|
|
691
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' defer list [--project X] [--limit 10]',
|
|
691
692
|
inputSchema: memDeferListSchema,
|
|
692
693
|
},
|
|
693
694
|
{
|
|
@@ -705,7 +706,7 @@ export const tools = [
|
|
|
705
706
|
' - Scope changed and the work is no longer needed\n' +
|
|
706
707
|
' - User explicitly says "drop the deferred X, never mind"\n' +
|
|
707
708
|
'\n' +
|
|
708
|
-
'Equivalent CLI:
|
|
709
|
+
'Equivalent CLI: ' + CLI_INVOKE + ' defer drop <D#N|ordinal> --reason "..."',
|
|
709
710
|
inputSchema: memDeferDropSchema,
|
|
710
711
|
},
|
|
711
712
|
];
|