@bip-krishna/agentmind 0.1.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.
Files changed (169) hide show
  1. package/.agentmind/memory.db +0 -0
  2. package/.context-memory/memory.db +0 -0
  3. package/README.md +176 -0
  4. package/apps/dashboard/src/server.ts +369 -0
  5. package/cli/src/index.ts +163 -0
  6. package/dist/apps/dashboard/src/server.d.ts +6 -0
  7. package/dist/apps/dashboard/src/server.d.ts.map +1 -0
  8. package/dist/apps/dashboard/src/server.js +360 -0
  9. package/dist/apps/dashboard/src/server.js.map +1 -0
  10. package/dist/cli/src/index.d.ts +3 -0
  11. package/dist/cli/src/index.d.ts.map +1 -0
  12. package/dist/cli/src/index.js +162 -0
  13. package/dist/cli/src/index.js.map +1 -0
  14. package/dist/examples/advanced.d.ts +2 -0
  15. package/dist/examples/advanced.d.ts.map +1 -0
  16. package/dist/examples/advanced.js +41 -0
  17. package/dist/examples/advanced.js.map +1 -0
  18. package/dist/examples/basic.d.ts +2 -0
  19. package/dist/examples/basic.d.ts.map +1 -0
  20. package/dist/examples/basic.js +22 -0
  21. package/dist/examples/basic.js.map +1 -0
  22. package/dist/packages/core/src/consolidation.d.ts +27 -0
  23. package/dist/packages/core/src/consolidation.d.ts.map +1 -0
  24. package/dist/packages/core/src/consolidation.js +97 -0
  25. package/dist/packages/core/src/consolidation.js.map +1 -0
  26. package/dist/packages/core/src/events.d.ts +18 -0
  27. package/dist/packages/core/src/events.d.ts.map +1 -0
  28. package/dist/packages/core/src/events.js +13 -0
  29. package/dist/packages/core/src/events.js.map +1 -0
  30. package/dist/packages/core/src/extraction.d.ts +32 -0
  31. package/dist/packages/core/src/extraction.d.ts.map +1 -0
  32. package/dist/packages/core/src/extraction.js +65 -0
  33. package/dist/packages/core/src/extraction.js.map +1 -0
  34. package/dist/packages/core/src/index.d.ts +10 -0
  35. package/dist/packages/core/src/index.d.ts.map +1 -0
  36. package/dist/packages/core/src/index.js +7 -0
  37. package/dist/packages/core/src/index.js.map +1 -0
  38. package/dist/packages/core/src/memory-store.d.ts +36 -0
  39. package/dist/packages/core/src/memory-store.d.ts.map +1 -0
  40. package/dist/packages/core/src/memory-store.js +2 -0
  41. package/dist/packages/core/src/memory-store.js.map +1 -0
  42. package/dist/packages/core/src/memory.d.ts +75 -0
  43. package/dist/packages/core/src/memory.d.ts.map +1 -0
  44. package/dist/packages/core/src/memory.js +402 -0
  45. package/dist/packages/core/src/memory.js.map +1 -0
  46. package/dist/packages/core/src/policy.d.ts +17 -0
  47. package/dist/packages/core/src/policy.d.ts.map +1 -0
  48. package/dist/packages/core/src/policy.js +22 -0
  49. package/dist/packages/core/src/policy.js.map +1 -0
  50. package/dist/packages/core/src/types.d.ts +102 -0
  51. package/dist/packages/core/src/types.d.ts.map +1 -0
  52. package/dist/packages/core/src/types.js +23 -0
  53. package/dist/packages/core/src/types.js.map +1 -0
  54. package/dist/packages/embeddings/src/index.d.ts +4 -0
  55. package/dist/packages/embeddings/src/index.d.ts.map +1 -0
  56. package/dist/packages/embeddings/src/index.js +4 -0
  57. package/dist/packages/embeddings/src/index.js.map +1 -0
  58. package/dist/packages/embeddings/src/local.d.ts +15 -0
  59. package/dist/packages/embeddings/src/local.d.ts.map +1 -0
  60. package/dist/packages/embeddings/src/local.js +45 -0
  61. package/dist/packages/embeddings/src/local.js.map +1 -0
  62. package/dist/packages/embeddings/src/openai.d.ts +17 -0
  63. package/dist/packages/embeddings/src/openai.d.ts.map +1 -0
  64. package/dist/packages/embeddings/src/openai.js +41 -0
  65. package/dist/packages/embeddings/src/openai.js.map +1 -0
  66. package/dist/packages/embeddings/src/provider.d.ts +8 -0
  67. package/dist/packages/embeddings/src/provider.d.ts.map +1 -0
  68. package/dist/packages/embeddings/src/provider.js +12 -0
  69. package/dist/packages/embeddings/src/provider.js.map +1 -0
  70. package/dist/packages/integrations/src/anthropic.d.ts +15 -0
  71. package/dist/packages/integrations/src/anthropic.d.ts.map +1 -0
  72. package/dist/packages/integrations/src/anthropic.js +25 -0
  73. package/dist/packages/integrations/src/anthropic.js.map +1 -0
  74. package/dist/packages/integrations/src/index.d.ts +4 -0
  75. package/dist/packages/integrations/src/index.d.ts.map +1 -0
  76. package/dist/packages/integrations/src/index.js +4 -0
  77. package/dist/packages/integrations/src/index.js.map +1 -0
  78. package/dist/packages/integrations/src/openai.d.ts +27 -0
  79. package/dist/packages/integrations/src/openai.d.ts.map +1 -0
  80. package/dist/packages/integrations/src/openai.js +26 -0
  81. package/dist/packages/integrations/src/openai.js.map +1 -0
  82. package/dist/packages/integrations/src/vercel.d.ts +24 -0
  83. package/dist/packages/integrations/src/vercel.d.ts.map +1 -0
  84. package/dist/packages/integrations/src/vercel.js +20 -0
  85. package/dist/packages/integrations/src/vercel.js.map +1 -0
  86. package/dist/packages/retrieval/src/index.d.ts +3 -0
  87. package/dist/packages/retrieval/src/index.d.ts.map +1 -0
  88. package/dist/packages/retrieval/src/index.js +3 -0
  89. package/dist/packages/retrieval/src/index.js.map +1 -0
  90. package/dist/packages/retrieval/src/ranker.d.ts +21 -0
  91. package/dist/packages/retrieval/src/ranker.d.ts.map +1 -0
  92. package/dist/packages/retrieval/src/ranker.js +54 -0
  93. package/dist/packages/retrieval/src/ranker.js.map +1 -0
  94. package/dist/packages/retrieval/src/token.d.ts +3 -0
  95. package/dist/packages/retrieval/src/token.d.ts.map +1 -0
  96. package/dist/packages/retrieval/src/token.js +7 -0
  97. package/dist/packages/retrieval/src/token.js.map +1 -0
  98. package/dist/packages/storage/src/config.d.ts +14 -0
  99. package/dist/packages/storage/src/config.d.ts.map +1 -0
  100. package/dist/packages/storage/src/config.js +41 -0
  101. package/dist/packages/storage/src/config.js.map +1 -0
  102. package/dist/packages/storage/src/index.d.ts +4 -0
  103. package/dist/packages/storage/src/index.d.ts.map +1 -0
  104. package/dist/packages/storage/src/index.js +4 -0
  105. package/dist/packages/storage/src/index.js.map +1 -0
  106. package/dist/packages/storage/src/memory-store.d.ts +37 -0
  107. package/dist/packages/storage/src/memory-store.d.ts.map +1 -0
  108. package/dist/packages/storage/src/memory-store.js +107 -0
  109. package/dist/packages/storage/src/memory-store.js.map +1 -0
  110. package/dist/packages/storage/src/migrations.d.ts +12 -0
  111. package/dist/packages/storage/src/migrations.d.ts.map +1 -0
  112. package/dist/packages/storage/src/migrations.js +89 -0
  113. package/dist/packages/storage/src/migrations.js.map +1 -0
  114. package/dist/packages/storage/src/postgres-store.d.ts +45 -0
  115. package/dist/packages/storage/src/postgres-store.d.ts.map +1 -0
  116. package/dist/packages/storage/src/postgres-store.js +282 -0
  117. package/dist/packages/storage/src/postgres-store.js.map +1 -0
  118. package/dist/packages/storage/src/sqlite-store.d.ts +36 -0
  119. package/dist/packages/storage/src/sqlite-store.d.ts.map +1 -0
  120. package/dist/packages/storage/src/sqlite-store.js +243 -0
  121. package/dist/packages/storage/src/sqlite-store.js.map +1 -0
  122. package/examples/advanced.ts +46 -0
  123. package/examples/basic.ts +25 -0
  124. package/package.json +52 -0
  125. package/packages/core/package.json +13 -0
  126. package/packages/core/src/consolidation.ts +104 -0
  127. package/packages/core/src/events.ts +25 -0
  128. package/packages/core/src/extraction.ts +81 -0
  129. package/packages/core/src/index.ts +22 -0
  130. package/packages/core/src/memory-store.ts +50 -0
  131. package/packages/core/src/memory.ts +447 -0
  132. package/packages/core/src/policy.ts +36 -0
  133. package/packages/core/src/types.ts +119 -0
  134. package/packages/core/tsconfig.json +6 -0
  135. package/packages/embeddings/package.json +9 -0
  136. package/packages/embeddings/src/index.ts +3 -0
  137. package/packages/embeddings/src/local.ts +47 -0
  138. package/packages/embeddings/src/openai.ts +50 -0
  139. package/packages/embeddings/src/provider.d.ts.map +1 -0
  140. package/packages/embeddings/src/provider.js.map +1 -0
  141. package/packages/embeddings/src/provider.ts +17 -0
  142. package/packages/embeddings/tsconfig.json +6 -0
  143. package/packages/integrations/package.json +7 -0
  144. package/packages/integrations/src/anthropic.ts +34 -0
  145. package/packages/integrations/src/index.ts +3 -0
  146. package/packages/integrations/src/openai.ts +52 -0
  147. package/packages/integrations/src/vercel.ts +26 -0
  148. package/packages/retrieval/package.json +8 -0
  149. package/packages/retrieval/src/index.ts +2 -0
  150. package/packages/retrieval/src/ranker.d.ts.map +1 -0
  151. package/packages/retrieval/src/ranker.js.map +1 -0
  152. package/packages/retrieval/src/ranker.ts +77 -0
  153. package/packages/retrieval/src/token.d.ts.map +1 -0
  154. package/packages/retrieval/src/token.js.map +1 -0
  155. package/packages/retrieval/src/token.ts +5 -0
  156. package/packages/retrieval/tsconfig.json +6 -0
  157. package/packages/storage/package.json +11 -0
  158. package/packages/storage/src/config.ts +44 -0
  159. package/packages/storage/src/index.ts +3 -0
  160. package/packages/storage/src/memory-store.ts +100 -0
  161. package/packages/storage/src/migrations.ts +96 -0
  162. package/packages/storage/src/postgres-store.ts +299 -0
  163. package/packages/storage/src/sqlite-store.ts +298 -0
  164. package/packages/storage/tsconfig.json +6 -0
  165. package/test-mvp.mjs +82 -0
  166. package/tests/consolidation.test.ts +45 -0
  167. package/tests/eval.test.ts +102 -0
  168. package/tsconfig.json +21 -0
  169. package/vitest.config.ts +8 -0
@@ -0,0 +1,299 @@
1
+ import type { MemoryStore } from "../../core/src/memory-store.js";
2
+ import type { Memory, Project, Source, SearchQuery, SearchResult, RetrievalEvent } from "../../core/src/types.js";
3
+ import { MIGRATIONS } from "./migrations.js";
4
+
5
+ /**
6
+ * Postgres + pgvector adapter — same MemoryStore contract as SqliteMemoryStore.
7
+ * Requires: CREATE EXTENSION vector;
8
+ * Embeddings stored dual: JSON text `vector` + native `vec` column for pgvector search.
9
+ */
10
+
11
+ type PgPool = {
12
+ query: (text: string, params?: any[]) => Promise<{ rows: any[] }>;
13
+ end: () => Promise<void>;
14
+ };
15
+
16
+ export interface PostgresStoreOptions {
17
+ connectionString: string;
18
+ maxPool?: number;
19
+ vectorDimensions?: number; // default 1536
20
+ }
21
+
22
+ export class PostgresMemoryStore implements MemoryStore {
23
+ private pool: PgPool;
24
+ private dimensions: number;
25
+ private ready: Promise<void>;
26
+
27
+ constructor(opts: string | PostgresStoreOptions) {
28
+ const connectionString = typeof opts === "string" ? opts : opts.connectionString;
29
+ this.dimensions = typeof opts === "object" ? (opts.vectorDimensions ?? 1536) : 1536;
30
+ // dynamic import to avoid hard dep if not used
31
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
32
+ const { Pool } = require("pg");
33
+ this.pool = new Pool({ connectionString, max: typeof opts === "object" ? (opts.maxPool ?? 10) : 10 });
34
+ this.ready = this.migrate();
35
+ }
36
+
37
+ private async migrate() {
38
+ // schema_migrations + core tables
39
+ for (const m of MIGRATIONS) {
40
+ if (m.version === 2) {
41
+ // pgvector extension + vec column
42
+ try {
43
+ await this.pool.query(`CREATE EXTENSION IF NOT EXISTS vector`);
44
+ await this.pool.query(`ALTER TABLE embeddings ADD COLUMN IF NOT EXISTS vec vector(${this.dimensions})`);
45
+ await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_embeddings_vec ON embeddings USING hnsw (vec vector_cosine_ops)`);
46
+ } catch {
47
+ // ignore if extension not available
48
+ }
49
+ continue;
50
+ }
51
+ // For version 1, run each statement separately for pg
52
+ const stmts = m.up.split(";").map((s) => s.trim()).filter(Boolean);
53
+ for (const stmt of stmts) {
54
+ try { await this.pool.query(stmt); } catch {}
55
+ }
56
+ }
57
+ // Ensure tables exist with pg-compatible types (TEXT -> VARCHAR etc handled)
58
+ await this.pool.query(`
59
+ CREATE TABLE IF NOT EXISTS projects (
60
+ id TEXT PRIMARY KEY,
61
+ name TEXT UNIQUE NOT NULL,
62
+ description TEXT,
63
+ created_at TIMESTAMPTZ NOT NULL,
64
+ updated_at TIMESTAMPTZ NOT NULL
65
+ );
66
+ CREATE TABLE IF NOT EXISTS sources (
67
+ id TEXT PRIMARY KEY,
68
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
69
+ type TEXT NOT NULL,
70
+ content TEXT,
71
+ created_at TIMESTAMPTZ NOT NULL,
72
+ metadata JSONB NOT NULL
73
+ );
74
+ CREATE TABLE IF NOT EXISTS memories (
75
+ id TEXT PRIMARY KEY,
76
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
77
+ type TEXT NOT NULL,
78
+ content TEXT NOT NULL,
79
+ importance REAL NOT NULL,
80
+ confidence REAL NOT NULL,
81
+ status TEXT NOT NULL,
82
+ created_at TIMESTAMPTZ NOT NULL,
83
+ updated_at TIMESTAMPTZ NOT NULL,
84
+ last_accessed_at TIMESTAMPTZ,
85
+ access_count INTEGER NOT NULL,
86
+ source_id TEXT REFERENCES sources(id) ON DELETE SET NULL,
87
+ metadata JSONB NOT NULL,
88
+ archived INTEGER NOT NULL DEFAULT 0
89
+ );
90
+ CREATE TABLE IF NOT EXISTS embeddings (
91
+ memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
92
+ vector TEXT NOT NULL,
93
+ model TEXT NOT NULL,
94
+ dimensions INTEGER NOT NULL,
95
+ vec vector(${this.dimensions})
96
+ );
97
+ CREATE TABLE IF NOT EXISTS retrieval_events (
98
+ id TEXT PRIMARY KEY,
99
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
100
+ query TEXT NOT NULL,
101
+ retrieved_memory_ids JSONB NOT NULL,
102
+ scores JSONB NOT NULL,
103
+ final_context_tokens INTEGER NOT NULL,
104
+ created_at TIMESTAMPTZ NOT NULL
105
+ );
106
+ CREATE TABLE IF NOT EXISTS consolidations (
107
+ id TEXT PRIMARY KEY,
108
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
109
+ summary_memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,
110
+ source_memory_ids JSONB NOT NULL,
111
+ created_at TIMESTAMPTZ NOT NULL
112
+ );
113
+ `);
114
+ }
115
+
116
+ private async ensureReady() { await this.ready; }
117
+
118
+ // ---- Projects ----
119
+ async createProject(project: Project): Promise<Project> {
120
+ await this.ensureReady();
121
+ await this.pool.query(`INSERT INTO projects (id, name, description, created_at, updated_at) VALUES ($1,$2,$3,$4,$5)`, [project.id, project.name, project.description ?? null, project.createdAt, project.updatedAt]);
122
+ return project;
123
+ }
124
+ async getProject(id: string): Promise<Project | null> {
125
+ await this.ensureReady();
126
+ const { rows } = await this.pool.query(`SELECT * FROM projects WHERE id=$1`, [id]);
127
+ if (!rows[0]) return null;
128
+ return this.mapProject(rows[0]);
129
+ }
130
+ async getProjectByName(name: string): Promise<Project | null> {
131
+ await this.ensureReady();
132
+ const { rows } = await this.pool.query(`SELECT * FROM projects WHERE name=$1`, [name]);
133
+ if (!rows[0]) return null;
134
+ return this.mapProject(rows[0]);
135
+ }
136
+ async listProjects(): Promise<Project[]> {
137
+ await this.ensureReady();
138
+ const { rows } = await this.pool.query(`SELECT * FROM projects`);
139
+ return rows.map(this.mapProject);
140
+ }
141
+ async deleteProject(projectId: string): Promise<void> {
142
+ await this.ensureReady();
143
+ await this.pool.query(`DELETE FROM projects WHERE id=$1`, [projectId]);
144
+ }
145
+
146
+ // ---- Sources ----
147
+ async createSource(source: Source): Promise<Source> {
148
+ await this.ensureReady();
149
+ await this.pool.query(`INSERT INTO sources (id, project_id, type, content, created_at, metadata) VALUES ($1,$2,$3,$4,$5,$6)`, [source.id, source.projectId, source.type, source.content ?? null, source.createdAt, JSON.stringify(source.metadata)]);
150
+ return source;
151
+ }
152
+ async getSource(id: string): Promise<Source | null> {
153
+ await this.ensureReady();
154
+ const { rows } = await this.pool.query(`SELECT * FROM sources WHERE id=$1`, [id]);
155
+ if (!rows[0]) return null;
156
+ const r = rows[0];
157
+ return { id: r.id, projectId: r.project_id, type: r.type, content: r.content ?? undefined, createdAt: new Date(r.created_at), metadata: typeof r.metadata === "string" ? JSON.parse(r.metadata) : r.metadata };
158
+ }
159
+
160
+ // ---- Memories ----
161
+ async createMemory(memory: Memory): Promise<void> {
162
+ await this.ensureReady();
163
+ await this.pool.query(`INSERT INTO memories (id, project_id, type, content, importance, confidence, status, created_at, updated_at, last_accessed_at, access_count, source_id, metadata, archived) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)`, [memory.id, memory.projectId, memory.type, memory.content, memory.importance, memory.confidence ?? 1, memory.status ?? "active", memory.createdAt, memory.updatedAt, memory.lastAccessedAt ?? null, memory.accessCount, memory.sourceId ?? null, JSON.stringify(memory.metadata), memory.archived ? 1 : 0]);
164
+ }
165
+ async getMemory(id: string): Promise<Memory | null> {
166
+ await this.ensureReady();
167
+ const { rows } = await this.pool.query(`SELECT * FROM memories WHERE id=$1`, [id]);
168
+ if (!rows[0]) return null;
169
+ return this.mapMemory(rows[0]);
170
+ }
171
+ async updateMemory(id: string, update: Partial<Memory>): Promise<void> {
172
+ await this.ensureReady();
173
+ const existing = await this.getMemory(id);
174
+ if (!existing) return;
175
+ const merged: Memory = { ...existing, ...update, id, updatedAt: update.updatedAt ?? new Date() } as Memory;
176
+ await this.pool.query(`UPDATE memories SET type=$1, content=$2, importance=$3, confidence=$4, status=$5, created_at=$6, updated_at=$7, last_accessed_at=$8, access_count=$9, source_id=$10, metadata=$11, archived=$12 WHERE id=$13`, [merged.type, merged.content, merged.importance, merged.confidence, merged.status ?? "active", merged.createdAt, merged.updatedAt, merged.lastAccessedAt ?? null, merged.accessCount, merged.sourceId ?? null, JSON.stringify(merged.metadata), merged.archived ? 1 : 0, id]);
177
+ }
178
+ async deleteMemory(id: string): Promise<void> {
179
+ await this.ensureReady();
180
+ // Deleting embedding automatically via cascade, but also ensure vector cleanup
181
+ await this.pool.query(`DELETE FROM memories WHERE id=$1`, [id]);
182
+ }
183
+ async archiveMemory(id: string): Promise<void> {
184
+ await this.ensureReady();
185
+ await this.pool.query(`UPDATE memories SET archived=1, status='archived', updated_at=$1 WHERE id=$2`, [new Date(), id]);
186
+ }
187
+ async listMemories(projectId: string): Promise<Memory[]> {
188
+ await this.ensureReady();
189
+ const { rows } = await this.pool.query(`SELECT * FROM memories WHERE project_id=$1 ORDER BY updated_at DESC`, [projectId]);
190
+ return rows.map(this.mapMemory);
191
+ }
192
+ async search(query: SearchQuery): Promise<SearchResult[]> {
193
+ await this.ensureReady();
194
+ // Enforce project isolation first
195
+ let sql = `SELECT * FROM memories WHERE project_id=$1`;
196
+ const params: any[] = [query.projectId];
197
+ let idx = 2;
198
+ if (query.types?.length) {
199
+ sql += ` AND type IN (${query.types.map(() => `$${idx++}`).join(",")})`;
200
+ params.push(...query.types);
201
+ }
202
+ if (query.minImportance !== undefined) {
203
+ sql += ` AND importance >= $${idx++}`;
204
+ params.push(query.minImportance);
205
+ }
206
+ if (!query.includeArchived) sql += ` AND archived=0 AND status != 'archived'`;
207
+ const { rows } = await this.pool.query(sql, params);
208
+ const memories = rows.map(this.mapMemory);
209
+ // Keyword fallback scoring (vector search is done at MemoryEngine level with pgvector via separate query if needed)
210
+ const qLower = query.query.toLowerCase();
211
+ const qTokens = new Set(qLower.split(/\W+/).filter(Boolean));
212
+ const scored = memories.map((m) => {
213
+ const cLower = m.content.toLowerCase();
214
+ const cTokens = cLower.split(/\W+/).filter(Boolean);
215
+ let hits = 0;
216
+ for (const t of cTokens) if (qTokens.has(t)) hits++;
217
+ const phraseBoost = cLower.includes(qLower) ? 0.5 : 0;
218
+ const keywordScore = qTokens.size ? Math.min(1, hits / qTokens.size + phraseBoost) : 0;
219
+ const recency = Math.exp(-(Date.now() - m.updatedAt.getTime()) / (1000 * 60 * 60 * 24 * 30));
220
+ const final = keywordScore * 0.7 + m.importance * 0.15 + recency * 0.15;
221
+ return { memory: m, score: final, semanticScore: 0, keywordScore };
222
+ });
223
+ scored.sort((a, b) => b.score - a.score);
224
+ return scored.slice(0, query.limit ?? 5);
225
+ }
226
+
227
+ // ---- Embeddings ----
228
+ async upsertEmbedding(memoryId: string, vector: number[], model: string): Promise<void> {
229
+ await this.ensureReady();
230
+ const vecStr = `[${vector.join(",")}]`;
231
+ await this.pool.query(`INSERT INTO embeddings (memory_id, vector, model, dimensions, vec) VALUES ($1,$2,$3,$4,$5::vector) ON CONFLICT (memory_id) DO UPDATE SET vector=EXCLUDED.vector, model=EXCLUDED.model, dimensions=EXCLUDED.dimensions, vec=EXCLUDED.vec`, [memoryId, JSON.stringify(vector), model, vector.length, vecStr]);
232
+ }
233
+ async getEmbedding(memoryId: string): Promise<{ vector: number[]; model: string } | null> {
234
+ await this.ensureReady();
235
+ const { rows } = await this.pool.query(`SELECT vector, model FROM embeddings WHERE memory_id=$1`, [memoryId]);
236
+ if (!rows[0]) return null;
237
+ return { vector: JSON.parse(rows[0].vector), model: rows[0].model };
238
+ }
239
+
240
+ /** pgvector semantic search — use when MemoryEngine wants native vector ranking */
241
+ async vectorSearch(projectId: string, queryVector: number[], limit = 10): Promise<Array<{ memory: Memory; distance: number }>> {
242
+ await this.ensureReady();
243
+ const vecStr = `[${queryVector.join(",")}]`;
244
+ const { rows } = await this.pool.query(`
245
+ SELECT m.*, e.vec <=> $1::vector as distance
246
+ FROM memories m
247
+ JOIN embeddings e ON e.memory_id = m.id
248
+ WHERE m.project_id = $2 AND m.archived=0 AND m.status != 'archived'
249
+ ORDER BY distance ASC
250
+ LIMIT $3
251
+ `, [vecStr, projectId, limit]);
252
+ return rows.map((r) => ({ memory: this.mapMemory(r), distance: Number(r.distance) }));
253
+ }
254
+
255
+ // ---- Retrieval logs ----
256
+ async logRetrieval(event: RetrievalEvent): Promise<void> {
257
+ await this.ensureReady();
258
+ await this.pool.query(`INSERT INTO retrieval_events (id, project_id, query, retrieved_memory_ids, scores, final_context_tokens, created_at) VALUES ($1,$2,$3,$4,$5,$6,$7)`, [event.id, event.projectId, event.query, JSON.stringify(event.retrievedMemoryIds), JSON.stringify(event.scores), event.finalContextTokens, event.createdAt]);
259
+ }
260
+ async listRetrievals(projectId: string, limit = 20): Promise<RetrievalEvent[]> {
261
+ await this.ensureReady();
262
+ const { rows } = await this.pool.query(`SELECT * FROM retrieval_events WHERE project_id=$1 ORDER BY created_at DESC LIMIT $2`, [projectId, limit]);
263
+ return rows.map((r) => ({
264
+ id: r.id,
265
+ projectId: r.project_id,
266
+ query: r.query,
267
+ retrievedMemoryIds: typeof r.retrieved_memory_ids === "string" ? JSON.parse(r.retrieved_memory_ids) : r.retrieved_memory_ids,
268
+ scores: typeof r.scores === "string" ? JSON.parse(r.scores) : r.scores,
269
+ finalContextTokens: r.final_context_tokens,
270
+ createdAt: new Date(r.created_at),
271
+ }));
272
+ }
273
+
274
+ async close(): Promise<void> { await this.pool.end(); }
275
+
276
+ private mapProject = (row: any): Project => ({
277
+ id: row.id,
278
+ name: row.name,
279
+ description: row.description ?? undefined,
280
+ createdAt: new Date(row.created_at),
281
+ updatedAt: new Date(row.updated_at),
282
+ });
283
+ private mapMemory = (row: any): Memory => ({
284
+ id: row.id,
285
+ projectId: row.project_id,
286
+ type: row.type,
287
+ content: row.content,
288
+ importance: Number(row.importance),
289
+ confidence: Number(row.confidence),
290
+ status: row.status,
291
+ createdAt: new Date(row.created_at),
292
+ updatedAt: new Date(row.updated_at),
293
+ lastAccessedAt: row.last_accessed_at ? new Date(row.last_accessed_at) : undefined,
294
+ accessCount: Number(row.access_count),
295
+ sourceId: row.source_id ?? undefined,
296
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata,
297
+ archived: !!row.archived,
298
+ });
299
+ }
@@ -0,0 +1,298 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import type { MemoryStore } from "../../core/src/memory-store.js";
5
+ import type { Memory, Project, Source, SearchQuery, SearchResult, RetrievalEvent } from "../../core/src/types.js";
6
+
7
+ export interface SqliteStoreOptions {
8
+ path?: string; // file path or ":memory:"
9
+ }
10
+
11
+ export class SqliteMemoryStore implements MemoryStore {
12
+ private db: DatabaseSync;
13
+
14
+ constructor(opts: SqliteStoreOptions = {}) {
15
+ const dbPath = opts.path ?? ":memory:";
16
+ if (dbPath !== ":memory:") {
17
+ const dir = path.dirname(dbPath);
18
+ if (dir && dir !== "." && dir !== "") fs.mkdirSync(dir, { recursive: true });
19
+ }
20
+ this.db = new DatabaseSync(dbPath);
21
+ this.migrate();
22
+ }
23
+
24
+ private migrate() {
25
+ this.db.exec(`
26
+ CREATE TABLE IF NOT EXISTS projects (
27
+ id TEXT PRIMARY KEY,
28
+ name TEXT UNIQUE NOT NULL,
29
+ description TEXT,
30
+ created_at TEXT NOT NULL,
31
+ updated_at TEXT NOT NULL
32
+ );
33
+ CREATE TABLE IF NOT EXISTS sources (
34
+ id TEXT PRIMARY KEY,
35
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
36
+ type TEXT NOT NULL,
37
+ content TEXT,
38
+ created_at TEXT NOT NULL,
39
+ metadata TEXT NOT NULL
40
+ );
41
+ CREATE TABLE IF NOT EXISTS memories (
42
+ id TEXT PRIMARY KEY,
43
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
44
+ type TEXT NOT NULL,
45
+ content TEXT NOT NULL,
46
+ importance REAL NOT NULL,
47
+ confidence REAL NOT NULL,
48
+ status TEXT NOT NULL,
49
+ created_at TEXT NOT NULL,
50
+ updated_at TEXT NOT NULL,
51
+ last_accessed_at TEXT,
52
+ access_count INTEGER NOT NULL,
53
+ source_id TEXT REFERENCES sources(id) ON DELETE SET NULL,
54
+ metadata TEXT NOT NULL,
55
+ archived INTEGER NOT NULL DEFAULT 0
56
+ );
57
+ CREATE TABLE IF NOT EXISTS embeddings (
58
+ memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
59
+ vector TEXT NOT NULL,
60
+ model TEXT NOT NULL,
61
+ dimensions INTEGER NOT NULL
62
+ );
63
+ CREATE TABLE IF NOT EXISTS retrieval_events (
64
+ id TEXT PRIMARY KEY,
65
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
66
+ query TEXT NOT NULL,
67
+ retrieved_memory_ids TEXT NOT NULL,
68
+ scores TEXT NOT NULL,
69
+ final_context_tokens INTEGER NOT NULL,
70
+ created_at TEXT NOT NULL
71
+ );
72
+ CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_id);
73
+ CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
74
+ CREATE TABLE IF NOT EXISTS consolidations (
75
+ id TEXT PRIMARY KEY,
76
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
77
+ summary_memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,
78
+ source_memory_ids TEXT NOT NULL,
79
+ created_at TEXT NOT NULL
80
+ );
81
+ CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status);
82
+ `);
83
+ }
84
+
85
+ // ---- Projects ----
86
+ async createProject(project: Project): Promise<Project> {
87
+ this.db.prepare(`INSERT INTO projects (id, name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`).run(project.id, project.name, project.description ?? null, project.createdAt.toISOString(), project.updatedAt.toISOString());
88
+ return project;
89
+ }
90
+
91
+ async getProject(id: string): Promise<Project | null> {
92
+ const row = this.db.prepare(`SELECT * FROM projects WHERE id = ?`).get(id) as any;
93
+ if (!row) return null;
94
+ return this.mapProject(row);
95
+ }
96
+
97
+ async getProjectByName(name: string): Promise<Project | null> {
98
+ const row = this.db.prepare(`SELECT * FROM projects WHERE name = ?`).get(name) as any;
99
+ if (!row) return null;
100
+ return this.mapProject(row);
101
+ }
102
+
103
+ async listProjects(): Promise<Project[]> {
104
+ const rows = this.db.prepare(`SELECT * FROM projects`).all() as any[];
105
+ return rows.map(this.mapProject);
106
+ }
107
+
108
+ async deleteProject(projectId: string): Promise<void> {
109
+ this.db.prepare(`DELETE FROM projects WHERE id = ?`).run(projectId);
110
+ }
111
+
112
+ // ---- Sources ----
113
+ async createSource(source: Source): Promise<Source> {
114
+ this.db.prepare(`INSERT INTO sources (id, project_id, type, content, created_at, metadata) VALUES (?, ?, ?, ?, ?, ?)`).run(
115
+ source.id, source.projectId, source.type, source.content ?? null, source.createdAt.toISOString(), JSON.stringify(source.metadata)
116
+ );
117
+ return source;
118
+ }
119
+
120
+ async getSource(id: string): Promise<Source | null> {
121
+ const row = this.db.prepare(`SELECT * FROM sources WHERE id = ?`).get(id) as any;
122
+ if (!row) return null;
123
+ return {
124
+ id: row.id,
125
+ projectId: row.project_id,
126
+ type: row.type,
127
+ content: row.content ?? undefined,
128
+ createdAt: new Date(row.created_at),
129
+ metadata: JSON.parse(row.metadata),
130
+ };
131
+ }
132
+
133
+ // ---- Memories ----
134
+ async createMemory(memory: Memory): Promise<void> {
135
+ this.db.prepare(`
136
+ INSERT INTO memories (id, project_id, type, content, importance, confidence, status, created_at, updated_at, last_accessed_at, access_count, source_id, metadata, archived)
137
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
138
+ `).run(
139
+ memory.id,
140
+ memory.projectId,
141
+ memory.type,
142
+ memory.content,
143
+ memory.importance,
144
+ memory.confidence ?? 1,
145
+ memory.status ?? "active",
146
+ memory.createdAt.toISOString(),
147
+ memory.updatedAt.toISOString(),
148
+ memory.lastAccessedAt ? memory.lastAccessedAt.toISOString() : null,
149
+ memory.accessCount,
150
+ memory.sourceId ?? null,
151
+ JSON.stringify(memory.metadata),
152
+ memory.archived ? 1 : 0
153
+ );
154
+ }
155
+
156
+ async getMemory(id: string): Promise<Memory | null> {
157
+ const row = this.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id) as any;
158
+ if (!row) return null;
159
+ return this.mapMemory(row);
160
+ }
161
+
162
+ async updateMemory(id: string, update: Partial<Memory>): Promise<void> {
163
+ const existing = await this.getMemory(id);
164
+ if (!existing) return;
165
+ const merged: Memory = { ...existing, ...update, id, updatedAt: update.updatedAt ?? new Date() } as Memory;
166
+ this.db.prepare(`
167
+ UPDATE memories SET type=?, content=?, importance=?, confidence=?, status=?, created_at=?, updated_at=?, last_accessed_at=?, access_count=?, source_id=?, metadata=?, archived=?
168
+ WHERE id=?
169
+ `).run(
170
+ merged.type,
171
+ merged.content,
172
+ merged.importance,
173
+ merged.confidence,
174
+ (merged.status ?? "active"),
175
+ merged.createdAt.toISOString(),
176
+ merged.updatedAt.toISOString(),
177
+ merged.lastAccessedAt ? merged.lastAccessedAt.toISOString() : null,
178
+ merged.accessCount,
179
+ merged.sourceId ?? null,
180
+ JSON.stringify(merged.metadata),
181
+ merged.archived ? 1 : 0,
182
+ id
183
+ );
184
+ }
185
+
186
+ async deleteMemory(id: string): Promise<void> {
187
+ this.db.prepare(`DELETE FROM memories WHERE id = ?`).run(id);
188
+ }
189
+
190
+ async archiveMemory(id: string): Promise<void> {
191
+ this.db.prepare(`UPDATE memories SET archived=1, status='archived', updated_at=? WHERE id=?`).run(new Date().toISOString(), id);
192
+ }
193
+
194
+ async listMemories(projectId: string): Promise<Memory[]> {
195
+ const rows = this.db.prepare(`SELECT * FROM memories WHERE project_id = ? ORDER BY updated_at DESC`).all(projectId) as any[];
196
+ return rows.map(this.mapMemory);
197
+ }
198
+
199
+ async search(query: SearchQuery): Promise<SearchResult[]> {
200
+ let sql = `SELECT * FROM memories WHERE project_id = ?`;
201
+ const params: any[] = [query.projectId];
202
+ if (query.types && query.types.length) {
203
+ sql += ` AND type IN (${query.types.map(() => "?").join(",")})`;
204
+ params.push(...query.types);
205
+ }
206
+ if (query.minImportance !== undefined) {
207
+ sql += ` AND importance >= ?`;
208
+ params.push(query.minImportance);
209
+ }
210
+ if (!query.includeArchived) {
211
+ sql += ` AND archived = 0 AND status != 'archived'`;
212
+ }
213
+ const rows = this.db.prepare(sql).all(...params) as any[];
214
+ const memories = rows.map(this.mapMemory);
215
+
216
+ const qLower = query.query.toLowerCase();
217
+ const qTokens = new Set(qLower.split(/\W+/).filter(Boolean));
218
+ const scored = memories.map((m) => {
219
+ const cLower = m.content.toLowerCase();
220
+ const cTokens = cLower.split(/\W+/).filter(Boolean);
221
+ let hits = 0;
222
+ for (const t of cTokens) if (qTokens.has(t)) hits++;
223
+ const phraseBoost = cLower.includes(qLower) ? 0.5 : 0;
224
+ const keywordScore = qTokens.size ? Math.min(1, hits / qTokens.size + phraseBoost) : 0;
225
+ const recency = Math.exp(-(Date.now() - m.updatedAt.getTime()) / (1000 * 60 * 60 * 24 * 30));
226
+ const final = keywordScore * 0.7 + m.importance * 0.15 + recency * 0.15;
227
+ return { memory: m, score: final, semanticScore: 0, keywordScore };
228
+ });
229
+ scored.sort((a, b) => b.score - a.score);
230
+ return scored.slice(0, query.limit ?? 5);
231
+ }
232
+
233
+ // ---- Embeddings ----
234
+ async upsertEmbedding(memoryId: string, vector: number[], model: string): Promise<void> {
235
+ this.db.prepare(`INSERT INTO embeddings (memory_id, vector, model, dimensions) VALUES (?, ?, ?, ?)
236
+ ON CONFLICT(memory_id) DO UPDATE SET vector=excluded.vector, model=excluded.model, dimensions=excluded.dimensions
237
+ `).run(memoryId, JSON.stringify(vector), model, vector.length);
238
+ }
239
+
240
+ async getEmbedding(memoryId: string): Promise<{ vector: number[]; model: string } | null> {
241
+ const row = this.db.prepare(`SELECT * FROM embeddings WHERE memory_id = ?`).get(memoryId) as any;
242
+ if (!row) return null;
243
+ return { vector: JSON.parse(row.vector), model: row.model };
244
+ }
245
+
246
+ async deleteEmbedding(memoryId: string): Promise<void> {
247
+ this.db.prepare(`DELETE FROM embeddings WHERE memory_id = ?`).run(memoryId);
248
+ }
249
+
250
+ // ---- Retrieval logs ----
251
+ async logRetrieval(event: RetrievalEvent): Promise<void> {
252
+ this.db.prepare(`INSERT INTO retrieval_events (id, project_id, query, retrieved_memory_ids, scores, final_context_tokens, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`).run(
253
+ event.id, event.projectId, event.query, JSON.stringify(event.retrievedMemoryIds), JSON.stringify(event.scores), event.finalContextTokens, event.createdAt.toISOString()
254
+ );
255
+ }
256
+
257
+ async listRetrievals(projectId: string, limit = 20): Promise<RetrievalEvent[]> {
258
+ const rows = this.db.prepare(`SELECT * FROM retrieval_events WHERE project_id = ? ORDER BY created_at DESC LIMIT ?`).all(projectId, limit) as any[];
259
+ return rows.map((r) => ({
260
+ id: r.id,
261
+ projectId: r.project_id,
262
+ query: r.query,
263
+ retrievedMemoryIds: JSON.parse(r.retrieved_memory_ids),
264
+ scores: JSON.parse(r.scores),
265
+ finalContextTokens: r.final_context_tokens,
266
+ createdAt: new Date(r.created_at),
267
+ }));
268
+ }
269
+
270
+ async close(): Promise<void> {
271
+ this.db.close();
272
+ }
273
+
274
+ private mapProject = (row: any): Project => ({
275
+ id: row.id,
276
+ name: row.name,
277
+ description: row.description ?? undefined,
278
+ createdAt: new Date(row.created_at),
279
+ updatedAt: new Date(row.updated_at),
280
+ });
281
+
282
+ private mapMemory = (row: any): Memory => ({
283
+ id: row.id,
284
+ projectId: row.project_id,
285
+ type: row.type,
286
+ content: row.content,
287
+ importance: row.importance,
288
+ confidence: row.confidence,
289
+ status: row.status,
290
+ createdAt: new Date(row.created_at),
291
+ updatedAt: new Date(row.updated_at),
292
+ lastAccessedAt: row.last_accessed_at ? new Date(row.last_accessed_at) : undefined,
293
+ accessCount: row.access_count,
294
+ sourceId: row.source_id ?? undefined,
295
+ metadata: JSON.parse(row.metadata),
296
+ archived: !!row.archived,
297
+ });
298
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "composite": false
5
+ }
6
+ }
package/test-mvp.mjs ADDED
@@ -0,0 +1,82 @@
1
+ import { Memory } from "./dist/packages/core/src/memory.js";
2
+ import { InMemoryStore } from "./dist/packages/storage/src/memory-store.js";
3
+ import { SqliteMemoryStore } from "./dist/packages/storage/src/sqlite-store.js";
4
+ import { LocalEmbeddingProvider } from "./dist/packages/embeddings/src/local.js";
5
+
6
+ async function runTests() {
7
+ console.log("=== MVP Tests ===");
8
+
9
+ // Test 1: InMemoryStore + Local embeddings + basic remember/search
10
+ const store = new InMemoryStore();
11
+ const embed = new LocalEmbeddingProvider(64);
12
+ const memory = await Memory.create({ project: "research-mesh", store, embeddingProvider: embed });
13
+
14
+ await memory.remember({ type: "decision", content: "We decided to use PostgreSQL with pgvector.", importance: 0.9 });
15
+ await memory.remember({ type: "fact", content: "API-4821 is about payment gateway timeout.", importance: 0.8 });
16
+ await memory.remember({ type: "knowledge", content: "Backend uses Node.js and Fastify.", importance: 0.6 });
17
+
18
+ // Test search
19
+ const result = await memory.search("What database did we decide to use?");
20
+ console.log("Test 1 - Semantic search:", result.length > 0 ? "PASS" : "FAIL", "->", result[0]?.memory.content);
21
+ if (!result[0]?.memory.content.includes("PostgreSQL")) throw new Error("Search failed to retrieve PostgreSQL memory");
22
+
23
+ // Test hybrid keyword: API-4821
24
+ const kwResult = await memory.search("What's the status of API-4821?");
25
+ console.log("Test 2 - Hybrid keyword:", kwResult[0]?.memory.content.includes("API-4821") ? "PASS" : "FAIL", "->", kwResult[0]?.memory.content);
26
+
27
+ // Test context budgeting
28
+ const ctx = await memory.context({ query: "What database did we decide to use?", maxTokens: 6000 });
29
+ console.log("Test 3 - Context:", ctx.text.includes("PostgreSQL") ? "PASS" : "FAIL", `tokens=${ctx.tokenCount}`);
30
+ console.log(" text:", ctx.text.slice(0, 200));
31
+
32
+ // Test project isolation
33
+ const memoryB = await Memory.create({ project: "personal", store, embeddingProvider: embed });
34
+ await memoryB.remember({ type: "fact", content: "We use MongoDB for personal project.", importance: 0.9 });
35
+ const isolated = await memory.search("What database do we use?");
36
+ console.log("Test 4 - Project isolation:", isolated[0]?.memory.content.includes("PostgreSQL") && !isolated.some(r => r.memory.content.includes("MongoDB")) ? "PASS" : "FAIL", "->", isolated[0]?.memory.content);
37
+ const crossLeak = isolated.some(r => r.memory.projectId !== memory.project.id);
38
+ console.log(" cross-project leak:", crossLeak ? "FAIL" : "PASS");
39
+
40
+ // Test deduplication
41
+ const beforeCount = (await store.listMemories(memory.project.id)).length;
42
+ await memory.remember({ type: "decision", content: "We decided to use PostgreSQL with pgvector.", importance: 0.9 });
43
+ const afterCount = (await store.listMemories(memory.project.id)).length;
44
+ console.log("Test 5 - Deduplication:", beforeCount === afterCount ? "PASS" : "FAIL", `before=${beforeCount} after=${afterCount}`);
45
+
46
+ // Test conflict resolution
47
+ await memory.remember({ type: "decision", content: "We've switched to PostgreSQL with pgvector instead of MongoDB for production storage.", importance: 0.95 });
48
+ // should supersede any conflicting? Check statuses
49
+ const all = await store.listMemories(memory.project.id);
50
+ console.log("Test 6 - Conflict handling: memories", all.map(m => `${m.content.slice(0,30)} [${m.status}]`).join(" | "));
51
+
52
+ // Test token budgeting with many memories
53
+ for (let i = 0; i < 20; i++) {
54
+ await memory.remember({ content: `Fact ${i}: Lorem ipsum dolor sit amet consectetur adipiscing elit.`.repeat(5), importance: 0.5 });
55
+ }
56
+ const bigCtx = await memory.context({ query: "Fact", maxTokens: 100 });
57
+ console.log("Test 7 - Token budgeting:", bigCtx.tokenCount <= 100 ? "PASS" : "FAIL", `tokens=${bigCtx.tokenCount} limit=100`);
58
+
59
+ // Test Sqlite store (node:sqlite)
60
+ console.log("\n=== SQLite Store Test ===");
61
+ const sqliteStore = new SqliteMemoryStore({ path: ":memory:" });
62
+ const sqliteMemory = await Memory.create({ project: "sqlite-test", store: sqliteStore, embeddingProvider: embed });
63
+ await sqliteMemory.remember({ content: "SQLite test memory with PostgreSQL pgvector", importance: 0.9 });
64
+ const sqliteSearch = await sqliteMemory.search("PostgreSQL");
65
+ console.log("SQLite search:", sqliteSearch[0]?.memory.content.includes("PostgreSQL") ? "PASS" : "FAIL");
66
+ await sqliteStore.close();
67
+
68
+ // Test goldfish & cross-project leakage eval
69
+ console.log("\n=== Evaluation Tests ===");
70
+ const evalStore = new InMemoryStore();
71
+ const projA = await Memory.create({ project: "project-a", store: evalStore, embeddingProvider: embed });
72
+ const projB = await Memory.create({ project: "project-b", store: evalStore, embeddingProvider: embed });
73
+ await projA.remember({ content: "We use PostgreSQL.", importance: 0.9 });
74
+ await projB.remember({ content: "We use MongoDB.", importance: 0.9 });
75
+ const queryA = await projA.search("What database do we use?");
76
+ console.log("Goldfish cross-project:", queryA[0]?.memory.content.includes("PostgreSQL") ? "PASS" : "FAIL", "->", queryA[0]?.memory.content);
77
+ console.log("Cross-project leakage (should be 0):", queryA.some(r => r.memory.content.includes("MongoDB")) ? "FAIL" : "PASS");
78
+
79
+ console.log("\n=== All MVP tests completed ===");
80
+ }
81
+
82
+ runTests().catch(e => { console.error(e); process.exit(1); });