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.
Files changed (98) hide show
  1. package/CHANGELOG.md +433 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1051 -0
  4. package/bin/mcp-memory-keeper +52 -0
  5. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  6. package/dist/__tests__/helpers/test-server.js +92 -0
  7. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  8. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  9. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  10. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  11. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  12. package/dist/__tests__/integration/channels.test.js +376 -0
  13. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  14. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  15. package/dist/__tests__/integration/context-operations.test.js +243 -0
  16. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  17. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  18. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  19. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  20. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  21. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  22. package/dist/__tests__/integration/contextSearch.test.js +938 -0
  23. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  24. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  25. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  26. package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
  27. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  28. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  29. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  30. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  31. package/dist/__tests__/integration/error-cases.test.js +407 -0
  32. package/dist/__tests__/integration/export-import.test.js +367 -0
  33. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  34. package/dist/__tests__/integration/file-operations.test.js +264 -0
  35. package/dist/__tests__/integration/git-integration.test.js +237 -0
  36. package/dist/__tests__/integration/index-tools.test.js +496 -0
  37. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  38. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  39. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  40. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  41. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  42. package/dist/__tests__/integration/migrations.test.js +528 -0
  43. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  44. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  45. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  46. package/dist/__tests__/integration/project-directory.test.js +283 -0
  47. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  48. package/dist/__tests__/integration/retention.test.js +513 -0
  49. package/dist/__tests__/integration/search.test.js +333 -0
  50. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  51. package/dist/__tests__/integration/server-initialization.test.js +307 -0
  52. package/dist/__tests__/integration/session-management.test.js +219 -0
  53. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  54. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  55. package/dist/__tests__/integration/summarization.test.js +308 -0
  56. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  57. package/dist/__tests__/security/input-validation.test.js +115 -0
  58. package/dist/__tests__/utils/agents.test.js +473 -0
  59. package/dist/__tests__/utils/database.test.js +177 -0
  60. package/dist/__tests__/utils/git.test.js +122 -0
  61. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  62. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  63. package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
  64. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  65. package/dist/__tests__/utils/validation.test.js +200 -0
  66. package/dist/__tests__/utils/vector-store.test.js +231 -0
  67. package/dist/handlers/contextWatchHandlers.js +206 -0
  68. package/dist/index.js +4310 -0
  69. package/dist/index.phase1.backup.js +410 -0
  70. package/dist/index.phase2.backup.js +704 -0
  71. package/dist/migrations/003_add_channels.js +174 -0
  72. package/dist/migrations/004_add_context_watch.js +151 -0
  73. package/dist/migrations/005_add_context_watch.js +98 -0
  74. package/dist/migrations/simplify-sharing.js +117 -0
  75. package/dist/repositories/BaseRepository.js +30 -0
  76. package/dist/repositories/CheckpointRepository.js +140 -0
  77. package/dist/repositories/ContextRepository.js +1873 -0
  78. package/dist/repositories/FileRepository.js +104 -0
  79. package/dist/repositories/RepositoryManager.js +62 -0
  80. package/dist/repositories/SessionRepository.js +66 -0
  81. package/dist/repositories/WatcherRepository.js +252 -0
  82. package/dist/repositories/index.js +15 -0
  83. package/dist/server.js +384 -0
  84. package/dist/test-helpers/database-helper.js +128 -0
  85. package/dist/types/entities.js +3 -0
  86. package/dist/utils/agents.js +791 -0
  87. package/dist/utils/channels.js +150 -0
  88. package/dist/utils/database.js +731 -0
  89. package/dist/utils/feature-flags.js +476 -0
  90. package/dist/utils/git.js +145 -0
  91. package/dist/utils/knowledge-graph.js +264 -0
  92. package/dist/utils/migrationHealthCheck.js +373 -0
  93. package/dist/utils/migrations.js +452 -0
  94. package/dist/utils/retention.js +460 -0
  95. package/dist/utils/timestamps.js +112 -0
  96. package/dist/utils/validation.js +296 -0
  97. package/dist/utils/vector-store.js +247 -0
  98. package/package.json +84 -0
@@ -0,0 +1,307 @@
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 globals_1 = require("@jest/globals");
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const os = __importStar(require("os"));
41
+ (0, globals_1.describe)('Server Initialization Tests', () => {
42
+ let tempDir;
43
+ let serverProcess = null;
44
+ (0, globals_1.beforeEach)(() => {
45
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-test-'));
46
+ });
47
+ (0, globals_1.afterEach)(async () => {
48
+ if (serverProcess && !serverProcess.killed) {
49
+ // First try graceful shutdown
50
+ serverProcess.kill('SIGTERM');
51
+ // Wait for process to actually exit
52
+ await new Promise(resolve => {
53
+ const timeout = setTimeout(() => {
54
+ // Force kill if still running
55
+ if (serverProcess && !serverProcess.killed) {
56
+ serverProcess.kill('SIGKILL');
57
+ }
58
+ resolve();
59
+ }, 5000);
60
+ serverProcess?.on('exit', () => {
61
+ clearTimeout(timeout);
62
+ resolve();
63
+ });
64
+ serverProcess?.on('error', () => {
65
+ clearTimeout(timeout);
66
+ resolve();
67
+ });
68
+ });
69
+ // Remove event listeners to prevent leaks
70
+ serverProcess?.removeAllListeners();
71
+ // Track for global cleanup
72
+ if (global.testProcesses) {
73
+ const index = global.testProcesses.indexOf(serverProcess);
74
+ if (index > -1) {
75
+ global.testProcesses.splice(index, 1);
76
+ }
77
+ }
78
+ }
79
+ serverProcess = null;
80
+ // Clean up temp directory
81
+ try {
82
+ fs.rmSync(tempDir, { recursive: true, force: true });
83
+ }
84
+ catch (error) {
85
+ console.warn('Error cleaning up temp directory:', error);
86
+ }
87
+ });
88
+ (0, globals_1.it)('should start server and respond to initialize request', done => {
89
+ const dbPath = path.join(tempDir, 'test.db');
90
+ // Start the server
91
+ serverProcess = (0, child_process_1.spawn)('node', [path.join(__dirname, '../../../dist/index.js')], {
92
+ env: {
93
+ ...process.env,
94
+ MCP_DB_PATH: dbPath,
95
+ },
96
+ stdio: ['pipe', 'pipe', 'pipe'],
97
+ });
98
+ // Track for global cleanup
99
+ if (!global.testProcesses) {
100
+ global.testProcesses = [];
101
+ }
102
+ global.testProcesses.push(serverProcess);
103
+ // Track for global cleanup
104
+ if (!global.testProcesses) {
105
+ global.testProcesses = [];
106
+ }
107
+ global.testProcesses.push(serverProcess);
108
+ let output = '';
109
+ let initialized = false;
110
+ serverProcess.stdout?.on('data', _data => {
111
+ output += _data.toString();
112
+ // Look for initialization response
113
+ const lines = output.split('\n').filter(line => line.trim());
114
+ for (const line of lines) {
115
+ try {
116
+ const msg = JSON.parse(line);
117
+ if (msg.id === 1 && msg.result) {
118
+ initialized = true;
119
+ (0, globals_1.expect)(msg.result).toHaveProperty('protocolVersion');
120
+ (0, globals_1.expect)(msg.result).toHaveProperty('capabilities');
121
+ done();
122
+ }
123
+ }
124
+ catch (_e) {
125
+ // Not JSON, continue
126
+ }
127
+ }
128
+ });
129
+ serverProcess.stderr?.on('data', _data => {
130
+ console.error('Server error:', _data.toString());
131
+ });
132
+ // Send initialization request
133
+ setTimeout(() => {
134
+ serverProcess?.stdin?.write(JSON.stringify({
135
+ jsonrpc: '2.0',
136
+ method: 'initialize',
137
+ params: {
138
+ protocolVersion: '2024-11-05',
139
+ capabilities: {},
140
+ clientInfo: {
141
+ name: 'test-client',
142
+ version: '1.0.0',
143
+ },
144
+ },
145
+ id: 1,
146
+ }) + '\n');
147
+ }, 500);
148
+ // Timeout after 5 seconds
149
+ setTimeout(() => {
150
+ if (!initialized) {
151
+ done(new Error('Server did not initialize within timeout'));
152
+ }
153
+ }, 5000);
154
+ });
155
+ (0, globals_1.it)('should create database file on startup', done => {
156
+ const dbPath = path.join(tempDir, 'context.db');
157
+ // Change to temp directory
158
+ const originalCwd = process.cwd();
159
+ try {
160
+ process.chdir(tempDir);
161
+ // Start the server
162
+ serverProcess = (0, child_process_1.spawn)('node', [path.join(__dirname, '../../../dist/index.js')], {
163
+ stdio: ['pipe', 'pipe', 'pipe'],
164
+ });
165
+ // Track for global cleanup
166
+ if (!global.testProcesses) {
167
+ global.testProcesses = [];
168
+ }
169
+ global.testProcesses.push(serverProcess);
170
+ // Wait for server to start
171
+ setTimeout(() => {
172
+ try {
173
+ // Check if database file was created
174
+ (0, globals_1.expect)(fs.existsSync(dbPath)).toBe(true);
175
+ // Check if it's a valid SQLite database
176
+ const header = fs.readFileSync(dbPath).slice(0, 16).toString();
177
+ (0, globals_1.expect)(header).toBe('SQLite format 3\x00');
178
+ process.chdir(originalCwd);
179
+ done();
180
+ }
181
+ catch (error) {
182
+ process.chdir(originalCwd);
183
+ done(error);
184
+ }
185
+ }, 1000);
186
+ }
187
+ catch (error) {
188
+ process.chdir(originalCwd);
189
+ done(error);
190
+ }
191
+ });
192
+ (0, globals_1.it)('should handle invalid requests gracefully', done => {
193
+ const dbPath = path.join(tempDir, 'test.db');
194
+ serverProcess = (0, child_process_1.spawn)('node', [path.join(__dirname, '../../../dist/index.js')], {
195
+ env: {
196
+ ...process.env,
197
+ MCP_DB_PATH: dbPath,
198
+ },
199
+ stdio: ['pipe', 'pipe', 'pipe'],
200
+ });
201
+ // Track for global cleanup
202
+ if (!global.testProcesses) {
203
+ global.testProcesses = [];
204
+ }
205
+ global.testProcesses.push(serverProcess);
206
+ let output = '';
207
+ let errorReceived = false;
208
+ serverProcess.stdout?.on('data', _data => {
209
+ output += _data.toString();
210
+ const lines = output.split('\n').filter(line => line.trim());
211
+ for (const line of lines) {
212
+ try {
213
+ const msg = JSON.parse(line);
214
+ if (msg.id === 2 && msg.error) {
215
+ errorReceived = true;
216
+ (0, globals_1.expect)(msg.error).toHaveProperty('code');
217
+ (0, globals_1.expect)(msg.error).toHaveProperty('message');
218
+ done();
219
+ }
220
+ }
221
+ catch (_e) {
222
+ // Not JSON, continue
223
+ }
224
+ }
225
+ });
226
+ // Send invalid request after initialization
227
+ setTimeout(() => {
228
+ // Initialize first
229
+ serverProcess?.stdin?.write(JSON.stringify({
230
+ jsonrpc: '2.0',
231
+ method: 'initialize',
232
+ params: {
233
+ protocolVersion: '2024-11-05',
234
+ capabilities: {},
235
+ },
236
+ id: 1,
237
+ }) + '\n');
238
+ // Then send invalid request
239
+ setTimeout(() => {
240
+ serverProcess?.stdin?.write(JSON.stringify({
241
+ jsonrpc: '2.0',
242
+ method: 'tools/call',
243
+ params: {
244
+ name: 'non_existent_tool',
245
+ arguments: {},
246
+ },
247
+ id: 2,
248
+ }) + '\n');
249
+ }, 500);
250
+ }, 500);
251
+ // Timeout
252
+ setTimeout(() => {
253
+ if (!errorReceived) {
254
+ done(new Error('Server did not return error for invalid request'));
255
+ }
256
+ }, 5000);
257
+ });
258
+ (0, globals_1.it)('should handle server shutdown gracefully', done => {
259
+ const dbPath = path.join(tempDir, 'test.db');
260
+ serverProcess = (0, child_process_1.spawn)('node', [path.join(__dirname, '../../../dist/index.js')], {
261
+ env: {
262
+ ...process.env,
263
+ MCP_DB_PATH: dbPath,
264
+ },
265
+ stdio: ['pipe', 'pipe', 'pipe'],
266
+ });
267
+ // Track for global cleanup
268
+ if (!global.testProcesses) {
269
+ global.testProcesses = [];
270
+ }
271
+ global.testProcesses.push(serverProcess);
272
+ let serverStarted = false;
273
+ let timeout;
274
+ const cleanup = () => {
275
+ if (timeout)
276
+ clearTimeout(timeout);
277
+ };
278
+ serverProcess.stdout?.on('data', _data => {
279
+ serverStarted = true;
280
+ // Server is ready, wait a bit then terminate
281
+ setTimeout(() => {
282
+ serverProcess?.kill('SIGTERM');
283
+ }, 100);
284
+ });
285
+ serverProcess.stderr?.on('data', _data => {
286
+ // Any stderr output means server is working
287
+ serverStarted = true;
288
+ });
289
+ serverProcess.on('exit', code => {
290
+ cleanup();
291
+ (0, globals_1.expect)(serverStarted).toBe(true);
292
+ (0, globals_1.expect)(code).toBe(null); // Killed by signal, not error
293
+ done();
294
+ });
295
+ serverProcess.on('error', err => {
296
+ cleanup();
297
+ done(err);
298
+ });
299
+ // Fallback: if server doesn't start in 3 seconds, kill it anyway
300
+ timeout = setTimeout(() => {
301
+ if (!serverStarted) {
302
+ serverStarted = true; // Assume it started but didn't output yet
303
+ }
304
+ serverProcess?.kill('SIGTERM');
305
+ }, 3000);
306
+ }, 15000); // Increase test timeout to 15 seconds
307
+ });
@@ -0,0 +1,219 @@
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('Session Management Integration Tests', () => {
42
+ let dbManager;
43
+ let tempDbPath;
44
+ let db;
45
+ beforeEach(() => {
46
+ tempDbPath = path.join(os.tmpdir(), `test-session-${Date.now()}.db`);
47
+ dbManager = new database_1.DatabaseManager({
48
+ filename: tempDbPath,
49
+ maxSize: 10 * 1024 * 1024,
50
+ walMode: true,
51
+ });
52
+ db = dbManager.getDatabase();
53
+ });
54
+ afterEach(() => {
55
+ dbManager.close();
56
+ try {
57
+ fs.unlinkSync(tempDbPath);
58
+ fs.unlinkSync(`${tempDbPath}-wal`);
59
+ fs.unlinkSync(`${tempDbPath}-shm`);
60
+ }
61
+ catch (_e) {
62
+ // Ignore
63
+ }
64
+ });
65
+ describe('Session lifecycle', () => {
66
+ it('should create a new session', () => {
67
+ const sessionId = (0, uuid_1.v4)();
68
+ const result = db
69
+ .prepare('INSERT INTO sessions (id, name, description, branch) VALUES (?, ?, ?, ?)')
70
+ .run(sessionId, 'Test Session', 'Test Description', 'main');
71
+ expect(result.changes).toBe(1);
72
+ const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(sessionId);
73
+ expect(session).toBeDefined();
74
+ expect(session.name).toBe('Test Session');
75
+ expect(session.description).toBe('Test Description');
76
+ expect(session.branch).toBe('main');
77
+ });
78
+ it('should list sessions in order', () => {
79
+ // Create multiple sessions
80
+ const sessions = [
81
+ { id: (0, uuid_1.v4)(), name: 'Session 1' },
82
+ { id: (0, uuid_1.v4)(), name: 'Session 2' },
83
+ { id: (0, uuid_1.v4)(), name: 'Session 3' },
84
+ ];
85
+ sessions.forEach((s, index) => {
86
+ // Add delay to ensure different timestamps
87
+ const date = new Date();
88
+ date.setSeconds(date.getSeconds() - (sessions.length - index));
89
+ db.prepare('INSERT INTO sessions (id, name, created_at) VALUES (?, ?, ?)').run(s.id, s.name, date.toISOString());
90
+ });
91
+ const results = db
92
+ .prepare('SELECT * FROM sessions ORDER BY created_at DESC LIMIT 10')
93
+ .all();
94
+ expect(results).toHaveLength(3);
95
+ expect(results[0].name).toBe('Session 3');
96
+ expect(results[2].name).toBe('Session 1');
97
+ });
98
+ it('should copy context when continuing from previous session', () => {
99
+ // Create source session
100
+ const sourceSessionId = (0, uuid_1.v4)();
101
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sourceSessionId, 'Source Session');
102
+ // Add context items
103
+ const items = [
104
+ { key: 'key1', value: 'value1', category: 'task', priority: 'high' },
105
+ { key: 'key2', value: 'value2', category: 'decision', priority: 'normal' },
106
+ ];
107
+ items.forEach(item => {
108
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), sourceSessionId, item.key, item.value, item.category, item.priority);
109
+ });
110
+ // Create new session and copy items
111
+ const newSessionId = (0, uuid_1.v4)();
112
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(newSessionId, 'New Session');
113
+ dbManager.transaction(() => {
114
+ const sourceItems = db
115
+ .prepare('SELECT * FROM context_items WHERE session_id = ?')
116
+ .all(sourceSessionId);
117
+ sourceItems.forEach((item) => {
118
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), newSessionId, item.key, item.value, item.category, item.priority);
119
+ });
120
+ });
121
+ // Verify items were copied
122
+ const copiedItems = db
123
+ .prepare('SELECT * FROM context_items WHERE session_id = ? ORDER BY key')
124
+ .all(newSessionId);
125
+ expect(copiedItems).toHaveLength(2);
126
+ expect(copiedItems[0].key).toBe('key1');
127
+ expect(copiedItems[0].value).toBe('value1');
128
+ expect(copiedItems[1].key).toBe('key2');
129
+ expect(copiedItems[1].value).toBe('value2');
130
+ });
131
+ });
132
+ describe('Context storage', () => {
133
+ it('should save and retrieve context items', () => {
134
+ const sessionId = (0, uuid_1.v4)();
135
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test Session');
136
+ // Save context item
137
+ const itemId = (0, uuid_1.v4)();
138
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run(itemId, sessionId, 'test_key', 'test_value', 'task', 'high');
139
+ // Retrieve item
140
+ const item = db
141
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
142
+ .get(sessionId, 'test_key');
143
+ expect(item).toBeDefined();
144
+ expect(item.value).toBe('test_value');
145
+ expect(item.category).toBe('task');
146
+ expect(item.priority).toBe('high');
147
+ });
148
+ it('should handle unique key constraint per session', () => {
149
+ const sessionId = (0, uuid_1.v4)();
150
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test Session');
151
+ // Insert first item
152
+ db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, 'unique_key', 'value1');
153
+ // Try to insert duplicate key - should replace
154
+ db.prepare('INSERT OR REPLACE INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, 'unique_key', 'value2');
155
+ const items = db
156
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
157
+ .all(sessionId, 'unique_key');
158
+ expect(items).toHaveLength(1);
159
+ expect(items[0].value).toBe('value2');
160
+ });
161
+ it('should filter by category and priority', () => {
162
+ const sessionId = (0, uuid_1.v4)();
163
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test Session');
164
+ // Insert items with different categories and priorities
165
+ const items = [
166
+ { key: 'task1', category: 'task', priority: 'high' },
167
+ { key: 'task2', category: 'task', priority: 'low' },
168
+ { key: 'decision1', category: 'decision', priority: 'high' },
169
+ { key: 'note1', category: 'note', priority: 'normal' },
170
+ ];
171
+ items.forEach(item => {
172
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, item.key, 'value', item.category, item.priority);
173
+ });
174
+ // Filter by category
175
+ const tasks = db
176
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND category = ?')
177
+ .all(sessionId, 'task');
178
+ expect(tasks).toHaveLength(2);
179
+ // Filter by priority
180
+ const highPriority = db
181
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND priority = ?')
182
+ .all(sessionId, 'high');
183
+ expect(highPriority).toHaveLength(2);
184
+ expect(highPriority.map((i) => i.key).sort()).toEqual(['decision1', 'task1']);
185
+ });
186
+ });
187
+ describe('File caching', () => {
188
+ it('should cache file content with hash', () => {
189
+ const sessionId = (0, uuid_1.v4)();
190
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test Session');
191
+ const content = 'This is file content';
192
+ const hash = require('crypto').createHash('sha256').update(content).digest('hex');
193
+ db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, '/test/file.txt', content, hash);
194
+ const cached = db
195
+ .prepare('SELECT * FROM file_cache WHERE session_id = ? AND file_path = ?')
196
+ .get(sessionId, '/test/file.txt');
197
+ expect(cached).toBeDefined();
198
+ expect(cached.content).toBe(content);
199
+ expect(cached.hash).toBe(hash);
200
+ });
201
+ it('should detect file changes', () => {
202
+ const sessionId = (0, uuid_1.v4)();
203
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test Session');
204
+ const originalContent = 'Original content';
205
+ const originalHash = require('crypto')
206
+ .createHash('sha256')
207
+ .update(originalContent)
208
+ .digest('hex');
209
+ db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, '/test/file.txt', originalContent, originalHash);
210
+ const newContent = 'Modified content';
211
+ const newHash = require('crypto').createHash('sha256').update(newContent).digest('hex');
212
+ const cached = db
213
+ .prepare('SELECT * FROM file_cache WHERE session_id = ? AND file_path = ?')
214
+ .get(sessionId, '/test/file.txt');
215
+ expect(cached.hash).not.toBe(newHash);
216
+ expect(originalHash).not.toBe(newHash);
217
+ });
218
+ });
219
+ });