claude-mem-lite 3.24.0 → 3.25.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.
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.
|
|
13
|
+
"version": "3.25.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.25.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.25.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,7 +206,7 @@ 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
|
}
|
|
@@ -219,7 +233,8 @@ const TECH_IDENTIFIER_RE_G = new RegExp(TECH_IDENTIFIER_RE.source, 'g');
|
|
|
219
233
|
// All tech-identifier tokens in `text`, lowercased + de-duped (for case-insensitive
|
|
220
234
|
// row matching). Empty array when none — callers treat that as "no bypass candidates".
|
|
221
235
|
export function extractTechIdentifiers(text) {
|
|
222
|
-
return [...new Set((String(text || '').match(TECH_IDENTIFIER_RE_G) || []).map(s => s.toLowerCase()))]
|
|
236
|
+
return [...new Set((String(text || '').match(TECH_IDENTIFIER_RE_G) || []).map(s => s.toLowerCase()))]
|
|
237
|
+
.filter(s => !IDENTIFIER_STOPWORDS.has(s));
|
|
223
238
|
}
|
|
224
239
|
|
|
225
240
|
// True when the obs row's title or lesson contains any of `idsLower` as a standalone
|
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 ?? '';
|