mcp-memory-keeper 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +433 -0
- package/LICENSE +21 -0
- package/README.md +1051 -0
- package/bin/mcp-memory-keeper +52 -0
- package/dist/__tests__/helpers/database-test-helper.js +160 -0
- package/dist/__tests__/helpers/test-server.js +92 -0
- package/dist/__tests__/integration/advanced-features.test.js +614 -0
- package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
- package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
- package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
- package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
- package/dist/__tests__/integration/channels.test.js +376 -0
- package/dist/__tests__/integration/checkpoint.test.js +251 -0
- package/dist/__tests__/integration/concurrent-access.test.js +190 -0
- package/dist/__tests__/integration/context-operations.test.js +243 -0
- package/dist/__tests__/integration/contextDiff.test.js +852 -0
- package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
- package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
- package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
- package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
- package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
- package/dist/__tests__/integration/contextSearch.test.js +938 -0
- package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
- package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
- package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
- package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
- package/dist/__tests__/integration/database-initialization.test.js +134 -0
- package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
- package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
- package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
- package/dist/__tests__/integration/error-cases.test.js +407 -0
- package/dist/__tests__/integration/export-import.test.js +367 -0
- package/dist/__tests__/integration/feature-flags.test.js +542 -0
- package/dist/__tests__/integration/file-operations.test.js +264 -0
- package/dist/__tests__/integration/git-integration.test.js +237 -0
- package/dist/__tests__/integration/index-tools.test.js +496 -0
- package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
- package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
- package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
- package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
- package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
- package/dist/__tests__/integration/migrations.test.js +528 -0
- package/dist/__tests__/integration/multi-agent.test.js +546 -0
- package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
- package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
- package/dist/__tests__/integration/project-directory.test.js +283 -0
- package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
- package/dist/__tests__/integration/retention.test.js +513 -0
- package/dist/__tests__/integration/search.test.js +333 -0
- package/dist/__tests__/integration/semantic-search.test.js +266 -0
- package/dist/__tests__/integration/server-initialization.test.js +307 -0
- package/dist/__tests__/integration/session-management.test.js +219 -0
- package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
- package/dist/__tests__/integration/smart-compaction.test.js +230 -0
- package/dist/__tests__/integration/summarization.test.js +308 -0
- package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
- package/dist/__tests__/security/input-validation.test.js +115 -0
- package/dist/__tests__/utils/agents.test.js +473 -0
- package/dist/__tests__/utils/database.test.js +177 -0
- package/dist/__tests__/utils/git.test.js +122 -0
- package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
- package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
- package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/validation.test.js +200 -0
- package/dist/__tests__/utils/vector-store.test.js +231 -0
- package/dist/handlers/contextWatchHandlers.js +206 -0
- package/dist/index.js +4310 -0
- package/dist/index.phase1.backup.js +410 -0
- package/dist/index.phase2.backup.js +704 -0
- package/dist/migrations/003_add_channels.js +174 -0
- package/dist/migrations/004_add_context_watch.js +151 -0
- package/dist/migrations/005_add_context_watch.js +98 -0
- package/dist/migrations/simplify-sharing.js +117 -0
- package/dist/repositories/BaseRepository.js +30 -0
- package/dist/repositories/CheckpointRepository.js +140 -0
- package/dist/repositories/ContextRepository.js +1873 -0
- package/dist/repositories/FileRepository.js +104 -0
- package/dist/repositories/RepositoryManager.js +62 -0
- package/dist/repositories/SessionRepository.js +66 -0
- package/dist/repositories/WatcherRepository.js +252 -0
- package/dist/repositories/index.js +15 -0
- package/dist/server.js +384 -0
- package/dist/test-helpers/database-helper.js +128 -0
- package/dist/types/entities.js +3 -0
- package/dist/utils/agents.js +791 -0
- package/dist/utils/channels.js +150 -0
- package/dist/utils/database.js +731 -0
- package/dist/utils/feature-flags.js +476 -0
- package/dist/utils/git.js +145 -0
- package/dist/utils/knowledge-graph.js +264 -0
- package/dist/utils/migrationHealthCheck.js +373 -0
- package/dist/utils/migrations.js +452 -0
- package/dist/utils/retention.js +460 -0
- package/dist/utils/timestamps.js +112 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/package.json +84 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const database_1 = require("../../utils/database");
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const uuid_1 = require("uuid");
|
|
41
|
+
describe('Search Integration Tests', () => {
|
|
42
|
+
let dbManager;
|
|
43
|
+
let tempDbPath;
|
|
44
|
+
let db;
|
|
45
|
+
let testSessionId;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
tempDbPath = path.join(os.tmpdir(), `test-search-${Date.now()}.db`);
|
|
48
|
+
dbManager = new database_1.DatabaseManager({
|
|
49
|
+
filename: tempDbPath,
|
|
50
|
+
maxSize: 10 * 1024 * 1024,
|
|
51
|
+
walMode: true,
|
|
52
|
+
});
|
|
53
|
+
db = dbManager.getDatabase();
|
|
54
|
+
// Create test session
|
|
55
|
+
testSessionId = (0, uuid_1.v4)();
|
|
56
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(testSessionId, 'Search Test Session');
|
|
57
|
+
});
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
dbManager.close();
|
|
60
|
+
try {
|
|
61
|
+
fs.unlinkSync(tempDbPath);
|
|
62
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
63
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
64
|
+
}
|
|
65
|
+
catch (_e) {
|
|
66
|
+
// Ignore
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
describe('context_search', () => {
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
// Create test session if not exists
|
|
72
|
+
const session = db.prepare('SELECT id FROM sessions WHERE id = ?').get(testSessionId);
|
|
73
|
+
if (!session) {
|
|
74
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(testSessionId, 'Search Test Session');
|
|
75
|
+
}
|
|
76
|
+
// Add diverse test data
|
|
77
|
+
const items = [
|
|
78
|
+
{
|
|
79
|
+
key: 'auth_bug',
|
|
80
|
+
value: 'Fixed authentication bug in login flow',
|
|
81
|
+
category: 'task',
|
|
82
|
+
priority: 'high',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: 'auth_decision',
|
|
86
|
+
value: 'Decided to use JWT for authentication',
|
|
87
|
+
category: 'decision',
|
|
88
|
+
priority: 'high',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
key: 'api_design',
|
|
92
|
+
value: 'Design REST API endpoints for user management',
|
|
93
|
+
category: 'task',
|
|
94
|
+
priority: 'normal',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: 'database_choice',
|
|
98
|
+
value: 'Selected PostgreSQL for user data storage',
|
|
99
|
+
category: 'decision',
|
|
100
|
+
priority: 'normal',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
key: 'security_note',
|
|
104
|
+
value: 'Remember to implement rate limiting on auth endpoints',
|
|
105
|
+
category: 'note',
|
|
106
|
+
priority: 'high',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: 'performance',
|
|
110
|
+
value: 'Optimize database queries for better performance',
|
|
111
|
+
category: 'task',
|
|
112
|
+
priority: 'low',
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
items.forEach(item => {
|
|
116
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, item.key, item.value, item.category, item.priority);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
it('should search by value content', () => {
|
|
120
|
+
// Search for 'authentication'
|
|
121
|
+
const results = db
|
|
122
|
+
.prepare(`SELECT * FROM context_items
|
|
123
|
+
WHERE session_id = ?
|
|
124
|
+
AND value LIKE ?
|
|
125
|
+
ORDER BY priority DESC, created_at DESC`)
|
|
126
|
+
.all(testSessionId, '%authentication%');
|
|
127
|
+
expect(results).toHaveLength(2);
|
|
128
|
+
expect(results[0].key).toBe('auth_bug'); // High priority first
|
|
129
|
+
expect(results[1].key).toBe('auth_decision');
|
|
130
|
+
});
|
|
131
|
+
it('should search by key pattern', () => {
|
|
132
|
+
// Search for keys starting with 'auth'
|
|
133
|
+
const results = db
|
|
134
|
+
.prepare(`SELECT * FROM context_items
|
|
135
|
+
WHERE session_id = ?
|
|
136
|
+
AND key LIKE ?
|
|
137
|
+
ORDER BY created_at DESC`)
|
|
138
|
+
.all(testSessionId, 'auth%');
|
|
139
|
+
expect(results).toHaveLength(2);
|
|
140
|
+
expect(results.map((r) => r.key)).toContain('auth_bug');
|
|
141
|
+
expect(results.map((r) => r.key)).toContain('auth_decision');
|
|
142
|
+
});
|
|
143
|
+
it('should filter by category', () => {
|
|
144
|
+
// Search for tasks only
|
|
145
|
+
const results = db
|
|
146
|
+
.prepare(`SELECT * FROM context_items
|
|
147
|
+
WHERE session_id = ?
|
|
148
|
+
AND category = ?
|
|
149
|
+
ORDER BY
|
|
150
|
+
CASE priority
|
|
151
|
+
WHEN 'critical' THEN 1
|
|
152
|
+
WHEN 'high' THEN 2
|
|
153
|
+
WHEN 'normal' THEN 3
|
|
154
|
+
WHEN 'low' THEN 4
|
|
155
|
+
END`)
|
|
156
|
+
.all(testSessionId, 'task');
|
|
157
|
+
expect(results).toHaveLength(3);
|
|
158
|
+
expect(results[0].key).toBe('auth_bug'); // High priority
|
|
159
|
+
expect(results[2].key).toBe('performance'); // Low priority
|
|
160
|
+
});
|
|
161
|
+
it('should combine search criteria', () => {
|
|
162
|
+
// Search for high priority items containing 'auth'
|
|
163
|
+
const results = db
|
|
164
|
+
.prepare(`SELECT * FROM context_items
|
|
165
|
+
WHERE session_id = ?
|
|
166
|
+
AND priority = ?
|
|
167
|
+
AND (value LIKE ? OR key LIKE ?)
|
|
168
|
+
ORDER BY created_at DESC`)
|
|
169
|
+
.all(testSessionId, 'high', '%auth%', '%auth%');
|
|
170
|
+
expect(results).toHaveLength(3); // auth_bug, auth_decision, security_note
|
|
171
|
+
});
|
|
172
|
+
it('should search across multiple sessions', () => {
|
|
173
|
+
// Create another session
|
|
174
|
+
const sessionId2 = (0, uuid_1.v4)();
|
|
175
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId2, 'Second Session');
|
|
176
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId2, 'auth_other', 'Another auth-related item');
|
|
177
|
+
// Search across all sessions
|
|
178
|
+
const results = db
|
|
179
|
+
.prepare(`SELECT ci.*, s.name as session_name
|
|
180
|
+
FROM context_items ci
|
|
181
|
+
JOIN sessions s ON ci.session_id = s.id
|
|
182
|
+
WHERE ci.value LIKE ?
|
|
183
|
+
ORDER BY ci.created_at DESC`)
|
|
184
|
+
.all('%auth%');
|
|
185
|
+
expect(results.length).toBeGreaterThanOrEqual(3);
|
|
186
|
+
expect(results.some((r) => r.session_name === 'Search Test Session')).toBe(true);
|
|
187
|
+
expect(results.some((r) => r.session_name === 'Second Session')).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
it('should handle case-insensitive search', () => {
|
|
190
|
+
// SQLite LIKE is case-insensitive by default
|
|
191
|
+
const results = db
|
|
192
|
+
.prepare(`SELECT * FROM context_items
|
|
193
|
+
WHERE session_id = ?
|
|
194
|
+
AND value LIKE ?`)
|
|
195
|
+
.all(testSessionId, '%AUTH%');
|
|
196
|
+
expect(results).toHaveLength(3); // Should find 'authentication' and 'auth' items
|
|
197
|
+
// auth_bug: "Fixed authentication bug in login flow"
|
|
198
|
+
// auth_decision: "Decided to use JWT for authentication"
|
|
199
|
+
// security_note: "Remember to implement rate limiting on auth endpoints"
|
|
200
|
+
});
|
|
201
|
+
it('should search with date range', () => {
|
|
202
|
+
// Add an old item
|
|
203
|
+
const oldDate = new Date();
|
|
204
|
+
oldDate.setDate(oldDate.getDate() - 7);
|
|
205
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, created_at) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'old_item', 'Old authentication method', oldDate.toISOString());
|
|
206
|
+
// Search for items from last 3 days
|
|
207
|
+
const threeDaysAgo = new Date();
|
|
208
|
+
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
|
|
209
|
+
const results = db
|
|
210
|
+
.prepare(`SELECT * FROM context_items
|
|
211
|
+
WHERE session_id = ?
|
|
212
|
+
AND datetime(created_at) > datetime(?)
|
|
213
|
+
AND value LIKE ?`)
|
|
214
|
+
.all(testSessionId, threeDaysAgo.toISOString(), '%authentication%');
|
|
215
|
+
expect(results).toHaveLength(2); // Should not include old_item
|
|
216
|
+
expect(results.every((r) => r.key !== 'old_item')).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
it('should rank results by relevance', () => {
|
|
219
|
+
// Add items with varying relevance
|
|
220
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, priority) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'exact', 'authentication', 'normal');
|
|
221
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, priority) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'partial', 'implemented authentication system', 'normal');
|
|
222
|
+
// Search with exact match having higher relevance
|
|
223
|
+
const results = db
|
|
224
|
+
.prepare(`SELECT *,
|
|
225
|
+
CASE
|
|
226
|
+
WHEN value = ? THEN 3
|
|
227
|
+
WHEN value LIKE ? AND value LIKE ? THEN 2
|
|
228
|
+
WHEN value LIKE ? THEN 1
|
|
229
|
+
END as relevance
|
|
230
|
+
FROM context_items
|
|
231
|
+
WHERE session_id = ?
|
|
232
|
+
AND value LIKE ?
|
|
233
|
+
ORDER BY relevance DESC, priority DESC`)
|
|
234
|
+
.all('authentication', '%authentication%', '%', '%authentication%', testSessionId, '%authentication%');
|
|
235
|
+
expect(results[0].key).toBe('exact'); // Exact match first
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
describe('File content search', () => {
|
|
239
|
+
beforeEach(() => {
|
|
240
|
+
// Ensure session exists
|
|
241
|
+
const session = db.prepare('SELECT id FROM sessions WHERE id = ?').get(testSessionId);
|
|
242
|
+
if (!session) {
|
|
243
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(testSessionId, 'Search Test Session');
|
|
244
|
+
}
|
|
245
|
+
// Add file cache entries
|
|
246
|
+
const files = [
|
|
247
|
+
{
|
|
248
|
+
path: '/src/auth.ts',
|
|
249
|
+
content: 'export function authenticate(user: User) { /* auth logic */ }',
|
|
250
|
+
},
|
|
251
|
+
{ path: '/src/api.ts', content: 'router.post("/login", authenticate);' },
|
|
252
|
+
{
|
|
253
|
+
path: '/tests/auth.test.ts',
|
|
254
|
+
content: 'describe("authentication", () => { /* tests */ })',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
path: '/docs/README.md',
|
|
258
|
+
content: '# Authentication\nThis module handles user authentication.',
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
files.forEach(file => {
|
|
262
|
+
const hash = require('crypto').createHash('sha256').update(file.content).digest('hex');
|
|
263
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, file.path, file.content, hash);
|
|
264
|
+
});
|
|
265
|
+
// Also add context items for the combine test
|
|
266
|
+
const contextItems = [
|
|
267
|
+
{ key: 'auth_feature', value: 'Implement authentication system', category: 'task' },
|
|
268
|
+
{ key: 'auth_config', value: 'Configure auth middleware', category: 'task' },
|
|
269
|
+
];
|
|
270
|
+
contextItems.forEach(item => {
|
|
271
|
+
db.prepare('INSERT OR IGNORE INTO context_items (id, session_id, key, value, category) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, item.key, item.value, item.category);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
it('should search file contents', () => {
|
|
275
|
+
const results = db
|
|
276
|
+
.prepare(`SELECT * FROM file_cache
|
|
277
|
+
WHERE session_id = ?
|
|
278
|
+
AND content LIKE ?
|
|
279
|
+
ORDER BY file_path`)
|
|
280
|
+
.all(testSessionId, '%authenticate%');
|
|
281
|
+
expect(results).toHaveLength(2); // auth.ts, api.ts only
|
|
282
|
+
// auth.ts: "export function authenticate(user: User) { /* auth logic */ }"
|
|
283
|
+
// api.ts: "router.post("/login", authenticate);"
|
|
284
|
+
// tests/auth.test.ts: has "authentication" not "authenticate"
|
|
285
|
+
// README.md: has "authentication" not "authenticate"
|
|
286
|
+
expect(results.map((r) => r.file_path)).not.toContain('/tests/auth.test.ts');
|
|
287
|
+
});
|
|
288
|
+
it('should search by file path pattern', () => {
|
|
289
|
+
const results = db
|
|
290
|
+
.prepare(`SELECT * FROM file_cache
|
|
291
|
+
WHERE session_id = ?
|
|
292
|
+
AND file_path LIKE ?`)
|
|
293
|
+
.all(testSessionId, '/src/%');
|
|
294
|
+
expect(results).toHaveLength(2);
|
|
295
|
+
expect(results.every((r) => r.file_path.startsWith('/src/'))).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
it('should combine file and context search', () => {
|
|
298
|
+
// Search for 'auth' in both context and files (broader search)
|
|
299
|
+
const contextResults = db
|
|
300
|
+
.prepare(`SELECT 'context' as source, key as name, value as content
|
|
301
|
+
FROM context_items
|
|
302
|
+
WHERE session_id = ? AND (value LIKE ? OR key LIKE ?)`)
|
|
303
|
+
.all(testSessionId, '%auth%', '%auth%');
|
|
304
|
+
const fileResults = db
|
|
305
|
+
.prepare(`SELECT 'file' as source, file_path as name, content
|
|
306
|
+
FROM file_cache
|
|
307
|
+
WHERE session_id = ? AND (content LIKE ? OR file_path LIKE ?)`)
|
|
308
|
+
.all(testSessionId, '%auth%', '%auth%');
|
|
309
|
+
const allResults = [...contextResults, ...fileResults];
|
|
310
|
+
expect(allResults.length).toBeGreaterThanOrEqual(4);
|
|
311
|
+
expect(allResults.some((r) => r.source === 'context')).toBe(true);
|
|
312
|
+
expect(allResults.some((r) => r.source === 'file')).toBe(true);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
describe('Search performance', () => {
|
|
316
|
+
it('should use indexes efficiently', () => {
|
|
317
|
+
// Add many items to test performance
|
|
318
|
+
for (let i = 0; i < 100; i++) {
|
|
319
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, `key${i}`, `value ${i} with some text`, i % 2 === 0 ? 'task' : 'note');
|
|
320
|
+
}
|
|
321
|
+
const start = Date.now();
|
|
322
|
+
const results = db
|
|
323
|
+
.prepare(`SELECT * FROM context_items
|
|
324
|
+
WHERE session_id = ?
|
|
325
|
+
AND category = ?
|
|
326
|
+
LIMIT 10`)
|
|
327
|
+
.all(testSessionId, 'task');
|
|
328
|
+
const duration = Date.now() - start;
|
|
329
|
+
expect(results).toHaveLength(10);
|
|
330
|
+
expect(duration).toBeLessThan(100); // Should be fast with indexes
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const database_1 = require("../../utils/database");
|
|
37
|
+
const vector_store_1 = require("../../utils/vector-store");
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const uuid_1 = require("uuid");
|
|
42
|
+
describe('Semantic Search Integration Tests', () => {
|
|
43
|
+
let dbManager;
|
|
44
|
+
let vectorStore;
|
|
45
|
+
let tempDbPath;
|
|
46
|
+
let db;
|
|
47
|
+
let testSessionId;
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
tempDbPath = path.join(os.tmpdir(), `test-semantic-${Date.now()}.db`);
|
|
50
|
+
dbManager = new database_1.DatabaseManager({
|
|
51
|
+
filename: tempDbPath,
|
|
52
|
+
maxSize: 10 * 1024 * 1024,
|
|
53
|
+
walMode: true,
|
|
54
|
+
});
|
|
55
|
+
db = dbManager.getDatabase();
|
|
56
|
+
vectorStore = new vector_store_1.VectorStore(db);
|
|
57
|
+
// Create test session
|
|
58
|
+
testSessionId = (0, uuid_1.v4)();
|
|
59
|
+
db.prepare('INSERT INTO sessions (id, name, description) VALUES (?, ?, ?)').run(testSessionId, 'Semantic Search Test', 'Testing semantic search integration');
|
|
60
|
+
});
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
dbManager.close();
|
|
63
|
+
try {
|
|
64
|
+
fs.unlinkSync(tempDbPath);
|
|
65
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
66
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
67
|
+
}
|
|
68
|
+
catch (_e) {
|
|
69
|
+
// Ignore
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
describe('context_semantic_search', () => {
|
|
73
|
+
beforeEach(async () => {
|
|
74
|
+
// Create diverse context items
|
|
75
|
+
const items = [
|
|
76
|
+
{
|
|
77
|
+
key: 'auth_implementation',
|
|
78
|
+
value: 'Implemented JWT authentication with refresh tokens. The tokens expire after 24 hours.',
|
|
79
|
+
category: 'progress',
|
|
80
|
+
priority: 'high',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: 'database_decision',
|
|
84
|
+
value: 'Decided to use PostgreSQL for the main database due to its JSON support and reliability.',
|
|
85
|
+
category: 'decision',
|
|
86
|
+
priority: 'high',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: 'api_design',
|
|
90
|
+
value: 'Designed RESTful API endpoints following OpenAPI 3.0 specification.',
|
|
91
|
+
category: 'task',
|
|
92
|
+
priority: 'normal',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
key: 'security_note',
|
|
96
|
+
value: 'Remember to implement rate limiting on authentication endpoints to prevent brute force attacks.',
|
|
97
|
+
category: 'note',
|
|
98
|
+
priority: 'high',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
key: 'testing_strategy',
|
|
102
|
+
value: 'Unit tests for individual functions, integration tests for API endpoints, and e2e tests for user flows.',
|
|
103
|
+
category: 'decision',
|
|
104
|
+
priority: 'normal',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: 'performance_optimization',
|
|
108
|
+
value: 'Added database indexing on user email field to speed up authentication queries.',
|
|
109
|
+
category: 'progress',
|
|
110
|
+
priority: 'normal',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
key: 'bug_fix',
|
|
114
|
+
value: 'Fixed memory leak in WebSocket connection handler by properly cleaning up event listeners.',
|
|
115
|
+
category: 'progress',
|
|
116
|
+
priority: 'high',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
key: 'architecture_pattern',
|
|
120
|
+
value: 'Using Repository pattern for data access layer to abstract database operations.',
|
|
121
|
+
category: 'decision',
|
|
122
|
+
priority: 'normal',
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
for (const item of items) {
|
|
126
|
+
const itemId = (0, uuid_1.v4)();
|
|
127
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run(itemId, testSessionId, item.key, item.value, item.category, item.priority);
|
|
128
|
+
// Create embedding
|
|
129
|
+
await vectorStore.storeDocument(itemId, `${item.key}: ${item.value}`, {
|
|
130
|
+
key: item.key,
|
|
131
|
+
category: item.category,
|
|
132
|
+
priority: item.priority,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
it('should find relevant results for authentication queries', async () => {
|
|
137
|
+
const results = await vectorStore.searchInSession(testSessionId, 'authentication security tokens', 5, 0.1);
|
|
138
|
+
expect(results.length).toBeGreaterThan(0);
|
|
139
|
+
// Should find auth implementation and security note
|
|
140
|
+
const authResults = results.filter(r => r.content.includes('auth_implementation') || r.content.includes('security_note'));
|
|
141
|
+
expect(authResults.length).toBeGreaterThanOrEqual(2);
|
|
142
|
+
});
|
|
143
|
+
it('should find relevant results for database queries', async () => {
|
|
144
|
+
const results = await vectorStore.searchInSession(testSessionId, 'database performance optimization', 5, 0.1);
|
|
145
|
+
expect(results.length).toBeGreaterThan(0);
|
|
146
|
+
// Should find database decision and performance optimization
|
|
147
|
+
const dbResults = results.filter(r => r.content.includes('database_decision') || r.content.includes('performance_optimization'));
|
|
148
|
+
expect(dbResults.length).toBeGreaterThanOrEqual(1);
|
|
149
|
+
});
|
|
150
|
+
it('should rank results by similarity', async () => {
|
|
151
|
+
const results = await vectorStore.searchInSession(testSessionId, 'JWT token authentication', 10, 0.0);
|
|
152
|
+
expect(results.length).toBeGreaterThan(0);
|
|
153
|
+
// First result should be the auth implementation
|
|
154
|
+
expect(results[0].content).toContain('auth_implementation');
|
|
155
|
+
expect(results[0].similarity).toBeGreaterThan(0.5);
|
|
156
|
+
// Results should be ordered by similarity
|
|
157
|
+
for (let i = 1; i < results.length; i++) {
|
|
158
|
+
expect(results[i].similarity).toBeLessThanOrEqual(results[i - 1].similarity);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
it('should handle queries with no good matches', async () => {
|
|
162
|
+
const results = await vectorStore.searchInSession(testSessionId, 'quantum computing blockchain AI', 10, 0.5 // High threshold
|
|
163
|
+
);
|
|
164
|
+
expect(results.length).toBe(0);
|
|
165
|
+
});
|
|
166
|
+
it('should respect metadata in results', async () => {
|
|
167
|
+
const results = await vectorStore.searchInSession(testSessionId, 'important security decisions', 10, 0.1);
|
|
168
|
+
const resultsWithMetadata = results.filter(r => r.metadata);
|
|
169
|
+
expect(resultsWithMetadata.length).toBeGreaterThan(0);
|
|
170
|
+
// Check metadata structure
|
|
171
|
+
const firstWithMeta = resultsWithMetadata[0];
|
|
172
|
+
expect(firstWithMeta.metadata).toHaveProperty('key');
|
|
173
|
+
expect(firstWithMeta.metadata).toHaveProperty('category');
|
|
174
|
+
expect(firstWithMeta.metadata).toHaveProperty('priority');
|
|
175
|
+
});
|
|
176
|
+
it('should handle natural language queries', async () => {
|
|
177
|
+
const naturalQueries = [
|
|
178
|
+
'what did we decide about the database?',
|
|
179
|
+
'how are we handling user authentication?',
|
|
180
|
+
'what testing approach are we using?',
|
|
181
|
+
'any security concerns to remember?',
|
|
182
|
+
];
|
|
183
|
+
for (const query of naturalQueries) {
|
|
184
|
+
const results = await vectorStore.searchInSession(testSessionId, query, 3, 0.1);
|
|
185
|
+
expect(results.length).toBeGreaterThan(0);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
it('should find conceptually related items', async () => {
|
|
189
|
+
const results = await vectorStore.searchInSession(testSessionId, 'code quality and maintainability', 5, 0.1);
|
|
190
|
+
// Should find testing strategy and architecture pattern
|
|
191
|
+
const qualityResults = results.filter(r => r.content.includes('testing_strategy') || r.content.includes('architecture_pattern'));
|
|
192
|
+
expect(qualityResults.length).toBeGreaterThan(0);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
describe('Performance and edge cases', () => {
|
|
196
|
+
it('should handle large contexts efficiently', async () => {
|
|
197
|
+
// Create many context items
|
|
198
|
+
const itemCount = 100;
|
|
199
|
+
const promises = [];
|
|
200
|
+
for (let i = 0; i < itemCount; i++) {
|
|
201
|
+
const itemId = (0, uuid_1.v4)();
|
|
202
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run(itemId, testSessionId, `item_${i}`, `This is test content number ${i} with some variation`);
|
|
203
|
+
promises.push(vectorStore.storeDocument(itemId, `item_${i}: This is test content number ${i} with some variation`));
|
|
204
|
+
}
|
|
205
|
+
await Promise.all(promises);
|
|
206
|
+
const startTime = Date.now();
|
|
207
|
+
const results = await vectorStore.searchInSession(testSessionId, 'test content variation', 10, 0.1);
|
|
208
|
+
const endTime = Date.now();
|
|
209
|
+
expect(results.length).toBeGreaterThan(0);
|
|
210
|
+
expect(endTime - startTime).toBeLessThan(1000); // Should complete within 1 second
|
|
211
|
+
});
|
|
212
|
+
it('should handle special characters in queries', async () => {
|
|
213
|
+
const itemId = (0, uuid_1.v4)();
|
|
214
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run(itemId, testSessionId, 'special_chars', 'Using C++ and C# with ASP.NET @decorators');
|
|
215
|
+
await vectorStore.storeDocument(itemId, 'special_chars: Using C++ and C# with ASP.NET @decorators');
|
|
216
|
+
const results = await vectorStore.searchInSession(testSessionId, 'C++ C# ASP.NET', 3, 0.1);
|
|
217
|
+
expect(results.length).toBe(1);
|
|
218
|
+
expect(results[0].content).toContain('special_chars');
|
|
219
|
+
});
|
|
220
|
+
it('should handle very long queries', async () => {
|
|
221
|
+
const longQuery = 'authentication ' + 'security '.repeat(50) + 'tokens';
|
|
222
|
+
const results = await vectorStore.searchInSession(testSessionId, longQuery, 5, 0.05);
|
|
223
|
+
// Long repetitive queries might have lower similarity scores
|
|
224
|
+
// Just verify it doesn't crash and returns an array
|
|
225
|
+
expect(Array.isArray(results)).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
it('should handle empty session gracefully', async () => {
|
|
228
|
+
const emptySessionId = (0, uuid_1.v4)();
|
|
229
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(emptySessionId, 'Empty Session');
|
|
230
|
+
const results = await vectorStore.searchInSession(emptySessionId, 'any query', 10, 0.1);
|
|
231
|
+
expect(results).toEqual([]);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
describe('Multi-session search', () => {
|
|
235
|
+
let otherSessionId;
|
|
236
|
+
beforeEach(async () => {
|
|
237
|
+
// Create another session with different content
|
|
238
|
+
otherSessionId = (0, uuid_1.v4)();
|
|
239
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(otherSessionId, 'Other Project');
|
|
240
|
+
const items = [
|
|
241
|
+
{
|
|
242
|
+
key: 'frontend_framework',
|
|
243
|
+
value: 'Using React with TypeScript for the frontend application',
|
|
244
|
+
},
|
|
245
|
+
{ key: 'state_management', value: 'Redux Toolkit for global state management' },
|
|
246
|
+
];
|
|
247
|
+
for (const item of items) {
|
|
248
|
+
const itemId = (0, uuid_1.v4)();
|
|
249
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run(itemId, otherSessionId, item.key, item.value);
|
|
250
|
+
await vectorStore.storeDocument(itemId, `${item.key}: ${item.value}`);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
it('should search across all sessions when not specified', async () => {
|
|
254
|
+
const results = await vectorStore.search('frontend React TypeScript', 10, 0.1);
|
|
255
|
+
// Should find results from other session
|
|
256
|
+
const frontendResults = results.filter(r => r.content.includes('frontend_framework'));
|
|
257
|
+
expect(frontendResults.length).toBe(1);
|
|
258
|
+
});
|
|
259
|
+
it('should isolate search to specific session when specified', async () => {
|
|
260
|
+
const results = await vectorStore.searchInSession(testSessionId, 'React Redux frontend', 10, 0.1);
|
|
261
|
+
// Should not find results from other session
|
|
262
|
+
const frontendResults = results.filter(r => r.content.includes('frontend_framework') || r.content.includes('state_management'));
|
|
263
|
+
expect(frontendResults.length).toBe(0);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
});
|