claude-mem-lite 3.9.1 → 3.10.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/lib/search-core.mjs +19 -2
- package/mem-cli.mjs +39 -3
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.
|
|
13
|
+
"version": "3.10.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.
|
|
3
|
+
"version": "3.10.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/lib/search-core.mjs
CHANGED
|
@@ -59,9 +59,26 @@ export function parseDateBounds(fromRaw, toRaw) {
|
|
|
59
59
|
* per-source SQL double-applied it and gapped/overlapped pages, because the
|
|
60
60
|
* obs hybrid path (AND→OR fallback / vector / concept stages) re-adds rows the
|
|
61
61
|
* SQL OFFSET already skipped (#8217/#8638).
|
|
62
|
+
*
|
|
63
|
+
* D#30 re-audit (reopened): perSourceLimit MUST be offset-independent. The old
|
|
64
|
+
* `offset + limit + 10` term grew the pool for deeper pages, and because RRF
|
|
65
|
+
* fusion of FTS + vector ranks is candidate-pool-sensitive (an item present in
|
|
66
|
+
* BOTH lists outranks one in a single list), a larger pool RE-RANKS the prefix —
|
|
67
|
+
* so page(offset=0) and page(offset=N) sliced DIFFERENT orderings and overlapped
|
|
68
|
+
* /gapped on the real (vector-populated) DB. #8642 missed this because its guard
|
|
69
|
+
* test seeds no vectors (FTS-only is prefix-stable). The fix makes the pool a
|
|
70
|
+
* function of `limit` only:
|
|
71
|
+
* - MIN_FUSION_POOL floor (= default limit 20 × the 3× buffer): every limit ≤ 20
|
|
72
|
+
* fuses ONE 60-candidate pool, so same-limit --offset pages are disjoint/stable
|
|
73
|
+
* and top-N is limit-stable (top-5 ⊂ top-10 ⊂ top-20). limit=20 offset=0 stays
|
|
74
|
+
* byte-identical to before (60) → no change on the benchmarked single-page path.
|
|
75
|
+
* - limit > 20 keeps limit*3 (the over-fetch buffer the fallback stages need).
|
|
76
|
+
* Trade-off: pages beyond the pool now return empty instead of the (overlapping,
|
|
77
|
+
* wrong) rows the offset-scaling used to surface — stability over deep reach.
|
|
62
78
|
*/
|
|
63
|
-
export
|
|
64
|
-
|
|
79
|
+
export const MIN_FUSION_POOL = 60;
|
|
80
|
+
export function computePerSourceWindow(limit, offset) { // eslint-disable-line no-unused-vars
|
|
81
|
+
return { perSourceLimit: Math.max(limit * 3, MIN_FUSION_POOL), perSourceOffset: 0 };
|
|
65
82
|
}
|
|
66
83
|
|
|
67
84
|
/** obs-side total query: when the AND→OR fallback fired, count the OR set. */
|
package/mem-cli.mjs
CHANGED
|
@@ -133,7 +133,15 @@ async function cmdSearch(db, args, { llm } = {}) {
|
|
|
133
133
|
// --deep proceeds even when the literal query sanitizes to nothing — its LLM
|
|
134
134
|
// rewrite may still produce searchable variants (F3, parity with server.mjs).
|
|
135
135
|
if (!ftsQuery && deepMode === 'normal') {
|
|
136
|
-
|
|
136
|
+
// A query that sanitizes to an empty FTS expression (only operators/punctuation/
|
|
137
|
+
// sub-min-length tokens) is a zero-result search, not a malformed one. In --json
|
|
138
|
+
// mode emit the same empty envelope as the no-match path below so programmatic
|
|
139
|
+
// consumers always get parseable stdout (the human path keeps the stderr hint).
|
|
140
|
+
if (jsonOutput) {
|
|
141
|
+
out(JSON.stringify({ query, total: 0, returned: 0, offset, limit, deep: false, results: [] }));
|
|
142
|
+
} else {
|
|
143
|
+
fail(`[mem] No valid search terms in "${query}"`);
|
|
144
|
+
}
|
|
137
145
|
return;
|
|
138
146
|
}
|
|
139
147
|
// --deep ignores --or: each variant runs AND + the engine's built-in
|
|
@@ -607,7 +615,20 @@ function cmdTimeline(db, args) {
|
|
|
607
615
|
if (flags.anchor !== undefined && flags.anchor !== true) {
|
|
608
616
|
const resolved = resolveAnchorToken(db, flags.anchor, { project });
|
|
609
617
|
if (!resolved.ok) {
|
|
610
|
-
|
|
618
|
+
// --json must always emit a parseable envelope. An explicit-but-missing anchor is
|
|
619
|
+
// a direct-lookup miss (like `get` on a bad id) → anchor:null + error code, rc=1.
|
|
620
|
+
if (jsonOutput) {
|
|
621
|
+
process.exitCode = 1;
|
|
622
|
+
out(JSON.stringify({
|
|
623
|
+
anchor: null,
|
|
624
|
+
anchor_note: formatAnchorError(resolved.error, 'mcp'),
|
|
625
|
+
before: [],
|
|
626
|
+
after: [],
|
|
627
|
+
error: resolved.error.code || 'anchor_resolution_failed',
|
|
628
|
+
}));
|
|
629
|
+
} else {
|
|
630
|
+
fail(formatAnchorError(resolved.error, 'cli'));
|
|
631
|
+
}
|
|
611
632
|
return;
|
|
612
633
|
}
|
|
613
634
|
anchorId = resolved.anchorId;
|
|
@@ -663,7 +684,20 @@ function cmdTimeline(db, args) {
|
|
|
663
684
|
// Window fetch (access-count bump + project auto-scope) shared with MCP.
|
|
664
685
|
const win = fetchTimelineWindow(db, anchorId, { before, after, project });
|
|
665
686
|
if (!win) {
|
|
666
|
-
|
|
687
|
+
// Anchor resolved to a real id but the window fetch found no row (e.g. project
|
|
688
|
+
// mismatch). Same --json contract as the resolution-miss path above.
|
|
689
|
+
if (jsonOutput) {
|
|
690
|
+
process.exitCode = 1;
|
|
691
|
+
out(JSON.stringify({
|
|
692
|
+
anchor: null,
|
|
693
|
+
anchor_note: `Observation #${anchorId} not found.`,
|
|
694
|
+
before: [],
|
|
695
|
+
after: [],
|
|
696
|
+
error: 'id-not-found',
|
|
697
|
+
}));
|
|
698
|
+
} else {
|
|
699
|
+
fail(`[mem] Observation #${anchorId} not found`);
|
|
700
|
+
}
|
|
667
701
|
return;
|
|
668
702
|
}
|
|
669
703
|
const { anchor, beforeRows, afterRows } = win;
|
|
@@ -2334,6 +2368,8 @@ Commands:
|
|
|
2334
2368
|
--project P Filter by project
|
|
2335
2369
|
drop <D#N|ordinal>[,...] Drop one or more deferred items (no fix needed)
|
|
2336
2370
|
--reason "..." Required audit trail
|
|
2371
|
+
--project P Project for ordinal resolution (default: current; must
|
|
2372
|
+
match the "defer list --project P" you read ordinals from)
|
|
2337
2373
|
|
|
2338
2374
|
delete <id1,id2,...> Delete observations by ID
|
|
2339
2375
|
--confirm Execute deletion (preview by default)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.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",
|