claude-mem-lite 3.96.0 → 3.97.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.
Files changed (143) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/adopt-cli.mjs +125 -51
  4. package/bash-utils.mjs +105 -29
  5. package/claudemd.mjs +63 -19
  6. package/cli/activity.mjs +36 -18
  7. package/cli/common.mjs +101 -18
  8. package/cli/doctor.mjs +29 -9
  9. package/cli.mjs +69 -10
  10. package/deep-search.mjs +63 -20
  11. package/format-utils.mjs +15 -7
  12. package/haiku-client.mjs +189 -111
  13. package/hash-utils.mjs +15 -6
  14. package/hook-context.mjs +135 -46
  15. package/hook-episode.mjs +76 -23
  16. package/hook-handoff.mjs +216 -84
  17. package/hook-llm.mjs +476 -216
  18. package/hook-memory.mjs +106 -43
  19. package/hook-optimize.mjs +272 -109
  20. package/hook-precompact.mjs +1 -1
  21. package/hook-semaphore.mjs +46 -12
  22. package/hook-shared.mjs +101 -131
  23. package/hook-update.mjs +286 -88
  24. package/hook.mjs +1207 -617
  25. package/install-metadata.mjs +246 -125
  26. package/install.mjs +1327 -917
  27. package/lib/activity.mjs +52 -29
  28. package/lib/atomic-write.mjs +23 -4
  29. package/lib/binding-probe.mjs +40 -38
  30. package/lib/browse-core.mjs +22 -8
  31. package/lib/citation-tracker.mjs +189 -79
  32. package/lib/cite-back-hint.mjs +37 -19
  33. package/lib/cite-recall-path.mjs +3 -1
  34. package/lib/cli-flags.mjs +4 -2
  35. package/lib/cli-project.mjs +8 -5
  36. package/lib/compress-core.mjs +34 -9
  37. package/lib/cooldown-path.mjs +3 -1
  38. package/lib/db-backup.mjs +33 -11
  39. package/lib/deferred-work.mjs +73 -32
  40. package/lib/delete-core.mjs +25 -9
  41. package/lib/doctor-benchmark.mjs +5 -1
  42. package/lib/doctor-drift.mjs +25 -8
  43. package/lib/edge-attribution.mjs +23 -9
  44. package/lib/env-number.mjs +7 -8
  45. package/lib/err-sampler.mjs +22 -11
  46. package/lib/error-recall-core.mjs +15 -17
  47. package/lib/events-injection.mjs +25 -8
  48. package/lib/export-columns.mjs +49 -10
  49. package/lib/fast-summary.mjs +22 -9
  50. package/lib/file-edge-match.mjs +4 -2
  51. package/lib/file-intel.mjs +38 -13
  52. package/lib/frontmatter.mjs +22 -5
  53. package/lib/get-core.mjs +78 -11
  54. package/lib/handoff-constants.mjs +18 -0
  55. package/lib/hook-stdin.mjs +10 -3
  56. package/lib/hook-stdout.mjs +21 -7
  57. package/lib/hook-telemetry.mjs +44 -17
  58. package/lib/id-routing.mjs +11 -6
  59. package/lib/import-jsonl.mjs +92 -41
  60. package/lib/inject-search-core.mjs +5 -2
  61. package/lib/injected-ids.mjs +19 -12
  62. package/lib/install-shape.mjs +47 -23
  63. package/lib/keyctx-marker.mjs +3 -1
  64. package/lib/lesson-bridge.mjs +8 -5
  65. package/lib/llm-call.mjs +76 -0
  66. package/lib/llm-provider-probe.mjs +24 -12
  67. package/lib/low-signal-patterns.mjs +25 -22
  68. package/lib/maintain-core.mjs +221 -92
  69. package/lib/mem-override.mjs +4 -2
  70. package/lib/metrics.mjs +30 -25
  71. package/lib/native-binding-hint.mjs +29 -8
  72. package/lib/observation-write.mjs +74 -19
  73. package/lib/patha-exclude-meter.mjs +10 -6
  74. package/lib/persist-reminder.mjs +22 -10
  75. package/lib/plan-reader.mjs +2 -2
  76. package/lib/private-strip.mjs +3 -3
  77. package/lib/proc-lock.mjs +15 -3
  78. package/lib/proxy-fetch.mjs +49 -13
  79. package/lib/quiet-scope.mjs +42 -0
  80. package/lib/recall-core.mjs +19 -7
  81. package/lib/recent-core.mjs +18 -5
  82. package/lib/registry-core.mjs +12 -6
  83. package/lib/release-digest.mjs +4 -1
  84. package/lib/relevance-floor.mjs +5 -2
  85. package/lib/reread-guard.mjs +16 -8
  86. package/lib/resolve-data-dir.mjs +1 -1
  87. package/lib/rrf.mjs +4 -1
  88. package/lib/save-enrich.mjs +36 -18
  89. package/lib/save-observation.mjs +78 -35
  90. package/lib/scrub-record.mjs +20 -6
  91. package/lib/search-core.mjs +456 -123
  92. package/lib/shard-gc.mjs +50 -0
  93. package/lib/startup-dashboard.mjs +20 -9
  94. package/lib/stats-core.mjs +101 -35
  95. package/lib/stats-quality.mjs +56 -20
  96. package/lib/summary-extractor.mjs +2 -2
  97. package/lib/task-imperative.mjs +12 -4
  98. package/lib/timeline-core.mjs +88 -28
  99. package/lib/tmp-fixture-sweep.mjs +32 -9
  100. package/lib/transcript-scan.mjs +20 -4
  101. package/lib/upgrade-banner.mjs +11 -7
  102. package/mem-cli.mjs +1314 -506
  103. package/memdir.mjs +53 -21
  104. package/nlp.mjs +68 -38
  105. package/npm-shrinkwrap.json +2 -2
  106. package/package.json +9 -2
  107. package/plugin-cache-guard.mjs +31 -15
  108. package/project-utils.mjs +44 -20
  109. package/registry-enricher.mjs +26 -6
  110. package/registry-github.mjs +4 -2
  111. package/registry-importer.mjs +151 -40
  112. package/registry-recommend.mjs +189 -96
  113. package/registry-retriever.mjs +229 -70
  114. package/registry-scanner.mjs +11 -6
  115. package/registry.mjs +163 -66
  116. package/rerank.mjs +18 -4
  117. package/resource-discovery.mjs +14 -4
  118. package/schema.mjs +258 -99
  119. package/scoring-sql.mjs +21 -10
  120. package/scripts/binding-probe-cli.mjs +17 -12
  121. package/scripts/hook-launcher.mjs +148 -31
  122. package/scripts/launch-preflight.mjs +5 -7
  123. package/scripts/launch.mjs +21 -9
  124. package/scripts/post-tool-recall.js +21 -5
  125. package/scripts/pre-agent-inject.js +51 -13
  126. package/scripts/pre-skill-bridge.js +22 -7
  127. package/scripts/pre-tool-recall.js +157 -77
  128. package/scripts/prompt-search-utils.mjs +70 -25
  129. package/scripts/setup.sh +16 -1
  130. package/scripts/user-prompt-search.js +238 -97
  131. package/search-engine.mjs +329 -96
  132. package/search-scoring.mjs +75 -33
  133. package/secret-scrub.mjs +48 -12
  134. package/server/fts-check.mjs +12 -9
  135. package/server.mjs +674 -243
  136. package/skip-tools.mjs +19 -8
  137. package/source-files.mjs +54 -21
  138. package/stop-words.mjs +152 -19
  139. package/synonyms.mjs +579 -164
  140. package/tfidf.mjs +130 -40
  141. package/tier.mjs +1 -1
  142. package/tool-schemas.mjs +381 -131
  143. package/utils.mjs +184 -36
package/deep-search.mjs CHANGED
@@ -71,10 +71,17 @@ export function hasEscalatableCorpus(db, project, min = AUTO_DEEP_MIN_CORPUS) {
71
71
  try {
72
72
  const where = [liveObsFilterSql('')];
73
73
  const params = [];
74
- if (project) { where.push('project = ?'); params.push(project); }
75
- const row = db.prepare(`SELECT COUNT(*) AS c FROM observations WHERE ${where.join(' AND ')}`).get(...params);
74
+ if (project) {
75
+ where.push('project = ?');
76
+ params.push(project);
77
+ }
78
+ const row = db
79
+ .prepare(`SELECT COUNT(*) AS c FROM observations WHERE ${where.join(' AND ')}`)
80
+ .get(...params);
76
81
  return (row?.c ?? 0) >= min;
77
- } catch { return true; } // on any error, don't suppress escalation (fail open)
82
+ } catch {
83
+ return true;
84
+ } // on any error, don't suppress escalation (fail open)
78
85
  }
79
86
 
80
87
  /**
@@ -95,7 +102,9 @@ export function autoDeepLlmReady(env = process.env, injectedLlm) {
95
102
  // escalation by default; the burst/latency cost is bounded by the auto
96
103
  // provider (fail-fast + throttle) and a failed rewrite degrades to baseline.
97
104
  // Kill switch honors the common disable spellings, not just the exact '0'.
98
- const off = String(env.CLAUDE_MEM_AUTO_DEEP_CLI ?? '').trim().toLowerCase();
105
+ const off = String(env.CLAUDE_MEM_AUTO_DEEP_CLI ?? '')
106
+ .trim()
107
+ .toLowerCase();
99
108
  return !(off === '0' || off === 'false' || off === 'no' || off === 'off');
100
109
  }
101
110
 
@@ -142,7 +151,11 @@ export function autoDeepLlmReady(env = process.env, injectedLlm) {
142
151
  * @param {number} [opts.minCorpus=AUTO_DEEP_MIN_CORPUS] corpus-size floor (when db given)
143
152
  * @returns {boolean}
144
153
  */
145
- export function shouldEscalateToDeep(results, _ctx, { minResults = AUTO_DEEP_MIN_RESULTS, db, project = null, minCorpus = AUTO_DEEP_MIN_CORPUS } = {}) {
154
+ export function shouldEscalateToDeep(
155
+ results,
156
+ _ctx,
157
+ { minResults = AUTO_DEEP_MIN_RESULTS, db, project = null, minCorpus = AUTO_DEEP_MIN_CORPUS } = {},
158
+ ) {
146
159
  const n = Array.isArray(results) ? results.length : 0;
147
160
  if (n >= minResults) return false;
148
161
  // Count is weak. If a db was supplied, also require an escalatable corpus —
@@ -181,7 +194,7 @@ const INJECTION_GUARD =
181
194
 
182
195
  export const REWRITE_SYSTEM =
183
196
  'You reformulate a memory-search query into search variants that bridge the gap ' +
184
- 'between a user\'s wording and the technical terms a stored memory actually uses.\n' +
197
+ "between a user's wording and the technical terms a stored memory actually uses.\n" +
185
198
  'Output STRICT JSON only, no prose: {"variants": ["v1", "v2", "v3"]}\n' +
186
199
  ' - v1: the same intent in concrete keyword / technical-term form\n' +
187
200
  ' - v2: concept expansion — synonyms and closely related terms\n' +
@@ -236,20 +249,24 @@ export function assembleVariants(query, parsed, { max = MAX_VARIANTS } = {}) {
236
249
  // so it must be fail-fast (short timeout, no retry), throttled (bound bursts),
237
250
  // and cached (skip repeat rewrites). The EXPLICIT deep=true path stays patient.
238
251
 
239
- export const AUTO_DEEP_TIMEOUT_MS = 5000; // fail-fast budget for the auto path; no retry
240
- export const AUTO_DEEP_THROTTLE_MS = 3000; // min gap between auto LLM rewrites, per process (bounds spawn rate)
241
- const REWRITE_CACHE_MAX = 256; // LRU cap for the query→variants cache
252
+ export const AUTO_DEEP_TIMEOUT_MS = 5000; // fail-fast budget for the auto path; no retry
253
+ export const AUTO_DEEP_THROTTLE_MS = 3000; // min gap between auto LLM rewrites, per process (bounds spawn rate)
254
+ const REWRITE_CACHE_MAX = 256; // LRU cap for the query→variants cache
242
255
 
243
256
  let _lastAutoLlmAt = 0;
244
257
  const _rewriteCache = new Map(); // normalized query → variants (string[]); successes only
245
258
 
246
259
  /** Reset auto-path throttle + cache. Test-only; production state is per-process. */
247
- export function _resetAutoDeepState() { _lastAutoLlmAt = 0; _rewriteCache.clear(); }
260
+ export function _resetAutoDeepState() {
261
+ _lastAutoLlmAt = 0;
262
+ _rewriteCache.clear();
263
+ }
248
264
 
249
265
  function cacheGet(key) {
250
266
  if (!_rewriteCache.has(key)) return null;
251
267
  const v = _rewriteCache.get(key);
252
- _rewriteCache.delete(key); _rewriteCache.set(key, v); // LRU bump
268
+ _rewriteCache.delete(key);
269
+ _rewriteCache.set(key, v); // LRU bump
253
270
  return v.slice();
254
271
  }
255
272
  function cacheSet(key, variants) {
@@ -330,7 +347,8 @@ export async function rewriteQuery(query, { llm = defaultLLM, retries = 1, cache
330
347
  parsed = null;
331
348
  }
332
349
  const variants = assembleVariants(original, parsed);
333
- if (variants.length > 1) { // got at least one real rewrite
350
+ if (variants.length > 1) {
351
+ // got at least one real rewrite
334
352
  if (cache) cacheSet(key, variants); // cache successes only — failures retry next time
335
353
  return variants;
336
354
  }
@@ -357,8 +375,7 @@ export function rrfFuseN(rankedLists, k = RRF_K) {
357
375
  // convention) plus an rrfScore field. rrfAccumulate already keeps each id's
358
376
  // best-ranked row, so query-dependent fields (notably the FTS snippet) come from
359
377
  // the strongest variant rather than first-seen (F10).
360
- return rrfAccumulate(rankedLists, k)
361
- .map(({ row, score }) => ({ ...row, score: -score, rrfScore: score }));
378
+ return rrfAccumulate(rankedLists, k).map(({ row, score }) => ({ ...row, score: -score, rrfScore: score }));
362
379
  }
363
380
 
364
381
  // Build the searchObservationsHybrid ctx for one variant. Mirrors the
@@ -403,7 +420,8 @@ function defaultRerankText(db, rows) {
403
420
  const ids = rows.map((r) => r.id);
404
421
  const ph = ids.map(() => '?').join(',');
405
422
  const found = new Map(
406
- db.prepare(`SELECT id, narrative, title, subtitle FROM observations WHERE id IN (${ph})`)
423
+ db
424
+ .prepare(`SELECT id, narrative, title, subtitle FROM observations WHERE id IN (${ph})`)
407
425
  .all(...ids)
408
426
  .map((o) => [o.id, o.narrative || [o.title, o.subtitle].filter(Boolean).join(' — ')]),
409
427
  );
@@ -435,7 +453,20 @@ function defaultRerankText(db, rows) {
435
453
  * @param {(db:Database, rows:Array)=>Map} [deps.rerankTextFn] id→text builder for the rerank prompt
436
454
  * @returns {Promise<{results: Array, variants: string[], reranked: boolean}>}
437
455
  */
438
- export async function deepSearch(db, params, { llm, searchFn = defaultSearchFn, rrfK = RRF_K, auto = false, rerank = false, rerankLlm, rerankTopK = RERANK_TOPK, rerankTextFn = defaultRerankText } = {}) {
456
+ export async function deepSearch(
457
+ db,
458
+ params,
459
+ {
460
+ llm,
461
+ searchFn = defaultSearchFn,
462
+ rrfK = RRF_K,
463
+ auto = false,
464
+ rerank = false,
465
+ rerankLlm,
466
+ rerankTopK = RERANK_TOPK,
467
+ rerankTextFn = defaultRerankText,
468
+ } = {},
469
+ ) {
439
470
  const query = String(params?.query ?? '').trim();
440
471
  if (!query) return { results: [], variants: [], reranked: false };
441
472
 
@@ -446,8 +477,11 @@ export async function deepSearch(db, params, { llm, searchFn = defaultSearchFn,
446
477
  let retries = 1;
447
478
  let cache = false;
448
479
  if (!rewriteLlm) {
449
- if (auto) { rewriteLlm = makeAutoLlm(); retries = 0; cache = true; }
450
- else rewriteLlm = defaultLLM;
480
+ if (auto) {
481
+ rewriteLlm = makeAutoLlm();
482
+ retries = 0;
483
+ cache = true;
484
+ } else rewriteLlm = defaultLLM;
451
485
  }
452
486
  const variants = await rewriteQuery(query, { llm: rewriteLlm, retries, cache });
453
487
  const lists = variants.map((v, i) => {
@@ -457,7 +491,13 @@ export async function deepSearch(db, params, { llm, searchFn = defaultSearchFn,
457
491
  // swallowed into an empty result (F5). Only rewrite variants are best-effort.
458
492
  let list;
459
493
  if (i === 0) list = searchFn(db, v, params) || [];
460
- else { try { list = searchFn(db, v, params) || []; } catch { list = []; } }
494
+ else {
495
+ try {
496
+ list = searchFn(db, v, params) || [];
497
+ } catch {
498
+ list = [];
499
+ }
500
+ }
461
501
  // rrfFuseN fuses by array index as rank, so each list MUST already be in
462
502
  // composite-score order. searchObservationsHybrid appends downweighted
463
503
  // concept(×0.7)/PRF(×0.6) expansion rows to the TAIL unsorted and, on the
@@ -501,7 +541,10 @@ export async function deepSearch(db, params, { llm, searchFn = defaultSearchFn,
501
541
  // (server.mjs also skips its context re-rank/re-sort when reranked, so the LLM
502
542
  // judgement is the final order — the re-stamp keeps score honest regardless.)
503
543
  const scores = top.map((r) => r.score).sort((a, b) => a - b);
504
- head.forEach((r, i) => { r.score = scores[i]; r.rrfScore = -scores[i]; });
544
+ head.forEach((r, i) => {
545
+ r.score = scores[i];
546
+ r.rrfScore = -scores[i];
547
+ });
505
548
  ordered = [...head, ...fused.slice(k)];
506
549
  reranked = true;
507
550
  }
package/format-utils.mjs CHANGED
@@ -21,7 +21,7 @@ export function truncate(str, max = 80) {
21
21
  // code unit is a high surrogate, drop it so we cut on a code-point boundary.
22
22
  let end = max - 1;
23
23
  const last = str.charCodeAt(end - 1);
24
- if (last >= 0xD800 && last <= 0xDBFF) end--;
24
+ if (last >= 0xd800 && last <= 0xdbff) end--;
25
25
  return str.slice(0, end) + '\u2026';
26
26
  }
27
27
 
@@ -47,7 +47,8 @@ export function truncate(str, max = 80) {
47
47
  // (<system-reminder foo="\u2026">). Unrelated tags (<other-tag>) are left intact.
48
48
  // Reachable by editing files that contain these tokens \u2014 e.g. developing claude-mem-lite
49
49
  // itself, where source/observations carry the delimiter names.
50
- const CONTEXT_DELIMITER_RE = /<\/?(?:claude-mem-context|memory-context|session-handoff|system-reminder|task-notification|(?:antml:)?function_calls|(?:antml:)?function_results|(?:antml:)?invoke|(?:antml:)?parameter)(?:\s[^>]*)?>/gi;
50
+ const CONTEXT_DELIMITER_RE =
51
+ /<\/?(?:claude-mem-context|memory-context|session-handoff|system-reminder|task-notification|(?:antml:)?function_calls|(?:antml:)?function_results|(?:antml:)?invoke|(?:antml:)?parameter)(?:\s[^>]*)?>/gi;
51
52
 
52
53
  // Pass cap for the fixpoint loop below. 32 nested layers of a forged delimiter is far past
53
54
  // anything prose produces; the cap exists only to bound the ADVERSARIAL cost (an unbounded
@@ -84,10 +85,10 @@ function defangToFixpoint(s, re) {
84
85
  let text = String(s ?? '');
85
86
  for (let pass = 0; pass < DEFANG_MAX_PASSES; pass++) {
86
87
  const next = text.replace(re, (m) => m.slice(1, -1));
87
- if (next === text) return text; // fixpoint: nothing left to defang
88
+ if (next === text) return text; // fixpoint: nothing left to defang
88
89
  text = next;
89
90
  }
90
- return text.replace(/[<>]/g, ''); // pathological nesting \u2192 fail closed
91
+ return text.replace(/[<>]/g, ''); // pathological nesting \u2192 fail closed
91
92
  }
92
93
 
93
94
  /**
@@ -171,7 +172,7 @@ export function formatErrorRecallHints(rows) {
171
172
  }
172
173
  return head;
173
174
  });
174
- const ids = rows.map(r => r.id).join(',');
175
+ const ids = rows.map((r) => r.id).join(',');
175
176
  return `[claude-mem-lite] Related memories found for this error:\n${lines.join('\n')}\n \u2192 Use mem_get(ids=[${ids}]) for details.\n`;
176
177
  }
177
178
 
@@ -181,13 +182,20 @@ export function formatErrorRecallHints(rows) {
181
182
  * @returns {string} Emoji icon for the type
182
183
  */
183
184
  export function typeIcon(type) {
184
- const icons = { decision: '\uD83D\uDFE1', bugfix: '\uD83D\uDD34', feature: '\uD83D\uDFE2', refactor: '\uD83D\uDD35', discovery: '\uD83D\uDD0D', change: '\uD83D\uDCDD' };
185
+ const icons = {
186
+ decision: '\uD83D\uDFE1',
187
+ bugfix: '\uD83D\uDD34',
188
+ feature: '\uD83D\uDFE2',
189
+ refactor: '\uD83D\uDD35',
190
+ discovery: '\uD83D\uDD0D',
191
+ change: '\uD83D\uDCDD',
192
+ };
185
193
  return icons[type] || '\u26AA';
186
194
  }
187
195
 
188
196
  // ─── Date Formatting ─────────────────────────────────────────────────────────
189
197
 
190
- const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
198
+ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
191
199
 
192
200
  /**
193
201
  * Format an ISO date string as "Mon DD HH:MM" for compact display.