claude-mem-lite 2.34.2 → 2.34.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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "2.34.2",
13
+ "version": "2.34.3",
14
14
  "source": "./",
15
15
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall"
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.34.2",
3
+ "version": "2.34.3",
4
4
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.34.2",
3
+ "version": "2.34.3",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "engines": {
@@ -16,18 +16,18 @@ const INJECTED_IDS_FILE = join(DB_DIR, 'runtime', `.claude-mem-injected-${inferP
16
16
  const MAX_RESULTS = 5;
17
17
  const LOOKBACK_MS = 60 * 86400000; // 60 days
18
18
 
19
- // T3 (v2.31): BM25 magnitude threshold. OBS_BM25 (in scoring-sql.mjs) returns the
20
- // raw bm25() value, which in SQLite FTS5 is always negative lower = better match.
21
- // The `relevance` column multiplies that negative bm25 by positive decay / type /
22
- // importance weights, keeping the sign negative. "Stronger match" therefore means
23
- // larger magnitude, so we compare against `Math.abs(relevance)`.
19
+ // T3 (v2.31): per-row BM25 magnitude floor. OBS_BM25 (in scoring-sql.mjs)
20
+ // returns the raw bm25() value negative, smaller = better. Multiplied by
21
+ // decay × type-quality × (0.5+0.5·importance), sign stays negative. We
22
+ // compare against Math.abs(relevance).
24
23
  //
25
- // Empirically (see Task 3 probe in docs/plans/2026-04-14-mem-v2.31-mvp.md):
26
- // - OR-fallback single-stem match: |rel| ~ 3e-6
27
- // - Multi-term AND match w/ importance+type boost: |rel| ~ 2e-5 .. 5e-5
28
- // The plan's hinted default (3.5) was a guess that's six orders of magnitude too
29
- // high for this codebase's scoring expression. 1e-5 suppresses OR-fallback noise
30
- // while preserving real hits. Env-overridable for tuning without a redeploy.
24
+ // v2.34.3 note: the historic comment claimed |rel| falls in 3e-6..5e-5 range.
25
+ // Re-measured against real data (see v2.34.3 CHANGELOG probe), actual scores
26
+ // span ~6..133 across SIGNAL / META / NOISE prompts the scoring expression
27
+ // was revised in later versions and this constant was never retuned. 1e-5 now
28
+ // acts as a NULL-rel guard, not a real noise filter. The primary noise gate
29
+ // is TOP_REL_FLOOR below, which drops the whole FTS set when the best match
30
+ // is weak.
31
31
  const BM25_MIN_SCORE = Number(process.env.CLAUDE_MEM_UPS_BM25_MIN || 1e-5);
32
32
  // Raw-character minimum length for the prompt. Additional to the CJK-weighted
33
33
  // `shouldSkip()` effective-length gate; catches medium-short Latin prompts that
@@ -41,6 +41,27 @@ const PROMPT_MIN_LENGTH = 15;
41
41
  const FOLLOWUP_PROMPT_MIN_LENGTH = 8;
42
42
  const FOLLOWUP_BM25_MIN_SCORE = Number(process.env.CLAUDE_MEM_UPS_BM25_MIN_FOLLOWUP || 5e-6);
43
43
 
44
+ // v2.34.3: top-|rel| sanity gate. BM25_MIN_SCORE filters per-row; this floor
45
+ // gates the entire FTS set. Noise prompts ("today's date", "current time")
46
+ // produce OR-fallback leakage where every hit shares one tangential stem and
47
+ // per-row filtering leaves all of them through. When the best match scores
48
+ // below this floor, the whole FTS result set is dropped.
49
+ //
50
+ // Empirical distribution (v2.34.3 probe, 12 prompts):
51
+ // SIGNAL top-|rel| 60..133
52
+ // NOISE top-|rel| 25..48
53
+ // WEAK-META 6.86..33
54
+ // Default 50 sits in the clean 48→60 gap. Env override for project tuning.
55
+ // Error-signature hits (sigRows) and file-recall (fileRows) bypass this gate —
56
+ // both are precision passes with independent relevance signal.
57
+ //
58
+ // Note: no follow-up halving (unlike PROMPT_MIN_LENGTH / BM25_MIN_SCORE).
59
+ // Those lower the length/per-row bar to let short context-dependent prompts
60
+ // through, but the top-|rel| gap is an absolute distribution separator —
61
+ // lowering it in follow-up mode re-admits the 37..48 noise band that the
62
+ // gate exists to drop.
63
+ const TOP_REL_FLOOR = Number(process.env.CLAUDE_MEM_UPS_TOP_MIN || 50);
64
+
44
65
  function isFollowUpSession() {
45
66
  try {
46
67
  const raw = readFileSync(INJECTED_IDS_FILE, 'utf8');
@@ -323,6 +344,16 @@ async function main() {
323
344
  typeof r.relevance === 'number' && Math.abs(r.relevance) >= bm25Floor
324
345
  );
325
346
 
347
+ // v2.34.3: top-|rel| sanity gate. Per-row filtering above leaves noise
348
+ // prompts intact when many rows share a weak stem (all in 25..48 range).
349
+ // If the best remaining FTS match is below the top floor, drop the
350
+ // whole FTS set — noise prompts should produce no FTS injection.
351
+ // Query orders by `relevance` ASC; negative values → ftsRows[0] has the
352
+ // largest magnitude (strongest match) in this scoring expression.
353
+ if (ftsRows.length > 0 && Math.abs(ftsRows[0].relevance) < TOP_REL_FLOOR) {
354
+ ftsRows = [];
355
+ }
356
+
326
357
  // Merge: FTS results first, then file results, deduplicated
327
358
  const seen = new Set(ftsRows.map(r => r.id));
328
359
  rows = [...ftsRows];