@aeriondyseti/vector-memory-mcp 1.1.0-dev.5 → 2.0.0-rc
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/README.md +22 -4
- package/package.json +12 -18
- package/scripts/migrate-from-lancedb.ts +56 -0
- package/scripts/smoke-test.ts +699 -0
- package/scripts/test-runner.ts +11 -1
- package/src/db/connection.ts +18 -4
- package/src/db/conversation.repository.ts +164 -78
- package/src/db/memory.repository.ts +180 -168
- package/src/db/migrations.ts +70 -0
- package/src/db/sqlite-utils.ts +78 -0
- package/src/http/server.ts +40 -35
- package/src/index.ts +33 -3
- package/src/mcp/server.ts +2 -1
- package/src/migration.ts +254 -0
- package/dist/package.json +0 -71
- package/dist/scripts/test-runner.d.ts +0 -9
- package/dist/scripts/test-runner.d.ts.map +0 -1
- package/dist/scripts/test-runner.js +0 -61
- package/dist/scripts/test-runner.js.map +0 -1
- package/dist/scripts/warmup.d.ts +0 -8
- package/dist/scripts/warmup.d.ts.map +0 -1
- package/dist/scripts/warmup.js +0 -61
- package/dist/scripts/warmup.js.map +0 -1
- package/dist/src/config/index.d.ts +0 -41
- package/dist/src/config/index.d.ts.map +0 -1
- package/dist/src/config/index.js +0 -75
- package/dist/src/config/index.js.map +0 -1
- package/dist/src/db/connection.d.ts +0 -3
- package/dist/src/db/connection.d.ts.map +0 -1
- package/dist/src/db/connection.js +0 -10
- package/dist/src/db/connection.js.map +0 -1
- package/dist/src/db/conversation.repository.d.ts +0 -26
- package/dist/src/db/conversation.repository.d.ts.map +0 -1
- package/dist/src/db/conversation.repository.js +0 -72
- package/dist/src/db/conversation.repository.js.map +0 -1
- package/dist/src/db/conversation.schema.d.ts +0 -4
- package/dist/src/db/conversation.schema.d.ts.map +0 -1
- package/dist/src/db/conversation.schema.js +0 -15
- package/dist/src/db/conversation.schema.js.map +0 -1
- package/dist/src/db/lancedb-utils.d.ts +0 -45
- package/dist/src/db/lancedb-utils.d.ts.map +0 -1
- package/dist/src/db/lancedb-utils.js +0 -106
- package/dist/src/db/lancedb-utils.js.map +0 -1
- package/dist/src/db/memory.repository.d.ts +0 -40
- package/dist/src/db/memory.repository.d.ts.map +0 -1
- package/dist/src/db/memory.repository.js +0 -183
- package/dist/src/db/memory.repository.js.map +0 -1
- package/dist/src/db/schema.d.ts +0 -7
- package/dist/src/db/schema.d.ts.map +0 -1
- package/dist/src/db/schema.js +0 -19
- package/dist/src/db/schema.js.map +0 -1
- package/dist/src/http/mcp-transport.d.ts +0 -19
- package/dist/src/http/mcp-transport.d.ts.map +0 -1
- package/dist/src/http/mcp-transport.js +0 -191
- package/dist/src/http/mcp-transport.js.map +0 -1
- package/dist/src/http/server.d.ts +0 -13
- package/dist/src/http/server.d.ts.map +0 -1
- package/dist/src/http/server.js +0 -224
- package/dist/src/http/server.js.map +0 -1
- package/dist/src/index.d.ts +0 -3
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -68
- package/dist/src/index.js.map +0 -1
- package/dist/src/mcp/handlers.d.ts +0 -15
- package/dist/src/mcp/handlers.d.ts.map +0 -1
- package/dist/src/mcp/handlers.js +0 -313
- package/dist/src/mcp/handlers.js.map +0 -1
- package/dist/src/mcp/server.d.ts +0 -5
- package/dist/src/mcp/server.d.ts.map +0 -1
- package/dist/src/mcp/server.js +0 -22
- package/dist/src/mcp/server.js.map +0 -1
- package/dist/src/mcp/tools.d.ts +0 -13
- package/dist/src/mcp/tools.d.ts.map +0 -1
- package/dist/src/mcp/tools.js +0 -352
- package/dist/src/mcp/tools.js.map +0 -1
- package/dist/src/services/conversation.service.d.ts +0 -38
- package/dist/src/services/conversation.service.d.ts.map +0 -1
- package/dist/src/services/conversation.service.js +0 -252
- package/dist/src/services/conversation.service.js.map +0 -1
- package/dist/src/services/embeddings.service.d.ts +0 -12
- package/dist/src/services/embeddings.service.d.ts.map +0 -1
- package/dist/src/services/embeddings.service.js +0 -37
- package/dist/src/services/embeddings.service.js.map +0 -1
- package/dist/src/services/memory.service.d.ts +0 -40
- package/dist/src/services/memory.service.d.ts.map +0 -1
- package/dist/src/services/memory.service.js +0 -258
- package/dist/src/services/memory.service.js.map +0 -1
- package/dist/src/services/parsers/claude-code.parser.d.ts +0 -8
- package/dist/src/services/parsers/claude-code.parser.d.ts.map +0 -1
- package/dist/src/services/parsers/claude-code.parser.js +0 -191
- package/dist/src/services/parsers/claude-code.parser.js.map +0 -1
- package/dist/src/services/parsers/types.d.ts +0 -9
- package/dist/src/services/parsers/types.d.ts.map +0 -1
- package/dist/src/services/parsers/types.js +0 -2
- package/dist/src/services/parsers/types.js.map +0 -1
- package/dist/src/types/conversation.d.ts +0 -99
- package/dist/src/types/conversation.d.ts.map +0 -1
- package/dist/src/types/conversation.js +0 -2
- package/dist/src/types/conversation.js.map +0 -1
- package/dist/src/types/memory.d.ts +0 -30
- package/dist/src/types/memory.d.ts.map +0 -1
- package/dist/src/types/memory.js +0 -18
- package/dist/src/types/memory.js.map +0 -1
- package/src/db/conversation.schema.ts +0 -33
- package/src/db/lancedb-utils.ts +0 -125
- package/src/db/schema.ts +0 -38
package/scripts/test-runner.ts
CHANGED
|
@@ -8,7 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
import { spawn } from "bun";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// Exclude benchmark tests — they're probabilistic quality metrics, not pass/fail gates.
|
|
12
|
+
// Run benchmarks separately with: bun run benchmark
|
|
13
|
+
const args = ["bun", "test", "--preload", "./tests/preload.ts"];
|
|
14
|
+
|
|
15
|
+
// Collect all test files except benchmarks
|
|
16
|
+
const glob = new Bun.Glob("tests/**/*.test.ts");
|
|
17
|
+
for (const path of glob.scanSync(".")) {
|
|
18
|
+
if (!path.includes("benchmark")) args.push(path);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const proc = spawn(args, {
|
|
12
22
|
stdout: "pipe",
|
|
13
23
|
stderr: "pipe",
|
|
14
24
|
env: { ...process.env, FORCE_COLOR: "1" },
|
package/src/db/connection.ts
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import * as sqliteVec from "sqlite-vec";
|
|
2
3
|
import { mkdirSync } from "fs";
|
|
3
4
|
import { dirname } from "path";
|
|
5
|
+
import { runMigrations } from "./migrations.js";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Open (or create) a SQLite database at the given path,
|
|
9
|
+
* load the sqlite-vec extension, and run schema migrations.
|
|
10
|
+
*/
|
|
11
|
+
export function connectToDatabase(dbPath: string): Database {
|
|
7
12
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
13
|
+
const db = new Database(dbPath);
|
|
14
|
+
|
|
15
|
+
// WAL mode for concurrent read performance
|
|
16
|
+
db.exec("PRAGMA journal_mode=WAL");
|
|
17
|
+
|
|
18
|
+
// Load sqlite-vec extension
|
|
19
|
+
sqliteVec.load(db);
|
|
20
|
+
|
|
21
|
+
// Ensure schema is up to date
|
|
22
|
+
runMigrations(db);
|
|
8
23
|
|
|
9
|
-
const db = await lancedb.connect(dbPath);
|
|
10
24
|
return db;
|
|
11
25
|
}
|
|
@@ -1,58 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { type Table } from "@lancedb/lancedb";
|
|
3
|
-
import {
|
|
4
|
-
CONVERSATION_TABLE_NAME,
|
|
5
|
-
conversationSchema,
|
|
6
|
-
} from "./conversation.schema.js";
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
7
2
|
import type {
|
|
8
3
|
ConversationHybridRow,
|
|
9
4
|
HistoryFilters,
|
|
10
5
|
} from "../types/conversation.js";
|
|
11
6
|
import {
|
|
12
|
-
|
|
13
|
-
createFtsMutex,
|
|
14
|
-
createRerankerMutex,
|
|
15
|
-
escapeSql,
|
|
7
|
+
serializeVector,
|
|
16
8
|
safeParseJsonObject,
|
|
17
|
-
|
|
9
|
+
sanitizeFtsQuery,
|
|
10
|
+
hybridRRF,
|
|
11
|
+
topByRRF,
|
|
12
|
+
} from "./sqlite-utils.js";
|
|
18
13
|
|
|
19
14
|
export class ConversationRepository {
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
// FTS index mutex — recreated after data mutations to force re-check
|
|
23
|
-
private ensureFtsIndex = createFtsMutex(() => this.getTable());
|
|
24
|
-
|
|
25
|
-
// Cached reranker — k=60 is constant, no need to recreate per search
|
|
26
|
-
private getReranker = createRerankerMutex();
|
|
27
|
-
|
|
28
|
-
constructor(private db: lancedb.Connection) {}
|
|
29
|
-
|
|
30
|
-
private getTable(): Promise<Table> {
|
|
31
|
-
if (!this.tablePromise) {
|
|
32
|
-
this.tablePromise = getOrCreateTable(
|
|
33
|
-
this.db,
|
|
34
|
-
CONVERSATION_TABLE_NAME,
|
|
35
|
-
conversationSchema,
|
|
36
|
-
).catch((err) => {
|
|
37
|
-
this.tablePromise = null;
|
|
38
|
-
throw err;
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return this.tablePromise;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
private rowToConversationHybridRow(
|
|
45
|
-
row: Record<string, unknown>
|
|
46
|
-
): ConversationHybridRow {
|
|
47
|
-
const metadata = safeParseJsonObject(row.metadata as string);
|
|
48
|
-
return {
|
|
49
|
-
id: row.id as string,
|
|
50
|
-
content: row.content as string,
|
|
51
|
-
metadata,
|
|
52
|
-
createdAt: new Date(row.created_at as number),
|
|
53
|
-
rrfScore: (row._relevance_score as number) ?? 0,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
15
|
+
constructor(private db: Database) {}
|
|
56
16
|
|
|
57
17
|
async insertBatch(
|
|
58
18
|
rows: Array<{
|
|
@@ -69,16 +29,79 @@ export class ConversationRepository {
|
|
|
69
29
|
}>
|
|
70
30
|
): Promise<void> {
|
|
71
31
|
if (rows.length === 0) return;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
32
|
+
|
|
33
|
+
const insertMain = this.db.prepare(
|
|
34
|
+
`INSERT OR REPLACE INTO conversation_history
|
|
35
|
+
(id, content, metadata, created_at, session_id, role, message_index_start, message_index_end, project)
|
|
36
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
37
|
+
);
|
|
38
|
+
const deleteVec = this.db.prepare(
|
|
39
|
+
`DELETE FROM conversation_history_vec WHERE id = ?`
|
|
40
|
+
);
|
|
41
|
+
const insertVec = this.db.prepare(
|
|
42
|
+
`INSERT INTO conversation_history_vec (id, vector) VALUES (?, ?)`
|
|
43
|
+
);
|
|
44
|
+
const deleteFts = this.db.prepare(
|
|
45
|
+
`DELETE FROM conversation_history_fts WHERE id = ?`
|
|
46
|
+
);
|
|
47
|
+
const insertFts = this.db.prepare(
|
|
48
|
+
`INSERT INTO conversation_history_fts (id, content) VALUES (?, ?)`
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const tx = this.db.transaction(() => {
|
|
52
|
+
for (const row of rows) {
|
|
53
|
+
insertMain.run(
|
|
54
|
+
row.id,
|
|
55
|
+
row.content,
|
|
56
|
+
row.metadata,
|
|
57
|
+
row.created_at,
|
|
58
|
+
row.session_id,
|
|
59
|
+
row.role,
|
|
60
|
+
row.message_index_start,
|
|
61
|
+
row.message_index_end,
|
|
62
|
+
row.project
|
|
63
|
+
);
|
|
64
|
+
deleteVec.run(row.id);
|
|
65
|
+
insertVec.run(row.id, serializeVector(row.vector));
|
|
66
|
+
deleteFts.run(row.id);
|
|
67
|
+
insertFts.run(row.id, row.content);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
tx();
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
async deleteBySessionId(sessionId: string): Promise<void> {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
const tx = this.db.transaction(() => {
|
|
76
|
+
const idRows = this.db
|
|
77
|
+
.prepare(
|
|
78
|
+
`SELECT id FROM conversation_history WHERE session_id = ?`
|
|
79
|
+
)
|
|
80
|
+
.all(sessionId) as Array<{ id: string }>;
|
|
81
|
+
|
|
82
|
+
if (idRows.length === 0) return;
|
|
83
|
+
|
|
84
|
+
const ids = idRows.map((r) => r.id);
|
|
85
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
86
|
+
|
|
87
|
+
this.db
|
|
88
|
+
.prepare(
|
|
89
|
+
`DELETE FROM conversation_history_vec WHERE id IN (${placeholders})`
|
|
90
|
+
)
|
|
91
|
+
.run(...ids);
|
|
92
|
+
|
|
93
|
+
this.db
|
|
94
|
+
.prepare(
|
|
95
|
+
`DELETE FROM conversation_history_fts WHERE id IN (${placeholders})`
|
|
96
|
+
)
|
|
97
|
+
.run(...ids);
|
|
98
|
+
|
|
99
|
+
this.db
|
|
100
|
+
.prepare(`DELETE FROM conversation_history WHERE session_id = ?`)
|
|
101
|
+
.run(sessionId);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
tx();
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
async findHybrid(
|
|
@@ -87,34 +110,97 @@ export class ConversationRepository {
|
|
|
87
110
|
limit: number,
|
|
88
111
|
filters?: HistoryFilters
|
|
89
112
|
): Promise<ConversationHybridRow[]> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
113
|
+
const candidateCount = limit * 3;
|
|
114
|
+
|
|
115
|
+
// Vector KNN search
|
|
116
|
+
const vecResults = this.db
|
|
117
|
+
.prepare(
|
|
118
|
+
`SELECT id FROM conversation_history_vec
|
|
119
|
+
WHERE vector MATCH ? AND k = ?
|
|
120
|
+
ORDER BY distance`
|
|
121
|
+
)
|
|
122
|
+
.all(serializeVector(embedding), candidateCount) as Array<{ id: string }>;
|
|
123
|
+
|
|
124
|
+
// FTS5 search
|
|
125
|
+
const ftsQuery = sanitizeFtsQuery(query);
|
|
126
|
+
const ftsResults = this.db
|
|
127
|
+
.prepare(
|
|
128
|
+
`SELECT id FROM conversation_history_fts
|
|
129
|
+
WHERE conversation_history_fts MATCH ?
|
|
130
|
+
ORDER BY rank
|
|
131
|
+
LIMIT ?`
|
|
132
|
+
)
|
|
133
|
+
.all(ftsQuery, candidateCount) as Array<{ id: string }>;
|
|
93
134
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.fullTextSearch(query)
|
|
98
|
-
.rerank(reranker);
|
|
135
|
+
// Compute RRF scores and get top ids
|
|
136
|
+
const rrfScores = hybridRRF(vecResults, ftsResults);
|
|
137
|
+
const topIds = topByRRF(rrfScores, limit);
|
|
99
138
|
|
|
139
|
+
if (topIds.length === 0) return [];
|
|
140
|
+
|
|
141
|
+
// Build filtered query for full rows
|
|
100
142
|
const conditions: string[] = [];
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
143
|
+
const params: (string | number)[] = [];
|
|
144
|
+
|
|
145
|
+
// IN clause for top ids
|
|
146
|
+
const placeholders = topIds.map(() => "?").join(", ");
|
|
147
|
+
conditions.push(`id IN (${placeholders})`);
|
|
148
|
+
params.push(...topIds);
|
|
149
|
+
|
|
150
|
+
// Apply filters
|
|
151
|
+
if (filters?.sessionId) {
|
|
152
|
+
conditions.push("session_id = ?");
|
|
153
|
+
params.push(filters.sessionId);
|
|
154
|
+
}
|
|
155
|
+
if (filters?.role) {
|
|
156
|
+
conditions.push("role = ?");
|
|
157
|
+
params.push(filters.role);
|
|
158
|
+
}
|
|
159
|
+
if (filters?.project) {
|
|
160
|
+
conditions.push("project = ?");
|
|
161
|
+
params.push(filters.project);
|
|
162
|
+
}
|
|
163
|
+
if (filters?.after) {
|
|
164
|
+
conditions.push("created_at > ?");
|
|
165
|
+
params.push(filters.after.getTime());
|
|
166
|
+
}
|
|
167
|
+
if (filters?.before) {
|
|
168
|
+
conditions.push("created_at < ?");
|
|
169
|
+
params.push(filters.before.getTime());
|
|
113
170
|
}
|
|
114
171
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
172
|
+
const whereClause = conditions.join(" AND ");
|
|
173
|
+
|
|
174
|
+
const fullRows = this.db
|
|
175
|
+
.prepare(
|
|
176
|
+
`SELECT id, content, metadata, created_at, session_id, role,
|
|
177
|
+
message_index_start, message_index_end, project
|
|
178
|
+
FROM conversation_history
|
|
179
|
+
WHERE ${whereClause}`
|
|
180
|
+
)
|
|
181
|
+
.all(...params) as Array<{
|
|
182
|
+
id: string;
|
|
183
|
+
content: string;
|
|
184
|
+
metadata: string;
|
|
185
|
+
created_at: number;
|
|
186
|
+
session_id: string;
|
|
187
|
+
role: string;
|
|
188
|
+
message_index_start: number;
|
|
189
|
+
message_index_end: number;
|
|
190
|
+
project: string;
|
|
191
|
+
}>;
|
|
192
|
+
|
|
193
|
+
// Build a lookup for ordering by RRF score
|
|
194
|
+
const scoreMap = new Map(topIds.map((id) => [id, rrfScores.get(id)!]));
|
|
195
|
+
|
|
196
|
+
return fullRows
|
|
197
|
+
.map((row) => ({
|
|
198
|
+
id: row.id,
|
|
199
|
+
content: row.content,
|
|
200
|
+
metadata: safeParseJsonObject(row.metadata),
|
|
201
|
+
createdAt: new Date(row.created_at),
|
|
202
|
+
rrfScore: scoreMap.get(row.id) ?? 0,
|
|
203
|
+
}))
|
|
204
|
+
.sort((a, b) => b.rrfScore - a.rrfScore);
|
|
119
205
|
}
|
|
120
206
|
}
|