@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.
Files changed (106) hide show
  1. package/README.md +22 -4
  2. package/package.json +12 -18
  3. package/scripts/migrate-from-lancedb.ts +56 -0
  4. package/scripts/smoke-test.ts +699 -0
  5. package/scripts/test-runner.ts +11 -1
  6. package/src/db/connection.ts +18 -4
  7. package/src/db/conversation.repository.ts +164 -78
  8. package/src/db/memory.repository.ts +180 -168
  9. package/src/db/migrations.ts +70 -0
  10. package/src/db/sqlite-utils.ts +78 -0
  11. package/src/http/server.ts +40 -35
  12. package/src/index.ts +33 -3
  13. package/src/mcp/server.ts +2 -1
  14. package/src/migration.ts +254 -0
  15. package/dist/package.json +0 -71
  16. package/dist/scripts/test-runner.d.ts +0 -9
  17. package/dist/scripts/test-runner.d.ts.map +0 -1
  18. package/dist/scripts/test-runner.js +0 -61
  19. package/dist/scripts/test-runner.js.map +0 -1
  20. package/dist/scripts/warmup.d.ts +0 -8
  21. package/dist/scripts/warmup.d.ts.map +0 -1
  22. package/dist/scripts/warmup.js +0 -61
  23. package/dist/scripts/warmup.js.map +0 -1
  24. package/dist/src/config/index.d.ts +0 -41
  25. package/dist/src/config/index.d.ts.map +0 -1
  26. package/dist/src/config/index.js +0 -75
  27. package/dist/src/config/index.js.map +0 -1
  28. package/dist/src/db/connection.d.ts +0 -3
  29. package/dist/src/db/connection.d.ts.map +0 -1
  30. package/dist/src/db/connection.js +0 -10
  31. package/dist/src/db/connection.js.map +0 -1
  32. package/dist/src/db/conversation.repository.d.ts +0 -26
  33. package/dist/src/db/conversation.repository.d.ts.map +0 -1
  34. package/dist/src/db/conversation.repository.js +0 -72
  35. package/dist/src/db/conversation.repository.js.map +0 -1
  36. package/dist/src/db/conversation.schema.d.ts +0 -4
  37. package/dist/src/db/conversation.schema.d.ts.map +0 -1
  38. package/dist/src/db/conversation.schema.js +0 -15
  39. package/dist/src/db/conversation.schema.js.map +0 -1
  40. package/dist/src/db/lancedb-utils.d.ts +0 -45
  41. package/dist/src/db/lancedb-utils.d.ts.map +0 -1
  42. package/dist/src/db/lancedb-utils.js +0 -106
  43. package/dist/src/db/lancedb-utils.js.map +0 -1
  44. package/dist/src/db/memory.repository.d.ts +0 -40
  45. package/dist/src/db/memory.repository.d.ts.map +0 -1
  46. package/dist/src/db/memory.repository.js +0 -183
  47. package/dist/src/db/memory.repository.js.map +0 -1
  48. package/dist/src/db/schema.d.ts +0 -7
  49. package/dist/src/db/schema.d.ts.map +0 -1
  50. package/dist/src/db/schema.js +0 -19
  51. package/dist/src/db/schema.js.map +0 -1
  52. package/dist/src/http/mcp-transport.d.ts +0 -19
  53. package/dist/src/http/mcp-transport.d.ts.map +0 -1
  54. package/dist/src/http/mcp-transport.js +0 -191
  55. package/dist/src/http/mcp-transport.js.map +0 -1
  56. package/dist/src/http/server.d.ts +0 -13
  57. package/dist/src/http/server.d.ts.map +0 -1
  58. package/dist/src/http/server.js +0 -224
  59. package/dist/src/http/server.js.map +0 -1
  60. package/dist/src/index.d.ts +0 -3
  61. package/dist/src/index.d.ts.map +0 -1
  62. package/dist/src/index.js +0 -68
  63. package/dist/src/index.js.map +0 -1
  64. package/dist/src/mcp/handlers.d.ts +0 -15
  65. package/dist/src/mcp/handlers.d.ts.map +0 -1
  66. package/dist/src/mcp/handlers.js +0 -313
  67. package/dist/src/mcp/handlers.js.map +0 -1
  68. package/dist/src/mcp/server.d.ts +0 -5
  69. package/dist/src/mcp/server.d.ts.map +0 -1
  70. package/dist/src/mcp/server.js +0 -22
  71. package/dist/src/mcp/server.js.map +0 -1
  72. package/dist/src/mcp/tools.d.ts +0 -13
  73. package/dist/src/mcp/tools.d.ts.map +0 -1
  74. package/dist/src/mcp/tools.js +0 -352
  75. package/dist/src/mcp/tools.js.map +0 -1
  76. package/dist/src/services/conversation.service.d.ts +0 -38
  77. package/dist/src/services/conversation.service.d.ts.map +0 -1
  78. package/dist/src/services/conversation.service.js +0 -252
  79. package/dist/src/services/conversation.service.js.map +0 -1
  80. package/dist/src/services/embeddings.service.d.ts +0 -12
  81. package/dist/src/services/embeddings.service.d.ts.map +0 -1
  82. package/dist/src/services/embeddings.service.js +0 -37
  83. package/dist/src/services/embeddings.service.js.map +0 -1
  84. package/dist/src/services/memory.service.d.ts +0 -40
  85. package/dist/src/services/memory.service.d.ts.map +0 -1
  86. package/dist/src/services/memory.service.js +0 -258
  87. package/dist/src/services/memory.service.js.map +0 -1
  88. package/dist/src/services/parsers/claude-code.parser.d.ts +0 -8
  89. package/dist/src/services/parsers/claude-code.parser.d.ts.map +0 -1
  90. package/dist/src/services/parsers/claude-code.parser.js +0 -191
  91. package/dist/src/services/parsers/claude-code.parser.js.map +0 -1
  92. package/dist/src/services/parsers/types.d.ts +0 -9
  93. package/dist/src/services/parsers/types.d.ts.map +0 -1
  94. package/dist/src/services/parsers/types.js +0 -2
  95. package/dist/src/services/parsers/types.js.map +0 -1
  96. package/dist/src/types/conversation.d.ts +0 -99
  97. package/dist/src/types/conversation.d.ts.map +0 -1
  98. package/dist/src/types/conversation.js +0 -2
  99. package/dist/src/types/conversation.js.map +0 -1
  100. package/dist/src/types/memory.d.ts +0 -30
  101. package/dist/src/types/memory.d.ts.map +0 -1
  102. package/dist/src/types/memory.js +0 -18
  103. package/dist/src/types/memory.js.map +0 -1
  104. package/src/db/conversation.schema.ts +0 -33
  105. package/src/db/lancedb-utils.ts +0 -125
  106. package/src/db/schema.ts +0 -38
@@ -8,7 +8,17 @@
8
8
 
9
9
  import { spawn } from "bun";
10
10
 
11
- const proc = spawn(["bun", "test", "--preload", "./tests/preload.ts"], {
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" },
@@ -1,11 +1,25 @@
1
- import * as lancedb from "@lancedb/lancedb";
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
- export async function connectToDatabase(dbPath: string): Promise<lancedb.Connection> {
6
- // Ensure directory exists
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 * as lancedb from "@lancedb/lancedb";
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
- getOrCreateTable,
13
- createFtsMutex,
14
- createRerankerMutex,
15
- escapeSql,
7
+ serializeVector,
16
8
  safeParseJsonObject,
17
- } from "./lancedb-utils.js";
9
+ sanitizeFtsQuery,
10
+ hybridRRF,
11
+ topByRRF,
12
+ } from "./sqlite-utils.js";
18
13
 
19
14
  export class ConversationRepository {
20
- private tablePromise: Promise<Table> | null = null;
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
- const table = await this.getTable();
73
- await table.add(rows);
74
- // Reset FTS mutex so index existence is re-verified after new data
75
- this.ensureFtsIndex = createFtsMutex(() => this.getTable());
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 table = await this.getTable();
80
- await table.delete(`session_id = '${escapeSql(sessionId)}'`);
81
- this.ensureFtsIndex = createFtsMutex(() => this.getTable());
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
- await this.ensureFtsIndex();
91
- const table = await this.getTable();
92
- const reranker = await this.getReranker();
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
- let queryBuilder = table
95
- .query()
96
- .nearestTo(embedding)
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
- if (filters?.sessionId)
102
- conditions.push(`session_id = '${escapeSql(filters.sessionId)}'`);
103
- if (filters?.role) conditions.push(`role = '${escapeSql(filters.role)}'`);
104
- if (filters?.project)
105
- conditions.push(`project = '${escapeSql(filters.project)}'`);
106
- if (filters?.after)
107
- conditions.push(`created_at > ${filters.after.getTime()}`);
108
- if (filters?.before)
109
- conditions.push(`created_at < ${filters.before.getTime()}`);
110
-
111
- if (conditions.length > 0) {
112
- queryBuilder = queryBuilder.where(conditions.join(" AND "));
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 results = await queryBuilder.limit(limit).toArray();
116
- return results.map((row) =>
117
- this.rowToConversationHybridRow(row as Record<string, unknown>)
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
  }