claude-mem-lite 3.16.2 → 3.16.3
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/adopt-content.mjs +12 -0
- package/format-utils.mjs +26 -0
- package/hook.mjs +5 -6
- package/lib/citation-tracker.mjs +85 -9
- package/mem-cli.mjs +45 -3
- package/package.json +1 -1
- package/search-scoring.mjs +7 -2
- package/utils.mjs +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.16.
|
|
13
|
+
"version": "3.16.3",
|
|
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.16.
|
|
3
|
+
"version": "3.16.3",
|
|
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/adopt-content.mjs
CHANGED
|
@@ -43,6 +43,8 @@ PreToolUse hooks already run \`mem_recall\` for past lessons before Read/Edit/Wr
|
|
|
43
43
|
| Deferring to a future session | \`mem_defer({title, priority:1|2|3, detail})\`; when fixed, add \`closes_deferred=[N]\` to \`mem_save\` |
|
|
44
44
|
| Looking up past work / history | \`mem_search "keywords"\` · \`mem_recent\` · \`mem_timeline\` |
|
|
45
45
|
|
|
46
|
+
Path cost is round-trips, not milliseconds: the PreToolUse hook above already recalls (0 calls) — prefer it. For an explicit query, if these \`mem_*\` tools are deferred behind ToolSearch this session, the Bash CLI (exact path in the detail doc) is one call vs two (ToolSearch + call).
|
|
47
|
+
|
|
46
48
|
Full tool + CLI tables, citation/decay rules, and save discipline → \`.claude/plugin_claude_mem_lite.md\``;
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -77,6 +79,16 @@ PreToolUse hook 在你 Read / Edit / Write 文件前已自动 \`mem_recall\` 该
|
|
|
77
79
|
\`mem_search\` / \`mem_recent\` / \`mem_recall\` / \`mem_get\` / \`mem_save\` / \`mem_timeline\` +
|
|
78
80
|
\`mem_defer\` / \`mem_defer_list\` / \`mem_defer_drop\`。
|
|
79
81
|
|
|
82
|
+
### 选 MCP 还是 CLI:按 round-trip,不是执行毫秒
|
|
83
|
+
|
|
84
|
+
真正的开销是模型往返次数,不是工具执行——暖 MCP 调用 ~25ms、CLI 冷启 ~90ms,在一次推理(秒级)面前都是噪声。按往返次数选路:
|
|
85
|
+
|
|
86
|
+
1. **被动 hook(0 往返)**:上面的 PreToolUse recall 已自动跑,最快,优先采纳,别重复调。
|
|
87
|
+
2. **CLI via Bash(1 往返)**:工具多的会话里 \`mem_*\` 会被 defer 到 ToolSearch 后面——这时一次 MCP 调用 = ToolSearch + call = **2 往返**,而 Bash 跑一条 CLI 只 **1 往返**。派出去的子 agent 通常也拿不到 \`mem_*\` 工具,CLI 是它唯一的 1-往返路径。用下面「CLI 速查」表里的命令。
|
|
88
|
+
3. **MCP 直调(已加载时 1 往返)**:\`mem_*\` 已在上下文里(未被 defer)就直接调,暖进程执行最快、省掉 ToolSearch。
|
|
89
|
+
|
|
90
|
+
一句话:能让 hook 代劳就别调;要显式查,若得先 ToolSearch 才能用 \`mem_*\`,改跑 CLI。
|
|
91
|
+
|
|
80
92
|
| 时机 | 工具 | 关键参数 |
|
|
81
93
|
|------|------|----------|
|
|
82
94
|
| Edit / Write 前 | \`mem_recall\` | \`file="<路径>"\`(hook 通常已代劳) |
|
package/format-utils.mjs
CHANGED
|
@@ -24,6 +24,32 @@ export function truncate(str, max = 80) {
|
|
|
24
24
|
return str.slice(0, end) + '\u2026';
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Render the PostToolUse error-recall hint block (hook.mjs::triggerErrorRecall).
|
|
29
|
+
* The single most-relevant hit (rows[0]) that carries a lesson_learned gets its
|
|
30
|
+
* lesson INLINED, so the agent can act with zero follow-up round-trips: the old
|
|
31
|
+
* "pointer + mem_get for details" form cost a deferred mem_get (2 model turns in
|
|
32
|
+
* tool-heavy sessions, where mem_* is gated behind ToolSearch) at the exact
|
|
33
|
+
* moment a fix is needed. Later rows stay as #ID pointers to keep the injected
|
|
34
|
+
* payload bounded (one body, not three). Upstream noise gating (low-signal title
|
|
35
|
+
* exclusion) is the SELECT's job (see triggerErrorRecall).
|
|
36
|
+
* @param {Array<{id:number,type:string,title:string,lesson_learned?:string}>} rows
|
|
37
|
+
* @returns {string} stdout block (trailing newline) or '' when there are no rows
|
|
38
|
+
*/
|
|
39
|
+
export function formatErrorRecallHints(rows) {
|
|
40
|
+
if (!rows || rows.length === 0) return '';
|
|
41
|
+
const lines = rows.map((r, i) => {
|
|
42
|
+
const head = ` #${r.id} [${r.type}] ${truncate(r.title, 60)}`;
|
|
43
|
+
// Inline the lesson body for the single most-relevant hit only (bounded payload).
|
|
44
|
+
if (i === 0 && typeof r.lesson_learned === 'string' && r.lesson_learned.trim()) {
|
|
45
|
+
return `${head} \u2014 ${truncate(r.lesson_learned.trim(), 200)}`;
|
|
46
|
+
}
|
|
47
|
+
return head;
|
|
48
|
+
});
|
|
49
|
+
const ids = rows.map(r => r.id).join(',');
|
|
50
|
+
return `[claude-mem-lite] Related memories found for this error:\n${lines.join('\n')}\n \u2192 Use mem_get(ids=[${ids}]) for details.\n`;
|
|
51
|
+
}
|
|
52
|
+
|
|
27
53
|
/**
|
|
28
54
|
* Map observation type to its display emoji icon.
|
|
29
55
|
* @param {string} type Observation type (decision, bugfix, feature, etc.)
|
package/hook.mjs
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
truncate, inferProject, detectBashSignificance,
|
|
27
27
|
extractErrorKeywords, extractFilePaths, isRelatedToEpisode,
|
|
28
28
|
makeEntryDesc, scrubSecrets, stripPrivate, EDIT_TOOLS, debugCatch, debugLog,
|
|
29
|
-
COMPRESSED_AUTO, COMPRESSED_PENDING_PURGE, OBS_BM25,
|
|
29
|
+
COMPRESSED_AUTO, COMPRESSED_PENDING_PURGE, OBS_BM25, notLowSignalTitleClause, formatErrorRecallHints,
|
|
30
30
|
} from './utils.mjs';
|
|
31
31
|
import {
|
|
32
32
|
readEpisodeRaw, episodeFile,
|
|
@@ -363,19 +363,18 @@ function triggerErrorRecall(db, toolInput, response) {
|
|
|
363
363
|
|
|
364
364
|
const nowR = Date.now();
|
|
365
365
|
const rows = db.prepare(`
|
|
366
|
-
SELECT o.id, o.type, o.title
|
|
366
|
+
SELECT o.id, o.type, o.title, o.lesson_learned
|
|
367
367
|
FROM observations_fts
|
|
368
368
|
JOIN observations o ON observations_fts.rowid = o.id
|
|
369
369
|
WHERE observations_fts MATCH ? AND o.project = ?
|
|
370
|
+
AND ${notLowSignalTitleClause('o')}
|
|
370
371
|
ORDER BY ${OBS_BM25}
|
|
371
372
|
* (1.0 + EXP(-0.693 * (? - o.created_at_epoch) / 1209600000.0))
|
|
372
373
|
LIMIT 3
|
|
373
374
|
`).all(ftsQuery, project, nowR);
|
|
374
375
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
process.stdout.write(`[claude-mem-lite] Related memories found for this error:\n${hints}\n → Use mem_get(ids=[${rows.map(r => r.id).join(',')}]) for details.\n`);
|
|
378
|
-
}
|
|
376
|
+
const out = formatErrorRecallHints(rows);
|
|
377
|
+
if (out) process.stdout.write(out);
|
|
379
378
|
} catch (e) { debugCatch(e, 'triggerErrorRecall'); }
|
|
380
379
|
}
|
|
381
380
|
|
package/lib/citation-tracker.mjs
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
// column UPDATE including access_count. Per-row UPDATEs wrapped in try-catch
|
|
11
11
|
// to prevent SQLITE_CORRUPT_VTAB cascades from stopping the whole scan.
|
|
12
12
|
|
|
13
|
-
import { readFileSync, existsSync } from 'fs';
|
|
13
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
14
|
+
import { join } from 'path';
|
|
14
15
|
import { debugCatch } from '../utils.mjs';
|
|
15
16
|
|
|
16
17
|
// `#123` / `#45678` at a word boundary — matches the CLAUDE.md cite pattern.
|
|
@@ -155,6 +156,11 @@ export function bumpCitationAccess(db, ids, project) {
|
|
|
155
156
|
// Bounded type list mirrors observations.type CHECK + the events table's allowed
|
|
156
157
|
// event_type values these surfaces can emit.
|
|
157
158
|
const INJECTED_RE = /#(\d{1,7})\s+\[(bugfix|decision|change|discovery|feature|refactor|lesson)\]/g;
|
|
159
|
+
// Line-anchored variant: a genuine injected ROW begins (after its short indent) with
|
|
160
|
+
// `#NN [type]`. pre-tool-recall AND error-recall inline a lesson_learned body into the
|
|
161
|
+
// row; a body that quotes another obs ("same as #1234 [decision]") must NOT count as
|
|
162
|
+
// injected: that id would pollute the citation-decay denominator and falsely demote.
|
|
163
|
+
const INJECTED_ROW_RE = new RegExp('^\\s{0,6}' + INJECTED_RE.source);
|
|
158
164
|
|
|
159
165
|
// Add a numeric obs id to `set` if it parses to a sane in-range positive int.
|
|
160
166
|
function addObsId(set, raw) {
|
|
@@ -229,9 +235,10 @@ export function extractInjectedFromPreToolUse(transcriptPath, opts = {}) {
|
|
|
229
235
|
const ids = new Set();
|
|
230
236
|
eachHookAttachment(transcriptPath, ({ command, text }) => {
|
|
231
237
|
if (!command.includes('pre-tool-recall')) return;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
for (const line of text.split('\n')) {
|
|
239
|
+
const m = INJECTED_ROW_RE.exec(line);
|
|
240
|
+
if (m) addObsId(ids, m[1]);
|
|
241
|
+
}
|
|
235
242
|
}, opts);
|
|
236
243
|
return ids;
|
|
237
244
|
}
|
|
@@ -293,11 +300,14 @@ export function extractInjectedFromErrorRecall(transcriptPath, opts = {}) {
|
|
|
293
300
|
eachHookAttachment(transcriptPath, ({ command, text }) => {
|
|
294
301
|
if (!command.includes('post-tool-use')) return;
|
|
295
302
|
if (!text.includes('Related memories found for this error')) return;
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
303
|
+
// Per-line anchored: match only a row that STARTS with `#NN [type]` (after its
|
|
304
|
+
// indent), NOT every such token in the block. The inlined lesson body (v3.16.x)
|
|
305
|
+
// can quote another obs id, which must not enter the injected set; the trailing
|
|
306
|
+
// `Use mem_get(ids=[...])` line (bare numbers) is excluded too.
|
|
307
|
+
for (const line of text.split('\n')) {
|
|
308
|
+
const m = INJECTED_ROW_RE.exec(line);
|
|
309
|
+
if (m) addObsId(ids, m[1]);
|
|
310
|
+
}
|
|
301
311
|
}, opts);
|
|
302
312
|
return ids;
|
|
303
313
|
}
|
|
@@ -353,6 +363,72 @@ export function extractAllInjected(transcriptPath, opts = {}) {
|
|
|
353
363
|
]);
|
|
354
364
|
}
|
|
355
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Cite-recall over ONE transcript file, using the SAME methodology as the
|
|
368
|
+
* citation-decay loop: injected = extractAllInjected (OUR hook injections only),
|
|
369
|
+
* cited = #NN in assistant text, ratio = |injected ∩ cited| / |injected|.
|
|
370
|
+
*
|
|
371
|
+
* Thread is keyed by FILE LOCATION, not the isSidechain field. Claude Code writes
|
|
372
|
+
* each subagent's turns to a SEPARATE file (<session>/subagents/agent-*.jsonl),
|
|
373
|
+
* NOT inline in the parent transcript (verified empirically: 0 isSidechain records
|
|
374
|
+
* across 60 parent transcripts; the subagent files carry isSidechain=true). So a
|
|
375
|
+
* whole subagent file IS the sidechain — aggregateProjectCiteRecall splits by path.
|
|
376
|
+
*
|
|
377
|
+
* @param {string} transcriptPath
|
|
378
|
+
* @returns {{injected: number, cited: number, recalled: number, ratio: number}}
|
|
379
|
+
*/
|
|
380
|
+
export function computeThreadCiteRecall(transcriptPath) {
|
|
381
|
+
const injected = extractAllInjected(transcriptPath);
|
|
382
|
+
const cited = extractCitationsFromTranscript(transcriptPath);
|
|
383
|
+
let recalled = 0;
|
|
384
|
+
for (const id of injected) if (cited.has(id)) recalled++;
|
|
385
|
+
return {
|
|
386
|
+
injected: injected.size,
|
|
387
|
+
cited: cited.size,
|
|
388
|
+
recalled,
|
|
389
|
+
ratio: injected.size > 0 ? recalled / injected.size : 0,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Aggregate cite-recall across a project's transcripts, split MAIN vs SIDECHAIN
|
|
395
|
+
* by file location. `txDir` = ~/.claude/projects/<encoded>/. Main = the top-level
|
|
396
|
+
* <session>.jsonl files; sidechain = every <session>/subagents/agent-*.jsonl.
|
|
397
|
+
* Descends ONE level into the literal `subagents` subdir only — no unbounded
|
|
398
|
+
* recursion. mtime-gated by `cutoff` (epoch ms; 0 = no window).
|
|
399
|
+
*
|
|
400
|
+
* @param {string} txDir
|
|
401
|
+
* @param {{cutoff?: number}} [opts]
|
|
402
|
+
* @returns {{main: {injected:number,recalled:number,files:number}, sidechain: {injected:number,recalled:number,files:number,withInjections:number}}}
|
|
403
|
+
*/
|
|
404
|
+
export function aggregateProjectCiteRecall(txDir, { cutoff = 0 } = {}) {
|
|
405
|
+
const main = { injected: 0, recalled: 0, files: 0 };
|
|
406
|
+
const sidechain = { injected: 0, recalled: 0, files: 0, withInjections: 0 };
|
|
407
|
+
const within = (p) => { try { return statSync(p).mtimeMs >= cutoff; } catch { return false; } };
|
|
408
|
+
let entries;
|
|
409
|
+
try { entries = readdirSync(txDir, { withFileTypes: true }); } catch { return { main, sidechain }; }
|
|
410
|
+
for (const ent of entries) {
|
|
411
|
+
const full = join(txDir, ent.name);
|
|
412
|
+
if (ent.isFile() && ent.name.endsWith('.jsonl')) {
|
|
413
|
+
if (!within(full)) continue;
|
|
414
|
+
const r = computeThreadCiteRecall(full);
|
|
415
|
+
main.injected += r.injected; main.recalled += r.recalled; main.files++;
|
|
416
|
+
} else if (ent.isDirectory()) {
|
|
417
|
+
let subFiles;
|
|
418
|
+
try { subFiles = readdirSync(join(full, 'subagents')); } catch { continue; }
|
|
419
|
+
for (const sf of subFiles) {
|
|
420
|
+
if (!sf.endsWith('.jsonl')) continue;
|
|
421
|
+
const sp = join(full, 'subagents', sf);
|
|
422
|
+
if (!within(sp)) continue;
|
|
423
|
+
const r = computeThreadCiteRecall(sp);
|
|
424
|
+
sidechain.injected += r.injected; sidechain.recalled += r.recalled; sidechain.files++;
|
|
425
|
+
if (r.injected > 0) sidechain.withInjections++;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return { main, sidechain };
|
|
430
|
+
}
|
|
431
|
+
|
|
356
432
|
/**
|
|
357
433
|
* True iff the transcript contains at least one non-whitespace text block from
|
|
358
434
|
* a main-thread assistant turn. Gates the citation-decay loop so a tool-only
|
package/mem-cli.mjs
CHANGED
|
@@ -26,8 +26,9 @@ import { buildSessionContextLines } from './hook-context.mjs';
|
|
|
26
26
|
import { cmdAdopt, cmdUnadopt } from './adopt-cli.mjs';
|
|
27
27
|
import { parseIntFlag, isNumericToken } from './lib/cli-flags.mjs';
|
|
28
28
|
import { auditMemdir, memdirPath } from './memdir.mjs';
|
|
29
|
+
import { aggregateProjectCiteRecall } from './lib/citation-tracker.mjs';
|
|
29
30
|
import { probeOtherSources as probeIdSources, bucketIdTokens } from './lib/id-routing.mjs';
|
|
30
|
-
import { join, sep } from 'path';
|
|
31
|
+
import { join, sep, dirname } from 'path';
|
|
31
32
|
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
32
33
|
|
|
33
34
|
// v2.41: shared CLI helpers extracted to cli/common.mjs. Keep this file as the
|
|
@@ -2210,20 +2211,61 @@ function cmdMemdirAudit(args) {
|
|
|
2210
2211
|
if (nonCompliant > 0) process.exitCode = 1;
|
|
2211
2212
|
}
|
|
2212
2213
|
|
|
2214
|
+
// `citation-stats --sidechain`: subagent (sidechain) cite-recall, the blind spot the
|
|
2215
|
+
// main decay loop excludes (it runs mainOnly). aggregateProjectCiteRecall scans THIS
|
|
2216
|
+
// project's transcripts: top-level <session>.jsonl = main, and
|
|
2217
|
+
// <session>/subagents/agent-*.jsonl = sidechain (descends ONE level into the literal
|
|
2218
|
+
// subagents/ dir only, no unbounded recursion). Same methodology, so comparable.
|
|
2219
|
+
function _reportSidechainCiteRecall({ days, json }) {
|
|
2220
|
+
const cutoff = Date.now() - days * 86400 * 1000;
|
|
2221
|
+
// memdir = ~/.claude/projects/<encoded>/memory; transcripts are its siblings.
|
|
2222
|
+
const txDir = dirname(memdirPath(process.cwd()));
|
|
2223
|
+
const { main, sidechain } = aggregateProjectCiteRecall(txDir, { cutoff });
|
|
2224
|
+
const rate = b => (b.injected > 0 ? (100 * b.recalled / b.injected) : null);
|
|
2225
|
+
const sideRate = rate(sidechain), mainRate = rate(main);
|
|
2226
|
+
|
|
2227
|
+
if (json) {
|
|
2228
|
+
out(JSON.stringify({
|
|
2229
|
+
window_days: days,
|
|
2230
|
+
main: { ...main, rate: mainRate },
|
|
2231
|
+
sidechain: { ...sidechain, rate: sideRate },
|
|
2232
|
+
}));
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
const pct = r => (r === null ? '—' : `${r.toFixed(1)}%`);
|
|
2237
|
+
out(`Sidechain (subagent) cite-recall — last ${days}d:`);
|
|
2238
|
+
out(` main ${pct(mainRate).padStart(6)} recalled ${main.recalled} / injected ${main.injected} (${main.files} transcript(s))`);
|
|
2239
|
+
out(` sidechain ${pct(sideRate).padStart(6)} recalled ${sidechain.recalled} / injected ${sidechain.injected} (${sidechain.files} subagent file(s), ${sidechain.withInjections} with injections)`);
|
|
2240
|
+
if (sidechain.files > 0 && sidechain.injected === 0) {
|
|
2241
|
+
out(' → subagent transcripts exist but received ZERO memory injections: claude-mem-lite');
|
|
2242
|
+
out(' hooks do NOT fire inside subagents (no PreToolUse/PostToolUse recall, no');
|
|
2243
|
+
out(' SessionStart block, no mem_* tools). Subagents are memory-blind — giving them');
|
|
2244
|
+
out(' memory needs a NEW surface (inject at Agent/Task dispatch), not deepening.');
|
|
2245
|
+
} else if (sidechain.files === 0) {
|
|
2246
|
+
out(' → no subagent transcripts in window.');
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2213
2250
|
/**
|
|
2214
2251
|
* `citation-stats` — visualize the citation-decay feedback loop:
|
|
2215
2252
|
* per-project cite rate + active decay queue + recently promoted.
|
|
2216
2253
|
* Read-only over observations.
|
|
2217
2254
|
*
|
|
2218
2255
|
* Flags:
|
|
2219
|
-
* --json
|
|
2220
|
-
* --days N
|
|
2256
|
+
* --json machine-readable output
|
|
2257
|
+
* --days N project cite-rate window (default 7)
|
|
2258
|
+
* --sidechain subagent (sidechain) cite-recall vs main — the decay-loop blind spot
|
|
2221
2259
|
*/
|
|
2222
2260
|
function cmdCitationStats(db, args) {
|
|
2223
2261
|
const { flags } = parseArgs(args);
|
|
2224
2262
|
const json = flags.json === true || flags.json === 'true';
|
|
2225
2263
|
const days = parseIntFlag(flags.days, { name: '--days', defaultValue: 7, max: 365 });
|
|
2226
2264
|
|
|
2265
|
+
if (flags.sidechain === true || flags.sidechain === 'true') {
|
|
2266
|
+
return _reportSidechainCiteRecall({ days, json });
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2227
2269
|
const cutoff = Date.now() - days * 86400 * 1000;
|
|
2228
2270
|
const perProject = db.prepare(`
|
|
2229
2271
|
SELECT project,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.16.
|
|
3
|
+
"version": "3.16.3",
|
|
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",
|
package/search-scoring.mjs
CHANGED
|
@@ -15,9 +15,14 @@ import { CLI_INVOKE } from './cli-path.mjs';
|
|
|
15
15
|
// Decision-rules sections; keeps the irreducible CLI/MCP tool list. Intended
|
|
16
16
|
// for users who adopted invited-memory (MEMORY.md sentinel carries the same
|
|
17
17
|
// triggers at higher authority). Default false preserves v2.31.2 behavior.
|
|
18
|
+
// The CLI-vs-MCP round-trip routing rule lives in BASE (not VERBOSE) on purpose:
|
|
19
|
+
// it must reach adopted (quiet) projects too, where tool-heavy sessions defer the
|
|
20
|
+
// mem_* tools behind ToolSearch (CLI via Bash = 1 model round-trip; ToolSearch +
|
|
21
|
+
// call = 2). Execution latency is NOT the lever — warm MCP (~25ms) actually beats
|
|
22
|
+
// CLI cold-start (~90ms); both are noise against one model turn. Round-trips are.
|
|
18
23
|
|
|
19
24
|
const INSTRUCTIONS_BASE = [
|
|
20
|
-
'Long-term memory across sessions. Hooks auto-inject context
|
|
25
|
+
'Long-term memory across sessions. Hooks auto-inject context (0 round-trips) — prefer adopting that over any call. For an explicit query, pick the path with fewer model round-trips (CLI vs MCP below).',
|
|
21
26
|
'',
|
|
22
27
|
`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\`):`,
|
|
23
28
|
` ${CLI_INVOKE} search "query" — FTS5 full-text search`,
|
|
@@ -27,7 +32,7 @@ const INSTRUCTIONS_BASE = [
|
|
|
27
32
|
` ${CLI_INVOKE} get 42,43 — full details by ID`,
|
|
28
33
|
` ${CLI_INVOKE} timeline --anchor 42 — chronological context`,
|
|
29
34
|
'',
|
|
30
|
-
'MCP tools: mem_search, mem_recent, mem_save, mem_get, mem_recall, mem_timeline
|
|
35
|
+
'MCP tools: mem_search, mem_recent, mem_save, mem_get, mem_recall, mem_timeline. If already loaded, call directly (warm server, fastest path). In tool-heavy sessions these are deferred behind ToolSearch — if using one would cost a ToolSearch load first, run the Bash CLI above instead: one call, not two. Neither needs a PATH/CLI install.',
|
|
31
36
|
'mem_save: Save non-obvious insights (bugfix lessons, architecture decisions).',
|
|
32
37
|
'Search tips: short keywords (2-3 words), filter with obs_type when relevant.',
|
|
33
38
|
];
|
package/utils.mjs
CHANGED
|
@@ -14,7 +14,7 @@ export { cjkBigrams, extractCjkSynonymTokens, extractCjkKeywords, extractCjkLike
|
|
|
14
14
|
export { resolveProject, _resetProjectCache } from './project-utils.mjs';
|
|
15
15
|
export { scrubSecrets, SECRET_PATTERNS } from './secret-scrub.mjs';
|
|
16
16
|
export { stripPrivate } from './lib/private-strip.mjs';
|
|
17
|
-
export { truncate, typeIcon, fmtDate, fmtTime, isoWeekKey } from './format-utils.mjs';
|
|
17
|
+
export { truncate, typeIcon, fmtDate, fmtTime, isoWeekKey, formatErrorRecallHints } from './format-utils.mjs';
|
|
18
18
|
export { computeMinHash, estimateJaccardFromMinHash, jaccardSimilarity } from './hash-utils.mjs';
|
|
19
19
|
export { detectBashSignificance, extractErrorKeywords, extractFilePaths, stripTestSuffix } from './bash-utils.mjs';
|
|
20
20
|
|