claude-mem-lite 3.24.0 → 3.26.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.mjs +4 -1
- package/mem-cli.mjs +4 -1
- package/package.json +1 -1
- package/scripts/pre-tool-recall.js +1 -1
- package/scripts/user-prompt-search.js +31 -14
- package/search-scoring.mjs +8 -6
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.
|
|
13
|
+
"version": "3.26.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.26.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.mjs
CHANGED
|
@@ -1424,7 +1424,10 @@ async function handleUserPrompt() {
|
|
|
1424
1424
|
process.stdout.write(lines.join('\n') + '\n');
|
|
1425
1425
|
}
|
|
1426
1426
|
if (imperativePick) {
|
|
1427
|
-
|
|
1427
|
+
// Guard the write on a non-empty return — formatTaskImperative yields '' for a
|
|
1428
|
+
// lesson that strips to empty (e.g. "."), which would otherwise emit a bare line.
|
|
1429
|
+
const imperativeLine = formatTaskImperative(imperativePick.lesson_learned, imperativePick.id);
|
|
1430
|
+
if (imperativeLine) process.stdout.write(imperativeLine + '\n');
|
|
1428
1431
|
}
|
|
1429
1432
|
} catch (e) { debugCatch(e, 'handleUserPrompt-memory'); }
|
|
1430
1433
|
} finally {
|
package/mem-cli.mjs
CHANGED
|
@@ -2867,7 +2867,10 @@ async function cmdOptimize(db, args) {
|
|
|
2867
2867
|
// Sibling-command flag footgun: optimize executes with --run (compress uses
|
|
2868
2868
|
// --execute, maintain uses positional `execute`). A bare --execute previously fell
|
|
2869
2869
|
// through to a silent preview, so the user thought they ran a mutation but didn't.
|
|
2870
|
-
|
|
2870
|
+
// Catch both `--execute` and the `--execute=value` form (the latter slipped a bare
|
|
2871
|
+
// `args.includes('--execute')` and fell through to a silent preview — review minor).
|
|
2872
|
+
const hasExecuteFlag = args.some(a => a === '--execute' || a.startsWith('--execute='));
|
|
2873
|
+
if (hasExecuteFlag && !run && !runAll) {
|
|
2871
2874
|
fail("[mem] optimize executes with --run, not --execute (--execute is compress's flag). Re-run: claude-mem-lite optimize --run");
|
|
2872
2875
|
return;
|
|
2873
2876
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.26.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",
|
|
@@ -116,7 +116,7 @@ function inferProject() {
|
|
|
116
116
|
const parent = basename(join(dir, '..'));
|
|
117
117
|
const raw = (parent && parent !== '.' && parent !== '/')
|
|
118
118
|
? `${parent}--${base}` : base;
|
|
119
|
-
return raw.replace(/[^a-zA-Z0-9_.-]/g, '-').slice(0, 100)
|
|
119
|
+
return raw.replace(/[^a-zA-Z0-9_.-]/g, '-').slice(0, 100);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
function readCooldown(cooldownPath) {
|
|
@@ -172,6 +172,20 @@ function isFollowUpSession() {
|
|
|
172
172
|
// catches the prompt via those channels rather than the identifier itself.
|
|
173
173
|
const TECH_IDENTIFIER_RE = /\b(?:[a-z][a-z0-9]*_[a-z0-9_]+|[A-Z][A-Z0-9]*_[A-Z0-9_]+|[A-Z]{2,}[0-9][A-Z0-9_]*|[a-z]{2,}[A-Z][a-zA-Z0-9]+|[a-z]+(?:-[a-z]+){2,})\b/;
|
|
174
174
|
|
|
175
|
+
// Reviewer #2 (v3.25.0): the kebab (≥3-seg) and camelCase arms above structurally
|
|
176
|
+
// match a handful of ordinary English phrases / product names that are NOT code
|
|
177
|
+
// identifiers — `up-to-date`, `state-of-the-art`, `macOS`, etc. Left unfiltered they
|
|
178
|
+
// (a) widen the default-ON hasExplicitSignal gate on prose-only prompts and (b) let
|
|
179
|
+
// such a token drive the opt-in identifier bypass. Stop-list them (lowercased). Real
|
|
180
|
+
// 3-segment kebab identifiers (`pre-tool-use`, `user-prompt-search`) are deliberately
|
|
181
|
+
// NOT here — only attested non-identifier prose.
|
|
182
|
+
const IDENTIFIER_STOPWORDS = new Set([
|
|
183
|
+
'up-to-date', 'out-of-date', 'up-to-speed', 'out-of-the-box', 'state-of-the-art',
|
|
184
|
+
'end-to-end', 'off-by-one', 'easy-to-use', 'day-to-day', 'step-by-step',
|
|
185
|
+
'face-to-face', 'one-to-one', 'one-on-one', 'back-to-back', 'side-by-side',
|
|
186
|
+
'apples-to-apples', 'macos',
|
|
187
|
+
]);
|
|
188
|
+
|
|
175
189
|
// CJK presence channel (Important #2): bilingual users (project memory
|
|
176
190
|
// `feedback_*` calls this out explicitly) ask CJK questions that may carry
|
|
177
191
|
// genuine debug intent without containing an English identifier. CJK is
|
|
@@ -192,34 +206,37 @@ export function hasExplicitSignal(text, { errSig, files, intent } = {}) {
|
|
|
192
206
|
if (errSig === undefined && extractErrorSignature(text)) return true;
|
|
193
207
|
if (files === undefined && extractFiles(text).length > 0) return true;
|
|
194
208
|
if (intent === undefined && detectIntent(text)) return true;
|
|
195
|
-
if (
|
|
209
|
+
if (extractTechIdentifiers(text).length > 0) return true;
|
|
196
210
|
if (CJK_CHAR_RE.test(text) && computeEffectiveLen(text) >= CJK_MIN_EFFECTIVE_LEN) return true;
|
|
197
211
|
return false;
|
|
198
212
|
}
|
|
199
213
|
|
|
200
|
-
// ─── Identifier-exact-match precision bypass (default
|
|
214
|
+
// ─── Identifier-exact-match precision bypass (default ON since v3.26.0) ──────
|
|
201
215
|
//
|
|
202
|
-
// CLAUDE_MEM_UPS_IDENTIFIER_BYPASS=
|
|
216
|
+
// Set CLAUDE_MEM_UPS_IDENTIFIER_BYPASS=0 to disable. Rationale: the score-floors below
|
|
203
217
|
// (OR_TOP_BM25_FLOOR / TOP_REL_FLOOR) drop the WHOLE FTS set when the top row's
|
|
204
218
|
// magnitude is weak. But a rare code identifier (camelCase / snake_case / CONST_CASE
|
|
205
219
|
// / kebab≥3) match has high *semantic* precision even at modest BM25 — a df=1 term
|
|
206
220
|
// like `sanitizeFtsQuery` scores only ~23 raw yet is an unambiguous hit. So an obs
|
|
207
221
|
// whose title/lesson EXACT-matches an identifier the prompt names is a precision pass
|
|
208
222
|
// — the same independent-signal rationale that already exempts sigRows (error
|
|
209
|
-
// signatures) and fileRows (file names) from these floors.
|
|
210
|
-
//
|
|
223
|
+
// signatures) and fileRows (file names) from these floors. Such rows are restored
|
|
224
|
+
// after the floors run.
|
|
211
225
|
//
|
|
212
|
-
// Default
|
|
213
|
-
// (
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
|
|
226
|
+
// Default ON — flipped in v3.26.0 after a corrected benchmark/ups-ab.mjs re-measurement
|
|
227
|
+
// (#8858 two-bucket: TRUE off-topic FP isolated from on-topic eagerness). Over 12
|
|
228
|
+
// positives / 7 true-precision hard-negatives: recall up, TRUE off-topic FP = 0 (stable
|
|
229
|
+
// ×3 runs) → VERDICT NET-POSITIVE. The only behavior delta is on-topic eagerness (naming
|
|
230
|
+
// an identifier surfaces its obs) — the highest-precision injection trigger there is. The
|
|
231
|
+
// prose stop-list (IDENTIFIER_STOPWORDS) keeps the extractor off ordinary English.
|
|
232
|
+
export const IDENTIFIER_BYPASS = process.env.CLAUDE_MEM_UPS_IDENTIFIER_BYPASS !== '0';
|
|
217
233
|
const TECH_IDENTIFIER_RE_G = new RegExp(TECH_IDENTIFIER_RE.source, 'g');
|
|
218
234
|
|
|
219
235
|
// All tech-identifier tokens in `text`, lowercased + de-duped (for case-insensitive
|
|
220
236
|
// row matching). Empty array when none — callers treat that as "no bypass candidates".
|
|
221
237
|
export function extractTechIdentifiers(text) {
|
|
222
|
-
return [...new Set((String(text || '').match(TECH_IDENTIFIER_RE_G) || []).map(s => s.toLowerCase()))]
|
|
238
|
+
return [...new Set((String(text || '').match(TECH_IDENTIFIER_RE_G) || []).map(s => s.toLowerCase()))]
|
|
239
|
+
.filter(s => !IDENTIFIER_STOPWORDS.has(s));
|
|
223
240
|
}
|
|
224
241
|
|
|
225
242
|
// True when the obs row's title or lesson contains any of `idsLower` as a standalone
|
|
@@ -600,8 +617,8 @@ async function main() {
|
|
|
600
617
|
const signalPresent = hasExplicitSignal(promptText, {
|
|
601
618
|
errSig, files: filesForGate, intent,
|
|
602
619
|
});
|
|
603
|
-
// Identifier tokens the prompt names (for the precision bypass below). Empty
|
|
604
|
-
//
|
|
620
|
+
// Identifier tokens the prompt names (for the precision bypass below). Empty only
|
|
621
|
+
// when CLAUDE_MEM_UPS_IDENTIFIER_BYPASS=0 (bypass is default-on), then it is a no-op.
|
|
605
622
|
const promptIdentifiers = IDENTIFIER_BYPASS ? extractTechIdentifiers(promptText) : [];
|
|
606
623
|
|
|
607
624
|
if (intent?.useRecent) {
|
|
@@ -634,7 +651,7 @@ async function main() {
|
|
|
634
651
|
typeof r.relevance === 'number' && Math.abs(r.relevance) >= bm25Floor
|
|
635
652
|
);
|
|
636
653
|
|
|
637
|
-
// Identifier-exact-match precision bypass (default
|
|
654
|
+
// Identifier-exact-match precision bypass (default on — see IDENTIFIER_BYPASS).
|
|
638
655
|
// Capture rows that exact-match a prompt identifier BEFORE the set-floors below;
|
|
639
656
|
// they carry independent precision signal (sigRows/fileRows rationale) and are
|
|
640
657
|
// restored after the floors so a low top-score can't drop a named-identifier hit.
|
package/search-scoring.mjs
CHANGED
|
@@ -151,12 +151,14 @@ export function markSuperseded(results) {
|
|
|
151
151
|
// Persistent superseding belongs in mem_maintain/mem_compress write paths.
|
|
152
152
|
for (const [, obsForFile] of fileMap) {
|
|
153
153
|
if (obsForFile.length < 2) continue;
|
|
154
|
-
// Sort newest-first by recency.
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
// a
|
|
159
|
-
//
|
|
154
|
+
// Sort newest-first by recency. Every production obs row carries `date`
|
|
155
|
+
// (= created_at ISO, set by ftsRowToResult); the FTS/type paths also carry
|
|
156
|
+
// created_at_epoch, the vector/type-list paths carry date-only. So the prior
|
|
157
|
+
// `b.date` sort already ordered correctly in production — this is defensive
|
|
158
|
+
// hardening, not a prod no-op fix (a post-release review corrected the original
|
|
159
|
+
// #8821 "missing-`date`" diagnosis). Prefer the numeric epoch, fall back to the
|
|
160
|
+
// ISO created_at (lexically chronological), then legacy `date`, so the key holds
|
|
161
|
+
// for any caller regardless of which recency fields it supplies.
|
|
160
162
|
obsForFile.sort((a, b) => {
|
|
161
163
|
const ka = a.created_at_epoch ?? a.created_at ?? a.date ?? '';
|
|
162
164
|
const kb = b.created_at_epoch ?? b.created_at ?? b.date ?? '';
|