claude-mem-lite 3.38.2 → 3.38.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": "3.38.
|
|
13
|
+
"version": "3.38.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.38.
|
|
3
|
+
"version": "3.38.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/hook-memory.mjs
CHANGED
|
@@ -408,17 +408,19 @@ export function recallForFile(db, filePath, project) {
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
/**
|
|
411
|
-
* Phase-2 task-imperative
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* with the prompt, top-1) — deliberately independent of searchRelevantMemories' coverage/
|
|
411
|
+
* Phase-2 task-imperative ranking (spec 2026-06-29 §4.1): score every candidate lesson
|
|
412
|
+
* relevant to THIS prompt (importance>=2 + non-empty lesson + identifier overlap with the
|
|
413
|
+
* prompt), sorted best-first — deliberately independent of searchRelevantMemories' coverage/
|
|
415
414
|
* BM25 filters so a high-value lesson is not dropped by the context-list scoring.
|
|
416
|
-
*
|
|
415
|
+
* `epochTo` (optional) restricts the pool to observations created at/before that epoch —
|
|
416
|
+
* for offline point-in-time replay (benchmark use); a no-op at every production call site,
|
|
417
|
+
* which never passes it.
|
|
418
|
+
* @returns {Array<{id:number, lesson_learned:string, importance:number, overlap:number, score:number}>}
|
|
417
419
|
*/
|
|
418
|
-
export function
|
|
419
|
-
if (!db || !userPrompt) return
|
|
420
|
+
export function rankImperativeCandidates(db, userPrompt, project, excludeIds = [], { epochTo = null } = {}) {
|
|
421
|
+
if (!db || !userPrompt) return [];
|
|
420
422
|
const promptIdents = new Set(extractIdents(userPrompt));
|
|
421
|
-
if (promptIdents.size === 0) return
|
|
423
|
+
if (promptIdents.size === 0) return []; // no symbol anchor → no imperative (precision-first)
|
|
422
424
|
const exclude = new Set(excludeIds);
|
|
423
425
|
let rows;
|
|
424
426
|
try {
|
|
@@ -432,19 +434,34 @@ export function selectImperativeLesson(db, userPrompt, project, excludeIds = [])
|
|
|
432
434
|
AND lesson_learned IS NOT NULL
|
|
433
435
|
AND TRIM(lesson_learned) != ''
|
|
434
436
|
AND LOWER(TRIM(lesson_learned)) != 'none'
|
|
437
|
+
AND (? IS NULL OR created_at_epoch <= ?)
|
|
435
438
|
ORDER BY importance DESC, created_at_epoch DESC
|
|
436
439
|
LIMIT 50
|
|
437
|
-
`).all(project);
|
|
438
|
-
} catch { return
|
|
439
|
-
|
|
440
|
+
`).all(project, epochTo, epochTo);
|
|
441
|
+
} catch { return []; }
|
|
442
|
+
const out = [];
|
|
440
443
|
for (const r of rows) {
|
|
441
444
|
if (exclude.has(r.id)) continue;
|
|
442
445
|
const overlap = extractIdents(`${r.lesson_learned} ${r.title || ''}`).filter((id) => promptIdents.has(id)).length;
|
|
443
446
|
if (overlap === 0) continue;
|
|
444
447
|
const score = (r.importance || 2) * overlap;
|
|
445
|
-
|
|
448
|
+
out.push({ id: r.id, lesson_learned: r.lesson_learned, importance: r.importance || 2, overlap, score });
|
|
446
449
|
}
|
|
447
|
-
|
|
450
|
+
// Array.prototype.sort is spec-guaranteed stable (ES2019+) — ties keep the pool's
|
|
451
|
+
// ORDER BY importance DESC, created_at_epoch DESC relative order, matching the old
|
|
452
|
+
// loop's strict `score > bestScore` (first-max-wins) tie-break exactly.
|
|
453
|
+
out.sort((a, b) => b.score - a.score);
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Phase-2 task-imperative selection: the single highest-value lesson relevant to THIS
|
|
459
|
+
* prompt — top-1 of rankImperativeCandidates, mapped to the pre-existing return shape.
|
|
460
|
+
* @returns {{id:number, lesson_learned:string}|null}
|
|
461
|
+
*/
|
|
462
|
+
export function selectImperativeLesson(db, userPrompt, project, excludeIds = []) {
|
|
463
|
+
const top = rankImperativeCandidates(db, userPrompt, project, excludeIds)[0];
|
|
464
|
+
return top ? { id: top.id, lesson_learned: top.lesson_learned } : null;
|
|
448
465
|
}
|
|
449
466
|
|
|
450
467
|
// P0 (2026-07-03): compose the subagent-dispatch injection. Given a PreToolUse
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.38.
|
|
3
|
+
"version": "3.38.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",
|
|
@@ -9,6 +9,7 @@ import { citeFactorClause } from '../scoring-sql.mjs';
|
|
|
9
9
|
import { cjkPrecisionOk } from '../nlp.mjs';
|
|
10
10
|
import { writeFileSync, readFileSync, existsSync, renameSync } from 'fs';
|
|
11
11
|
import { join, sep } from 'path';
|
|
12
|
+
import { pathToFileURL } from 'url';
|
|
12
13
|
import Database from 'better-sqlite3';
|
|
13
14
|
import { shouldSkip, computeEffectiveLen, detectIntent, shouldSkipByDedup, extractFiles, extractErrorSignature, DEDUP_STALE_MS, matchRegistrySkillName, detectMemOverride } from './prompt-search-utils.mjs';
|
|
14
15
|
import { recommendSkill } from '../registry-recommend.mjs';
|
|
@@ -265,14 +266,15 @@ export function rowMatchesIdentifier(row, idsLower) {
|
|
|
265
266
|
// Each row includes `bm25_raw` (pre-multiplier bm25 magnitude) alongside the
|
|
266
267
|
// composite `relevance`, so callers can distinguish raw-match strength from
|
|
267
268
|
// importance/type/decay inflation.
|
|
268
|
-
export function searchByFts(db, queryText, project, limit, typeFilter
|
|
269
|
+
export function searchByFts(db, queryText, project, limit, typeFilter,
|
|
270
|
+
{ nowT = Date.now(), epochTo = null } = {}) {
|
|
269
271
|
const ftsQuery = sanitizeFtsQuery(queryText);
|
|
270
272
|
if (!ftsQuery) return { rows: [], mode: null };
|
|
271
273
|
|
|
272
|
-
const cutoff =
|
|
274
|
+
const cutoff = nowT - LOOKBACK_MS;
|
|
273
275
|
|
|
274
276
|
const typeClause = typeFilter ? 'AND o.type = ?' : '';
|
|
275
|
-
const now =
|
|
277
|
+
const now = nowT;
|
|
276
278
|
// R1: notLowSignalTitleClause() excludes hook-llm degraded titles
|
|
277
279
|
// ("Modified X", "Worked on X", "Reviewed N files:", raw error logs).
|
|
278
280
|
// v26 P0: noise penalty shrinks relevance magnitude for obs with high
|
|
@@ -298,6 +300,7 @@ export function searchByFts(db, queryText, project, limit, typeFilter) {
|
|
|
298
300
|
AND o.project = ?
|
|
299
301
|
AND o.importance >= 1
|
|
300
302
|
AND o.created_at_epoch > ?
|
|
303
|
+
AND (? IS NULL OR o.created_at_epoch <= ?)
|
|
301
304
|
AND COALESCE(o.compressed_into, 0) = 0
|
|
302
305
|
AND o.superseded_at IS NULL
|
|
303
306
|
AND ${notLowSignalTitleClause('o')}
|
|
@@ -306,7 +309,7 @@ export function searchByFts(db, queryText, project, limit, typeFilter) {
|
|
|
306
309
|
LIMIT ?
|
|
307
310
|
`;
|
|
308
311
|
|
|
309
|
-
const params = [now, ftsQuery, project, cutoff];
|
|
312
|
+
const params = [now, ftsQuery, project, cutoff, epochTo, epochTo];
|
|
310
313
|
if (typeFilter) params.push(typeFilter);
|
|
311
314
|
params.push(limit);
|
|
312
315
|
|
|
@@ -821,4 +824,24 @@ async function main() {
|
|
|
821
824
|
// every sibling hook script upholds). Deliberately NOT `.finally(process.exit(0))` —
|
|
822
825
|
// this hook detaches a background `claude -p` search and a forced exit would kill it;
|
|
823
826
|
// letting the loop drain naturally exits 0 once the detached child is unref'd.
|
|
824
|
-
|
|
827
|
+
// Import guard (mirrors benchmark/longmemeval.mjs): only auto-run when this file is
|
|
828
|
+
// executed directly as the UserPromptSubmit hook, not when a benchmark/test harness
|
|
829
|
+
// imports it to call searchByFts() offline (see tests/adoption-searchbyfts-snapshot.test.mjs).
|
|
830
|
+
//
|
|
831
|
+
// Exported as a pure predicate (side-effect-free) so a regression test can
|
|
832
|
+
// exercise it directly without triggering main(). hook-launcher.mjs's
|
|
833
|
+
// self-heal retry (runEntry({ bustCache: true })) re-imports this entry with
|
|
834
|
+
// a cache-buster query appended (`?t=<ts>`) so Node's ESM cache doesn't
|
|
835
|
+
// return the earlier ERR_MODULE_NOT_FOUND rejection, while process.argv[1]
|
|
836
|
+
// stays query-less — a strict `===` here evaluated false on that retry,
|
|
837
|
+
// silently skipping main() so the healed hook fire did no memory search.
|
|
838
|
+
// Stripping the query before comparing fixes that without weakening the
|
|
839
|
+
// normal-launcher / execFileSync direct-invocation checks (both still match
|
|
840
|
+
// exactly, query or no query).
|
|
841
|
+
export function isDirectInvocation(metaUrl, argv1) {
|
|
842
|
+
if (!argv1) return false;
|
|
843
|
+
return metaUrl.split('?')[0] === pathToFileURL(argv1).href;
|
|
844
|
+
}
|
|
845
|
+
if (isDirectInvocation(import.meta.url, process.argv[1])) {
|
|
846
|
+
main().catch(() => {});
|
|
847
|
+
}
|