claude-mem-lite 3.7.0 → 3.8.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/README.md +3 -1
- package/README.zh-CN.md +1 -1
- package/hook-update.mjs +102 -2
- package/hook.mjs +403 -373
- package/install.mjs +666 -629
- package/lib/doctor-benchmark.mjs +4 -4
- package/lib/release-digest.mjs +106 -0
- package/lib/search-core.mjs +272 -16
- package/mem-cli.mjs +55 -174
- package/package.json +3 -2
- package/schema.mjs +7 -1
- package/scripts/setup.sh +2 -0
- package/search-engine.mjs +1 -1
- package/{server-internals.mjs → search-scoring.mjs} +6 -2
- package/server.mjs +72 -293
- package/source-files.mjs +5 -1
package/mem-cli.mjs
CHANGED
|
@@ -8,8 +8,8 @@ import { truncate, typeIcon, inferProject, scrubSecrets } from './utils.mjs';
|
|
|
8
8
|
import { resolveProject } from './project-utils.mjs';
|
|
9
9
|
import { TIER_CASE_SQL, tierSqlParams } from './tier.mjs';
|
|
10
10
|
import { _resetVocabCache } from './tfidf.mjs';
|
|
11
|
-
import { autoBoostIfNeeded, reRankWithContext, markSuperseded } from './
|
|
12
|
-
import { searchObservationsHybrid
|
|
11
|
+
import { autoBoostIfNeeded, reRankWithContext, markSuperseded } from './search-scoring.mjs';
|
|
12
|
+
import { searchObservationsHybrid } from './search-engine.mjs';
|
|
13
13
|
import { deepSearch, resolveDeepMode, shouldEscalateToDeep, autoDeepLlmReady } from './deep-search.mjs';
|
|
14
14
|
import { ensureRegistryDb, upsertResource } from './registry.mjs';
|
|
15
15
|
import { searchResources } from './registry-retriever.mjs';
|
|
@@ -37,7 +37,7 @@ import { saveObservation } from './lib/save-observation.mjs';
|
|
|
37
37
|
import { rebuildObservationDerived } from './lib/observation-write.mjs';
|
|
38
38
|
import { recallByFile } from './lib/recall-core.mjs';
|
|
39
39
|
import { resolveAnchorToken, formatAnchorError, resolveQueryAnchor, fetchRecentTimeline, fetchTimelineWindow } from './lib/timeline-core.mjs';
|
|
40
|
-
import { buildSearchFtsQuery, parseDateBounds,
|
|
40
|
+
import { buildSearchFtsQuery, parseDateBounds, coreRunSearchPipeline } from './lib/search-core.mjs';
|
|
41
41
|
import { AUTO_MERGE_THRESHOLD } from './lib/dedup-constants.mjs';
|
|
42
42
|
import { countRecentHookErrors } from './lib/hook-telemetry.mjs';
|
|
43
43
|
import { computeCitationFunnelTrend } from './lib/citation-tracker.mjs';
|
|
@@ -156,116 +156,51 @@ async function cmdSearch(db, args, { llm } = {}) {
|
|
|
156
156
|
? 'observations'
|
|
157
157
|
: (source || ((type || tier || minImportance || branch) ? 'observations' : null));
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
epochFrom: dateFrom,
|
|
205
|
-
epochTo: dateTo,
|
|
206
|
-
limit: perSourceLimit,
|
|
207
|
-
currentProject: project ? null : inferProject(),
|
|
208
|
-
}, llm ? { llm, rerank: rerank && !auto } : { auto, rerank: rerank && !auto });
|
|
209
|
-
deepVariants = ds.variants;
|
|
210
|
-
isReranked = ds.reranked;
|
|
211
|
-
if (deepVariants.length > 1) {
|
|
212
|
-
process.stderr.write(`[mem] Deep search: rewrote into ${deepVariants.length} query variants, RRF-fused\n`);
|
|
213
|
-
} else {
|
|
214
|
-
process.stderr.write('[mem] Deep search: rewrite returned no usable variants; used original query only\n');
|
|
215
|
-
}
|
|
216
|
-
if (rerank && !auto) {
|
|
217
|
-
process.stderr.write(ds.reranked
|
|
218
|
-
? '[mem] Deep search: LLM-reranked the fused top-20\n'
|
|
219
|
-
: '[mem] Deep search: rerank produced no usable order; kept fused order\n');
|
|
220
|
-
}
|
|
221
|
-
return ds.results;
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
let obsResults;
|
|
225
|
-
if (deepMode === 'deep') {
|
|
226
|
-
obsResults = await runDeep();
|
|
227
|
-
} else {
|
|
228
|
-
obsResults = searchObservationsHybrid(db, obsCtx);
|
|
229
|
-
if (obsCtx.orFallbackFired) orFallbackFired = true;
|
|
230
|
-
if (deepMode === 'auto' && autoDeepLlmReady(process.env, llm) && shouldEscalateToDeep(obsResults, obsCtx, { db, project: project || null })) {
|
|
231
|
-
process.stderr.write(`[mem] auto-escalated to deep search (weak results: ${obsResults.length} hits)\n`);
|
|
232
|
-
obsResults = await runDeep({ auto: true });
|
|
233
|
-
isDeep = true;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
for (const r of obsResults) results.push({ ...r, _source: 'obs', score: r.score ?? 0 });
|
|
237
|
-
|
|
238
|
-
// Tier post-filter — applied to ALL obs results from the engine.
|
|
239
|
-
if (tier) {
|
|
240
|
-
const filtered = applyTierFilter(db, results, { tier, sourceKey: '_source', currentProject: project || inferProject() });
|
|
241
|
-
results.length = 0;
|
|
242
|
-
results.push(...filtered);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Search sessions (shared engine with MCP mem_search — lib/search-core.mjs)
|
|
247
|
-
if ((!effectiveSource || effectiveSource === 'sessions') && !isDeep) {
|
|
248
|
-
try {
|
|
249
|
-
const sessRows = searchSessionsFts(db, {
|
|
250
|
-
ftsQuery, project, projectBoost: project ? null : inferProject(),
|
|
251
|
-
epochFrom: dateFrom, epochTo: dateTo, perSourceLimit, perSourceOffset,
|
|
252
|
-
});
|
|
253
|
-
for (const r of sessRows) results.push({ ...r, _source: 'session' });
|
|
254
|
-
} catch { /* session FTS may not exist in older DBs */ }
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Search prompts (shared engine incl. CJK precision gate + LIKE fallback)
|
|
258
|
-
if ((!effectiveSource || effectiveSource === 'prompts') && !isDeep) {
|
|
259
|
-
try {
|
|
260
|
-
const promptRows = searchPromptsFts(db, {
|
|
261
|
-
query, ftsQuery, project,
|
|
262
|
-
epochFrom: dateFrom, epochTo: dateTo, perSourceLimit, perSourceOffset,
|
|
263
|
-
});
|
|
264
|
-
for (const r of promptRows) results.push({ ...r, _source: 'prompt' });
|
|
265
|
-
} catch { /* prompt FTS may not exist in older DBs */ }
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (results.length === 0) {
|
|
159
|
+
const res = await coreRunSearchPipeline(
|
|
160
|
+
{
|
|
161
|
+
db, currentProject: project ? null : inferProject(), env: process.env,
|
|
162
|
+
searchObservationsHybrid, deepSearch, shouldEscalateToDeep, autoDeepLlmReady,
|
|
163
|
+
reRankWithContext, markSuperseded, llm,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
query, ftsQuery, effectiveSource, deepMode, rerank,
|
|
167
|
+
limit, offset, project: project || null, obsType: type, importance: minImportance,
|
|
168
|
+
branch, includeNoise, epochFrom: dateFrom, epochTo: dateTo, sort, tier,
|
|
169
|
+
// ── CLI surface policy ──
|
|
170
|
+
obsTypeFallback: false, // #8217 removed list-by-type fallback from the CLI
|
|
171
|
+
crossSourceEpochSortNoFts: false, // CLI never reaches cross-source with empty ftsQuery (fails earlier)
|
|
172
|
+
rerankPolicy: 'cli', // re-rank/supersede on any obs; re-sort gated on cross-source
|
|
173
|
+
rerankProject: project || inferProject(),
|
|
174
|
+
recentListingNoFts: false,
|
|
175
|
+
tolerateMissingFts: true, // pre-FTS legacy DBs: swallow session/prompt FTS errors
|
|
176
|
+
tierPosition: 'early', // tier filter inside the obs block (before sessions/prompts)
|
|
177
|
+
tierProject: project || inferProject(),
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
const isDeep = res.isDeep;
|
|
181
|
+
const orFallbackFired = res.orFallbackFired;
|
|
182
|
+
const deepVariants = res.variants;
|
|
183
|
+
const paged = res.page;
|
|
184
|
+
const total = res.total;
|
|
185
|
+
|
|
186
|
+
// Deep / escalation observability on stderr — reconstructed from core signals.
|
|
187
|
+
// The CLI emitted these inline in runDeep; same strings, same order (escalation →
|
|
188
|
+
// variants → rerank). rerank is only ever true on explicit --deep (never auto).
|
|
189
|
+
if (res.escalated) process.stderr.write(`[mem] auto-escalated to deep search (weak results: ${res.escalatedObsCount} hits)\n`);
|
|
190
|
+
if (isDeep && deepVariants) {
|
|
191
|
+
process.stderr.write(deepVariants.length > 1
|
|
192
|
+
? `[mem] Deep search: rewrote into ${deepVariants.length} query variants, RRF-fused\n`
|
|
193
|
+
: '[mem] Deep search: rewrite returned no usable variants; used original query only\n');
|
|
194
|
+
}
|
|
195
|
+
if (rerank) {
|
|
196
|
+
process.stderr.write(res.reranked
|
|
197
|
+
? '[mem] Deep search: LLM-reranked the fused top-20\n'
|
|
198
|
+
: '[mem] Deep search: rerank produced no usable order; kept fused order\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// "nothing matched" (no offset) vs "this page is empty" (with offset) — the two
|
|
202
|
+
// CLI messages. preFinalizeCount is the pre-pagination population (post-tier).
|
|
203
|
+
if (res.preFinalizeCount === 0) {
|
|
269
204
|
if (jsonOutput) {
|
|
270
205
|
out(JSON.stringify({ query, total: 0, returned: 0, offset, limit, deep: isDeep, variants: isDeep ? deepVariants : undefined, results: [] }));
|
|
271
206
|
} else {
|
|
@@ -274,60 +209,6 @@ async function cmdSearch(db, args, { llm } = {}) {
|
|
|
274
209
|
return;
|
|
275
210
|
}
|
|
276
211
|
|
|
277
|
-
// Cross-source score normalization (shared with mem_search).
|
|
278
|
-
// ftsQuery gate prevents normalization when scores are all 0 (no-FTS path).
|
|
279
|
-
const isCrossSource = isCrossSourceMode;
|
|
280
|
-
if (isCrossSource && results.length > 0 && ftsQuery) {
|
|
281
|
-
normalizeCrossSourceScores(results, '_source');
|
|
282
|
-
results.sort((a, b) => (a.score ?? 0) - (b.score ?? 0));
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Context re-ranking + superseded marking (aligned with MCP mem_search)
|
|
286
|
-
const obsResults = results.filter(r => r._source === 'obs');
|
|
287
|
-
if (obsResults.length > 0) {
|
|
288
|
-
// reRankWithContext/markSuperseded expect source='obs' — alias _source for compatibility
|
|
289
|
-
for (const r of obsResults) r.source = 'obs';
|
|
290
|
-
// Explicit LLM rerank order is final — skip file-context re-rank when reranked
|
|
291
|
-
// (paired-path with mem_search; markSuperseded still runs for stale-tagging).
|
|
292
|
-
if (!isReranked) reRankWithContext(db, obsResults, project || inferProject());
|
|
293
|
-
markSuperseded(obsResults);
|
|
294
|
-
if (isCrossSource) results.sort((a, b) => (a.score ?? 0) - (b.score ?? 0));
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Apply user-requested sort (after relevance scoring; shared with mem_search)
|
|
298
|
-
applyUserSort(results, sort);
|
|
299
|
-
|
|
300
|
-
// Trim to limit with offset. The engine always received perSourceOffset=0 and
|
|
301
|
-
// over-fetched (see above), so the merged+reranked `results` start at row 0 and
|
|
302
|
-
// the offset is applied exactly ONCE here — for every mode.
|
|
303
|
-
//
|
|
304
|
-
// `total` must be the TRUE population, independent of --limit/--offset (else the
|
|
305
|
-
// over-fetched candidate count grew with the page and broke the "N of M" /
|
|
306
|
-
// pagination contract). countSearchTotal mirrors each source's MATCH+filters;
|
|
307
|
-
// clamp to >= results.length so it never understates the rows actually shown
|
|
308
|
-
// (vector/concept augmentation can add obs rows beyond the FTS count).
|
|
309
|
-
// For --deep the population is the fused variant result set: deepSearch already
|
|
310
|
-
// returned all fused rows (capped at perSourceLimit) and they are the only rows
|
|
311
|
-
// in `results` (deep is obs-only). countSearchTotal would instead count the
|
|
312
|
-
// ORIGINAL query's FTS matches — wrong, and ~0 on the vocabulary-mismatch
|
|
313
|
-
// queries deep exists for, which falsely shrinks the "N of M" total (F1).
|
|
314
|
-
const total = isDeep
|
|
315
|
-
? results.length
|
|
316
|
-
: Math.max(countSearchTotal(db, {
|
|
317
|
-
effectiveSource,
|
|
318
|
-
ftsQuery,
|
|
319
|
-
obsFtsQuery: effectiveObsFtsQuery(ftsQuery, orFallbackFired),
|
|
320
|
-
args: { project: project || null, obs_type: type || null, importance: minImportance || null, branch: branch || null },
|
|
321
|
-
project: project || null,
|
|
322
|
-
epochFrom: dateFrom,
|
|
323
|
-
epochTo: dateTo,
|
|
324
|
-
includeNoise,
|
|
325
|
-
}), results.length);
|
|
326
|
-
const paged = results.slice(offset, offset + limit);
|
|
327
|
-
// Enrich the final page with the ~Nt fetch-cost hint (paired with MCP mem_search; #8654 both
|
|
328
|
-
// source keys handled). Batch-fetches heavy obs fields by id — no-op on an empty page.
|
|
329
|
-
attachBodyTokens(db, paged);
|
|
330
|
-
|
|
331
212
|
if (paged.length === 0) {
|
|
332
213
|
if (jsonOutput) {
|
|
333
214
|
out(JSON.stringify({ query, total, returned: 0, offset, limit, deep: isDeep, variants: isDeep ? deepVariants : undefined, results: [] }));
|
|
@@ -337,24 +218,24 @@ async function cmdSearch(db, args, { llm } = {}) {
|
|
|
337
218
|
return;
|
|
338
219
|
}
|
|
339
220
|
|
|
340
|
-
//
|
|
221
|
+
// "N of M" total when paged < total (paired-path with server.mjs formatSearchOutput, #8198).
|
|
341
222
|
const showTime = sort === 'time';
|
|
342
|
-
const hasMixed = paged.some(r => r.
|
|
223
|
+
const hasMixed = paged.some(r => r.source === 'session' || r.source === 'prompt');
|
|
343
224
|
// Suppressed when --or was explicit — user already asked for OR, no "fallback" there.
|
|
344
225
|
const fallbackHint = orFallbackFired && !useOr ? ' (relaxed AND→OR)' : '';
|
|
345
226
|
|
|
346
227
|
if (jsonOutput) {
|
|
347
228
|
const items = paged.map(r => {
|
|
348
229
|
const base = {
|
|
349
|
-
source: r.
|
|
230
|
+
source: r.source,
|
|
350
231
|
id: r.id,
|
|
351
232
|
created_at: r.created_at,
|
|
352
233
|
score: r.score ?? null,
|
|
353
234
|
};
|
|
354
|
-
if (r.
|
|
235
|
+
if (r.source === 'session') {
|
|
355
236
|
return { ...base, request: r.request || null, completed: r.completed || null, project: r.project || null };
|
|
356
237
|
}
|
|
357
|
-
if (r.
|
|
238
|
+
if (r.source === 'prompt') {
|
|
358
239
|
return { ...base, prompt_text: r.prompt_text || null };
|
|
359
240
|
}
|
|
360
241
|
return {
|
|
@@ -392,10 +273,10 @@ async function cmdSearch(db, args, { llm } = {}) {
|
|
|
392
273
|
const tok = r => (r.bodyTokens ? ` ~${r.bodyTokens}t` : '');
|
|
393
274
|
for (const r of paged) {
|
|
394
275
|
const timeStr = showTime && r.created_at_epoch ? ` (${relativeTime(r.created_at_epoch)})` : '';
|
|
395
|
-
if (r.
|
|
276
|
+
if (r.source === 'session') {
|
|
396
277
|
const date = fmtDateShort(r.created_at);
|
|
397
278
|
out(`S#${r.id} 📋 ${date}${timeStr} ${truncate(r.request || r.completed || '(no summary)', 80)}${tok(r)}`);
|
|
398
|
-
} else if (r.
|
|
279
|
+
} else if (r.source === 'prompt') {
|
|
399
280
|
const date = fmtDateShort(r.created_at);
|
|
400
281
|
out(`P#${r.id} 💬 ${date}${timeStr} ${truncate(r.prompt_text || '(empty)', 80)}${tok(r)}`);
|
|
401
282
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.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",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"cli-path.mjs",
|
|
29
29
|
"mem-cli.mjs",
|
|
30
30
|
"server.mjs",
|
|
31
|
-
"
|
|
31
|
+
"search-scoring.mjs",
|
|
32
32
|
"search-engine.mjs",
|
|
33
33
|
"deep-search.mjs",
|
|
34
34
|
"rerank.mjs",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"lib/binding-probe.mjs",
|
|
74
74
|
"lib/proc-lock.mjs",
|
|
75
75
|
"lib/atomic-write.mjs",
|
|
76
|
+
"lib/release-digest.mjs",
|
|
76
77
|
"lib/mem-override.mjs",
|
|
77
78
|
"lib/save-observation.mjs",
|
|
78
79
|
"lib/observation-write.mjs",
|
package/schema.mjs
CHANGED
|
@@ -894,7 +894,13 @@ export function ensureDb() {
|
|
|
894
894
|
db.pragma('foreign_keys = OFF'); // Enabled after dedup migration
|
|
895
895
|
|
|
896
896
|
try {
|
|
897
|
-
|
|
897
|
+
const ready = initSchema(db);
|
|
898
|
+
// P1-5: sentinel-gated data cleanups must run on EVERY open (schema.mjs:766).
|
|
899
|
+
// They were extracted out of initSchema into runDeferredCleanups but never
|
|
900
|
+
// wired into a production opener — without this call they ran nowhere but
|
|
901
|
+
// tests, silently halting orphan/normalize hygiene. Best-effort: never throws.
|
|
902
|
+
runDeferredCleanups(ready);
|
|
903
|
+
return ready;
|
|
898
904
|
} catch (e) {
|
|
899
905
|
try { db.close(); } catch {}
|
|
900
906
|
throw e;
|
package/scripts/setup.sh
CHANGED
|
@@ -86,6 +86,7 @@ mark_deps_broken() {
|
|
|
86
86
|
# having to re-derive them. Delegate JSON serialization to node so embedded
|
|
87
87
|
# quotes / shell metachars in $ROOT or $reason can't produce an invalid file
|
|
88
88
|
# (bash `printf '"..%s.."'` cannot escape arbitrary strings safely; v2.79.1 fix).
|
|
89
|
+
# shellcheck disable=SC2016 # node script single-quoted on purpose; vars passed via env (MARK_*), not shell expansion
|
|
89
90
|
MARK_REASON="$reason" MARK_ROOT="$ROOT" MARK_FLAG="$DEPS_FLAG" node -e '
|
|
90
91
|
const fs = require("fs");
|
|
91
92
|
const reason = process.env.MARK_REASON || "unknown";
|
|
@@ -141,6 +142,7 @@ fi
|
|
|
141
142
|
# versions; same shape as the .deps-broken self-heal pattern.
|
|
142
143
|
MCP_MIGRATION="$DATA_DIR/runtime/.mcp-dedup-v2.78"
|
|
143
144
|
if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" && ! -f "$MCP_MIGRATION" ]]; then
|
|
145
|
+
# shellcheck disable=SC2016 # node script single-quoted on purpose; CLAUDE_JSON passed via env, not shell expansion
|
|
144
146
|
CLAUDE_JSON="$HOME/.claude.json" node -e '
|
|
145
147
|
const fs = require("fs");
|
|
146
148
|
let changed = false;
|
package/search-engine.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
relaxFtsQueryToOr, debugLog, debugCatch, estimateTokens,
|
|
13
13
|
} from './utils.mjs';
|
|
14
14
|
import { getVocabulary, computeVector, vectorSearch, rrfMerge } from './tfidf.mjs';
|
|
15
|
-
import { extractPRFTerms, expandQueryByConcepts } from './
|
|
15
|
+
import { extractPRFTerms, expandQueryByConcepts } from './search-scoring.mjs';
|
|
16
16
|
|
|
17
17
|
// Scoring expressions — full adds project boost + access bonus; simple is for
|
|
18
18
|
// expansion paths where boost would over-amplify already-loose matches.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
// claude-mem-lite
|
|
2
|
-
//
|
|
1
|
+
// claude-mem-lite shared search-scoring / ranking helpers: re-ranking, supersede
|
|
2
|
+
// marking, PRF term extraction, concept-expansion — plus the MCP instructions
|
|
3
|
+
// builder and idle-cleanup/access-boost side helpers. Used by the MCP server,
|
|
4
|
+
// the CLI (mem-cli), and search-engine; originally extracted from server.mjs for
|
|
5
|
+
// testability (server.mjs has top-level side effects), hence the former
|
|
6
|
+
// "server-internals" name — renamed in audit P3 since it is not server-only.
|
|
3
7
|
|
|
4
8
|
import { debugCatch, COMPRESSED_AUTO, COMPRESSED_PENDING_PURGE, OBS_BM25 } from './utils.mjs';
|
|
5
9
|
import { BASE_STOP_WORDS } from './stop-words.mjs';
|