claude-mem-lite 3.81.0 → 3.82.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/hook-memory.mjs +60 -2
- package/lib/citation-tracker.mjs +30 -16
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.
|
|
13
|
+
"version": "3.82.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.
|
|
3
|
+
"version": "3.82.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/hook-memory.mjs
CHANGED
|
@@ -412,6 +412,57 @@ export function searchRelevantMemories(db, userPrompt, project, excludeIds = [])
|
|
|
412
412
|
// injection query is guarded by the subprocess cases in
|
|
413
413
|
// tests/pre-tool-recall.test.mjs. Do not reintroduce an in-process twin here.
|
|
414
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Upper bound on the imperative candidate pool. This is an OOM/latency BACKSTOP, not a
|
|
417
|
+
* ranking gate — read that literally before changing it (D#172, authorised 2026-08-25).
|
|
418
|
+
*
|
|
419
|
+
* The bound is applied in SQL, i.e. BEFORE the identifier-overlap filter below, so
|
|
420
|
+
* whatever it is set to is a hard REACHABILITY bound: a lesson outside the window cannot
|
|
421
|
+
* be picked however well it matches the prompt. At its original value of 50 that made
|
|
422
|
+
* this face reachable only from the 50 newest `importance >= 2` rows, and in five
|
|
423
|
+
* projects on this machine the importance=3 population ALONE exceeds 50 — so every
|
|
424
|
+
* importance=2 lesson in those projects was structurally unreachable, and a
|
|
425
|
+
* citation-decay demotion 3->2 EVICTED a row from the pool instead of down-ranking it.
|
|
426
|
+
* That eviction loop is the risk D#172 was filed on; raising the bound above any
|
|
427
|
+
* plausible per-project population is what closes it, because a 3->2 demotion then only
|
|
428
|
+
* changes the row's score multiplier, which is what the decay design intends.
|
|
429
|
+
*
|
|
430
|
+
* COUNT THE POPULATION WITH THE POOL'S OWN FILTER. Those figures are
|
|
431
|
+
* `liveObsFilterSql` + the `importance >= 2` + non-empty-lesson gates, i.e. what the query
|
|
432
|
+
* below can actually return — 327 / 121 / 56 / 53 / 51 for projects--mem, code-graph-mcp,
|
|
433
|
+
* ubuntu-sec, daagu, agentsmd. The first version of this note published the RAW
|
|
434
|
+
* importance=3 counts instead (365 / 131 / 62 / 69 / 51), which include superseded and
|
|
435
|
+
* compressed rows the pool can never see and overstated one project by a third; the
|
|
436
|
+
* pre-tag review caught it, and caught that those wrong numbers had replaced correct ones
|
|
437
|
+
* in lib/citation-tracker.mjs. Re-measure with `node benchmark/imperative-pool-replay.mjs
|
|
438
|
+
* --population`, never with a bare `SELECT ... WHERE importance = 3`.
|
|
439
|
+
*
|
|
440
|
+
* 3->2 IS NOW A DOWN-RANK; 2->1 IS STILL AN EVICTION. The pool gate is
|
|
441
|
+
* `COALESCE(importance, 1) >= 2`, so a row demoted to the IMPORTANCE_FLOOR of 1 leaves
|
|
442
|
+
* this face's reach until some other face cites it back up. Widening the bound is also
|
|
443
|
+
* what first makes importance=2 rows reachable here (56 of projects--mem's 383 eligible),
|
|
444
|
+
* so it creates the injections that can walk one down to 1. Measured exposure: of the
|
|
445
|
+
* picks the widening newly surfaces, one is importance=2 — `score = importance x overlap`
|
|
446
|
+
* keeps importance=3 rows ahead nearly always — so this is a known small edge, not a
|
|
447
|
+
* closed loop.
|
|
448
|
+
*
|
|
449
|
+
* MEASURED, and reproducible: `node benchmark/imperative-pool-replay.mjs`. Over 373 real
|
|
450
|
+
* user prompts replayed against their OWN project's live corpus (85 produced a candidate
|
|
451
|
+
* at all), the 50-row bound destroyed 7 picks outright (8.2%) and changed the top-1 in 3
|
|
452
|
+
* of 78 (3.8%). Small n, so the load-bearing argument is not that one: the wide pool is a
|
|
453
|
+
* SUPERSET of the narrow one, so its top-1 score is always >= the narrow one's and a
|
|
454
|
+
* stable sort keeps the incumbent on a tie — a different pick therefore always means a
|
|
455
|
+
* strictly higher score under this face's own objective. That harness attacks the claim on
|
|
456
|
+
* every prompt and exits non-zero on a counterexample; it currently finds none.
|
|
457
|
+
*
|
|
458
|
+
* COST, from the same harness against projects--mem (383 eligible rows under the shipped
|
|
459
|
+
* predicate): 0.44 ms/prompt at 50, 1.50 ms/prompt at 5000, on a UserPromptSubmit path
|
|
460
|
+
* budgeted in seconds. A synthetic 8000-row pool measured 8.3 ms/prompt, so the bound is
|
|
461
|
+
* doing real work at the top of its range and should not be raised casually. It is ~13x
|
|
462
|
+
* the largest eligible population on this machine.
|
|
463
|
+
*/
|
|
464
|
+
export const IMPERATIVE_POOL_BACKSTOP = 5000;
|
|
465
|
+
|
|
415
466
|
/**
|
|
416
467
|
* Phase-2 task-imperative ranking (spec 2026-06-29 §4.1): score every candidate lesson
|
|
417
468
|
* relevant to THIS prompt (importance>=2 + non-empty lesson + identifier overlap with the
|
|
@@ -428,6 +479,13 @@ export function rankImperativeCandidates(db, userPrompt, project, excludeIds = [
|
|
|
428
479
|
if (promptIdents.size === 0) return []; // no symbol anchor → no imperative (precision-first)
|
|
429
480
|
const exclude = new Set(excludeIds);
|
|
430
481
|
let rows;
|
|
482
|
+
// The ORDER BY ends in `id DESC` to make it a TOTAL order. Without that tiebreaker two
|
|
483
|
+
// rows sharing (importance, created_at_epoch) may come back in either relative order,
|
|
484
|
+
// and SQLite is free to use a bounded top-N sorter at a small LIMIT and a full sort at a
|
|
485
|
+
// large one — so "the narrow pool is a prefix of the wide pool", which the v3.82.0
|
|
486
|
+
// widening argument rests on, was an empirical property of this corpus rather than a
|
|
487
|
+
// guaranteed one. There are zero such collisions live, so it changes no behaviour here;
|
|
488
|
+
// it makes the guarantee hold on corpora nobody has seen.
|
|
431
489
|
try {
|
|
432
490
|
rows = db.prepare(`
|
|
433
491
|
SELECT id, title, lesson_learned, importance
|
|
@@ -439,8 +497,8 @@ export function rankImperativeCandidates(db, userPrompt, project, excludeIds = [
|
|
|
439
497
|
AND TRIM(lesson_learned) != ''
|
|
440
498
|
AND LOWER(TRIM(lesson_learned)) != 'none'
|
|
441
499
|
AND (? IS NULL OR created_at_epoch <= ?)
|
|
442
|
-
ORDER BY importance DESC, created_at_epoch DESC
|
|
443
|
-
LIMIT
|
|
500
|
+
ORDER BY importance DESC, created_at_epoch DESC, id DESC
|
|
501
|
+
LIMIT ${IMPERATIVE_POOL_BACKSTOP}
|
|
444
502
|
`).all(project, epochTo, epochTo);
|
|
445
503
|
} catch { return []; }
|
|
446
504
|
const out = [];
|
package/lib/citation-tracker.mjs
CHANGED
|
@@ -586,19 +586,31 @@ export function extractAllInjected(transcriptPath, opts = {}) {
|
|
|
586
586
|
* The product's own CLI calls that state "Active decay queue (uncited_streak >= 2, next
|
|
587
587
|
* miss -> demote)"; a release note claiming three sessions of margin contradicted it.
|
|
588
588
|
*
|
|
589
|
-
* THE RESIDUAL RISK
|
|
590
|
-
* importance=3,
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
589
|
+
* THE RESIDUAL RISK — CLOSED 2026-08-25, in rankImperativeCandidates rather than here.
|
|
590
|
+
* All 22 marginal rows are importance=3, and the pool took `LIMIT 50` ordered by
|
|
591
|
+
* importance DESC, so in the five largest projects (projects--mem 327, code-graph-mcp
|
|
592
|
+
* 121, ubuntu-sec 56, daagu 53, agentsmd 51 — counted with the pool's OWN
|
|
593
|
+
* `liveObsFilterSql`, which is the only count that means anything here) the imp=3
|
|
594
|
+
* population alone exceeded the limit: a demotion 3->2 EVICTED a lesson from this face's
|
|
595
|
+
* candidate pool rather than down-ranking it — a feedback loop the four original
|
|
596
|
+
* denominator faces do not have, because they select on FTS relevance.
|
|
597
|
+
*
|
|
598
|
+
* Measuring it turned up a bigger fact than the one filed. The limit sits in SQL,
|
|
599
|
+
* BEFORE the identifier-overlap filter, so it was never a ranking bound at all: the
|
|
600
|
+
* face could only ever pick from the 50 newest `importance >= 2` rows, which made every
|
|
601
|
+
* importance=2 lesson in those five projects unreachable no matter how well it matched.
|
|
602
|
+
* Replaying 373 real prompts against their own project's corpus
|
|
603
|
+
* (`benchmark/imperative-pool-replay.mjs`), the cap destroyed 7 of 85 picks outright and
|
|
604
|
+
* changed the top-1 in 3 of 78. The bound is now IMPERATIVE_POOL_BACKSTOP = 5000,
|
|
605
|
+
* documented there as an OOM backstop and not a relevance gate.
|
|
606
|
+
*
|
|
607
|
+
* A 3->2 demotion is a down-rank again. A 2->1 demotion is still an EVICTION, because
|
|
608
|
+
* the pool gate is `>= 2` and IMPORTANCE_FLOOR is 1 — and widening is what first makes
|
|
609
|
+
* importance=2 rows reachable by this face at all, so it creates the injections that can
|
|
610
|
+
* walk one there. Measured exposure is one row; see the constant's docblock. `subagent`
|
|
611
|
+
* shares that pool and inherits both halves. Still open in D#172: admitting `subagent`
|
|
612
|
+
* to the denominator, which is a separate decision needing the receiver-attributed cites
|
|
613
|
+
* merged asymmetrically.
|
|
602
614
|
*/
|
|
603
615
|
const DECAY_EXCLUDED_SURFACES = new Set();
|
|
604
616
|
|
|
@@ -633,9 +645,11 @@ export const DECAY_DENOMINATOR_SURFACES = ATTACHMENT_SURFACES.filter((f) => !DEC
|
|
|
633
645
|
* sampled (0.28%), or 3 of 1935 (0.16%) if you widen to every subagent-bearing
|
|
634
646
|
* session in the corpus; state which denominator you mean, the phrase "main-face
|
|
635
647
|
* ids" alone does not pin it; (2) the 33 rows it would newly add cite at 15.2% and are
|
|
636
|
-
* 94% importance=3
|
|
637
|
-
* `task_imperative` above,
|
|
638
|
-
*
|
|
648
|
+
* 94% importance=3. That second cost was the LIMIT-50 eviction loop described under
|
|
649
|
+
* `task_imperative` above, shared because both faces select through
|
|
650
|
+
* rankImperativeCandidates — CLOSED 2026-08-25 by raising that pool's bound out of
|
|
651
|
+
* relevance range, so it is no longer an argument against admitting this face. Three
|
|
652
|
+
* of those 33 would still demote over that corpus, as down-ranks rather than evictions.
|
|
639
653
|
*
|
|
640
654
|
* Its sibling was admitted on 2026-08-25; this one deliberately was NOT, and the
|
|
641
655
|
* difference is not the rate. task_imperative needed one line and no change to
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.82.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "claude-mem-lite",
|
|
9
|
-
"version": "3.
|
|
9
|
+
"version": "3.82.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
12
12
|
"better-sqlite3": "^12.6.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.82.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
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.9.2",
|