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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/lib/citation-tracker.mjs +43 -0
- package/mem-cli.mjs +25 -7
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.38.
|
|
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.
|
|
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"
|
package/lib/citation-tracker.mjs
CHANGED
|
@@ -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
|
|
2353
|
-
out(' hooks do NOT fire inside subagents
|
|
2354
|
-
out('
|
|
2355
|
-
out('
|
|
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
|
|
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
|
|
2455
|
-
|
|
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.
|
|
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",
|