claude-mem-lite 3.6.0 → 3.7.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 +21 -13
- package/README.zh-CN.md +1 -1
- package/deep-search.mjs +26 -4
- package/hook-update.mjs +17 -1
- package/hook.mjs +403 -373
- package/install.mjs +691 -639
- package/lib/atomic-write.mjs +38 -0
- package/lib/doctor-benchmark.mjs +4 -4
- package/lib/err-sampler.mjs +7 -3
- package/lib/lesson-idents.mjs +32 -0
- package/lib/proc-lock.mjs +112 -0
- package/lib/search-core.mjs +272 -16
- package/mem-cli.mjs +56 -175
- package/package.json +6 -2
- package/schema.mjs +119 -65
- package/scoring-sql.mjs +25 -0
- package/scripts/post-tool-recall.js +71 -0
- package/scripts/pre-tool-recall.js +27 -2
- package/search-engine.mjs +1 -1
- package/{server-internals.mjs → search-scoring.mjs} +6 -2
- package/server.mjs +85 -295
- package/source-files.mjs +11 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.
|
|
13
|
+
"version": "3.7.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.7.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
|
@@ -144,7 +144,7 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
|
|
|
144
144
|
|
|
145
145
|
## Requirements
|
|
146
146
|
|
|
147
|
-
- **Node.js** >=
|
|
147
|
+
- **Node.js** >= 20
|
|
148
148
|
- **Claude Code** CLI installed and configured (`claude` command available)
|
|
149
149
|
- **SQLite3** support (provided by `better-sqlite3`, compiled on install)
|
|
150
150
|
- **Platform**: Linux or macOS (see [Platform Support](#platform-support))
|
|
@@ -582,7 +582,7 @@ claude-mem-lite/
|
|
|
582
582
|
commands/
|
|
583
583
|
mem.md # /mem command definition
|
|
584
584
|
server.mjs # MCP server: tool definitions, FTS5 search, database init
|
|
585
|
-
|
|
585
|
+
search-scoring.mjs # Extracted search helpers: re-ranking, PRF, concept expansion
|
|
586
586
|
hook.mjs # Claude Code hooks: episode capture, error recall, session management
|
|
587
587
|
hook-llm.mjs # Background LLM workers: episode extraction, session summaries
|
|
588
588
|
hook-shared.mjs # Shared hook infrastructure: session management, DB access, LLM calls
|
|
@@ -632,17 +632,25 @@ claude-mem-lite/
|
|
|
632
632
|
|
|
633
633
|
## Search Quality
|
|
634
634
|
|
|
635
|
-
Benchmarked on 200 observations across 30 queries (standard + hard-negative categories)
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
|
641
|
-
|
|
642
|
-
|
|
|
643
|
-
|
|
|
644
|
-
|
|
645
|
-
|
|
635
|
+
Benchmarked on 200 observations across 30 queries (standard + hard-negative categories),
|
|
636
|
+
measuring the **production-hybrid** retriever (FTS5 BM25 + TF-IDF vector + RRF) — the path
|
|
637
|
+
`mem_search` / `recall` actually use. The CI gate (`npm run benchmark:gate`) runs this same
|
|
638
|
+
path and fails on regression.
|
|
639
|
+
|
|
640
|
+
| Metric | Score (production-hybrid) |
|
|
641
|
+
|--------|---------------------------|
|
|
642
|
+
| Recall@10 | 0.90 |
|
|
643
|
+
| Precision@10 | 0.79 |
|
|
644
|
+
| nDCG@10 | 0.97 |
|
|
645
|
+
| MRR@10 | 0.97 |
|
|
646
|
+
| P95 search latency | ~3ms |
|
|
647
|
+
|
|
648
|
+
> **Note on the path measured.** Earlier versions of this table reported the *lexical*
|
|
649
|
+
> FTS-only path (Precision@10 0.96, P95 0.15ms). The hybrid vector arm trades raw
|
|
650
|
+
> precision@10 for higher recall / nDCG / MRR by surfacing semantically-related candidates
|
|
651
|
+
> beyond exact lexical matches; the gate now measures the hybrid path so these numbers
|
|
652
|
+
> reflect real `mem_search` behavior. For field-comparable recall, see the LongMemEval
|
|
653
|
+
> section below.
|
|
646
654
|
|
|
647
655
|
### Recall on LongMemEval (standard benchmark)
|
|
648
656
|
|
package/README.zh-CN.md
CHANGED
|
@@ -524,7 +524,7 @@ claude-mem-lite/
|
|
|
524
524
|
commands/
|
|
525
525
|
mem.md # /mem 命令定义
|
|
526
526
|
server.mjs # MCP 服务器:工具定义、FTS5 搜索、数据库初始化
|
|
527
|
-
|
|
527
|
+
search-scoring.mjs # 搜索辅助模块:重排序、PRF、概念扩展
|
|
528
528
|
hook.mjs # Claude Code 钩子:episode 捕获、错误回忆、会话管理
|
|
529
529
|
hook-llm.mjs # 后台 LLM worker:episode 提取、会话摘要
|
|
530
530
|
hook-shared.mjs # 共享钩子基础设施:会话管理、数据库访问、LLM 调用
|
package/deep-search.mjs
CHANGED
|
@@ -103,7 +103,9 @@ export function autoDeepLlmReady(env = process.env, injectedLlm) {
|
|
|
103
103
|
* an LLM, so the decision itself is free — only a positive verdict costs a
|
|
104
104
|
* Haiku call (the escalation).
|
|
105
105
|
*
|
|
106
|
-
* Weak when: too few results (count below minResults floor)
|
|
106
|
+
* Weak when: too few results (count below minResults floor) AND the corpus is
|
|
107
|
+
* large enough that deep search could plausibly find more (see corpus guard
|
|
108
|
+
* below).
|
|
107
109
|
*
|
|
108
110
|
* NOTE: ctx.orFallbackFired was intentionally removed as an escalation trigger.
|
|
109
111
|
* orFallbackFired fires on SUCCESSFUL AND→OR recovery — when the fallback
|
|
@@ -114,17 +116,37 @@ export function autoDeepLlmReady(env = process.env, injectedLlm) {
|
|
|
114
116
|
* fails, OR also fails) is still caught: if OR recovers nothing, count is 0-2
|
|
115
117
|
* → escalates on count alone.
|
|
116
118
|
*
|
|
119
|
+
* Corpus guard (folded in): the count-based trigger above is correct for a real
|
|
120
|
+
* corpus, but on a near-empty / brand-new / benchmark project EVERY 0-hit query
|
|
121
|
+
* looks "weak", so a caller that only checks the count would auto-escalate (and
|
|
122
|
+
* fire a Haiku rewrite) on a store HyDE/multi-query can't possibly rescue — the
|
|
123
|
+
* "[mem] auto-escalated … 0 hits" spam. hasEscalatableCorpus used to be a
|
|
124
|
+
* SEPARATE function each caller had to remember to AND in; folding it in here
|
|
125
|
+
* means passing `db` self-suppresses escalation when the corpus is too small,
|
|
126
|
+
* without changing the (correct) count trigger for real corpora. Backward-
|
|
127
|
+
* compatible: callers that omit `db` keep the pure count behaviour (and may
|
|
128
|
+
* still AND hasEscalatableCorpus themselves — double-gating with the same
|
|
129
|
+
* predicate is idempotent, never a regression).
|
|
130
|
+
*
|
|
117
131
|
* @param {Array} results normal-search rows
|
|
118
132
|
* @param {object} ctx the hybrid ctx the engine mutated (unused; kept for
|
|
119
133
|
* backward-compat with callers that pass it)
|
|
120
134
|
* @param {object} [opts]
|
|
121
135
|
* @param {number} [opts.minResults=AUTO_DEEP_MIN_RESULTS]
|
|
136
|
+
* @param {Database} [opts.db] open handle — when given, the corpus-size guard is
|
|
137
|
+
* evaluated here so escalation is suppressed on a
|
|
138
|
+
* too-small store. Omit to keep pure count behaviour.
|
|
139
|
+
* @param {string} [opts.project] project scope for the corpus count (when db given)
|
|
140
|
+
* @param {number} [opts.minCorpus=AUTO_DEEP_MIN_CORPUS] corpus-size floor (when db given)
|
|
122
141
|
* @returns {boolean}
|
|
123
142
|
*/
|
|
124
|
-
export function shouldEscalateToDeep(results, _ctx, { minResults = AUTO_DEEP_MIN_RESULTS } = {}) {
|
|
143
|
+
export function shouldEscalateToDeep(results, _ctx, { minResults = AUTO_DEEP_MIN_RESULTS, db, project = null, minCorpus = AUTO_DEEP_MIN_CORPUS } = {}) {
|
|
125
144
|
const n = Array.isArray(results) ? results.length : 0;
|
|
126
|
-
if (n
|
|
127
|
-
|
|
145
|
+
if (n >= minResults) return false;
|
|
146
|
+
// Count is weak. If a db was supplied, also require an escalatable corpus —
|
|
147
|
+
// this is the fold-in that stops 0-hit escalation on a near-empty store.
|
|
148
|
+
if (db && !hasEscalatableCorpus(db, project, minCorpus)) return false;
|
|
149
|
+
return true;
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
/**
|
package/hook-update.mjs
CHANGED
|
@@ -13,6 +13,8 @@ import { debugCatch, debugLog } from './utils.mjs';
|
|
|
13
13
|
// extracted tarball's own source-files.mjs inside installExtractedRelease.
|
|
14
14
|
// See loadReleaseManifest below.
|
|
15
15
|
import { SOURCE_FILES as LOCAL_SOURCE_FILES, HOOK_SCRIPT_FILES as LOCAL_HOOK_SCRIPT_FILES } from './source-files.mjs';
|
|
16
|
+
import { acquireLock } from './lib/proc-lock.mjs';
|
|
17
|
+
import { atomicWriteFileSync } from './lib/atomic-write.mjs';
|
|
16
18
|
|
|
17
19
|
// ── Configuration ──────────────────────────────────────────
|
|
18
20
|
const GITHUB_REPO = 'sdsrss/claude-mem-lite';
|
|
@@ -379,6 +381,16 @@ export function validateExtractedTarball(sourceDir, expectedVersion, expectedNam
|
|
|
379
381
|
// the target's node_modules untouched. Dependency bumps still flow through the
|
|
380
382
|
// GitHub-tarball path (downloadAndInstall), which keeps skipNpmInstall=false.
|
|
381
383
|
export async function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR, opts = {}) {
|
|
384
|
+
// Cross-process lock: concurrent SessionStart self-heals / auto-updates must
|
|
385
|
+
// not interleave the rename loop below (→ mixed-version install). A live peer
|
|
386
|
+
// holding the lock means an install is already in flight — skip rather than
|
|
387
|
+
// race. Shared path with install.mjs so direct install + repair + auto-update
|
|
388
|
+
// are mutually exclusive.
|
|
389
|
+
const release = acquireLock(join(STATE_DIR, 'runtime', 'install.lock'));
|
|
390
|
+
if (!release) {
|
|
391
|
+
debugLog('DEBUG', 'hook-update', 'installExtractedRelease: another install/update is in progress — skipping');
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
382
394
|
const ts = `${Date.now()}-${process.pid}`;
|
|
383
395
|
const stagingDir = join(targetDir, `.update-staging-${ts}`);
|
|
384
396
|
const backupDir = join(targetDir, `.update-backup-${ts}`);
|
|
@@ -439,7 +451,9 @@ export async function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR
|
|
|
439
451
|
debugLog('DEBUG', 'hook-update', `Post-update: removed stale global MCP "${k}"`);
|
|
440
452
|
}
|
|
441
453
|
}
|
|
442
|
-
|
|
454
|
+
// Atomic + one-time backup: ~/.claude.json is the user's ENTIRE Claude
|
|
455
|
+
// Code config; a torn write here breaks them outside our control.
|
|
456
|
+
if (changed) atomicWriteFileSync(claudeJsonPath, JSON.stringify(cfg, null, 2) + '\n', { backup: true });
|
|
443
457
|
}
|
|
444
458
|
} catch (e) { debugCatch(e, 'post-update-mcp-dedup'); }
|
|
445
459
|
|
|
@@ -477,6 +491,8 @@ export async function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR
|
|
|
477
491
|
try { rmSync(stagingDir, { recursive: true, force: true }); } catch {}
|
|
478
492
|
try { rmSync(backupDir, { recursive: true, force: true }); } catch {}
|
|
479
493
|
return false;
|
|
494
|
+
} finally {
|
|
495
|
+
release();
|
|
480
496
|
}
|
|
481
497
|
}
|
|
482
498
|
|