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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/adopt-cli.mjs +125 -51
- package/bash-utils.mjs +105 -29
- package/claudemd.mjs +63 -19
- package/cli/activity.mjs +36 -18
- package/cli/common.mjs +101 -18
- package/cli/doctor.mjs +29 -9
- package/cli.mjs +69 -10
- package/deep-search.mjs +63 -20
- package/format-utils.mjs +15 -7
- package/haiku-client.mjs +189 -111
- package/hash-utils.mjs +15 -6
- package/hook-context.mjs +135 -46
- package/hook-episode.mjs +76 -23
- package/hook-handoff.mjs +216 -84
- package/hook-llm.mjs +476 -216
- package/hook-memory.mjs +106 -43
- package/hook-optimize.mjs +272 -109
- package/hook-precompact.mjs +1 -1
- package/hook-semaphore.mjs +46 -12
- package/hook-shared.mjs +101 -131
- package/hook-update.mjs +286 -88
- package/hook.mjs +1207 -617
- package/install-metadata.mjs +246 -125
- package/install.mjs +1327 -917
- package/lib/activity.mjs +52 -29
- package/lib/atomic-write.mjs +23 -4
- package/lib/binding-probe.mjs +40 -38
- package/lib/browse-core.mjs +22 -8
- package/lib/citation-tracker.mjs +189 -79
- package/lib/cite-back-hint.mjs +37 -19
- package/lib/cite-recall-path.mjs +3 -1
- package/lib/cli-flags.mjs +4 -2
- package/lib/cli-project.mjs +8 -5
- package/lib/compress-core.mjs +34 -9
- package/lib/cooldown-path.mjs +3 -1
- package/lib/db-backup.mjs +33 -11
- package/lib/deferred-work.mjs +73 -32
- package/lib/delete-core.mjs +25 -9
- package/lib/doctor-benchmark.mjs +5 -1
- package/lib/doctor-drift.mjs +25 -8
- package/lib/edge-attribution.mjs +23 -9
- package/lib/env-number.mjs +7 -8
- package/lib/err-sampler.mjs +22 -11
- package/lib/error-recall-core.mjs +15 -17
- package/lib/events-injection.mjs +25 -8
- package/lib/export-columns.mjs +49 -10
- package/lib/fast-summary.mjs +22 -9
- package/lib/file-edge-match.mjs +4 -2
- package/lib/file-intel.mjs +38 -13
- package/lib/frontmatter.mjs +22 -5
- package/lib/get-core.mjs +78 -11
- package/lib/handoff-constants.mjs +18 -0
- package/lib/hook-stdin.mjs +10 -3
- package/lib/hook-stdout.mjs +21 -7
- package/lib/hook-telemetry.mjs +44 -17
- package/lib/id-routing.mjs +11 -6
- package/lib/import-jsonl.mjs +92 -41
- package/lib/inject-search-core.mjs +5 -2
- package/lib/injected-ids.mjs +19 -12
- package/lib/install-shape.mjs +47 -23
- package/lib/keyctx-marker.mjs +3 -1
- package/lib/lesson-bridge.mjs +8 -5
- package/lib/llm-call.mjs +76 -0
- package/lib/llm-provider-probe.mjs +24 -12
- package/lib/low-signal-patterns.mjs +25 -22
- package/lib/maintain-core.mjs +221 -92
- package/lib/mem-override.mjs +4 -2
- package/lib/metrics.mjs +30 -25
- package/lib/native-binding-hint.mjs +29 -8
- package/lib/observation-write.mjs +74 -19
- package/lib/patha-exclude-meter.mjs +10 -6
- package/lib/persist-reminder.mjs +22 -10
- package/lib/plan-reader.mjs +2 -2
- package/lib/private-strip.mjs +3 -3
- package/lib/proc-lock.mjs +15 -3
- package/lib/proxy-fetch.mjs +49 -13
- package/lib/quiet-scope.mjs +42 -0
- package/lib/recall-core.mjs +19 -7
- package/lib/recent-core.mjs +18 -5
- package/lib/registry-core.mjs +12 -6
- package/lib/release-digest.mjs +4 -1
- package/lib/relevance-floor.mjs +5 -2
- package/lib/reread-guard.mjs +16 -8
- package/lib/resolve-data-dir.mjs +1 -1
- package/lib/rrf.mjs +4 -1
- package/lib/save-enrich.mjs +36 -18
- package/lib/save-observation.mjs +78 -35
- package/lib/scrub-record.mjs +20 -6
- package/lib/search-core.mjs +456 -123
- package/lib/shard-gc.mjs +50 -0
- package/lib/startup-dashboard.mjs +20 -9
- package/lib/stats-core.mjs +101 -35
- package/lib/stats-quality.mjs +56 -20
- package/lib/summary-extractor.mjs +2 -2
- package/lib/task-imperative.mjs +12 -4
- package/lib/timeline-core.mjs +88 -28
- package/lib/tmp-fixture-sweep.mjs +32 -9
- package/lib/transcript-scan.mjs +20 -4
- package/lib/upgrade-banner.mjs +11 -7
- package/mem-cli.mjs +1314 -506
- package/memdir.mjs +53 -21
- package/nlp.mjs +68 -38
- package/npm-shrinkwrap.json +2 -2
- package/package.json +9 -2
- package/plugin-cache-guard.mjs +31 -15
- package/project-utils.mjs +44 -20
- package/registry-enricher.mjs +26 -6
- package/registry-github.mjs +4 -2
- package/registry-importer.mjs +151 -40
- package/registry-recommend.mjs +189 -96
- package/registry-retriever.mjs +229 -70
- package/registry-scanner.mjs +11 -6
- package/registry.mjs +163 -66
- package/rerank.mjs +18 -4
- package/resource-discovery.mjs +14 -4
- package/schema.mjs +258 -99
- package/scoring-sql.mjs +21 -10
- package/scripts/binding-probe-cli.mjs +17 -12
- package/scripts/hook-launcher.mjs +148 -31
- package/scripts/launch-preflight.mjs +5 -7
- package/scripts/launch.mjs +21 -9
- package/scripts/post-tool-recall.js +21 -5
- package/scripts/pre-agent-inject.js +51 -13
- package/scripts/pre-skill-bridge.js +22 -7
- package/scripts/pre-tool-recall.js +157 -77
- package/scripts/prompt-search-utils.mjs +70 -25
- package/scripts/setup.sh +16 -1
- package/scripts/user-prompt-search.js +238 -97
- package/search-engine.mjs +329 -96
- package/search-scoring.mjs +75 -33
- package/secret-scrub.mjs +48 -12
- package/server/fts-check.mjs +12 -9
- package/server.mjs +674 -243
- package/skip-tools.mjs +19 -8
- package/source-files.mjs +54 -21
- package/stop-words.mjs +152 -19
- package/synonyms.mjs +579 -164
- package/tfidf.mjs +130 -40
- package/tier.mjs +1 -1
- package/tool-schemas.mjs +381 -131
- 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) {
|
|
75
|
-
|
|
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 {
|
|
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 ?? '')
|
|
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(
|
|
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
|
-
|
|
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;
|
|
240
|
-
export const AUTO_DEEP_THROTTLE_MS = 3000;
|
|
241
|
-
const REWRITE_CACHE_MAX = 256;
|
|
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() {
|
|
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);
|
|
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) {
|
|
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
|
|
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(
|
|
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) {
|
|
450
|
-
|
|
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 {
|
|
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) => {
|
|
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 >=
|
|
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 =
|
|
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;
|
|
88
|
+
if (next === text) return text; // fixpoint: nothing left to defang
|
|
88
89
|
text = next;
|
|
89
90
|
}
|
|
90
|
-
return text.replace(/[<>]/g, '');
|
|
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 = {
|
|
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.
|