claude-mem-lite 5.6.0 → 6.0.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +48 -30
- package/README.zh-CN.md +33 -7
- package/adopt-content.mjs +1 -1
- package/deep-search.mjs +10 -9
- package/hook-llm.mjs +2 -38
- package/hook-optimize.mjs +50 -136
- package/hook.mjs +0 -8
- package/lib/compress-core.mjs +4 -21
- package/lib/dedup-constants.mjs +4 -2
- package/lib/maintain-core.mjs +0 -60
- package/lib/observation-write.mjs +7 -92
- package/lib/rrf.mjs +15 -6
- package/lib/save-enrich.mjs +6 -17
- package/lib/save-observation.mjs +2 -12
- package/lib/search-core.mjs +19 -20
- package/mem-cli.mjs +6 -19
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/schema.mjs +19 -36
- package/search-engine.mjs +13 -120
- package/server.mjs +0 -1
- package/source-files.mjs +1 -1
- package/tfidf.mjs +23 -387
- package/tool-schemas.mjs +3 -14
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "
|
|
13
|
+
"version": "6.0.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
|
+
"version": "6.0.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
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# claude-mem-lite
|
|
4
4
|
|
|
5
|
-
`claude-mem-lite` is a **persistent memory** (also called *long-term memory* or *cross-session context*) system for **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** — Anthropic's CLI coding agent. It runs as an **[MCP](https://modelcontextprotocol.io/) server** plus a set of Claude Code hooks, automatically capturing coding observations, decisions, and bug fixes during sessions, then providing
|
|
5
|
+
`claude-mem-lite` is a **persistent memory** (also called *long-term memory* or *cross-session context*) system for **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** — Anthropic's CLI coding agent. It runs as an **[MCP](https://modelcontextprotocol.io/) server** plus a set of Claude Code hooks, automatically capturing coding observations, decisions, and bug fixes during sessions, then providing full-text search with query expansion to recall them later.
|
|
6
6
|
|
|
7
|
-
Compared to general-purpose LLM memory frameworks like [`mem0`](https://github.com/mem0ai/mem0) or the MCP reference [`memory`](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) server, claude-mem-lite is purpose-built for Claude Code's hook lifecycle: episode batching cuts LLM calls 7–10× vs the original [claude-mem](https://github.com/thedotmack/claude-mem) (an estimated ~600× lower total cost — see the cost model below; this is an architecture estimate, not a measured benchmark), while the
|
|
7
|
+
Compared to general-purpose LLM memory frameworks like [`mem0`](https://github.com/mem0ai/mem0) or the MCP reference [`memory`](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) server, claude-mem-lite is purpose-built for Claude Code's hook lifecycle: episode batching cuts LLM calls 7–10× vs the original [claude-mem](https://github.com/thedotmack/claude-mem) (an estimated ~600× lower total cost — see the cost model below; this is an architecture estimate, not a measured benchmark), while the FTS5 retriever benchmarks at 0.90 Recall@10 / 0.85 Precision@10
|
|
8
8
|
(see [Search Quality](#search-quality) for the reproduction command).
|
|
9
9
|
|
|
10
|
-
> 中文简介:claude-mem-lite 是 Claude Code 的轻量级**持久化记忆 / 长期记忆 / 跨会话上下文**插件,基于 MCP 协议 + 钩子机制,自动捕获编码会话中的决策、修复和上下文,并通过 FTS5
|
|
10
|
+
> 中文简介:claude-mem-lite 是 Claude Code 的轻量级**持久化记忆 / 长期记忆 / 跨会话上下文**插件,基于 MCP 协议 + 钩子机制,自动捕获编码会话中的决策、修复和上下文,并通过 FTS5 全文检索召回。详见 [中文 README](README.zh-CN.md)。
|
|
11
11
|
|
|
12
12
|
Zero external services. Single SQLite database. Minimal overhead.
|
|
13
13
|
|
|
@@ -66,7 +66,7 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
|
|
|
66
66
|
| **Target client** | Claude Code only | Any LLM app via SDK | Any MCP client | Claude Code only |
|
|
67
67
|
| **Capture model** | Auto via hooks | Manual `memory.add()` | Manual tool calls (`create_entities`, `add_observations`) | Auto via hooks |
|
|
68
68
|
| **Code-aware retrieval** | FTS5 + 100+ synonym pairs (incl. CJK↔EN) | General-purpose | Generic graph nodes | Code-aware |
|
|
69
|
-
| **Search** |
|
|
69
|
+
| **Search** | FTS5 BM25 + query expansion (PRF, concept co-occurrence) | Hybrid: semantic + BM25 + entity linking | Knowledge-graph traversal | FTS5 + Chroma vector |
|
|
70
70
|
| **Storage** | Single local SQLite | Pluggable; Qdrant or configurable vector store | Single JSONL file (knowledge graph) | SQLite + Chroma |
|
|
71
71
|
| **LLM dependency** | Haiku per episode (5–10 ops batched) | LLM per add/search op | None (graph CRUD only) | Sonnet per tool call |
|
|
72
72
|
| **Setup** | One command (`/plugin install` or `npx`) | SDK integration + vector store config | MCP install (per-client) | Bun + Python + Chroma |
|
|
@@ -76,7 +76,7 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
|
|
|
76
76
|
## Features
|
|
77
77
|
|
|
78
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
|
+
- **Lexical search with query expansion** -- FTS5 BM25 scoring, an AND->OR rescue pass, pseudo-relevance feedback and concept co-occurrence. A TF-IDF vector arm shipped alongside it until it was measured net-negative and removed; `--deep` still fuses multiple LLM-rewritten queries with Reciprocal Rank Fusion
|
|
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
|
|
82
82
|
- **Error-triggered recall** -- Automatically searches memory when Bash errors occur, surfacing relevant past fixes
|
|
@@ -92,8 +92,7 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
|
|
|
92
92
|
- **Two-tier dedup** -- Jaccard similarity (5-minute window) + MinHash signatures (7-day cross-session window) prevent duplicates
|
|
93
93
|
- **Synonym expansion** -- Abbreviations like `K8s`, `DB`, `auth` automatically expand to full forms in FTS5 search (100+ pairs including CJK↔EN cross-language mappings)
|
|
94
94
|
- **CJK synonym extraction** -- Unsegmented Chinese text is scanned for known vocabulary words (数据库→database, 搜索→search, etc.) enabling cross-language memory recall
|
|
95
|
-
- **Stop-word filtering** -- English stop words filtered from
|
|
96
|
-
- **Persisted vocabulary** -- TF-IDF vocabulary persisted to `vocab_state` table, preventing vector staleness when document frequencies shift. Vectors stay valid until explicit rebuild
|
|
95
|
+
- **Stop-word filtering** -- English stop words filtered from FTS queries, preventing false negatives from noise terms like "how", "the", "does"
|
|
97
96
|
- **Pseudo-relevance feedback (PRF)** -- Top results seed expansion queries for broader recall
|
|
98
97
|
- **Concept co-occurrence** -- Shared concepts across observations expand search to related topics
|
|
99
98
|
- **Context-aware re-ranking** -- Active file overlap boosts relevance (exact match + directory-level half-weight)
|
|
@@ -124,10 +123,10 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
|
|
|
124
123
|
- **Git-SHA continuation anchor** (v2.31.0) -- Handoff rows include `git_sha_at_handoff`; any handoff matching the current `HEAD` counts as continuation regardless of TTL. Code state is a stronger continuation signal than wall-clock time
|
|
125
124
|
- **Startup dashboard** (v2.31.0) -- SessionStart hook aggregates `git status` + `~/.claude/tasks/*.json` + `~/.claude/plans/*.md` + most-recent exit handoff + recent event count into a single structured block injected via `hookSpecificOutput.additionalContext`
|
|
126
125
|
- **Activity namespace** (v2.31.0) -- Dedicated `events` table + FTS5 for non-memdir types (`bugfix`, `lesson`, `bug`, `discovery`, `refactor`, `feature`, `observation`, `decision`) that don't compete with `WHAT_NOT_TO_SAVE` semantics on the observations table. CLI: `claude-mem-lite activity save|search|recent|show`. `hook-llm` routes non-memdir summary types through `persistHaikuSummary` so upgrades from observations→events are atomic. (v3.39: the `/lesson` and `/bug` slash commands were redirected from this events table to searchable **observations** — `mem_search` never read the events table, so explicit saves were unfindable; the events table remains the auto-capture activity log.)
|
|
127
|
-
- **In-place observation updates** -- `mem_update` tool modifies existing observations atomically (field update + FTS text rebuild
|
|
126
|
+
- **In-place observation updates** -- `mem_update` tool modifies existing observations atomically (field update + FTS text rebuild in one transaction), preserving original IDs and references
|
|
128
127
|
- **Bulk export** -- `mem_export` tool exports observations as JSON or JSONL, with project/type/date filtering and 1000-row pagination cap with batch guidance
|
|
129
128
|
- **FTS integrity management** -- `mem_fts_check` tool verifies FTS5 index health or rebuilds indexes on demand, useful after database recovery or when search results seem wrong
|
|
130
|
-
- **Atomic multi-table writes** -- `saveObservation` wraps observations + observation_files
|
|
129
|
+
- **Atomic multi-table writes** -- `saveObservation` wraps the observations + observation_files INSERTs in a single `db.transaction()`, preventing orphaned rows on crash
|
|
131
130
|
- **Modular NLP pipeline** -- Synonym maps, stop words, scoring constants, and query building extracted into focused modules (`synonyms.mjs`, `stop-words.mjs`, `scoring-sql.mjs`, `nlp.mjs`) for independent testing and maintenance
|
|
132
131
|
- **Porter-aligned PRF** -- Pseudo-relevance feedback terms are now stemmed with the same Porter algorithm used by FTS5, ensuring PRF expansion terms match the search index
|
|
133
132
|
|
|
@@ -229,6 +228,34 @@ rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
|
|
|
229
228
|
repos/ # Shallow-cloned source repos
|
|
230
229
|
```
|
|
231
230
|
|
|
231
|
+
<!-- vector-arm-removal-note:start -->
|
|
232
|
+
## Upgrading to 6.0.0 (breaking)
|
|
233
|
+
|
|
234
|
+
**The default search path does not change.** 6.0.0 removes the TF-IDF vector arm, which has
|
|
235
|
+
been disabled by default since 3.17.0 — if you never set CLAUDE_MEM_VECTORS, upgrading is
|
|
236
|
+
behaviour-identical and there is nothing to do.
|
|
237
|
+
|
|
238
|
+
Three surfaces are gone:
|
|
239
|
+
|
|
240
|
+
| Removed | What happens now |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `CLAUDE_MEM_VECTORS=1` | Inert. Setting it has no effect. |
|
|
243
|
+
| `maintain execute --ops rebuild_vectors` | Exits 1: `Unknown operation(s): rebuild_vectors`. |
|
|
244
|
+
| Tables `observation_vectors`, `vocab_state` | Dropped by schema migration v49 on first open. |
|
|
245
|
+
|
|
246
|
+
**The migration is one-way.** Once a 6.0.0 build has opened your database, older versions
|
|
247
|
+
refuse it — `schema.mjs`'s forward-incompat guard throws *"DB schema is v49 but this
|
|
248
|
+
claude-mem-lite binary supports up to v48"*. If you want to stay on the vector arm, pin
|
|
249
|
+
`claude-mem-lite@5.6.0` **before** upgrading. If you have already upgraded and need to go
|
|
250
|
+
back, either re-upgrade, point `CLAUDE_MEM_DIR` at a fresh directory, or restore a
|
|
251
|
+
pre-upgrade backup (`claude-mem-lite export` / the snapshots under your data dir).
|
|
252
|
+
|
|
253
|
+
Why it was removed: measured directly against the shipped path, the arm was negative on both
|
|
254
|
+
benchmark fixtures — including the vocabulary-mismatch suite that is the only reason a vector
|
|
255
|
+
arm would exist (Recall@10 0.3407 → 0.3018, and roughly +88% P95 latency). No observations
|
|
256
|
+
are lost; only the derived vector index is.
|
|
257
|
+
<!-- vector-arm-removal-note:end -->
|
|
258
|
+
|
|
232
259
|
## Usage
|
|
233
260
|
|
|
234
261
|
### MCP Tools (used automatically by Claude)
|
|
@@ -265,7 +292,7 @@ surface — reach them through the CLI column in the second table.
|
|
|
265
292
|
| `mem_stats` | `claude-mem-lite stats` | Counts, type distribution, daily activity. |
|
|
266
293
|
| `mem_delete` | `claude-mem-lite delete <id>` | Preview / confirm workflow, FTS5 cleanup. |
|
|
267
294
|
| `mem_compress` | `claude-mem-lite compress` | Roll up old low-value observations (preview default; `--execute` to apply). |
|
|
268
|
-
| `mem_maintain` | `claude-mem-lite maintain scan --ops dedup,decay` | dedup / decay / cleanup /
|
|
295
|
+
| `mem_maintain` | `claude-mem-lite maintain scan --ops dedup,decay` | dedup / decay / cleanup / vacuum (`scan` previews, `execute` applies). |
|
|
269
296
|
| `mem_optimize` | `claude-mem-lite optimize` | LLM-powered re-enrich / normalize / cluster-merge (preview default; `--run` to apply). |
|
|
270
297
|
| `mem_export` | `claude-mem-lite export` | JSON / JSONL dump, filters by project, type, date. |
|
|
271
298
|
| `mem_fts_check` | `claude-mem-lite fts-check <check\|rebuild>` | FTS5 integrity + rebuild. |
|
|
@@ -413,16 +440,6 @@ key_files, key_decisions, match_keywords, created_at_epoch
|
|
|
413
440
|
obs_id, filename
|
|
414
441
|
```
|
|
415
442
|
|
|
416
|
-
**observation_vectors** -- TF-IDF vector embeddings for hybrid search
|
|
417
|
-
```
|
|
418
|
-
observation_id, vector (BLOB Float32Array), vocab_version, created_at_epoch
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
**vocab_state** -- Persisted TF-IDF vocabulary for stable vector indexing
|
|
422
|
-
```
|
|
423
|
-
term, term_index, idf, version, created_at_epoch
|
|
424
|
-
```
|
|
425
|
-
|
|
426
443
|
FTS5 indexes: `observations_fts` (title, subtitle, narrative, text, facts, concepts, lesson_learned), `session_summaries_fts`, `user_prompts_fts`
|
|
427
444
|
|
|
428
445
|
## How It Works
|
|
@@ -617,7 +634,7 @@ claude-mem-lite/
|
|
|
617
634
|
hook-semaphore.mjs # LLM concurrency control: file-based semaphore for background workers
|
|
618
635
|
schema.mjs # Database schema: single source of truth for tables, migrations, FTS5
|
|
619
636
|
tool-schemas.mjs # Shared Zod schemas for MCP tool validation
|
|
620
|
-
tfidf.mjs #
|
|
637
|
+
tfidf.mjs # tokenization + Porter stemming (name is historical: the TF-IDF vector engine it held was removed)
|
|
621
638
|
tier.mjs # Temporal tier system: activity-based time window classification
|
|
622
639
|
utils.mjs # Re-export hub: backward-compatible surface for all utility modules
|
|
623
640
|
nlp.mjs # FTS5 query building: synonym expansion, CJK bigrams, sanitization
|
|
@@ -650,7 +667,7 @@ claude-mem-lite/
|
|
|
650
667
|
## Search Quality
|
|
651
668
|
|
|
652
669
|
Benchmarked on 200 observations across 30 queries (standard + hard-negative categories),
|
|
653
|
-
measuring the **production-hybrid** retriever (
|
|
670
|
+
measuring the **production-hybrid** retriever (the real `searchObservationsHybrid`) — the path
|
|
654
671
|
`mem_search` / `recall` actually use. The CI gate (`npm run benchmark:gate`) runs this same
|
|
655
672
|
path and fails on regression.
|
|
656
673
|
|
|
@@ -668,12 +685,14 @@ path and fails on regression.
|
|
|
668
685
|
> `npm run benchmark:gate` fails the build when a run drifts more than 5% from it. This is
|
|
669
686
|
> the single source for every retrieval figure quoted in this README.
|
|
670
687
|
|
|
671
|
-
> **Note on the path measured.**
|
|
672
|
-
>
|
|
673
|
-
>
|
|
674
|
-
>
|
|
675
|
-
>
|
|
676
|
-
>
|
|
688
|
+
> **Note on the path measured.** This table measures whatever `mem_search` actually runs,
|
|
689
|
+
> which is why the figures have moved twice. An older revision reported a narrower FTS-only
|
|
690
|
+
> harness (Precision@10 0.96, P95 0.15ms); a later one attributed the lower precision to a
|
|
691
|
+
> TF-IDF vector arm "trading precision for recall". **That attribution was wrong and the
|
|
692
|
+
> claim is withdrawn** — the gate's `hybrid_over_bm25` delta never executed a vector path at
|
|
693
|
+
> all, so it could not have measured that trade. The arm was later A/B'd directly, came out
|
|
694
|
+
> negative on both fixtures, and was removed; these numbers are the shipped path with no
|
|
695
|
+
> vector arm in it. For field-comparable recall, see the LongMemEval section below.
|
|
677
696
|
|
|
678
697
|
### Recall on LongMemEval (standard benchmark)
|
|
679
698
|
|
|
@@ -692,7 +711,7 @@ and `benchmark/longmemeval-rerank.mjs` (rerank).
|
|
|
692
711
|
|
|
693
712
|
| Retriever (zero embeddings) | @1 | @5 | @10 |
|
|
694
713
|
|---|---|---|---|
|
|
695
|
-
| Lexical
|
|
714
|
+
| Lexical — FTS5 BM25 + query expansion | **83.4%** | **95.2%** | **96.0%** |
|
|
696
715
|
| + one top-20 LLM rerank pass † | 92.8% | 96.8% | 97.4% |
|
|
697
716
|
|
|
698
717
|
*n = 500 questions.* The lexical row was re-measured 2026-07-18: the v3.39–v3.45
|
|
@@ -804,7 +823,6 @@ benchmark and A/B harness are calibrated against — changing them invalidates t
|
|
|
804
823
|
| `CLAUDE_MEM_DEEP_DISCLOSURE` | `off` suppresses the one-line caveat appended to a multi-variant deep result. The caveat exists because deep search fills the page even when the corpus cannot answer — measured at 10 of 10 slots on queries whose answers had been removed (`benchmark/deep-search-holdout.mjs`) — and `deep` is AUTO by default on the MCP surface, i.e. it escalates precisely when the honest answer is "nothing". It does not change retrieval, ranking, or which rows are returned. | _(on)_ |
|
|
805
824
|
| `CLAUDE_MEM_REACH_DISCLOSURE` | `off` suppresses the one-line note that fires when a search's reported `total` exceeds what its pagination can hand back. The candidate pool is sized from `limit` alone and deliberately does not grow with `offset` (D#30 — an offset-scaled pool re-ranks its own prefix under RRF, so pages overlapped and gapped), while `total` is the full match count. Measured on a 128-row corpus: at the default limit of 20 the last non-empty offset is 59, so 60 of 128 rows are unreachable at any offset. The note reports that; it does not change retrieval, ranking, or which rows are returned. | _(on)_ |
|
|
806
825
|
| `CLAUDE_MEM_AUTO_DEEP_CLI` | `0` disables the same auto-escalation on the CLI path only. | _(auto)_ |
|
|
807
|
-
| `CLAUDE_MEM_VECTORS` | `1` re-enables the persisted TF-IDF vector arm (off by default; also needs a vector rebuild via `maintain`). | _(off)_ |
|
|
808
826
|
| `CLAUDE_MEM_SCOPE_FILTER` | `1` stops environment-scoped observations from firing on file-triggered recall. They stay reachable via search. **Leave it off**: on the face it gates, `environment` is not the low-relevance class its premise assumes — it cites at least as well as `project` (47.5% vs 44.3%, intervals overlapping), and an earlier measurement left 173 recall groups empty with it on. | _(off)_ |
|
|
809
827
|
| `CLAUDE_MEM_READS_CARRY` | An episode flush collects `reads-<project>.txt` only when it will actually save an observation, so a flush that records nothing no longer discards the Read paths it swept up (42.2% of the paths a flush consumed, measured over 1122 transcripts). `0` restores the pre-v3.83.0 behaviour. | _(on)_ |
|
|
810
828
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# claude-mem-lite
|
|
4
4
|
|
|
5
|
-
`claude-mem-lite` 是 **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)**(Anthropic 官方 CLI 编程代理)的 **持久化记忆系统**(也称 **长期记忆 / 跨会话上下文 / Claude Code 记忆插件**)。它以 **[MCP](https://modelcontextprotocol.io/) 服务器** + Claude Code 钩子(hooks)的形式运行,在编码会话中自动捕获观察记录、决策、bug 修复,并通过 FTS5
|
|
5
|
+
`claude-mem-lite` 是 **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)**(Anthropic 官方 CLI 编程代理)的 **持久化记忆系统**(也称 **长期记忆 / 跨会话上下文 / Claude Code 记忆插件**)。它以 **[MCP](https://modelcontextprotocol.io/) 服务器** + Claude Code 钩子(hooks)的形式运行,在编码会话中自动捕获观察记录、决策、bug 修复,并通过 FTS5 全文检索(BM25 + 查询扩展)召回历史上下文。
|
|
6
6
|
|
|
7
|
-
与 [`mem0`](https://github.com/mem0ai/mem0)、MCP 官方参考实现的 [`memory`](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) 服务器等通用 LLM 记忆框架相比,claude-mem-lite 专为 Claude Code 的钩子生命周期定制:episode 批处理把 LLM 调用量相比原版 [claude-mem](https://github.com/thedotmack/claude-mem) 减少 7-10 倍(综合成本估算下降约 600 倍 —— 见下方成本模型,属架构估算而非实测基准);FTS5
|
|
7
|
+
与 [`mem0`](https://github.com/mem0ai/mem0)、MCP 官方参考实现的 [`memory`](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) 服务器等通用 LLM 记忆框架相比,claude-mem-lite 专为 Claude Code 的钩子生命周期定制:episode 批处理把 LLM 调用量相比原版 [claude-mem](https://github.com/thedotmack/claude-mem) 减少 7-10 倍(综合成本估算下降约 600 倍 —— 见下方成本模型,属架构估算而非实测基准);FTS5 检索在 30 个查询的基准上达到 **Recall@10 = 0.90 / Precision@10 = 0.85**(复现命令见[搜索质量](#搜索质量)一节)。
|
|
8
8
|
|
|
9
9
|
无需外部服务。单一 SQLite 数据库。开销极低。
|
|
10
10
|
|
|
@@ -192,6 +192,30 @@ rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
|
|
|
192
192
|
repos/ # 浅克隆的源代码仓库
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
+
<!-- vector-arm-removal-note:start -->
|
|
196
|
+
## 升级到 6.0.0(破坏性变更)
|
|
197
|
+
|
|
198
|
+
**默认检索路径不变。** 6.0.0 移除了 TF-IDF 向量臂——它自 3.17.0 起就默认关闭,如果你从未设置过
|
|
199
|
+
CLAUDE_MEM_VECTORS,升级前后行为完全一致,无需任何操作。
|
|
200
|
+
|
|
201
|
+
三个面被移除:
|
|
202
|
+
|
|
203
|
+
| 移除项 | 现在的行为 |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `CLAUDE_MEM_VECTORS=1` | 失效。设置它不再有任何作用。 |
|
|
206
|
+
| `maintain execute --ops rebuild_vectors` | 退出码 1:`Unknown operation(s): rebuild_vectors`。 |
|
|
207
|
+
| `observation_vectors`、`vocab_state` 两张表 | 由 schema 迁移 v49 在首次打开时 DROP。 |
|
|
208
|
+
|
|
209
|
+
**这个迁移是单向的。** 一旦 6.0.0 打开过你的数据库,旧版本就会拒绝它——`schema.mjs` 的
|
|
210
|
+
forward-incompat 守卫会抛出 *"DB schema is v49 but this claude-mem-lite binary supports up to
|
|
211
|
+
v48"*。想继续用向量臂,请在**升级之前**锁定 `claude-mem-lite@5.6.0`;如果已经升级又需要回退,
|
|
212
|
+
只能重新升级、把 `CLAUDE_MEM_DIR` 指向一个新目录,或从升级前的备份恢复
|
|
213
|
+
(`claude-mem-lite export` 或数据目录下的快照)。
|
|
214
|
+
|
|
215
|
+
移除原因:直接对着出货路径实测,该臂在两个基准语料上都是负的——包括「词表不匹配」这个向量臂唯一
|
|
216
|
+
的存在理由(Recall@10 0.3407 → 0.3018,P95 延迟约 +88%)。观察记录不会丢失,丢的只是派生的向量索引。
|
|
217
|
+
<!-- vector-arm-removal-note:end -->
|
|
218
|
+
|
|
195
219
|
## 使用方法
|
|
196
220
|
|
|
197
221
|
### MCP 工具
|
|
@@ -227,7 +251,7 @@ README 和 `docs/ARCHITECTURE.md` 都钉在它上面。)
|
|
|
227
251
|
| `mem_stats` | `claude-mem-lite stats` | 计数、类型分布、每日活动。 |
|
|
228
252
|
| `mem_delete` | `claude-mem-lite delete <id>` | 预览 / 确认流程,FTS5 自动清理。 |
|
|
229
253
|
| `mem_compress` | `claude-mem-lite compress` | 压缩旧的低价值观察(默认 preview;`--execute` 执行)。 |
|
|
230
|
-
| `mem_maintain` | `claude-mem-lite maintain scan --ops dedup,decay` | 去重 / decay / 清理 /
|
|
254
|
+
| `mem_maintain` | `claude-mem-lite maintain scan --ops dedup,decay` | 去重 / decay / 清理 / vacuum(`scan` 预览,`execute` 执行)。 |
|
|
231
255
|
| `mem_optimize` | `claude-mem-lite optimize` | LLM 深度优化:re-enrich / normalize / cluster-merge(默认 preview;`--run` 执行)。 |
|
|
232
256
|
| `mem_export` | `claude-mem-lite export` | JSON / JSONL 导出,支持项目/类型/日期过滤。 |
|
|
233
257
|
| `mem_fts_check` | `claude-mem-lite fts-check <check\|rebuild>` | FTS5 完整性检查与重建。 |
|
|
@@ -528,7 +552,7 @@ claude-mem-lite/
|
|
|
528
552
|
## 搜索质量
|
|
529
553
|
|
|
530
554
|
基于 200 条观察和 30 个查询(标准 + 困难负样本类别)的基准测试结果,测量的是
|
|
531
|
-
**production-hybrid**
|
|
555
|
+
**production-hybrid** 检索路径(真实的 `searchObservationsHybrid`)——也就是 `mem_search` /
|
|
532
556
|
`recall` 实际走的那条路径:
|
|
533
557
|
|
|
534
558
|
| 指标 | 得分(production-hybrid) |
|
|
@@ -543,9 +567,11 @@ claude-mem-lite/
|
|
|
543
567
|
> 固定语料、固定查询集、无采样)。CI 参考快照是 `benchmark/baseline.json`,
|
|
544
568
|
> `npm run benchmark:gate` 在偏离超过 5% 时让构建失败。本 README 中所有检索指标都以此为唯一来源。
|
|
545
569
|
|
|
546
|
-
> **关于测量路径。**
|
|
547
|
-
>
|
|
548
|
-
>
|
|
570
|
+
> **关于测量路径。** 本表测量的始终是 `mem_search` 实际走的那条路径,所以数字变过两次。
|
|
571
|
+
> 早期版本报告的是更窄的纯 FTS 测量口径(Precision@10 0.96、P95 0.15ms);后来一版把
|
|
572
|
+
> precision 的下降归因于「TF-IDF 向量臂用 precision 换 recall」。**那个归因是错的,该说法已撤回**
|
|
573
|
+
> ——门控的 `hybrid_over_bm25` 差值两个臂都不执行向量路径,根本量不到这笔交换。该臂后来被直接
|
|
574
|
+
> A/B 实测,两个语料上都是负的,已移除;此处的数字就是不含向量臂的出货路径。
|
|
549
575
|
|
|
550
576
|
## 开发
|
|
551
577
|
|
package/adopt-content.mjs
CHANGED
|
@@ -174,7 +174,7 @@ PreToolUse hook 在你 Read / Edit / Write 文件前已自动 \`mem_recall\` 该
|
|
|
174
174
|
| 改某条 | \`${CLI} update <id> [--lesson "<≤500>"] [--title T] [--type T] [--importance 1-3] [--narrative T] [--concepts "a b c"]\` |
|
|
175
175
|
| 事件日志 | \`${CLI} activity save --type <bugfix\\|lesson\\|bug\\|discovery\\|refactor\\|feature\\|observation\\|decision> "<title>" [--body T] [--files f1,f2]\` |
|
|
176
176
|
|
|
177
|
-
\`maintain\` / \`optimize\` / \`compress\` 见上方「维护 / 管理类工具」;\`maintain --ops\` 取值 \`cleanup,decay,boost,demote_pinned,dedup,purge_stale,
|
|
177
|
+
\`maintain\` / \`optimize\` / \`compress\` 见上方「维护 / 管理类工具」;\`maintain --ops\` 取值 \`cleanup,decay,boost,demote_pinned,dedup,purge_stale,vacuum\`,省略时默认 \`cleanup,decay,boost,demote_pinned\`(顺序有意义:demote_pinned 必须在 boost 之后);\`--retain-days\` ∈ [7,365]。
|
|
178
178
|
|
|
179
179
|
## 卸载 / 关闭
|
|
180
180
|
|
package/deep-search.mjs
CHANGED
|
@@ -33,8 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
import { searchObservationsHybrid } from './search-engine.mjs';
|
|
35
35
|
import { sanitizeFtsQuery } from './utils.mjs';
|
|
36
|
-
import { RRF_K } from './
|
|
37
|
-
import { rrfAccumulate } from './lib/rrf.mjs';
|
|
36
|
+
import { RRF_K, rrfAccumulate } from './lib/rrf.mjs';
|
|
38
37
|
import { llmRerankOrder, defaultRerankLLM } from './rerank.mjs';
|
|
39
38
|
import { liveObsFilterSql } from './lib/inject-search-core.mjs';
|
|
40
39
|
|
|
@@ -425,7 +424,7 @@ export async function rewriteQuery(query, { llm = defaultLLM, retries = 1, cache
|
|
|
425
424
|
/**
|
|
426
425
|
* N-way Reciprocal Rank Fusion. Each ranked list contributes 1/(k + rank) to an
|
|
427
426
|
* item's score (rank is 0-based array position; lists must already be in
|
|
428
|
-
* relevance order).
|
|
427
|
+
* relevance order). k=RRF_K and the 1/(k+rank+1) formula come from lib/rrf.mjs,
|
|
429
428
|
* generalized from 2 lists to N. A single list is returned in its original order
|
|
430
429
|
* (scores are strictly decreasing in rank), which is what guarantees deepSearch
|
|
431
430
|
* never reorders the baseline when the rewrite fails.
|
|
@@ -566,12 +565,14 @@ export async function deepSearch(
|
|
|
566
565
|
}
|
|
567
566
|
// rrfFuseN fuses by array index as rank, so each list MUST already be in
|
|
568
567
|
// composite-score order. searchObservationsHybrid appends downweighted
|
|
569
|
-
// concept(×0.7)/PRF(×0.6) expansion rows to the TAIL unsorted and
|
|
570
|
-
//
|
|
571
|
-
//
|
|
572
|
-
//
|
|
573
|
-
//
|
|
574
|
-
// the
|
|
568
|
+
// concept(×0.7)/PRF(×0.6) expansion rows to the TAIL unsorted and never sorts them —
|
|
569
|
+
// so a sparse variant (common in deep search, the vocabulary-mismatch path) would hand
|
|
570
|
+
// a tail-ranked expansion row to RRF at a worse rank than its score earns.
|
|
571
|
+
//
|
|
572
|
+
// THIS SORT IS NOW THE ONLY ONE. It used to be described as mirroring an in-engine sort
|
|
573
|
+
// that guarded the vector-RRF merge; that sort lived inside the vector block Phase-2
|
|
574
|
+
// deleted, so nothing upstream re-orders the list any more. Read the sentence that way
|
|
575
|
+
// before deleting this line as redundant — it is load-bearing, not a mirror.
|
|
575
576
|
list.sort((a, b) => (a.score ?? 0) - (b.score ?? 0));
|
|
576
577
|
return list;
|
|
577
578
|
});
|
package/hook-llm.mjs
CHANGED
|
@@ -25,12 +25,9 @@ import {
|
|
|
25
25
|
import { acquireLLMSlot, releaseLLMSlot } from './hook-semaphore.mjs';
|
|
26
26
|
import { BG_LLM_TIMEOUT_MS } from './haiku-client.mjs';
|
|
27
27
|
import { scrubRecord } from './lib/scrub-record.mjs';
|
|
28
|
-
import { vecTextForRow } from './tfidf.mjs';
|
|
29
28
|
import {
|
|
30
29
|
insertObservationRow,
|
|
31
30
|
insertObservationFiles,
|
|
32
|
-
insertObservationVector,
|
|
33
|
-
upsertObservationVector,
|
|
34
31
|
normalizeScope,
|
|
35
32
|
SCOPE_PROMPT_LEGEND,
|
|
36
33
|
} from './lib/observation-write.mjs';
|
|
@@ -210,22 +207,6 @@ function buildFtsTextField(obs) {
|
|
|
210
207
|
};
|
|
211
208
|
}
|
|
212
209
|
|
|
213
|
-
// TF-IDF vector text. Must mirror the FTS-searchable content so the vector arm and
|
|
214
|
-
// the BM25 arm rank on the same signal — including lesson_learned (highest FTS
|
|
215
|
-
// weight) and search_aliases (finding #8: previously omitted, so even with vectors
|
|
216
|
-
// enabled the paraphrase-bridge alias terms were invisible to cosine similarity).
|
|
217
|
-
export function buildVecText(obs) {
|
|
218
|
-
// Single source (V-F1): map the camelCase obs onto vecTextForRow's row shape so save and
|
|
219
|
-
// every rebuild path encode the identical field set (title/narrative/concepts/lesson/aliases).
|
|
220
|
-
return vecTextForRow({
|
|
221
|
-
title: obs.title,
|
|
222
|
-
narrative: obs.narrative,
|
|
223
|
-
concepts: obs.concepts,
|
|
224
|
-
lesson_learned: obs.lessonLearned,
|
|
225
|
-
search_aliases: obs.searchAliases,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
210
|
/**
|
|
230
211
|
* Save an observation to the database with three-tier dedup.
|
|
231
212
|
* @returns {number|null} The saved observation ID, or null if deduped.
|
|
@@ -370,7 +351,7 @@ export function saveObservation(obs, projectOverride, sessionIdOverride, externa
|
|
|
370
351
|
search_aliases: obs.searchAliases || null,
|
|
371
352
|
});
|
|
372
353
|
|
|
373
|
-
// Atomic: observation INSERT + observation_files
|
|
354
|
+
// Atomic: observation INSERT + observation_files in one transaction.
|
|
374
355
|
// Column list single-sourced in lib/observation-write (shared with manual mem_save).
|
|
375
356
|
const savedId = db.transaction(() => {
|
|
376
357
|
const id = insertObservationRow(db, {
|
|
@@ -399,7 +380,6 @@ export function saveObservation(obs, projectOverride, sessionIdOverride, externa
|
|
|
399
380
|
});
|
|
400
381
|
|
|
401
382
|
insertObservationFiles(db, id, obs.files);
|
|
402
|
-
insertObservationVector(db, id, buildVecText(obs));
|
|
403
383
|
|
|
404
384
|
return id;
|
|
405
385
|
})();
|
|
@@ -1218,7 +1198,7 @@ ${actionList}`;
|
|
|
1218
1198
|
debugLog('DEBUG', 'llm-episode', `upgrade-delete: obs #${episode.savedId} → event #${savedId}`);
|
|
1219
1199
|
} else {
|
|
1220
1200
|
// Non-event type (e.g. `change`) — upgrade pre-saved observations row in place
|
|
1221
|
-
// so the enriched FTS text field + minhash
|
|
1201
|
+
// so the enriched FTS text field + minhash are refreshed atomically.
|
|
1222
1202
|
const { conceptsText, factsText, textField } = buildFtsTextField(obs);
|
|
1223
1203
|
const minhashSig = computeMinHash((obs.title || '') + ' ' + (obs.narrative || ''));
|
|
1224
1204
|
// Scrub LLM-output text fields at the UPDATE boundary, mirroring the
|
|
@@ -1289,22 +1269,6 @@ ${actionList}`;
|
|
|
1289
1269
|
savedId = episode.savedId;
|
|
1290
1270
|
savedTable = 'observations';
|
|
1291
1271
|
debugLog('DEBUG', 'llm-episode', `upgraded pre-saved obs #${savedId}`);
|
|
1292
|
-
|
|
1293
|
-
// Update TF-IDF vector with enriched content. SQL + text derivation are
|
|
1294
|
-
// lib/observation-write.mjs's (audit 2026-09-02 P1-4); `gate: false` keeps this
|
|
1295
|
-
// path's prior behaviour, which never consulted vectorsEnabled().
|
|
1296
|
-
upsertObservationVector(
|
|
1297
|
-
db,
|
|
1298
|
-
savedId,
|
|
1299
|
-
{
|
|
1300
|
-
title: obs.title,
|
|
1301
|
-
narrative: obs.narrative,
|
|
1302
|
-
concepts: conceptsText,
|
|
1303
|
-
lesson_learned: safe.lesson_learned,
|
|
1304
|
-
search_aliases: safe.search_aliases,
|
|
1305
|
-
},
|
|
1306
|
-
{ gate: false, scope: 'handleLLMEpisode-vector' },
|
|
1307
|
-
);
|
|
1308
1272
|
}
|
|
1309
1273
|
}
|
|
1310
1274
|
} else {
|