claude-mem-lite 2.34.4 → 2.34.5

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": "2.34.4",
13
+ "version": "2.34.5",
14
14
  "source": "./",
15
15
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall"
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.34.4",
3
+ "version": "2.34.5",
4
4
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.34.4",
3
+ "version": "2.34.5",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "engines": {
@@ -163,6 +163,43 @@ function searchByFile(db, files, project, limit) {
163
163
  });
164
164
  }
165
165
 
166
+ // v2.34.5 Gap 1: prompts-table fallback. When observations-based paths
167
+ // (FTS / file-recall / sigRows / recent) all return empty, scan the user's
168
+ // own past prompts — meta/UX/"did we discuss this" questions often match
169
+ // prior prompts even when no observation was saved. Uses a simpler BM25
170
+ // ranking with no scoring multipliers and no top-|rel| gate (prompts are
171
+ // sparser and more surface-form than observations; the gate would rarely
172
+ // fire and mostly kill real hits).
173
+ function searchByUserPrompts(db, queryText, project, limit) {
174
+ const ftsQuery = sanitizeFtsQuery(queryText);
175
+ if (!ftsQuery) return [];
176
+
177
+ const cutoff = Date.now() - LOOKBACK_MS;
178
+ const sql = `
179
+ SELECT up.id, up.prompt_text, up.created_at_epoch,
180
+ bm25(user_prompts_fts) as relevance
181
+ FROM user_prompts_fts
182
+ JOIN user_prompts up ON up.id = user_prompts_fts.rowid
183
+ JOIN sdk_sessions s ON s.content_session_id = up.content_session_id
184
+ WHERE user_prompts_fts MATCH ?
185
+ AND s.project = ?
186
+ AND up.created_at_epoch > ?
187
+ ORDER BY relevance
188
+ LIMIT ?
189
+ `;
190
+
191
+ let rows = db.prepare(sql).all(ftsQuery, project, cutoff, limit);
192
+
193
+ if (rows.length === 0) {
194
+ const orQuery = relaxFtsQueryToOr(ftsQuery);
195
+ if (orQuery) {
196
+ try { rows = db.prepare(sql).all(orQuery, project, cutoff, limit); } catch {}
197
+ }
198
+ }
199
+
200
+ return rows;
201
+ }
202
+
166
203
  function searchRecent(db, project, limit) {
167
204
  const cutoff = Date.now() - LOOKBACK_MS;
168
205
  // R1: exclude LOW_SIGNAL degraded titles from "recent" recall intent
@@ -225,6 +262,20 @@ function formatResults(rows) {
225
262
  return lines.join('\n');
226
263
  }
227
264
 
265
+ // v2.34.5 Gap 1: distinct header signals to Claude that these are prior
266
+ // *user questions*, not codebase lessons — helps the reader interpret the
267
+ // row correctly (surface-form match, not a saved insight). Truncate to 80
268
+ // chars (slightly longer than obs titles because prompts carry more context).
269
+ function formatPromptResults(rows) {
270
+ if (!rows || rows.length === 0) return null;
271
+ const lines = ['[mem] Past similar questions:'];
272
+ for (const r of rows) {
273
+ const text = truncate((r.prompt_text || '').replace(/\s+/g, ' '), 80);
274
+ lines.push(`P#${r.id} 💬 ${text}`);
275
+ }
276
+ return lines.join('\n');
277
+ }
278
+
228
279
  // ─── Registry Skill Pointer (T4 v2.31) ─────────────────────────────────────
229
280
  // Formerly "auto-load": we used to read the full SKILL.md body (up to 16KB)
230
281
  // and inject it into stdout on keyword match. Now we only emit a short
@@ -376,10 +427,24 @@ async function main() {
376
427
  rows = [...sigRows, ...rows.filter(r => !sigIds.has(r.id))].slice(0, MAX_RESULTS);
377
428
  }
378
429
 
379
- const candidateIds = rows.map(r => r.id);
430
+ // v2.34.5 Gap 1: if observations-based search drew a blank, try the
431
+ // user_prompts corpus. Only fires when `rows` is empty (obs hits
432
+ // suppress the fallback to avoid noise). Namespace prompt IDs with
433
+ // a "P" prefix so shouldSkipByDedup's Set comparison doesn't collide
434
+ // with future observation IDs.
435
+ let promptRows = [];
436
+ if (rows.length === 0) {
437
+ promptRows = searchByUserPrompts(db, promptText, project, 3);
438
+ }
439
+
440
+ const candidateIds = rows.length > 0
441
+ ? rows.map(r => r.id)
442
+ : promptRows.map(r => `P${r.id}`);
380
443
  const dedupSkip = shouldSkipByDedup(candidateIds, INJECTED_IDS_FILE);
381
444
 
382
- const output = !dedupSkip ? formatResults(rows) : null;
445
+ const output = !dedupSkip
446
+ ? (rows.length > 0 ? formatResults(rows) : formatPromptResults(promptRows))
447
+ : null;
383
448
  if (output) {
384
449
  process.stdout.write(output + '\n');
385
450
  // Write injected IDs for dedup with hook.mjs handleUserPrompt + self-dedup