claude-mem-lite 3.28.3 → 3.29.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.28.3",
13
+ "version": "3.29.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.28.3",
3
+ "version": "3.29.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"
@@ -36,20 +36,45 @@ export function buildSearchFtsQuery(query, { or = false } = {}) {
36
36
  return ftsQuery;
37
37
  }
38
38
 
39
+ // Relative-duration units for `--since`. Months/years intentionally omitted —
40
+ // they're calendar-ambiguous; absolute --from/--to cover longer windows.
41
+ const DURATION_UNIT_MS = { s: 1000, m: 60000, h: 3600000, d: 86400000, w: 604800000 };
42
+
43
+ /**
44
+ * Parse a relative duration like "7d", "24h", "90m", "2w", "30s" into ms.
45
+ * Integer magnitude + single unit only (s/m/h/d/w, case-insensitive).
46
+ * @returns {{ ok: true, ms: number } | { ok: false }}
47
+ */
48
+ export function parseDuration(raw) {
49
+ if (typeof raw !== 'string') return { ok: false };
50
+ const m = raw.trim().match(/^(\d+)\s*([smhdw])$/i);
51
+ if (!m) return { ok: false };
52
+ const n = parseInt(m[1], 10);
53
+ if (!Number.isInteger(n) || n <= 0) return { ok: false };
54
+ return { ok: true, ms: n * DURATION_UNIT_MS[m[2].toLowerCase()] };
55
+ }
56
+
39
57
  /**
40
58
  * Parse from/to date bounds to epoch ms. Date-only `to` (YYYY-MM-DD) extends
41
- * to end-of-day so "to 2026-06-12" includes that day's rows.
59
+ * to end-of-day so "to 2026-06-12" includes that day's rows. An optional
60
+ * relative `sinceRaw` ("7d") sets the lower bound when no absolute `--from`
61
+ * is given; an explicit `--from` always wins (it's the more specific signal).
42
62
  * @returns {{ ok: true, epochFrom: number|null, epochTo: number|null }
43
- * | { ok: false, bad: 'from'|'to', value: string }}
63
+ * | { ok: false, bad: 'from'|'to'|'since', value: string }}
44
64
  */
45
- export function parseDateBounds(fromRaw, toRaw) {
46
- const epochFrom = fromRaw ? new Date(fromRaw).getTime() : null;
65
+ export function parseDateBounds(fromRaw, toRaw, sinceRaw = null, now = Date.now()) {
66
+ let epochFrom = fromRaw ? new Date(fromRaw).getTime() : null;
47
67
  let epochTo = toRaw ? new Date(toRaw).getTime() : null;
48
68
  if (epochTo !== null && toRaw && /^\d{4}-\d{2}-\d{2}$/.test(toRaw)) {
49
69
  epochTo += 86400000 - 1; // extend to 23:59:59.999
50
70
  }
51
71
  if (epochFrom !== null && isNaN(epochFrom)) return { ok: false, bad: 'from', value: fromRaw };
52
72
  if (epochTo !== null && isNaN(epochTo)) return { ok: false, bad: 'to', value: toRaw };
73
+ if (sinceRaw) {
74
+ const d = parseDuration(sinceRaw);
75
+ if (!d.ok) return { ok: false, bad: 'since', value: sinceRaw };
76
+ if (epochFrom === null) epochFrom = now - d.ms; // explicit --from takes precedence
77
+ }
53
78
  return { ok: true, epochFrom, epochTo };
54
79
  }
55
80
 
package/mem-cli.mjs CHANGED
@@ -42,7 +42,7 @@ import { saveObservation } from './lib/save-observation.mjs';
42
42
  import { rebuildObservationDerived } from './lib/observation-write.mjs';
43
43
  import { recallByFile } from './lib/recall-core.mjs';
44
44
  import { resolveAnchorToken, formatAnchorError, resolveQueryAnchor, fetchRecentTimeline, fetchTimelineWindow } from './lib/timeline-core.mjs';
45
- import { buildSearchFtsQuery, parseDateBounds, coreRunSearchPipeline } from './lib/search-core.mjs';
45
+ import { buildSearchFtsQuery, parseDateBounds, parseDuration, coreRunSearchPipeline } from './lib/search-core.mjs';
46
46
  import { AUTO_MERGE_THRESHOLD } from './lib/dedup-constants.mjs';
47
47
  import { countRecentHookErrors } from './lib/hook-telemetry.mjs';
48
48
  import { computeCitationFunnelTrend } from './lib/citation-tracker.mjs';
@@ -58,7 +58,7 @@ async function cmdSearch(db, args, { llm } = {}) {
58
58
  const { positional, flags } = parseArgs(args);
59
59
  const query = positional.join(' ');
60
60
  if (!query) {
61
- fail('[mem] Usage: claude-mem-lite search <query> [--type TYPE] [--source SOURCE] [--limit N] [--project P] [--from DATE] [--to DATE] [--importance N] [--branch B] [--offset N] [--sort relevance|time|importance] [--include-noise] [--deep] [--no-deep] [--rerank]');
61
+ fail('[mem] Usage: claude-mem-lite search <query> [--type TYPE] [--source SOURCE] [--limit N] [--project P] [--from DATE] [--to DATE] [--since DUR] [--importance N] [--branch B] [--offset N] [--sort relevance|time|importance] [--include-noise] [--deep] [--no-deep] [--rerank]');
62
62
  return;
63
63
  }
64
64
 
@@ -76,8 +76,12 @@ async function cmdSearch(db, args, { llm } = {}) {
76
76
  }
77
77
  const source = flags.source || null; // observations|sessions|prompts (null = all)
78
78
  const project = flags.project ? resolveProject(db, flags.project) : null;
79
- const bounds = parseDateBounds(flags.from, flags.to);
80
- if (!bounds.ok) { fail(`[mem] Invalid --${bounds.bad} date: "${bounds.value}". Use YYYY-MM-DD or ISO 8601.`); return; }
79
+ const bounds = parseDateBounds(flags.from, flags.to, flags.since);
80
+ if (!bounds.ok) {
81
+ if (bounds.bad === 'since') fail(`[mem] Invalid --since "${bounds.value}". Use <N><unit>, e.g. 7d, 24h, 90m, 2w.`);
82
+ else fail(`[mem] Invalid --${bounds.bad} date: "${bounds.value}". Use YYYY-MM-DD or ISO 8601.`);
83
+ return;
84
+ }
81
85
  const { epochFrom: dateFrom, epochTo: dateTo } = bounds;
82
86
  // Inverted range silently returns 0 rows; warn so users see the cause, don't error
83
87
  // (a deliberate "search for nothing in this window" is not malformed input).
@@ -352,6 +356,12 @@ function cmdRecent(db, args) {
352
356
  const wheres = ['COALESCE(compressed_into, 0) = 0', 'superseded_at IS NULL'];
353
357
  if (project) { wheres.push('project = ?'); params.push(project); }
354
358
  if (type) { wheres.push('type = ?'); params.push(type); }
359
+ // --since: relative lower bound on created_at (e.g. "recent 1000 --since 24h").
360
+ if (flags.since !== undefined) {
361
+ const d = parseDuration(flags.since);
362
+ if (!d.ok) { fail(`[mem] Invalid --since "${flags.since}". Use <N><unit>, e.g. 7d, 24h, 90m, 2w.`); return; }
363
+ wheres.push('created_at_epoch >= ?'); params.push(Date.now() - d.ms);
364
+ }
355
365
  params.push(limit);
356
366
 
357
367
  const rows = db.prepare(`
@@ -2485,6 +2495,7 @@ Commands:
2485
2495
  --project P Filter by project
2486
2496
  --from DATE Start date (YYYY-MM-DD or ISO 8601)
2487
2497
  --to DATE End date (YYYY-MM-DD or ISO 8601)
2498
+ --since DUR Relative lower bound: 7d|24h|90m|2w|30s (ignored if --from set)
2488
2499
  --importance N Minimum importance (1=routine, 2=notable, 3=critical)
2489
2500
  --branch B Filter by git branch
2490
2501
  --offset N Skip first N results (pagination)
@@ -2496,6 +2507,7 @@ Commands:
2496
2507
 
2497
2508
  recent [N] Show N most recent observations (default 10)
2498
2509
  --limit N Sibling-parity alias for [N] (max 1000)
2510
+ --since DUR Only items newer than a relative window: 7d|24h|90m|2w|30s
2499
2511
  --project P Filter by project
2500
2512
  --type T Filter obs type (bugfix|decision|discovery|feature|refactor|change)
2501
2513
  --json Output as JSON: {project,limit,type,total,results:[…]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.28.3",
3
+ "version": "3.29.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",