claude-mem-lite 3.30.0 → 3.31.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.30.0",
13
+ "version": "3.31.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.30.0",
3
+ "version": "3.31.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.30.0",
3
+ "version": "3.31.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",
package/server.mjs CHANGED
@@ -14,7 +14,7 @@ import { deepSearch, resolveDeepMode, shouldEscalateToDeep, autoDeepLlmReady } f
14
14
  import { selectCompressionCandidates, groupByProjectWeek, compressGroup } from './lib/compress-core.mjs';
15
15
  import { buildNotLowSignalSql } from './lib/low-signal-patterns.mjs';
16
16
  import { resolveAnchorToken, formatAnchorError, resolveQueryAnchor, fetchRecentTimeline, fetchTimelineWindow } from './lib/timeline-core.mjs';
17
- import { buildSearchFtsQuery, parseDateBounds, coreRunSearchPipeline } from './lib/search-core.mjs';
17
+ import { buildSearchFtsQuery, parseDateBounds, parseDuration, coreRunSearchPipeline } from './lib/search-core.mjs';
18
18
  import {
19
19
  cleanupBroken, decayAndMarkIdle, boostAccessed, demotePinned, mergeDuplicates,
20
20
  recoverOrphanedChildren,
@@ -346,41 +346,56 @@ server.registerTool(
346
346
 
347
347
  // ─── Tool: mem_recent ────────────────────────────────────────────────────────
348
348
 
349
+ // In-process test seam (mirrors handleSearchForTest, #8743): threads an injected
350
+ // db through the SAME body the registered handler runs. NOTE: a `project` arg is
351
+ // still resolved via resolveProject() against the MODULE db, not the injected one.
352
+ export async function handleRecentForTest(db, args) {
353
+ return runRecent(db, args);
354
+ }
355
+
356
+ async function runRecent(db, args) {
357
+ if (args.project) args = { ...args, project: resolveProject(args.project) };
358
+ const limit = args.limit ?? 10;
359
+ const project = args.project || inferProject();
360
+
361
+ const params = [];
362
+ const wheres = ['COALESCE(compressed_into, 0) = 0', 'superseded_at IS NULL'];
363
+ if (project) { wheres.push('project = ?'); params.push(project); }
364
+ // date_since: relative lower bound on created_at (CLI `recent --since` parity).
365
+ if (args.date_since !== undefined) {
366
+ const d = parseDuration(args.date_since);
367
+ if (!d.ok) throw new Error(`Invalid date_since: "${args.date_since}" (use <N><unit>, e.g. 7d, 24h, 90m, 2w)`);
368
+ wheres.push('created_at_epoch >= ?'); params.push(Date.now() - d.ms);
369
+ }
370
+ params.push(limit);
371
+
372
+ const rows = db.prepare(`
373
+ SELECT id, type, title, subtitle, project, created_at, created_at_epoch
374
+ FROM observations
375
+ WHERE ${wheres.join(' AND ')}
376
+ ORDER BY created_at_epoch DESC
377
+ LIMIT ?
378
+ `).all(...params);
379
+
380
+ if (rows.length === 0) {
381
+ return { content: [{ type: 'text', text: `No recent observations${project ? ` (${project})` : ''}.` }] };
382
+ }
383
+
384
+ const lines = [`Recent observations (${project || 'all'}):\n`];
385
+ for (const r of rows) {
386
+ lines.push(`#${r.id} ${typeIcon(r.type)} [${r.type}] ${truncate(r.title || r.subtitle || '(untitled)')} | ${r.project} | ${fmtDate(r.created_at)}`);
387
+ }
388
+ lines.push(`\nWorkflow: mem_get(ids=[...]) for full details | mem_timeline(anchor=ID) for context`);
389
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
390
+ }
391
+
349
392
  server.registerTool(
350
393
  'mem_recent',
351
394
  {
352
395
  description: descriptionOf('mem_recent'),
353
396
  inputSchema: memRecentSchema,
354
397
  },
355
- safeHandler(async (args) => {
356
- if (args.project) args = { ...args, project: resolveProject(args.project) };
357
- const limit = args.limit ?? 10;
358
- const project = args.project || inferProject();
359
-
360
- const params = [];
361
- const wheres = ['COALESCE(compressed_into, 0) = 0', 'superseded_at IS NULL'];
362
- if (project) { wheres.push('project = ?'); params.push(project); }
363
- params.push(limit);
364
-
365
- const rows = db.prepare(`
366
- SELECT id, type, title, subtitle, project, created_at, created_at_epoch
367
- FROM observations
368
- WHERE ${wheres.join(' AND ')}
369
- ORDER BY created_at_epoch DESC
370
- LIMIT ?
371
- `).all(...params);
372
-
373
- if (rows.length === 0) {
374
- return { content: [{ type: 'text', text: `No recent observations${project ? ` (${project})` : ''}.` }] };
375
- }
376
-
377
- const lines = [`Recent observations (${project || 'all'}):\n`];
378
- for (const r of rows) {
379
- lines.push(`#${r.id} ${typeIcon(r.type)} [${r.type}] ${truncate(r.title || r.subtitle || '(untitled)')} | ${r.project} | ${fmtDate(r.created_at)}`);
380
- }
381
- lines.push(`\nWorkflow: mem_get(ids=[...]) for full details | mem_timeline(anchor=ID) for context`);
382
- return { content: [{ type: 'text', text: lines.join('\n') }] };
383
- })
398
+ safeHandler(async (args) => runRecent(db, args))
384
399
  );
385
400
 
386
401
  // ─── Tool: mem_timeline ─────────────────────────────────────────────────────
package/tool-schemas.mjs CHANGED
@@ -101,6 +101,7 @@ export const memSearchSchema = {
101
101
  export const memRecentSchema = {
102
102
  limit: coerceInt.pipe(z.number().int().min(1).max(100)).optional().describe('Max results (default 10)'),
103
103
  project: z.string().optional().describe('Filter by project (default: inferred from CWD)'),
104
+ date_since: z.string().optional().describe('Relative lower bound from now: 7d/24h/90m/2w/30s. Only items newer than the window (pair with a high limit for "everything since X")'),
104
105
  };
105
106
 
106
107
  // Anchor accepts plain int, "123" string-int, or prefixed token from search output: