claude-mem-lite 3.38.0 → 3.38.2

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": "3.38.0",
13
+ "version": "3.38.2",
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.38.0",
3
+ "version": "3.38.2",
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"
@@ -377,8 +377,51 @@ export function extractAllInjected(transcriptPath, opts = {}) {
377
377
  * @param {string} transcriptPath
378
378
  * @returns {{injected: number, cited: number, recalled: number, ratio: number}}
379
379
  */
380
+ // pre-agent-inject.js (CLAUDE_MEM_SUBAGENT_INJECT) APPENDS formatSubagentContext to a
381
+ // dispatched subagent's task prompt via PreToolUse updatedInput — a PROMPT-embedded
382
+ // injection, NOT a hook attachment. extractAllInjected (the attachment path) misses it,
383
+ // so the sidechain instrument read a false 0 ("subagents memory-blind") even while the
384
+ // dispatch surface was live. The block is `[Project memory — surfaced by your operator's
385
+ // claude-mem-lite ...]` followed by a ` #NN — <lesson>.` tag line.
386
+ const SUBAGENT_INJECT_MARKER = /surfaced by your operator's claude-mem-lite/;
387
+ // Row-anchored to the `#NN — ` tag so a #NN quoted inside the lesson body does NOT enter
388
+ // the injected set — same discipline as INJECTED_ROW_RE for the attachment surfaces.
389
+ const SUBAGENT_INJECT_ID_RE = /^\s{0,4}#(\d{1,7})\s+—/;
390
+
391
+ /**
392
+ * Extract observation ids injected into a subagent's PROMPT by pre-agent-inject.js
393
+ * (formatSubagentContext). Only the `#NN — ` tag line counts; a body cross-reference is
394
+ * ignored. Returns numeric ids.
395
+ * @param {string|null|undefined} transcriptPath
396
+ * @returns {Set<number>}
397
+ */
398
+ export function extractInjectedFromSubagentPrompt(transcriptPath) {
399
+ const ids = new Set();
400
+ let raw;
401
+ try { raw = readFileSync(transcriptPath, 'utf8'); } catch { return ids; }
402
+ for (const line of raw.split('\n')) {
403
+ if (!line) continue;
404
+ let entry;
405
+ try { entry = JSON.parse(line); } catch { continue; }
406
+ const c = entry.message?.content;
407
+ const text = typeof c === 'string'
408
+ ? c
409
+ : Array.isArray(c) ? c.map((x) => (typeof x === 'string' ? x : x?.text || '')).join('\n') : '';
410
+ if (!text || !SUBAGENT_INJECT_MARKER.test(text)) continue;
411
+ for (const bl of text.split('\n')) {
412
+ const m = SUBAGENT_INJECT_ID_RE.exec(bl);
413
+ if (m) addObsId(ids, m[1]);
414
+ }
415
+ }
416
+ return ids;
417
+ }
418
+
380
419
  export function computeThreadCiteRecall(transcriptPath) {
381
420
  const injected = extractAllInjected(transcriptPath);
421
+ // Subagent files carry NO hook-attachment injection; pre-agent-inject.js injects into
422
+ // the PROMPT (updatedInput). Fold that in so sidechain recall isn't a false 0. On a
423
+ // main transcript this marker is absent → no-op.
424
+ for (const id of extractInjectedFromSubagentPrompt(transcriptPath)) injected.add(id);
382
425
  const cited = extractCitationsFromTranscript(transcriptPath);
383
426
  let recalled = 0;
384
427
  for (const id of injected) if (cited.has(id)) recalled++;
package/mem-cli.mjs CHANGED
@@ -2349,10 +2349,10 @@ function _reportSidechainCiteRecall({ days, json }) {
2349
2349
  out(` main ${pct(mainRate).padStart(6)} recalled ${main.recalled} / injected ${main.injected} (${main.files} transcript(s))`);
2350
2350
  out(` sidechain ${pct(sideRate).padStart(6)} recalled ${sidechain.recalled} / injected ${sidechain.injected} (${sidechain.files} subagent file(s), ${sidechain.withInjections} with injections)`);
2351
2351
  if (sidechain.files > 0 && sidechain.injected === 0) {
2352
- out(' → subagent transcripts exist but received ZERO memory injections: claude-mem-lite');
2353
- out(' hooks do NOT fire inside subagents (no PreToolUse/PostToolUse recall, no');
2354
- out(' SessionStart block, no mem_* tools). Subagents are memory-blind — giving them');
2355
- out(' memory needs a NEW surface (inject at Agent/Task dispatch), not deepening.');
2352
+ out(' → subagent transcripts exist but none carried a detectable memory injection in');
2353
+ out(' this window. claude-mem-lite hooks do NOT fire inside subagents; the dispatch-');
2354
+ out(' time surface (pre-agent-inject.js, CLAUDE_MEM_SUBAGENT_INJECT) prompt-injects');
2355
+ out(' a lesson when enabled — a 0 here means it was off or selected none for these.');
2356
2356
  } else if (sidechain.files === 0) {
2357
2357
  out(' → no subagent transcripts in window.');
2358
2358
  }
@@ -2443,16 +2443,34 @@ function cmdCitationStats(db, args) {
2443
2443
  // as the per-project cite rate above; funnel.prior/delta_pt show the direction.
2444
2444
  const funnel = computeCitationFunnelTrend(db, { days });
2445
2445
 
2446
+ // Survivorship-honesty: the per-project rate (cited_count/decay_seen_count over
2447
+ // SURVIVING in-window obs) is doubly biased — GC drops uncited obs from the
2448
+ // denominator and the window excludes older obs — so it overstates adoption (a
2449
+ // project can read 91% while its true lifetime inject→cite is ~6%). citation_log
2450
+ // rows persist across GC, so their per-project sum is the honest historical rate.
2451
+ // Attach both to each row so the text + JSON surface the biased and honest numbers.
2452
+ const funnelByProject = new Map(
2453
+ db.prepare(`SELECT project, COALESCE(SUM(injected_n), 0) inj, COALESCE(SUM(cited_n), 0) cit
2454
+ FROM citation_log GROUP BY project`).all().map(r => [r.project, r])
2455
+ );
2456
+ for (const r of perProject) {
2457
+ const f = funnelByProject.get(r.project);
2458
+ r.funnel_injected = f ? f.inj : 0;
2459
+ r.funnel_cited = f ? f.cit : 0;
2460
+ }
2461
+
2446
2462
  if (json) {
2447
2463
  out(JSON.stringify({ window_days: days, per_project: perProject, decay_queue: decayQueue, promoted, demoted, data_pollution_note: dataPollutionNote, funnel }, null, 2));
2448
2464
  return;
2449
2465
  }
2450
2466
 
2451
2467
  if (dataPollutionNote) out(`Note: ${dataPollutionNote}\n`);
2452
- out(`Cite rate by project (last ${days}d, cited / decay-resolutions):`);
2468
+ out(`Cite rate by project (last ${days}d):`);
2469
+ out(` funnel = lifetime injected→cited (citation_log, GC-durable = honest) · surviving = cited/decay over in-window non-GC'd obs (survivorship-biased, reads high):`);
2453
2470
  for (const r of perProject) {
2454
- const rate = r.resolved > 0 ? (r.cited * 100 / r.resolved).toFixed(1) + '%' : '—';
2455
- out(` ${r.project.padEnd(34)} ${String(rate).padStart(6)} cited:${r.cited}/${r.resolved} at_risk:${r.at_risk}`);
2471
+ const funnelRate = r.funnel_injected > 0 ? (r.funnel_cited * 100 / r.funnel_injected).toFixed(1) + '%' : '—';
2472
+ const survRate = r.resolved > 0 ? (r.cited * 100 / r.resolved).toFixed(1) + '%' : '—';
2473
+ out(` ${r.project.padEnd(30)} funnel ${String(funnelRate).padStart(6)} (${r.funnel_cited}/${r.funnel_injected}) · surviving ${String(survRate).padStart(6)} (${r.cited}/${r.resolved}) · at_risk:${r.at_risk}`);
2456
2474
  }
2457
2475
  out('');
2458
2476
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.38.0",
3
+ "version": "3.38.2",
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",