mcp-rag-memory 2.4.1 → 2.6.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/.env.example +3 -1
- package/LICENSE +21 -0
- package/README.md +41 -7
- package/dist/cli.js +85 -32
- package/dist/db/database.d.ts +2 -2
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/database.js +90 -18
- package/dist/db/database.js.map +1 -1
- package/dist/env.js +32 -12
- package/dist/mcp/rag-server.js +20 -12
- package/dist/mcp/rag-server.js.map +1 -1
- package/dist/memory/memory.d.ts +5 -1
- package/dist/memory/memory.d.ts.map +1 -1
- package/dist/memory/memory.js +234 -102
- package/dist/memory/memory.js.map +1 -1
- package/dist/rag/chunker.d.ts +7 -0
- package/dist/rag/chunker.d.ts.map +1 -1
- package/dist/rag/chunker.js +119 -11
- package/dist/rag/chunker.js.map +1 -1
- package/dist/rag/embeddings.d.ts.map +1 -1
- package/dist/rag/embeddings.js +14 -0
- package/dist/rag/embeddings.js.map +1 -1
- package/dist/rag/pipeline.d.ts +18 -1
- package/dist/rag/pipeline.d.ts.map +1 -1
- package/dist/rag/pipeline.js +210 -37
- package/dist/rag/pipeline.js.map +1 -1
- package/dist/rag/vector-search.d.ts +8 -1
- package/dist/rag/vector-search.d.ts.map +1 -1
- package/dist/rag/vector-search.js +126 -51
- package/dist/rag/vector-search.js.map +1 -1
- package/dist/session/transcript.d.ts +8 -5
- package/dist/session/transcript.d.ts.map +1 -1
- package/dist/session/transcript.js +118 -25
- package/dist/session/transcript.js.map +1 -1
- package/package.json +6 -3
package/.env.example
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copy to `.env` and adjust. Picked up automatically by `npx mcp-rag-memory`,
|
|
2
2
|
# `npm run dev`, and `npm run start` (see src/env.ts). Host environment
|
|
3
|
-
# variables always override these values
|
|
3
|
+
# variables always override these values, and only RAG_*/OPENCODE_*/EMBEDDING_*/
|
|
4
|
+
# SEARCH_MODE entries in this file are loaded — anything else is ignored.
|
|
4
5
|
|
|
5
6
|
# Where the SQLite store lives (default: <project>/.rag-data)
|
|
6
7
|
RAG_DB_DIR=.rag-data
|
|
@@ -24,6 +25,7 @@ RAG_AUTOSYNC=1
|
|
|
24
25
|
RAG_SESSION_CONDENSE=1
|
|
25
26
|
RAG_SESSION_CONDENSE_MIN_CHARS=120
|
|
26
27
|
RAG_SESSION_CONDENSE_RATIO=0.35
|
|
28
|
+
# RAG_SESSION_MAX_MSGS=500
|
|
27
29
|
|
|
28
30
|
# Optional pruning during consolidation
|
|
29
31
|
RAG_PRUNE=0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 mcp-rag-memory contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -22,12 +22,16 @@ context **across sessions**.
|
|
|
22
22
|
|
|
23
23
|
## Quick start
|
|
24
24
|
|
|
25
|
+
Requires **Node.js ≥ 22.12** (for `node:sqlite` read-only handles and
|
|
26
|
+
`busy_timeout`).
|
|
27
|
+
|
|
25
28
|
```bash
|
|
26
29
|
npm install
|
|
27
|
-
npm test #
|
|
30
|
+
npm test # 160 checks: unit + MCP round-trip via SDK client
|
|
28
31
|
npm run lint # oxlint
|
|
32
|
+
npm run typecheck # src + scripts + .opencode/plugin
|
|
29
33
|
npm run build # compile to dist/
|
|
30
|
-
npm run cli --
|
|
34
|
+
npm run cli -- docs # CLI playground
|
|
31
35
|
```
|
|
32
36
|
|
|
33
37
|
### Run from npm (npx)
|
|
@@ -133,6 +137,21 @@ The MCP server is registered here so it works in **every project**, not just thi
|
|
|
133
137
|
> from this project's `node_modules` and stores data in the same DB
|
|
134
138
|
> regardless of which project opencode is opened from.
|
|
135
139
|
|
|
140
|
+
**Re-assert this global config with one command.** opencode updates have
|
|
141
|
+
been observed to reset/wipe the global config (the MCP server, plugin,
|
|
142
|
+
LSP and instructions disappear from new sessions). `scripts/setup-opencode.*`
|
|
143
|
+
rewrite both `opencode.json` and `opencode.jsonc` (PowerShell on Windows,
|
|
144
|
+
portable bash elsewhere; dispatcher `npm run setup-opencode`), **merging**
|
|
145
|
+
with what is already there — user keys are never clobbered, arrays
|
|
146
|
+
(`instructions`, `plugin`) are unioned, broken files are backed up to
|
|
147
|
+
`.bak-<ts>`, and runs are idempotent. Re-run it (and restart opencode)
|
|
148
|
+
after any opencode update:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npm run setup-opencode # merge-in our defaults
|
|
152
|
+
npm run setup-opencode -- --check # exit 0 = up to date, 1 = changes pending
|
|
153
|
+
```
|
|
154
|
+
|
|
136
155
|
### Project config (`opencode.json` in this repo)
|
|
137
156
|
|
|
138
157
|
Keeps project-specific settings (model, LSP, permissions, instructions) — **no `mcp` block here**:
|
|
@@ -181,14 +200,14 @@ Restart opencode after any config change.
|
|
|
181
200
|
| | `rag_ingest_dir` | Recursively ingest source files |
|
|
182
201
|
| RAG query | `rag_search` | Hybrid (BM25 + vector) search, ranked chunks + scores |
|
|
183
202
|
| | `rag_retrieve` | Ready-to-inject context block with token count |
|
|
184
|
-
| RAG docs | `rag_list_documents`, `rag_document_stats` | Inventory |
|
|
203
|
+
| RAG docs | `rag_list_documents`, `rag_document_stats` | Inventory (`rag_list_documents` is paginated: `limit` 1–1000, `offset`) |
|
|
185
204
|
| | `rag_delete_document` | Remove a document + chunks + FTS rows |
|
|
186
205
|
| Session sync | `rag_sync_session` | Ingest an opencode session's user inputs (or the latest in a workspace) |
|
|
187
206
|
| Memory | `memory_remember` | Save long-term memory (type/importance/tags) |
|
|
188
207
|
| | `memory_recall` | Semantic memory search (decay + recall count) |
|
|
189
208
|
| | `memory_context` | Compact context block from memories for prompts |
|
|
190
209
|
| | `memory_list`, `memory_get`, `memory_update`, `memory_forget` | CRUD |
|
|
191
|
-
| | `memory_consolidate` | Dedupe near-identical + promote hot memories |
|
|
210
|
+
| | `memory_consolidate` | Dedupe near-identical (cosine **and** word overlap) + promote hot memories |
|
|
192
211
|
| | `memory_stats` | Counts, tokens, avg importance, by type |
|
|
193
212
|
|
|
194
213
|
## Run in other AI assistants
|
|
@@ -279,6 +298,7 @@ npm run cli -- recall "deployment schedule"
|
|
|
279
298
|
npm run cli -- ingest-dir ./src/rag
|
|
280
299
|
npm run cli -- search "vector similarity"
|
|
281
300
|
npm run cli -- search "auth bug" --source opencode-db # filter by source or doc-id
|
|
301
|
+
npm run cli -- docs --limit 20 --offset 20 # paginated inventory
|
|
282
302
|
npm run cli -- mem-context "database"
|
|
283
303
|
npm run cli -- sessions # list recent opencode sessions
|
|
284
304
|
npm run cli -- sync-session ses_123 # ingest one session's inputs
|
|
@@ -383,10 +403,19 @@ Data lives in `.rag-data/rag.sqlite` (git-ignored).
|
|
|
383
403
|
| `RAG_PRUNE_IMPORTANCE` | `0.2` | Delete memories below this importance |
|
|
384
404
|
| `RAG_PRUNE_AGE_DAYS` | `90` | ...and older than this (never-recalled only) |
|
|
385
405
|
| `RAG_AUTOSYNC` | `1` | Set `0` to disable the session-logger plugin's auto-sync |
|
|
386
|
-
| `OPENCODE_DB` / `OPENCODE_DB_LOCAL` | `~/.local/share/opencode/*.db` | Where session transcripts are read from |
|
|
406
|
+
| `OPENCODE_DB` / `OPENCODE_DB_LOCAL` | `~/.local/share/opencode/*.db` | Where session transcripts are read from (re-read on every call, so it can be set late) |
|
|
407
|
+
| `RAG_SESSION_MAX_MSGS` | `500` | Cap on user messages read from one session |
|
|
387
408
|
| `RAG_SESSION_CONDENSE` | `1` | Set `0` to store session inputs verbatim |
|
|
388
409
|
| `RAG_SESSION_CONDENSE_MIN_CHARS` | `120` | Inputs at/below this length are saved whole |
|
|
389
410
|
| `RAG_SESSION_CONDENSE_RATIO` | `0.35` | Fraction of long-input length to keep as key points |
|
|
411
|
+
| `RAG_ENV_FILE` | `<cwd>/.env` | Env file to load at boot |
|
|
412
|
+
|
|
413
|
+
A `.env` file can only set this server's own variables (`RAG_*`,
|
|
414
|
+
`OPENCODE_*`, `EMBEDDING_*`, `SEARCH_MODE`); anything else in it is
|
|
415
|
+
ignored with a warning on stderr, and a real environment variable always
|
|
416
|
+
wins over the file. The loader runs from `dist/index.js` and
|
|
417
|
+
`dist/cli.js` only — if you invoke `src/mcp/rag-server.ts` directly
|
|
418
|
+
(TSX/`npx tsx`), import `src/env.js` yourself.
|
|
390
419
|
|
|
391
420
|
## Testing
|
|
392
421
|
|
|
@@ -489,8 +518,13 @@ instant.
|
|
|
489
518
|
### Opencode feels sluggish / UI lag after adding plugins
|
|
490
519
|
|
|
491
520
|
Plugins that run synchronous I/O or write to stderr on every tool call
|
|
492
|
-
can slow down the TUI. This
|
|
493
|
-
|
|
521
|
+
can slow down the TUI. This repo ships exactly one plugin
|
|
522
|
+
(`.opencode/plugin/session-logger.ts`) and it is debounced to at most one
|
|
523
|
+
`npx … sync-latest` per 60s, spawned detached with `stdio: "ignore"` and
|
|
524
|
+
`shell: false`. If it still costs too much, set `RAG_AUTOSYNC=0` (keeps the
|
|
525
|
+
plugin, disables the auto-sync) or drop the `plugin` entry from
|
|
526
|
+
`opencode.json` — the MCP server and its 19 tools keep working, you just have
|
|
527
|
+
to call `rag_sync_session` yourself.
|
|
494
528
|
|
|
495
529
|
---
|
|
496
530
|
|
package/dist/cli.js
CHANGED
|
@@ -54,25 +54,33 @@ function assertMemoryType(type) {
|
|
|
54
54
|
throw new Error(`Invalid memory type "${type}". Valid: ${MEMORY_TYPES.join(", ")}`);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
function requireArg(value, usage) {
|
|
58
|
+
if (!value || value.startsWith("--"))
|
|
59
|
+
throw new Error(`Missing argument — usage: ${usage}`);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
57
62
|
async function run(cmd, args) {
|
|
58
63
|
let out;
|
|
59
64
|
try {
|
|
60
65
|
switch (cmd) {
|
|
61
66
|
case "ingest-text": {
|
|
62
|
-
const
|
|
67
|
+
const parsed = parseOpts(args);
|
|
68
|
+
const [title, fileOrDash] = parsed.positional;
|
|
69
|
+
if (!title || !fileOrDash)
|
|
70
|
+
throw new Error("Missing argument — usage: ingest-text <title> <file|->");
|
|
63
71
|
const content = fileOrDash === "-" ? fs.readFileSync(0, "utf-8") : fs.readFileSync(fileOrDash, "utf-8");
|
|
64
|
-
out = await ingestText(content, title
|
|
72
|
+
out = await ingestText(content, title);
|
|
65
73
|
break;
|
|
66
74
|
}
|
|
67
75
|
case "ingest-file":
|
|
68
|
-
out = await ingestFile(args[0]);
|
|
76
|
+
out = await ingestFile(requireArg(args[0], "ingest-file <path>"));
|
|
69
77
|
break;
|
|
70
78
|
case "ingest-dir":
|
|
71
|
-
out = await ingestDirectory(args[0], { recursive: true });
|
|
79
|
+
out = await ingestDirectory(requireArg(args[0], "ingest-dir <dir>"), { recursive: true });
|
|
72
80
|
break;
|
|
73
81
|
case "search": {
|
|
74
82
|
const parsed = parseOpts(args);
|
|
75
|
-
out = await searchDocs(parsed.positional.
|
|
83
|
+
out = await searchDocs(queryArg(parsed.positional), toNum(parsed.opts["top-k"], 10), toNum(parsed.opts["min-score"], 0.08), {
|
|
76
84
|
docId: parsed.opts["doc-id"],
|
|
77
85
|
source: parsed.opts.source,
|
|
78
86
|
});
|
|
@@ -80,67 +88,84 @@ async function run(cmd, args) {
|
|
|
80
88
|
}
|
|
81
89
|
case "retrieve": {
|
|
82
90
|
const parsed = parseOpts(args);
|
|
83
|
-
out = await retrieve(parsed.positional.
|
|
91
|
+
out = await retrieve(queryArg(parsed.positional), toNum(parsed.opts["top-k"], 6), toNum(parsed.opts["min-score"], 0.08), {
|
|
84
92
|
docId: parsed.opts["doc-id"],
|
|
85
93
|
source: parsed.opts.source,
|
|
86
94
|
});
|
|
87
95
|
break;
|
|
88
96
|
}
|
|
89
|
-
case "docs":
|
|
90
|
-
|
|
97
|
+
case "docs": {
|
|
98
|
+
const parsed = parseOpts(args);
|
|
99
|
+
out = listDocuments({ limit: toNum(parsed.opts.limit, undefined), offset: toNum(parsed.opts.offset, undefined) });
|
|
91
100
|
break;
|
|
101
|
+
}
|
|
92
102
|
case "doc-stats":
|
|
93
103
|
out = documentStats();
|
|
94
104
|
break;
|
|
95
105
|
case "rm-doc":
|
|
96
|
-
out = deleteDocument(args[0]);
|
|
106
|
+
out = deleteDocument(requireArg(args[0], "rm-doc <doc_id>"));
|
|
97
107
|
break;
|
|
98
108
|
case "remember": {
|
|
99
109
|
const parsed = parseOpts(args);
|
|
110
|
+
const content = parsed.positional.join(" ");
|
|
111
|
+
if (!content)
|
|
112
|
+
throw new Error("Missing argument — usage: remember <content> [--type fact] [--importance 0.5] [--tag x]");
|
|
100
113
|
const type = parsed.opts.type ?? "fact";
|
|
101
114
|
assertMemoryType(type);
|
|
102
115
|
out = await remember({
|
|
103
|
-
content
|
|
116
|
+
content,
|
|
104
117
|
type,
|
|
105
|
-
importance:
|
|
118
|
+
importance: toNum(parsed.opts.importance, 0.5),
|
|
106
119
|
tags: parsed.opts.tag ? [parsed.opts.tag] : undefined,
|
|
107
120
|
});
|
|
108
121
|
break;
|
|
109
122
|
}
|
|
110
|
-
case "recall":
|
|
111
|
-
|
|
123
|
+
case "recall": {
|
|
124
|
+
const parsed = parseOpts(args);
|
|
125
|
+
out = await recall(queryArg(parsed.positional), toNum(parsed.opts["top-k"], 8), toNum(parsed.opts["min-score"], 0.1));
|
|
112
126
|
break;
|
|
127
|
+
}
|
|
113
128
|
case "memory-list": {
|
|
114
129
|
const parsed = parseOpts(args);
|
|
130
|
+
const type = parsed.opts.type;
|
|
131
|
+
if (type !== undefined)
|
|
132
|
+
assertMemoryType(type);
|
|
115
133
|
out = listMemories({
|
|
116
|
-
type
|
|
134
|
+
type,
|
|
117
135
|
tag: parsed.opts.tag,
|
|
118
|
-
minImportance: parsed.opts["min-importance"]
|
|
136
|
+
minImportance: toNum(parsed.opts["min-importance"], undefined),
|
|
137
|
+
limit: toNum(parsed.opts.limit, undefined),
|
|
119
138
|
});
|
|
120
139
|
break;
|
|
121
140
|
}
|
|
122
141
|
case "memory-get":
|
|
123
|
-
out = getMemory(args[0]);
|
|
142
|
+
out = getMemory(requireArg(args[0], "memory-get <id>"));
|
|
124
143
|
break;
|
|
125
144
|
case "memory-update": {
|
|
126
145
|
const [id, ...restArgs] = args;
|
|
127
146
|
const parsed = parseOpts(restArgs);
|
|
128
|
-
|
|
129
|
-
|
|
147
|
+
if (!id)
|
|
148
|
+
throw new Error("Missing argument — usage: memory-update <id> [--content ...] [--importance 0.8] [--type fact] [--tag x]");
|
|
149
|
+
const type = parsed.opts.type;
|
|
150
|
+
if (type !== undefined)
|
|
151
|
+
assertMemoryType(type);
|
|
152
|
+
// --content=... (or --content <text>) sets the body; leftover positional
|
|
153
|
+
// words are the body too. The old `!== "true"` check silently discarded
|
|
154
|
+
// any content that was literally the word "true".
|
|
155
|
+
const content = parsed.opts.content ?? (parsed.positional.length ? parsed.positional.join(" ") : undefined);
|
|
156
|
+
if (content === undefined && type === undefined && parsed.opts.importance === undefined && parsed.opts.tag === undefined) {
|
|
157
|
+
throw new Error("Nothing to update — pass --content, --type, --importance and/or --tag");
|
|
158
|
+
}
|
|
130
159
|
out = await updateMemory(id, {
|
|
131
|
-
content
|
|
132
|
-
|
|
133
|
-
: parsed.positional.length
|
|
134
|
-
? parsed.positional.join(" ")
|
|
135
|
-
: undefined,
|
|
136
|
-
importance: parsed.opts.importance !== undefined ? Number(parsed.opts.importance) : undefined,
|
|
160
|
+
content,
|
|
161
|
+
importance: toNum(parsed.opts.importance, undefined),
|
|
137
162
|
type,
|
|
138
163
|
tags: parsed.opts.tag ? [parsed.opts.tag] : undefined,
|
|
139
164
|
});
|
|
140
165
|
break;
|
|
141
166
|
}
|
|
142
167
|
case "forget":
|
|
143
|
-
out = forget(args[0]);
|
|
168
|
+
out = forget(requireArg(args[0], "forget <id>"));
|
|
144
169
|
break;
|
|
145
170
|
case "consolidate":
|
|
146
171
|
out = await consolidate();
|
|
@@ -148,15 +173,17 @@ async function run(cmd, args) {
|
|
|
148
173
|
case "memory-stats":
|
|
149
174
|
out = memoryStats();
|
|
150
175
|
break;
|
|
151
|
-
case "mem-context":
|
|
152
|
-
|
|
176
|
+
case "mem-context": {
|
|
177
|
+
const parsed = parseOpts(args);
|
|
178
|
+
out = await contextPrompt(queryArg(parsed.positional), toNum(parsed.opts["top-k"], 6));
|
|
153
179
|
break;
|
|
180
|
+
}
|
|
154
181
|
case "stats":
|
|
155
182
|
out = { documents: documentStats(), memories: memoryStats(), dbDir: STORAGE_DIR };
|
|
156
183
|
break;
|
|
157
184
|
case "sessions": {
|
|
158
185
|
const parsed = parseOpts(args);
|
|
159
|
-
out = listOpenCodeSessions(parsed.opts.limit
|
|
186
|
+
out = listOpenCodeSessions(toNum(parsed.opts.limit, 15) ?? 15, { directory: parsed.opts.dir });
|
|
160
187
|
break;
|
|
161
188
|
}
|
|
162
189
|
case "sync-session": {
|
|
@@ -164,12 +191,15 @@ async function run(cmd, args) {
|
|
|
164
191
|
const parsed = parseOpts(restArgs);
|
|
165
192
|
if (!id)
|
|
166
193
|
throw new Error("sync-session <sessionId> — run 'cli sessions' to list ids");
|
|
167
|
-
out = await ingestSession(id, { limit: parsed.opts.limit
|
|
194
|
+
out = await ingestSession(id, { limit: toNum(parsed.opts.limit, 0) ?? 0 });
|
|
168
195
|
break;
|
|
169
196
|
}
|
|
170
197
|
case "sync-latest": {
|
|
171
198
|
const parsed = parseOpts(args);
|
|
172
|
-
out = await ingestLatest({
|
|
199
|
+
out = await ingestLatest({
|
|
200
|
+
limit: toNum(parsed.opts.limit, 0) ?? 0,
|
|
201
|
+
directory: parsed.opts.dir ?? parsed.positional[0],
|
|
202
|
+
});
|
|
173
203
|
break;
|
|
174
204
|
}
|
|
175
205
|
case "sync-logs": {
|
|
@@ -178,7 +208,7 @@ async function run(cmd, args) {
|
|
|
178
208
|
break;
|
|
179
209
|
}
|
|
180
210
|
case "ingest-jsonl":
|
|
181
|
-
out = await ingestJsonlFile(args[0]);
|
|
211
|
+
out = await ingestJsonlFile(requireArg(args[0], "ingest-jsonl <file>"));
|
|
182
212
|
break;
|
|
183
213
|
default:
|
|
184
214
|
console.error(`Unknown command: ${cmd}`);
|
|
@@ -188,23 +218,39 @@ async function run(cmd, args) {
|
|
|
188
218
|
}
|
|
189
219
|
catch (e) {
|
|
190
220
|
console.error(`Error: ${e.message}`);
|
|
221
|
+
closeDB();
|
|
191
222
|
process.exit(1);
|
|
192
223
|
}
|
|
193
224
|
console.log(JSON.stringify(out, null, 2));
|
|
194
225
|
}
|
|
226
|
+
/** Query text from positional words — empty input is an error, not a silent []. */
|
|
227
|
+
function queryArg(positional) {
|
|
228
|
+
const q = positional.join(" ").trim();
|
|
229
|
+
if (!q)
|
|
230
|
+
throw new Error("Missing query — usage: <command> <query>");
|
|
231
|
+
return q;
|
|
232
|
+
}
|
|
195
233
|
function parseOpts(args) {
|
|
196
234
|
const positional = [];
|
|
197
235
|
const opts = {};
|
|
198
236
|
for (let i = 0; i < args.length; i++) {
|
|
199
237
|
const a = args[i];
|
|
200
238
|
if (a.startsWith("--")) {
|
|
239
|
+
const eq = a.indexOf("=");
|
|
240
|
+
if (eq > 0) {
|
|
241
|
+
// --key=value: the value may legitimately start with "--".
|
|
242
|
+
opts[a.slice(2, eq)] = a.slice(eq + 1);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
201
245
|
const key = a.slice(2);
|
|
202
246
|
const next = args[i + 1];
|
|
203
|
-
if (next && !next.startsWith("--")) {
|
|
247
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
204
248
|
opts[key] = next;
|
|
205
249
|
i++;
|
|
206
250
|
}
|
|
207
251
|
else {
|
|
252
|
+
// Boolean flag. Previously the literal "true" was stored as the value,
|
|
253
|
+
// which then leaked into --content and --dir.
|
|
208
254
|
opts[key] = "true";
|
|
209
255
|
}
|
|
210
256
|
}
|
|
@@ -214,6 +260,13 @@ function parseOpts(args) {
|
|
|
214
260
|
}
|
|
215
261
|
return { positional, opts };
|
|
216
262
|
}
|
|
263
|
+
/** Parse a numeric CLI option; falls back to `fallback` for missing/garbage input. */
|
|
264
|
+
function toNum(raw, fallback) {
|
|
265
|
+
if (raw === undefined)
|
|
266
|
+
return fallback;
|
|
267
|
+
const n = Number(raw);
|
|
268
|
+
return Number.isFinite(n) ? n : fallback;
|
|
269
|
+
}
|
|
217
270
|
main().catch((e) => {
|
|
218
271
|
console.error(`Error: ${e.message}`);
|
|
219
272
|
process.exit(1);
|
package/dist/db/database.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ export declare function getDB(): DatabaseSync;
|
|
|
5
5
|
/** SHA-256 hex of raw content — used for document dedup. */
|
|
6
6
|
export declare function contentHash(content: string): string;
|
|
7
7
|
export declare function newId(): string;
|
|
8
|
-
/** Serialize a Float64Array embedding to a SQLite BLOB. */
|
|
8
|
+
/** Serialize a Float64Array embedding to a SQLite BLOB (view-safe). */
|
|
9
9
|
export declare function packVector(v: Float64Array): Uint8Array;
|
|
10
|
-
/** Deserialize a BLOB back into a Float64Array. */
|
|
10
|
+
/** Deserialize a BLOB back into a Float64Array. Returns null on absent/partial data. */
|
|
11
11
|
export declare function unpackVector(blob: Uint8Array | null): Float64Array | null;
|
|
12
12
|
export declare function nowMs(): number;
|
|
13
13
|
export declare function closeDB(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,eAAO,MAAM,WAAW,QAEoB,CAAC;AAE7C,eAAO,MAAM,OAAO,QAAuC,CAAC;AAU5D,wBAAgB,KAAK,IAAI,YAAY,
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,eAAO,MAAM,WAAW,QAEoB,CAAC;AAE7C,eAAO,MAAM,OAAO,QAAuC,CAAC;AAU5D,wBAAgB,KAAK,IAAI,YAAY,CA0BpC;AA+GD,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAED,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,CAAC,EAAE,YAAY,GAAG,UAAU,CAEtD;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,YAAY,GAAG,IAAI,CAGzE;AAED,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAED,wBAAgB,OAAO,SAItB;AASD,wBAAgB,WAAW,IAAI,MAAM,CAWpC;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAStD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAwBhE;AAED,wBAAgB,aAAa,IAAI,MAAM,CAKtC"}
|
package/dist/db/database.js
CHANGED
|
@@ -17,18 +17,59 @@ export function getDB() {
|
|
|
17
17
|
return _db;
|
|
18
18
|
ensureDir();
|
|
19
19
|
const db = new DatabaseSync(DB_PATH);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
try {
|
|
21
|
+
// busy_timeout first: every later statement should inherit it.
|
|
22
|
+
db.exec("PRAGMA busy_timeout = 5000;");
|
|
23
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
24
|
+
db.exec("PRAGMA synchronous = NORMAL;");
|
|
25
|
+
db.exec("PRAGMA foreign_keys = ON;");
|
|
26
|
+
// Migrate BEFORE caching the handle: a failed migration must surface and be
|
|
27
|
+
// retried on the next call, never leave a half-migrated connection cached.
|
|
28
|
+
migrate(db);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
// The handle is NOT cached on failure, so without this close every failed
|
|
32
|
+
// init leaked a file handle — reachable whenever a concurrent writer holds
|
|
33
|
+
// the lock, and it blocked later cleanup of the store directory on Windows.
|
|
34
|
+
try {
|
|
35
|
+
db.close();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
/* already closed */
|
|
39
|
+
}
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
23
42
|
_db = db;
|
|
24
|
-
migrate(db);
|
|
25
43
|
return db;
|
|
26
44
|
}
|
|
27
45
|
function columnExists(db, table, column) {
|
|
46
|
+
// PRAGMA cannot take a bound parameter, so the identifier is interpolated.
|
|
47
|
+
// Only ever called with literals; assert it so a future caller cannot inject.
|
|
48
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table))
|
|
49
|
+
throw new Error(`Unsafe table name: ${table}`);
|
|
28
50
|
const cols = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
29
51
|
return cols.some((c) => c.name === column);
|
|
30
52
|
}
|
|
31
53
|
function migrate(db) {
|
|
54
|
+
// One IMMEDIATE transaction for the whole schema step: two processes opening
|
|
55
|
+
// the same fresh store (CLI + MCP server + plugin) must not race and end up
|
|
56
|
+
// with half-applied DDL or a "duplicate column" failure.
|
|
57
|
+
db.exec("BEGIN IMMEDIATE;");
|
|
58
|
+
try {
|
|
59
|
+
createSchema(db);
|
|
60
|
+
db.exec("COMMIT;");
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
try {
|
|
64
|
+
db.exec("ROLLBACK;");
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* transaction already closed */
|
|
68
|
+
}
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function createSchema(db) {
|
|
32
73
|
db.exec(`
|
|
33
74
|
CREATE TABLE IF NOT EXISTS documents (
|
|
34
75
|
id TEXT PRIMARY KEY,
|
|
@@ -51,6 +92,10 @@ function migrate(db) {
|
|
|
51
92
|
created_at INTEGER NOT NULL
|
|
52
93
|
);
|
|
53
94
|
CREATE INDEX IF NOT EXISTS idx_chunks_doc_id ON chunks(doc_id);
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_documents_updated ON documents(updated_at DESC);
|
|
96
|
+
-- Filtered searches resolve doc ids via source/doc_id first; without this
|
|
97
|
+
-- every rag_search(source=...) scanned the whole documents table.
|
|
98
|
+
CREATE INDEX IF NOT EXISTS idx_documents_source ON documents(source);
|
|
54
99
|
|
|
55
100
|
CREATE TABLE IF NOT EXISTS memories (
|
|
56
101
|
id TEXT PRIMARY KEY,
|
|
@@ -66,6 +111,7 @@ function migrate(db) {
|
|
|
66
111
|
);
|
|
67
112
|
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
68
113
|
CREATE INDEX IF NOT EXISTS idx_memories_prune ON memories(importance, recall_count, created_at);
|
|
114
|
+
CREATE INDEX IF NOT EXISTS idx_memories_updated ON memories(updated_at DESC);
|
|
69
115
|
|
|
70
116
|
CREATE TABLE IF NOT EXISTS meta (
|
|
71
117
|
key TEXT PRIMARY KEY,
|
|
@@ -84,17 +130,24 @@ function migrate(db) {
|
|
|
84
130
|
const ftsRow = db
|
|
85
131
|
.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'chunks_fts'")
|
|
86
132
|
.get();
|
|
87
|
-
if (
|
|
88
|
-
db.exec("CREATE VIRTUAL TABLE chunks_fts USING fts5(content, tokenize = 'trigram');");
|
|
89
|
-
}
|
|
90
|
-
else if (!ftsRow.sql.includes("trigram")) {
|
|
133
|
+
if (ftsRow) {
|
|
91
134
|
// Existing store built with 'unicode61' — recreate with trigram and backfill.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
135
|
+
if (!(ftsRow.sql ?? "").includes("trigram"))
|
|
136
|
+
rebuildFts(db);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// First run with this schema (or a store whose FTS table was dropped):
|
|
140
|
+
// create AND backfill, otherwise existing chunks silently lose keyword search
|
|
141
|
+
// forever — the next boot sees "trigram" and would skip the rebuild.
|
|
142
|
+
rebuildFts(db);
|
|
96
143
|
}
|
|
97
144
|
}
|
|
145
|
+
/** Runs inside the migrate() transaction. */
|
|
146
|
+
function rebuildFts(db) {
|
|
147
|
+
db.exec("DROP TABLE IF EXISTS chunks_fts;");
|
|
148
|
+
db.exec("CREATE VIRTUAL TABLE chunks_fts USING fts5(content, tokenize = 'trigram');");
|
|
149
|
+
db.exec("INSERT INTO chunks_fts(rowid, content) SELECT rowid, content FROM chunks;");
|
|
150
|
+
}
|
|
98
151
|
/** SHA-256 hex of raw content — used for document dedup. */
|
|
99
152
|
export function contentHash(content) {
|
|
100
153
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
@@ -102,13 +155,13 @@ export function contentHash(content) {
|
|
|
102
155
|
export function newId() {
|
|
103
156
|
return randomUUID();
|
|
104
157
|
}
|
|
105
|
-
/** Serialize a Float64Array embedding to a SQLite BLOB. */
|
|
158
|
+
/** Serialize a Float64Array embedding to a SQLite BLOB (view-safe). */
|
|
106
159
|
export function packVector(v) {
|
|
107
|
-
return new Uint8Array(v.buffer.slice(
|
|
160
|
+
return new Uint8Array(v.buffer.slice(v.byteOffset, v.byteOffset + v.byteLength));
|
|
108
161
|
}
|
|
109
|
-
/** Deserialize a BLOB back into a Float64Array. */
|
|
162
|
+
/** Deserialize a BLOB back into a Float64Array. Returns null on absent/partial data. */
|
|
110
163
|
export function unpackVector(blob) {
|
|
111
|
-
if (!blob || blob.byteLength === 0)
|
|
164
|
+
if (!blob || blob.byteLength === 0 || blob.byteLength % 8 !== 0)
|
|
112
165
|
return null;
|
|
113
166
|
return new Float64Array(blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength));
|
|
114
167
|
}
|
|
@@ -118,18 +171,28 @@ export function nowMs() {
|
|
|
118
171
|
export function closeDB() {
|
|
119
172
|
_db?.close();
|
|
120
173
|
_db = null;
|
|
174
|
+
cachedDim = null;
|
|
121
175
|
}
|
|
122
176
|
/* ------------------------------------------------------------------ */
|
|
123
177
|
/* Embedding dimension metadata */
|
|
124
178
|
/* ------------------------------------------------------------------ */
|
|
179
|
+
/** Cached embed_dim: locked on first write, so it is read once per process. */
|
|
180
|
+
let cachedDim = null;
|
|
125
181
|
export function getEmbedDim() {
|
|
126
|
-
|
|
127
|
-
|
|
182
|
+
if (cachedDim === null) {
|
|
183
|
+
const r = getDB().prepare("SELECT value FROM meta WHERE key = 'embed_dim'").get();
|
|
184
|
+
const n = Number(r?.value);
|
|
185
|
+
// A corrupt row must not cache NaN: it would wedge this process with a
|
|
186
|
+
// misleading "dimension mismatch" until restart.
|
|
187
|
+
cachedDim = Number.isInteger(n) && n > 0 ? n : 0;
|
|
188
|
+
}
|
|
189
|
+
return cachedDim;
|
|
128
190
|
}
|
|
129
191
|
export function setEmbedMeta(dim, model) {
|
|
130
192
|
const db = getDB();
|
|
131
193
|
db.prepare("INSERT INTO meta(key, value) VALUES ('embed_dim', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(String(dim));
|
|
132
194
|
db.prepare("INSERT INTO meta(key, value) VALUES ('embed_model', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(model);
|
|
195
|
+
cachedDim = dim;
|
|
133
196
|
}
|
|
134
197
|
/**
|
|
135
198
|
* Lock the store to one embedding dimension. Call after producing a vector.
|
|
@@ -146,6 +209,15 @@ export function ensureDim(vec, model) {
|
|
|
146
209
|
`new vectors are ${vec.length} (model: ${model}). ` +
|
|
147
210
|
`Use a consistent EMBEDDING_PROVIDER or wipe ${STORAGE_DIR}.`);
|
|
148
211
|
}
|
|
212
|
+
// Same-dimension model swaps are the dangerous case: blending two vector
|
|
213
|
+
// spaces degrades search to noise with no error anywhere, and the only clue
|
|
214
|
+
// was a stale model name inside a dimension-mismatch message that never fired.
|
|
215
|
+
const currentModel = getEmbedModel();
|
|
216
|
+
if (currentModel && currentModel !== model) {
|
|
217
|
+
throw new Error(`Embedding model mismatch: DB stores ${currentModel}, requested ${model} (both ${current}-dim). ` +
|
|
218
|
+
`Point RAG_DB_DIR at a fresh store or re-ingest with a consistent EMBEDDING_MODEL. ` +
|
|
219
|
+
`Current store: ${STORAGE_DIR}.`);
|
|
220
|
+
}
|
|
149
221
|
}
|
|
150
222
|
export function getEmbedModel() {
|
|
151
223
|
const r = getDB().prepare("SELECT value FROM meta WHERE key = 'embed_model'").get();
|
package/dist/db/database.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU;IAC/C,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IACtC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAC;AAE7C,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AAE5D,IAAI,GAAG,GAAwB,IAAI,CAAC;AAEpC,SAAS,SAAS;IAChB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,KAAK;IACnB,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC;IACpB,SAAS,EAAE,CAAC;IACZ,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC;IACrC,EAAE,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
|