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.24.0",
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.24.0",
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
- process.stdout.write(formatTaskImperative(imperativePick.lesson_learned, imperativePick.id) + '\n');
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
- if (args.includes('--execute') && !run && !runAll) {
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.24.0",
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) || 'unknown';
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 (TECH_IDENTIFIER_RE.test(text)) return true;
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
@@ -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. The production search pipeline supplies
155
- // created_at_epoch (numeric) + created_at (ISO) NOT `date`; the prior
156
- // `b.date` sort silently no-op'd there (every key undefined localeCompare 0),
157
- // so "newest" degraded to relevance order and a current top-importance obs got
158
- // a false [SUPERSEDED] tag (#8821). Prefer numeric epoch, fall back to the ISO
159
- // string (lexically chronological), then legacy `date` for older callers/tests.
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 ?? '';