claude-mem-lite 5.0.0 → 5.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": "5.0.0",
13
+ "version": "5.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": "5.0.0",
3
+ "version": "5.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
@@ -75,7 +75,7 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
75
75
 
76
76
  ## Features
77
77
 
78
- - **Automatic capture** -- Hooks into Claude Code lifecycle (PostToolUse, SessionStart, Stop, UserPromptSubmit) to record observations without manual effort
78
+ - **Automatic capture** -- Hooks into the Claude Code lifecycle (SessionStart, PreCompact, PreToolUse, PostToolUse, PostToolUseFailure, Stop, UserPromptSubmit — the seven events in `hooks/hooks.json`) to record observations without manual effort
79
79
  - **Hybrid search** -- FTS5 BM25 + TF-IDF vector cosine similarity, merged via Reciprocal Rank Fusion (RRF). FTS5 handles keyword matching; 512-dim TF-IDF vectors capture semantic similarity for recall beyond exact terms
80
80
  - **Timeline browsing** -- Navigate observations chronologically with anchor-based context windows
81
81
  - **Episode batching** -- Groups related file operations into coherent episodes before LLM encoding
@@ -105,14 +105,12 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
105
105
  - **Atomic writes** -- All file writes (episodes, CLAUDE.md) use write-to-tmp + rename to prevent corruption on crash
106
106
  - **Robust locking** -- PID-aware lock files with automatic stale/orphan cleanup (>30s timeout or dead PID)
107
107
  - **Stale session cleanup** -- Sessions active for >24h are automatically marked as abandoned on next start
108
- - **Unified resource discovery** -- Shared filesystem traversal layer (`resource-discovery.mjs`) used by both runtime scanner and offline indexer, supporting flat directories, plugin nesting, and loose `.md` files
109
- - **Domain synonym expansion** -- Registry search queries expand to domain synonyms (e.g., "fix" → debug, bugfix, troubleshoot, diagnose, repair)
108
+ - **Domain synonym expansion** -- Search queries expand to domain synonyms (e.g., "fix" debug, bugfix, troubleshoot, diagnose, repair)
110
109
  - **Multi-provider LLM mode** -- Provider priority `ANTHROPIC_API_KEY` (direct Anthropic API) → `OPENROUTER_API_KEY` (OpenRouter, OpenAI-compatible — point it at any model via `OPENROUTER_MODEL`) → `claude -p` CLI fallback when no key is set
111
110
  - **Lesson-learned indexing** -- `lesson_learned` field indexed in FTS5 with weight 8, making past debugging insights directly searchable
112
111
  - **Cross-source normalization** -- `mem_search` normalizes scores across observations, sessions, and prompts before merging, preventing any source from dominating results
113
112
  - **Exponential recency decay** -- Type-differentiated half-lives (decisions: 90d, discoveries: 60d, bugfixes: 14d, changes: 7d) consistently applied in all ranking paths
114
113
  - **Prompt-time memory injection** -- UserPromptSubmit hook automatically searches and injects relevant past observations with recency and importance weighting
115
- - **Smart skill invocation** -- Auto-loaded and searched managed skills/agents include portable `~` paths with `Read()` guidance; native plugin skills recommend `Skill("full:name")`; prevents `Skill()` misuse for managed resources that aren't registered with Claude Code's native handler
116
114
  - **Dual injection dedup** -- `user-prompt-search.js` and `handleUserPrompt` coordinate via temp file to prevent duplicate memory injection
117
115
  - **Plugin cache hook self-heal** -- Claude Code runtime reads plugin hooks from `~/.claude/plugins/cache/<mp>/<plugin>/<ver>/hooks/hooks.json`, not from the marketplace source. When `install.mjs`-managed `settings.json` hooks coexist with a stale cache `hooks.json` (e.g. from a previous marketplace install or a plugin auto-update), the runtime registers hooks twice → every session start / user prompt fires twice. `install.mjs` and `hook-update.mjs` now clear cache `hooks.json` in every version dir, and `hook.mjs session-start` self-heals on every session (gated by `hasInstallManagedHooks` so plugin-only users are not affected). `install.mjs status` reports cache pollution state (since v2.31.1/2.31.2).
118
116
  - **Result-dedup cooldown** -- User-prompt memory injection uses result-overlap detection (>80% ID overlap → skip) instead of time-based cooldown, allowing topic switches within seconds while preventing redundant injections
@@ -188,8 +186,8 @@ Source files stay in the cloned repo. Update via `git pull && node install.mjs i
188
186
  ### What happens during installation
189
187
 
190
188
  1. **Install dependencies** -- `npm install --omit=dev` (compiles native `better-sqlite3`)
191
- 2. **Register MCP server** -- `mem-lite` server with 20 tools (9 core exposed via `tools/list` + 11 hidden-but-callable; see the Usage section for the full table). The pre-v2.78 generic server name `mem` is renamed to `mem-lite` for namespace hygiene; the tool names themselves (`mem_search`, `mem_recall`, ...) are unchanged.
192
- 3. **Configure hooks** -- `PostToolUse`, `SessionStart`, `Stop`, `UserPromptSubmit` lifecycle hooks
189
+ 2. **Register MCP server** -- `mem-lite` server with 18 tools (9 core exposed via `tools/list` + 9 hidden-but-callable; see the Usage section for the full table). The pre-v2.78 generic server name `mem` is renamed to `mem-lite` for namespace hygiene; the tool names themselves (`mem_search`, `mem_recall`, ...) are unchanged.
190
+ 3. **Configure hooks** -- all seven lifecycle events: `SessionStart`, `PreCompact`, `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `Stop`, `UserPromptSubmit`
193
191
  4. **Create data directory** -- `~/.claude-mem-lite/` (hidden) for database, runtime, and managed resource files
194
192
  5. **Auto-migrate** -- If `~/.claude-mem/` (original claude-mem) or `~/claude-mem-lite/` (pre-v0.5 unhidden) exists, migrates database and runtime files to `~/.claude-mem-lite/`, preserving the original untouched
195
193
  6. **Initialize database** -- SQLite with WAL mode, FTS5 indexes created on first server start
@@ -228,8 +226,6 @@ rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
228
226
  ep-flush-*.json # Flushed episodes awaiting processing
229
227
  reads-<project>.txt # Read file paths (collected on flush)
230
228
  managed/
231
- skills/ # Standalone skills: {name}/SKILL.md
232
- agents/ # Agent plugins: {group}/agents/{name}.md + skills/*/SKILL.md
233
229
  repos/ # Shallow-cloned source repos
234
230
  ```
235
231
 
@@ -237,11 +233,14 @@ rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
237
233
 
238
234
  ### MCP Tools (used automatically by Claude)
239
235
 
240
- As of v2.70.0, the server registers 20 tools in total but only the 9 **core**
241
- tools appear in `tools/list`. The 11 **hidden** tools remain callable at the
236
+ As of v2.70.0, the server registers 18 tools in total but only the 9 **core**
237
+ tools appear in `tools/list`. The 9 **hidden** tools remain callable at the
242
238
  protocol layer (`tools/call` by exact name still routes normally); they're
243
- omitted from the list response so Claude Code sessions don't load 11 extra
244
- tool schemas at startup. Hidden tools are the maintenance / admin / browser
239
+ omitted from the list response so Claude Code sessions don't load 9 extra
240
+ tool schemas at startup. (It read 20 / 11 until v5.0.0 removed the two skill-registry
241
+ tools — `tool-schemas.mjs` is the source of truth, and
242
+ `tests/tool-count-docs.test.mjs` now holds this paragraph, both README tool tables,
243
+ `README.zh-CN.md`, `llms.txt` and `docs/ARCHITECTURE.md` to it.) Hidden tools are the maintenance / admin / browser
245
244
  surface — reach them through the CLI column in the second table.
246
245
 
247
246
  **Core (9, exposed to Claude Code)**
@@ -258,7 +257,7 @@ surface — reach them through the CLI column in the second table.
258
257
  | `mem_defer_list` | List open deferred items for the current project. |
259
258
  | `mem_defer_drop` | Drop a deferred item without fixing it; requires a `reason` for the audit trail. |
260
259
 
261
- **Hidden-but-callable (11, CLI-routed)**
260
+ **Hidden-but-callable (9, CLI-routed)**
262
261
 
263
262
  | Tool | CLI equivalent | Notes |
264
263
  |------|----------------|-------|
@@ -452,9 +451,6 @@ UserPromptSubmit (two parallel paths)
452
451
  -> [user-prompt-search.js] Auto-search memory via FTS5 + active file context
453
452
  -> [user-prompt-search.js] Inject relevant past observations with recency/importance weighting
454
453
  -> [user-prompt-search.js] Write injected IDs to temp file for dedup
455
- -> [user-prompt-search.js] L1 skill auto-load: match managed skill names in prompt
456
- -> Load content with portable ~ path + Read() guidance
457
- -> source="managed-skill|managed-agent", path="~/.claude-mem-lite/managed/..."
458
454
  -> [hook.mjs handleUserPrompt] Capture user prompt text to user_prompts table
459
455
  -> [hook.mjs handleUserPrompt] Increment session prompt counter
460
456
  -> [hook.mjs handleUserPrompt] Handoff: detect continuation intent → inject previous session context
@@ -547,7 +543,7 @@ Shows MCP registration, hook configuration, plugin disabled state, and database
547
543
 
548
544
  ### Recovery (stuck install / hook errors)
549
545
 
550
- If you see `ERR_MODULE_NOT_FOUND` on PreToolUse:Read/Edit/Skill hooks, or `claude-mem-lite` commands crash with import errors, you're likely hit by a partial auto-update — the updater copied new scripts but missed a sibling `lib/*` file, breaking the hook chain (and the next auto-update that would have healed it).
546
+ If you see `ERR_MODULE_NOT_FOUND` on PreToolUse:Read/Edit hooks, or `claude-mem-lite` commands crash with import errors, you're likely hit by a partial auto-update — the updater copied new scripts but missed a sibling `lib/*` file, breaking the hook chain (and the next auto-update that would have healed it).
551
547
 
552
548
  **v2.84.0+** ships a `repair` subcommand that re-syncs from the latest GitHub release:
553
549
 
@@ -636,12 +632,11 @@ claude-mem-lite/
636
632
  scripts/
637
633
  setup.sh # Setup hook: npm install + migration (hidden dir + old dir)
638
634
  post-tool-use.sh # Bash pre-filter: skips noise in ~5ms, tracks Read paths
639
- user-prompt-search.js # UserPromptSubmit hook: auto-search memory + L1 skill auto-load
640
- pre-skill-bridge.js # PreToolUse hook: L2 skill bridge for managed resources
635
+ user-prompt-search.js # UserPromptSubmit hook: auto-search memory on user prompts
641
636
  pre-tool-recall.js # PreToolUse hook: file lesson recall before Edit/Write
637
+ post-tool-recall.js # PostToolUse hook: error recall after a failed tool call
638
+ pre-agent-inject.sh # PreToolUse hook: context for spawned agents
642
639
  prompt-search-utils.mjs # Shared logic: skip patterns, intent detection, name matching
643
- convert-commands.mjs # Converts command .md → SKILL.md in managed plugins
644
- index-managed.mjs # Offline indexer for managed resources
645
640
  # Test & benchmark (dev only)
646
641
  tests/ # Unit, property, integration, contract, E2E, pipeline tests
647
642
  benchmark/ # BM25 search quality benchmarks + CI gate
@@ -765,7 +760,7 @@ claude-mem-lite.
765
760
 
766
761
  | Variable | Description | Default |
767
762
  |----------|-------------|---------|
768
- | `CLAUDE_MEM_ALL_TOOLS` | `1` exposes all 20 MCP tools in `tools/list` instead of the 9 core ones (pre-v2.34.0 behavior). The 11 hidden tools stay callable by exact name either way. | _(9 core)_ |
763
+ | `CLAUDE_MEM_ALL_TOOLS` | `1` exposes all 18 MCP tools in `tools/list` instead of the 9 core ones (pre-v2.34.0 behavior). The 9 hidden tools stay callable by exact name either way. | _(9 core)_ |
769
764
  | `CLAUDE_MEM_FILE_INTEL` | `0` disables the file-intel block injected before `Read` (past observations about the file you are about to open). | _(on)_ |
770
765
  | `CLAUDE_MEM_FILE_INTEL_MIN_TOKENS` | Files smaller than this stay silent — file-intel only pays for itself on large files. | `800` |
771
766
  | `CLAUDE_MEM_REREAD_GUARD` | `0` disables the warning when the same file is read twice in a session. Never fires on `offset`/`limit` paging. | _(on)_ |
@@ -835,7 +830,7 @@ what is already stored — only whether new work runs.
835
830
  | `CLAUDE_MEM_SKIP_MAINTAIN` | Skip the 24h auto-maintain pass (decay, purge, backup). | _(runs)_ |
836
831
  | `CLAUDE_MEM_SKIP_OPTIMIZE` | Skip the LLM optimization pass (re-enrich, normalize, cluster-merge). | _(runs)_ |
837
832
  | `CLAUDE_MEM_SKIP_AUTO_DEDUP_FUZZY` | Skip the MinHash near-duplicate pass, keeping exact dedup. | _(runs)_ |
838
- | `CLAUDE_MEM_SKIP_MARKER_GC` | Skip the runtime-marker sweep. | _(runs)_ |
833
+ | `CLAUDE_MEM_SKIP_MARKER_GC` | Skip the runtime-marker sweep. **Must be exactly `1`** — unlike the other `CLAUDE_MEM_SKIP_*` flags, which accept any truthy value, this one compares against the string `1`. That is deliberate: a truthy check makes `=0` mean "skip", which is the opposite of what anyone typing it intends. | _(runs)_ |
839
834
  | `CLAUDE_MEM_SKIP_UPDATE` | Skip the 24h auto-update check against GitHub Releases. | _(runs)_ |
840
835
  | `CLAUDE_MEM_SKIP_SIG_VERIFY` | Skip Ed25519 signature verification of a downloaded update. **Escape hatch — leaves updates unauthenticated.** | _(verifies)_ |
841
836
  | `CLAUDE_MEM_NO_LESSON_RETRY` | `1` disables the one-shot retry that re-asks for a missing `lesson_learned`. | _(retries)_ |
package/README.zh-CN.md CHANGED
@@ -54,7 +54,7 @@
54
54
 
55
55
  ## 功能特性
56
56
 
57
- - **自动捕获** -- 挂载到 Claude Code 生命周期(PostToolUse、PreToolUse、SessionStart、Stop、UserPromptSubmit),无需手动操作即可记录观察
57
+ - **自动捕获** -- 挂载到 Claude Code 生命周期(`hooks/hooks.json` 里的七个事件:SessionStartPreCompact、PreToolUse、PostToolUsePostToolUseFailure、Stop、UserPromptSubmit),无需手动操作即可记录观察
58
58
  - **FTS5 搜索** -- 基于 BM25 排名的全文搜索,覆盖观察、会话摘要和用户提示,支持重要度加权
59
59
  - **时间线浏览** -- 基于锚点的时间上下文窗口,按时间顺序浏览观察
60
60
  - **Episode 批处理** -- 将相关文件操作分组为连贯的 episode,再进行 LLM 编码
@@ -81,16 +81,13 @@
81
81
  - **原子写入** -- 所有文件写入(episode、CLAUDE.md)使用 write-to-tmp + rename 防止崩溃时损坏
82
82
  - **健壮锁机制** -- PID 感知的锁文件,自动清理过期(>30s)或孤儿(PID 已死)锁
83
83
  - **过期会话清理** -- 活跃超过 24 小时的会话在下次启动时自动标记为 abandoned
84
- - **统一资源发现** -- 共享文件系统遍历层(`resource-discovery.mjs`),运行时扫描器和离线索引器共用,支持扁平目录、插件嵌套和松散 `.md` 文件
85
- - **领域同义词扩展** -- 注册表搜索查询自动扩展领域同义词(如 "修复" → fix, debug, bugfix, repair, error)
86
- - **持久化冷却机制** -- 5 分钟跨会话冷却 + 同会话去重,避免重复推荐 skill 自动加载
84
+ - **领域同义词扩展** -- 搜索查询自动扩展领域同义词(如 "修复" → fix, debug, bugfix, repair, error)
87
85
  - **多 provider LLM 调用** -- provider 优先级 `ANTHROPIC_API_KEY`(直连 Anthropic API)→ `OPENROUTER_API_KEY`(OpenRouter,OpenAI 兼容,可用 `OPENROUTER_MODEL` 指向任意模型)→ 无 key 时回退 `claude -p` CLI
88
86
  - **Haiku 熔断器** -- 连续 3 次 LLM 失败后,禁用 Haiku 调度 5 分钟,防止级联延迟
89
87
  - **否定意图感知** -- 正确处理 "不要测试了,先修 bug" 等复杂提示,排除被否定的意图,支持中英文混合输入
90
88
  - **可配置 LLM 模型** -- 通过 `CLAUDE_MEM_MODEL` 环境变量在 Haiku(快速/低成本)和 Sonnet(深度分析)之间切换
91
89
  - **数据库自动恢复** -- 启动时检测并清理损坏的 WAL/SHM 文件;定期 WAL checkpoint 防止无限增长
92
90
  - **Schema 自动迁移** -- 每次启动运行幂等的 `ALTER TABLE` 迁移,安全地添加新列和索引,不丢失数据
93
- - **探索奖励** -- 注册表中的新资源在复合排名中获得公平机会;高推荐零采纳的"僵尸"资源被惩罚
94
91
  - **LLM 并发控制** -- 基于文件的信号量将后台 worker 限制为 2 个并发 LLM 调用,防止资源争用
95
92
  - **stdin 溢出保护** -- Hook 输入在 256KB 处截断,对超大工具输出使用正则挽救关键信息
96
93
  - **跨会话交接** -- 在 `/clear` 或 `/exit` 时捕获会话状态(请求、已完成工作、后续步骤、关键文件),下次会话检测到继续意图时自动注入上下文(支持显式关键词和 FTS5 术语重叠匹配)
@@ -109,7 +106,7 @@
109
106
 
110
107
  ## 环境要求
111
108
 
112
- - **Node.js** >= 18
109
+ - **Node.js** >= 22(v4.0.0 起:better-sqlite3 13 要求 >=22,Node 20 已于 2026-04 EOL;`package.json` 的 `engines` 是唯一事实来源)
113
110
  - **Claude Code** CLI 已安装并配置(`claude` 命令可用)
114
111
  - **SQLite3** 支持(由 `better-sqlite3` 提供,安装时编译)
115
112
  - **平台**:Linux 或 macOS(参见[平台支持](#平台支持))
@@ -148,13 +145,13 @@ node install.mjs install
148
145
  ### 安装过程
149
146
 
150
147
  1. **安装依赖** -- `npm install --omit=dev`(编译原生 `better-sqlite3`)
151
- 2. **注册 MCP 服务器** -- `mem-lite` 服务器,包含 20 个工具(9 个核心通过 `tools/list` 暴露 + 11 个隐藏但可调;完整表见 Usage 段)。v2.78 前服务器名为通用的 `mem`,现已改名为 `mem-lite` 避免与用户其它 `.mcp.json` 冲突;工具名(`mem_search`/`mem_recall` 等)保持不变。
148
+ 2. **注册 MCP 服务器** -- `mem-lite` 服务器,包含 18 个工具(9 个核心通过 `tools/list` 暴露 + 9 个隐藏但可调;完整表见 Usage 段)。v2.78 前服务器名为通用的 `mem`,现已改名为 `mem-lite` 避免与用户其它 `.mcp.json` 冲突;工具名(`mem_search`/`mem_recall` 等)保持不变。
152
149
 
153
150
  > **自动 adopt 会写进你的项目,且每次 SessionStart 都跑(v3.13+)。** 插件向**项目自己的 `<cwd>/CLAUDE.md`**(通常是会进 git 的文件)写入一个 slug 限定的**托管块**,外加 `<cwd>/.claude/plugin_claude_mem_lite.md` 详情文件。该块是一条提升 Claude 主动调用 `mem_recall` / `mem_save` 的 system-authority 指针;块以外的内容逐字保留,也能与其它插件的块共存于同一文件。这是**每次** SessionStart 都做的幂等同步,不只是第一次——块被删掉会重新写回,出货模板变了会刷新。**任何安装路径都生效**(npm、npx、`/plugin`、手动),**无需再手动跑 `/adopt`**。
154
151
  >
155
152
  > 关闭方式:项目级 `claude-mem-lite adopt --disable`(重新启用用 `--enable`);全局 `export MEM_NO_AUTO_ADOPT=1`;只冻结模板刷新用 `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`。`claude-mem-lite unadopt` 可移除托管块与详情文件。手动 `/adopt` 仍保留用于编辑后重写或 `--all` 批量场景。
156
- 3. **配置钩子** -- `PostToolUse`、`PreToolUse`、`SessionStart`、`Stop`、`UserPromptSubmit` 生命周期钩子
157
- 4. **创建数据目录** -- `~/.claude-mem-lite/`(隐藏目录),存放数据库、运行时和托管资源文件
153
+ 3. **配置钩子** -- 全部七个生命周期事件:`SessionStart`、`PreCompact`、`PreToolUse`、`PostToolUse`、`PostToolUseFailure`、`Stop`、`UserPromptSubmit`
154
+ 4. **创建数据目录** -- `~/.claude-mem-lite/`(隐藏目录),存放数据库与运行时文件
158
155
  5. **自动迁移** -- 自动检测 `~/.claude-mem/`(原版 claude-mem)或 `~/claude-mem-lite/`(v0.5 前的非隐藏目录),将数据库和运行时文件迁移到 `~/.claude-mem-lite/`,原目录保持不变
159
156
  6. **初始化数据库** -- SQLite WAL 模式,FTS5 索引在服务器首次启动时创建
160
157
 
@@ -192,8 +189,6 @@ rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
192
189
  ep-flush-*.json # 已刷新的 episode,等待处理
193
190
  reads-<project>.txt # Read 文件路径(刷新时收集)
194
191
  managed/
195
- skills/ # 独立 skill:{name}/SKILL.md
196
- agents/ # Agent 插件:{group}/agents/{name}.md + skills/*/SKILL.md
197
192
  repos/ # 浅克隆的源代码仓库
198
193
  ```
199
194
 
@@ -201,12 +196,16 @@ rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
201
196
 
202
197
  ### MCP 工具
203
198
 
204
- v2.34.0 起服务端注册 17 个工具,但 `tools/list` 只暴露 6 **核心** 工具;其余
205
- 11 个 **隐藏** 工具仍然注册在 MCP 层(按名 `tools/call` 仍命中),只是不会出现
206
- 在列表响应里,以避免 Claude Code 会话启动时加载 11 份额外的工具 schema。隐藏
207
- 工具走下面表格的 CLI 入口。
199
+ v2.34.0 起服务端只把一部分工具暴露给 `tools/list`。当前是 18 个工具,其中 9
200
+ **核心** 工具出现在列表里,另外 9 个 **隐藏** 工具仍然注册在 MCP 层(按名
201
+ `tools/call` 仍命中),只是不出现在列表响应里,以避免 Claude Code 会话启动时
202
+ 多加载 9 份工具 schema。隐藏工具走下面表格的 CLI 入口。
208
203
 
209
- **核心(6 个,暴露给 Claude Code)**
204
+ (这里长期写着 17 / 6 / 11,而英文 README 写着 20 / 9 / 11 —— 两个都不对。
205
+ `tool-schemas.mjs` 是唯一事实来源,`tests/tool-count-docs.test.mjs` 现在把两份
206
+ README 和 `docs/ARCHITECTURE.md` 都钉在它上面。)
207
+
208
+ **核心(9 个,暴露给 Claude Code)**
210
209
 
211
210
  | 工具 | 描述 |
212
211
  |------|------|
@@ -216,8 +215,11 @@ v2.34.0 起服务端注册 17 个工具,但 `tools/list` 只暴露 6 个 **核
216
215
  | `mem_timeline` | 围绕锚点按时间顺序浏览观察。 |
217
216
  | `mem_get` | 获取指定观察 ID 的完整详情(包含重要度和关联 ID)。 |
218
217
  | `mem_save` | 手动保存记忆/观察。 |
218
+ | `mem_defer` | 记录一条跨会话待办(deferred work)。 |
219
+ | `mem_defer_list` | 列出当前项目未关闭的待办。 |
220
+ | `mem_defer_drop` | 带理由地关闭一条待办。 |
219
221
 
220
- **隐藏但可按名调用(11 个,走 CLI)**
222
+ **隐藏但可按名调用(9 个,走 CLI)**
221
223
 
222
224
  | 工具 | 对应 CLI | 说明 |
223
225
  |------|----------|------|
@@ -368,9 +370,6 @@ PostToolUse(每次工具执行)
368
370
  UserPromptSubmit(两个并行路径)
369
371
  -> [user-prompt-search.js] 通过 FTS5 + 活跃文件上下文自动搜索记忆
370
372
  -> [user-prompt-search.js] 注入相关历史观察(按时效和重要性加权)
371
- -> [user-prompt-search.js] L1 Skill 自动加载:匹配 prompt 中的 managed skill 名
372
- -> 加载内容 + 便携 ~ 路径 + Read() 调用指引
373
- -> source="managed-skill|managed-agent", path="~/.claude-mem-lite/managed/..."
374
373
  -> [hook.mjs] 捕获用户提示文本到 user_prompts 表
375
374
  -> [hook.mjs] 递增会话提示计数器
376
375
  -> [hook.mjs] 交接:检测继续意图 → 注入上一次会话上下文
@@ -430,7 +429,7 @@ npx claude-mem-lite doctor # 诊断问题
430
429
 
431
430
  ### 故障恢复(安装卡死 / hook 报错)
432
431
 
433
- 如果你看到 PreToolUse:Read/Edit/Skill hook 报 `ERR_MODULE_NOT_FOUND`,或者 `claude-mem-lite` 命令本身因为 import 错误崩溃,多半是被部分自动更新坑了——更新器复制了新脚本但漏了配套的 `lib/*` 文件,hook 链就此断掉(连下一次本可自愈的自动更新也跑不了)。
432
+ 如果你看到 PreToolUse:Read/Edit hook 报 `ERR_MODULE_NOT_FOUND`,或者 `claude-mem-lite` 命令本身因为 import 错误崩溃,多半是被部分自动更新坑了——更新器复制了新脚本但漏了配套的 `lib/*` 文件,hook 链就此断掉(连下一次本可自愈的自动更新也跑不了)。
434
433
 
435
434
  **v2.84.0+** 提供 `repair` 子命令,从 GitHub 最新 release 重新同步:
436
435
 
@@ -516,12 +515,11 @@ claude-mem-lite/
516
515
  scripts/
517
516
  setup.sh # Setup 钩子:npm install + 迁移(隐藏目录 + 旧目录)
518
517
  post-tool-use.sh # Bash 预过滤器:~5ms 跳过噪声,追踪 Read 路径
519
- user-prompt-search.js # UserPromptSubmit 钩子:自动搜索记忆 + L1 skill 自动加载
520
- pre-skill-bridge.js # PreToolUse 钩子:L2 managed skill 桥接
518
+ user-prompt-search.js # UserPromptSubmit 钩子:用户提问时自动搜索记忆
521
519
  pre-tool-recall.js # PreToolUse 钩子:Edit/Write 前文件教训回忆
520
+ post-tool-recall.js # PostToolUse 钩子:工具失败后的错误召回
521
+ pre-agent-inject.sh # PreToolUse 钩子:为子代理注入上下文
522
522
  prompt-search-utils.mjs # 共享逻辑:跳过模式、意图检测、名称匹配
523
- convert-commands.mjs # 将 command .md 转换为托管插件中的 SKILL.md
524
- index-managed.mjs # 托管资源离线索引器
525
523
  # 测试和基准(仅开发)
526
524
  tests/ # 单元、属性、集成、契约、E2E、管线测试
527
525
  benchmark/ # BM25 搜索质量基准 + CI 门控
@@ -564,7 +562,7 @@ npm run benchmark:gate # CI 门控:指标回退超过 5% 容差时失败
564
562
 
565
563
  | 变量 | 说明 | 默认值 |
566
564
  |------|------|--------|
567
- | `CLAUDE_MEM_DIR` | 自定义数据目录。所有数据库、运行时文件和托管资源均存储在此。 | `~/.claude-mem-lite/` |
565
+ | `CLAUDE_MEM_DIR` | 自定义数据目录。所有数据库与运行时文件均存储在此。 | `~/.claude-mem-lite/` |
568
566
  | `CLAUDE_MEM_MODEL` | 后台 LLM 调用模型(Episode 提取、会话总结、调度)。可选 `haiku` 或 `sonnet`。 | `haiku` |
569
567
  | `ANTHROPIC_API_KEY` | Anthropic API key。设置后所有后台 LLM 调用直连 Anthropic Messages API(带 prompt caching),优先级最高。 | _(未设 → CLI)_ |
570
568
  | `OPENROUTER_API_KEY` | OpenRouter API key(OpenAI 兼容)。当**未设** `ANTHROPIC_API_KEY` 时用于后台 LLM 调用;两者都未设则回退到 `claude -p` CLI。 | _(未设)_ |
package/commands/mem.md CHANGED
@@ -30,9 +30,9 @@ When the user invokes `/mem`, parse their intent:
30
30
  - `/mem save <text>` → call `mem_save` MCP tool with the text as content
31
31
  - `/mem stats` → run `node ${CLAUDE_PLUGIN_ROOT}/cli.mjs stats` via Bash
32
32
  - `/mem get <ids>` → run `node ${CLAUDE_PLUGIN_ROOT}/cli.mjs get <ids>` via Bash
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
- - `/mem cleanup Nd` (e.g. `60d`) → same as above but use `retain_days=N` to only purge items older than N days
35
- - `/mem cleanup keep Nd` (e.g. `keep 14d`) → same as above with `retain_days=N`
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"], confirm=true)` if confirmed. **`confirm=true` is required and is not optional politeness:** without it the call returns a dry-run PREVIEW and deletes nothing, while still succeeding — so you would report a cleanup that never happened.
34
+ - `/mem cleanup Nd` (e.g. `60d`) → same as above but add `retain_days=N` to only purge items older than N days. **`retain_days` must be between 7 and 365**; anything outside that range is rejected by the schema, so `/mem cleanup 3d` cannot be honoured — say so rather than silently substituting the default.
35
+ - `/mem cleanup keep Nd` (e.g. `keep 14d`) → same as above with `retain_days=N`, same 7–365 range.
36
36
  - `/mem <query>` (no subcommand) → treat as search, run `node ${CLAUDE_PLUGIN_ROOT}/cli.mjs search <query>` via Bash
37
37
 
38
38
  Use Bash commands first. For detailed data, use `node ${CLAUDE_PLUGIN_ROOT}/cli.mjs get <id>` via Bash.
@@ -22,7 +22,7 @@ When the user invokes `/mem:update`, perform the following maintenance cycle:
22
22
  3. Call `mem_maintain(action="execute", operations=["cleanup","decay","boost"])` to apply safe automatic changes
23
23
  4. If duplicates were found in scan, review them and call `mem_maintain(action="execute", operations=["dedup"], merge_ids=[[keepId, removeId1, ...], ...])` — keep the more important/recent observation in each pair
24
24
  5. Run `mem_compress(preview=false)` for old low-value observations
25
- 6. **If pending purge items > 0**: Report the count to the user and ask for confirmation. If confirmed, call `mem_maintain(action="execute", operations=["purge_stale"])`. User may optionally specify `retain_days` (default 30) to control how many days of data to keep. Do NOT purge without explicit user confirmation.
25
+ 6. **If pending purge items > 0**: Report the count to the user and ask for confirmation. If confirmed, call `mem_maintain(action="execute", operations=["purge_stale"], confirm=true)`. **`confirm=true` is required:** without it the call returns a dry-run preview and deletes nothing while still succeeding, so you would report a purge that never happened. User may optionally specify `retain_days` (default 30, allowed range 7–365) to control how many days of data to keep. Do NOT purge without explicit user confirmation.
26
26
 
27
27
  ### Phase 2: Summary
28
28
 
package/haiku-client.mjs CHANGED
@@ -6,13 +6,40 @@
6
6
  // overridable via OPENROUTER_MODEL
7
7
 
8
8
  import { execFileSync, spawn } from 'child_process';
9
+ import { mkdirSync } from 'fs';
9
10
  import { readFileSync } from 'fs';
10
11
  import { join } from 'path';
11
12
  import { randomUUID } from 'crypto';
12
13
  import { debugLog, debugCatch, parseJsonFromLLM } from './utils.mjs';
13
14
  import { DB_DIR } from './schema.mjs';
15
+ import { resolveRuntimeDir } from './lib/resolve-data-dir.mjs';
14
16
  import { httpConnectProxyFor, postViaConnectProxy } from './lib/proxy-fetch.mjs';
15
17
 
18
+ /**
19
+ * cwd for every `claude -p` spawn. R10 P2-13.
20
+ *
21
+ * This was `/tmp`, which is world-writable. Claude Code loads a project-level CLAUDE.md
22
+ * and .claude/settings.json from its cwd, so on a shared host ANY local account could
23
+ * create /tmp/CLAUDE.md and inject instructions into every episode summary, session
24
+ * summary and optimize call this process makes — and the CLI leg is the fallback every
25
+ * keyed provider failure lands on, so it is not an exotic path.
26
+ *
27
+ * The original reason for /tmp was ghost sessions in the user's /resume list, and that is
28
+ * already solved by --no-session-persistence on the same spawns. A private directory under
29
+ * the runtime dir (whose parent is 0700) keeps that property and removes the injection
30
+ * surface. Created lazily; if creation fails we still do not fall back to /tmp — an
31
+ * unwritable cwd fails the spawn loudly, which is the better failure.
32
+ */
33
+ function cliSpawnCwd() {
34
+ const dir = join(resolveRuntimeDir(DB_DIR), 'cli-cwd');
35
+ try {
36
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
37
+ } catch {
38
+ /* already there, or unwritable — the spawn reports it */
39
+ }
40
+ return dir;
41
+ }
42
+
16
43
  // ─── Model Resolution ────────────────────────────────────────────────────────
17
44
 
18
45
  // CLI name → API model ID mapping
@@ -237,7 +264,10 @@ export async function callHaikuJSON(prompt, opts) {
237
264
 
238
265
  /**
239
266
  * Non-blocking sibling of callHaikuJSON for callers reachable from an MCP request
240
- * handler (registry enrichment: mem_registry `enrich` / `import_url`). Same
267
+ * handler. R10 P3-28: this used to name `mem_registry enrich / import_url` as the caller,
268
+ * a tool removed in v5.0.0 — read as a live example, it sent readers looking for a handler
269
+ * that does not exist. The REASON is what still applies to whatever calls it next: an MCP
270
+ * request handler must not block the server event loop. Same
241
271
  * provider priority; the CLI leg — primary AND post-provider-failure fallback —
242
272
  * is the async spawn, so a keyed-provider outage cannot freeze the server event
243
273
  * loop for BG_LLM_TIMEOUT_MS (D#138 MEDIUM-3).
@@ -245,8 +275,9 @@ export async function callHaikuJSON(prompt, opts) {
245
275
  * `resolveModel().cli`, NOT the literal 'haiku': despite the name, callHaikuJSON
246
276
  * reaches the model through resolveModel() on ALL three legs (callHaikuAPI,
247
277
  * callOpenRouterAPI, callHaikuCLI), so it honours the documented CLAUDE_MEM_MODEL
248
- * knob. Pinning 'haiku' here would silently downgrade registry enrichment for
249
- * every user who set CLAUDE_MEM_MODEL=sonnet — pre-tag review finding, v3.68.0.
278
+ * knob. Pinning 'haiku' here would silently downgrade any caller's model for every user
279
+ * who set CLAUDE_MEM_MODEL=sonnet — pre-tag review finding, v3.68.0, when the caller in
280
+ * question was registry enrichment.
250
281
  *
251
282
  * Defaults also mirror callHaiku (10s / 500 tokens), not callModelJSONAsync's
252
283
  * 15s / 1000: a caller that omits opts must get the sync twin's budget.
@@ -565,7 +596,7 @@ export function execClaudeCliSync(modelName, { input, timeout }) {
565
596
  encoding: 'utf8',
566
597
  env: { ...process.env, CLAUDE_MEM_HOOK_RUNNING: '1', DISABLE_CLAUDEMD_HOOKS: '1' },
567
598
  stdio: ['pipe', 'pipe', 'pipe'],
568
- cwd: '/tmp', // Prevent ghost sessions in the user's /resume list
599
+ cwd: cliSpawnCwd(), // private dir, not /tmp see cliSpawnCwd (R10 P2-13)
569
600
  };
570
601
  const args = claudeArgs(modelName);
571
602
  const started = Date.now();
@@ -650,7 +681,7 @@ export async function callModelCLIAsync(prompt, model, { timeout }) {
650
681
  // Same headless-tax flags + flag-compat retry as callModelCLI (rationale there).
651
682
  child = spawn(getClaudePath(), args, {
652
683
  env: { ...process.env, CLAUDE_MEM_HOOK_RUNNING: '1', DISABLE_CLAUDEMD_HOOKS: '1' },
653
- cwd: '/tmp',
684
+ cwd: cliSpawnCwd(), // private dir, not /tmp — see cliSpawnCwd (R10 P2-13)
654
685
  stdio: ['pipe', 'pipe', 'pipe'],
655
686
  });
656
687
  } catch (e) {
package/hash-utils.mjs CHANGED
@@ -53,6 +53,27 @@ export function computeMinHash(text, numHashes = 64) {
53
53
  .replace(/[^a-z0-9\s]/g, ' ')
54
54
  .split(/\s+/)
55
55
  .filter((t) => t.length > 2);
56
+ // R10 P3-10: CJK fallback, and ONLY as a fallback. The tokenizer above deletes every
57
+ // non-ASCII character, so a title or narrative written entirely in Chinese or Japanese
58
+ // produced zero tokens and this returned null — and a null signature makes the row
59
+ // invisible to the MinHash prefilter that findDuplicates and selectFuzzyDedupeIds run,
60
+ // so CJK-only rows could never be deduplicated against anything.
61
+ //
62
+ // Confined to rows that get NOTHING from the ASCII path, which is the whole point.
63
+ // Signatures are STORED, and estimateJaccardFromMinHash compares a stored signature
64
+ // against a freshly computed one; widening the tokenization for text that already
65
+ // signs would make the entire existing corpus incomparable with everything written
66
+ // afterwards — dedup degrading everywhere, silently, until a full rebuild. Every text
67
+ // that signs today still produces the same bytes.
68
+ //
69
+ // Character bigrams rather than nlp.mjs's dictionary segmentation: this module is
70
+ // dependency-free on purpose, and bigrams are the standard CJK shingle for exactly this
71
+ // job — no vocabulary to maintain and no dependence on a dictionary that lags usage.
72
+ if (tokens.length === 0) {
73
+ for (const run of text.match(/[\u4e00-\u9fff\u3400-\u4dbf\u3040-\u30ff]{2,}/g) || []) {
74
+ for (let i = 0; i + 2 <= run.length; i++) tokens.push(run.slice(i, i + 2));
75
+ }
76
+ }
56
77
  // Require at least 3 tokens for meaningful signature (avoids high collision on short texts)
57
78
  if (tokens.length < 3) return null;
58
79
 
package/hook-episode.mjs CHANGED
@@ -248,7 +248,13 @@ export function mergePendingEntries(episode) {
248
248
  let files;
249
249
  try {
250
250
  files = readdirSync(RUNTIME_DIR)
251
- .filter((f) => f.startsWith('pending-'))
251
+ // R10 P3-4: `.json.tmp` is the WRITER's in-flight temp, not a pending entry.
252
+ // writePendingEntry writes `pending-<ts>-<rand>.json.tmp` then renames it into place;
253
+ // the bare `pending-` prefix matched the temp too, this loop's JSON.parse failed on a
254
+ // partial file, the catch deleted it as corrupt, and the writer's rename then failed
255
+ // ENOENT inside its own swallowed catch. One tool entry lost, silently, every time
256
+ // the two raced.
257
+ .filter((f) => f.startsWith('pending-') && f.endsWith('.json'))
252
258
  .sort();
253
259
  } catch {
254
260
  return;