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,190 @@
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 fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const os = __importStar(require("os"));
40
+ const database_helper_js_1 = require("../../test-helpers/database-helper.js");
41
+ (0, globals_1.describe)('Concurrent Access Tests', () => {
42
+ let tempDir;
43
+ let tempDbPath;
44
+ let dbManager;
45
+ (0, globals_1.beforeEach)(() => {
46
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-test-'));
47
+ tempDbPath = path.join(tempDir, 'test.db');
48
+ dbManager = database_helper_js_1.TestDatabaseHelper.createTestDatabase();
49
+ });
50
+ (0, globals_1.afterEach)(async () => {
51
+ // Clean up all tracked databases
52
+ await database_helper_js_1.TestDatabaseHelper.cleanupAll();
53
+ // Clean up temp directory
54
+ try {
55
+ fs.rmSync(tempDir, { recursive: true, force: true });
56
+ }
57
+ catch (_error) {
58
+ console.warn('Error cleaning up temp directory:', _error);
59
+ }
60
+ // Clean up any WAL/SHM files
61
+ database_helper_js_1.TestDatabaseHelper.cleanupDbFiles(tempDbPath);
62
+ });
63
+ (0, globals_1.it)('should handle concurrent session writes without corruption', async () => {
64
+ const db = dbManager.getDatabase();
65
+ const sessionId = 'test-session';
66
+ // Create session
67
+ db.prepare(`
68
+ INSERT INTO sessions (id, name, description, branch, working_directory)
69
+ VALUES (?, ?, ?, ?, ?)
70
+ `).run(sessionId, 'Test Session', 'Test', null, null);
71
+ // Simulate concurrent writes to same session
72
+ const promises = [];
73
+ for (let i = 0; i < 10; i++) {
74
+ promises.push(new Promise(resolve => {
75
+ setTimeout(() => {
76
+ try {
77
+ db.prepare(`
78
+ INSERT OR REPLACE INTO context_items (id, session_id, key, value, category, priority)
79
+ VALUES (?, ?, ?, ?, ?, ?)
80
+ `).run(`item-${i}`, sessionId, `key-${i}`, `value-${i}`, 'test', 'normal');
81
+ resolve();
82
+ }
83
+ catch (_error) {
84
+ resolve(); // Still resolve to continue test
85
+ }
86
+ }, Math.random() * 10);
87
+ }));
88
+ }
89
+ await Promise.all(promises);
90
+ // Verify all items were written
91
+ const items = db
92
+ .prepare('SELECT COUNT(*) as count FROM context_items WHERE session_id = ?')
93
+ .get(sessionId);
94
+ (0, globals_1.expect)(items.count).toBe(10);
95
+ });
96
+ (0, globals_1.it)('should handle concurrent updates to same key', async () => {
97
+ const db = dbManager.getDatabase();
98
+ const sessionId = 'test-session';
99
+ const key = 'shared-key';
100
+ // Create session
101
+ db.prepare(`
102
+ INSERT INTO sessions (id, name, description, branch, working_directory)
103
+ VALUES (?, ?, ?, ?, ?)
104
+ `).run(sessionId, 'Test Session', 'Test', null, null);
105
+ // Initial value
106
+ db.prepare(`
107
+ INSERT INTO context_items (id, session_id, key, value, category, priority)
108
+ VALUES (?, ?, ?, ?, ?, ?)
109
+ `).run('initial', sessionId, key, 'initial-value', 'test', 'normal');
110
+ // Concurrent updates
111
+ const updates = [];
112
+ for (let i = 0; i < 20; i++) {
113
+ updates.push(new Promise(resolve => {
114
+ setTimeout(() => {
115
+ try {
116
+ db.prepare(`
117
+ UPDATE context_items
118
+ SET value = ?, updated_at = CURRENT_TIMESTAMP
119
+ WHERE session_id = ? AND key = ?
120
+ `).run(`value-${i}`, sessionId, key);
121
+ resolve();
122
+ }
123
+ catch (_error) {
124
+ resolve();
125
+ }
126
+ }, Math.random() * 10);
127
+ }));
128
+ }
129
+ await Promise.all(updates);
130
+ // Should have exactly one item with the key
131
+ const items = db
132
+ .prepare('SELECT COUNT(*) as count FROM context_items WHERE session_id = ? AND key = ?')
133
+ .get(sessionId, key);
134
+ (0, globals_1.expect)(items.count).toBe(1);
135
+ });
136
+ (0, globals_1.it)('should handle database locks gracefully', () => {
137
+ const db = dbManager.getDatabase();
138
+ // In WAL mode, readers don't block writers and vice versa
139
+ // So we need to test a different scenario - two writers
140
+ // Start a write transaction
141
+ db.prepare('BEGIN IMMEDIATE').run();
142
+ // Try to start another write transaction (should fail)
143
+ let errorThrown = false;
144
+ try {
145
+ // This should fail because we already have a write transaction
146
+ db.prepare('BEGIN IMMEDIATE').run();
147
+ }
148
+ catch (error) {
149
+ errorThrown = true;
150
+ // The exact error code might vary
151
+ (0, globals_1.expect)(error.message).toMatch(/database is locked|cannot start a transaction/i);
152
+ }
153
+ db.prepare('ROLLBACK').run();
154
+ // If no error was thrown, it might be because SQLite is configured differently
155
+ // In that case, just verify we can at least do transactions
156
+ if (!errorThrown) {
157
+ // Verify basic transaction works
158
+ db.prepare('BEGIN').run();
159
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run('test', 'Test');
160
+ db.prepare('COMMIT').run();
161
+ const result = db.prepare('SELECT * FROM sessions WHERE id = ?').get('test');
162
+ (0, globals_1.expect)(result.name).toBe('Test');
163
+ }
164
+ });
165
+ (0, globals_1.it)('should handle WAL mode concurrent reads during write', () => {
166
+ const db = dbManager.getDatabase();
167
+ // Verify WAL mode is enabled
168
+ const journalMode = db.pragma('journal_mode');
169
+ (0, globals_1.expect)(journalMode[0].journal_mode).toBe('wal');
170
+ // Create a session
171
+ const sessionId = 'wal-test';
172
+ db.prepare(`
173
+ INSERT INTO sessions (id, name, description, branch, working_directory)
174
+ VALUES (?, ?, ?, ?, ?)
175
+ `).run(sessionId, 'WAL Test', 'Test', null, null);
176
+ // Start a write transaction
177
+ const writeStmt = db.prepare('BEGIN IMMEDIATE');
178
+ writeStmt.run();
179
+ // Insert item in transaction
180
+ db.prepare(`
181
+ INSERT INTO context_items (id, session_id, key, value, category, priority)
182
+ VALUES (?, ?, ?, ?, ?, ?)
183
+ `).run('item-1', sessionId, 'key-1', 'value-1', 'test', 'normal');
184
+ // Read should work even with ongoing write transaction (WAL mode benefit)
185
+ const readResult = db.prepare('SELECT COUNT(*) as count FROM sessions').get();
186
+ (0, globals_1.expect)(readResult.count).toBeGreaterThan(0);
187
+ // Commit the transaction
188
+ db.prepare('COMMIT').run();
189
+ });
190
+ });
@@ -0,0 +1,243 @@
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('Context Operations Integration Tests', () => {
42
+ let dbManager;
43
+ let tempDbPath;
44
+ let db;
45
+ let testSessionId;
46
+ beforeEach(() => {
47
+ tempDbPath = path.join(os.tmpdir(), `test-context-${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, '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_save', () => {
70
+ it('should save a basic context item', () => {
71
+ const result = db
72
+ .prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)')
73
+ .run((0, uuid_1.v4)(), testSessionId, 'test_key', 'test_value');
74
+ expect(result.changes).toBe(1);
75
+ const saved = db
76
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
77
+ .get(testSessionId, 'test_key');
78
+ expect(saved).toBeDefined();
79
+ expect(saved.key).toBe('test_key');
80
+ expect(saved.value).toBe('test_value');
81
+ });
82
+ it('should save context with category and priority', () => {
83
+ const id = (0, uuid_1.v4)();
84
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run(id, testSessionId, 'task_item', 'Fix bug in auth', 'task', 'high');
85
+ const saved = db.prepare('SELECT * FROM context_items WHERE id = ?').get(id);
86
+ expect(saved.category).toBe('task');
87
+ expect(saved.priority).toBe('high');
88
+ });
89
+ it('should update existing key in same session', () => {
90
+ // Insert first
91
+ db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'update_key', 'original_value');
92
+ // Update using INSERT OR REPLACE
93
+ db.prepare('INSERT OR REPLACE INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'update_key', 'updated_value');
94
+ const items = db
95
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
96
+ .all(testSessionId, 'update_key');
97
+ expect(items).toHaveLength(1);
98
+ expect(items[0].value).toBe('updated_value');
99
+ });
100
+ it('should handle large values', () => {
101
+ const largeValue = 'x'.repeat(10000); // 10KB of data
102
+ const result = db
103
+ .prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)')
104
+ .run((0, uuid_1.v4)(), testSessionId, 'large_key', largeValue);
105
+ expect(result.changes).toBe(1);
106
+ const saved = db
107
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
108
+ .get(testSessionId, 'large_key');
109
+ expect(saved.value).toBe(largeValue);
110
+ });
111
+ it('should handle special characters in values', () => {
112
+ const specialValue = `Line 1\nLine 2\t"quoted"\n'single'\n\`backtick\``;
113
+ db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, 'special_key', specialValue);
114
+ const saved = db
115
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
116
+ .get(testSessionId, 'special_key');
117
+ expect(saved.value).toBe(specialValue);
118
+ });
119
+ });
120
+ describe('context_get', () => {
121
+ beforeEach(() => {
122
+ // Add test data
123
+ const items = [
124
+ { key: 'item1', value: 'value1', category: 'task', priority: 'high' },
125
+ { key: 'item2', value: 'value2', category: 'task', priority: 'normal' },
126
+ { key: 'item3', value: 'value3', category: 'decision', priority: 'high' },
127
+ { key: 'item4', value: 'value4', category: 'note', priority: 'low' },
128
+ ];
129
+ items.forEach(item => {
130
+ 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);
131
+ });
132
+ });
133
+ it('should get item by key', () => {
134
+ const item = db
135
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
136
+ .get(testSessionId, 'item1');
137
+ expect(item).toBeDefined();
138
+ expect(item.value).toBe('value1');
139
+ expect(item.category).toBe('task');
140
+ });
141
+ it('should get all items for session', () => {
142
+ const items = db
143
+ .prepare('SELECT * FROM context_items WHERE session_id = ? ORDER BY key')
144
+ .all(testSessionId);
145
+ expect(items).toHaveLength(4);
146
+ expect(items[0].key).toBe('item1');
147
+ expect(items[3].key).toBe('item4');
148
+ });
149
+ it('should filter by category', () => {
150
+ const tasks = db
151
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND category = ?')
152
+ .all(testSessionId, 'task');
153
+ expect(tasks).toHaveLength(2);
154
+ expect(tasks.every((t) => t.category === 'task')).toBe(true);
155
+ });
156
+ it('should filter by priority', () => {
157
+ const highPriority = db
158
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND priority = ?')
159
+ .all(testSessionId, 'high');
160
+ expect(highPriority).toHaveLength(2);
161
+ expect(highPriority.every((t) => t.priority === 'high')).toBe(true);
162
+ });
163
+ it('should filter by category and priority', () => {
164
+ const highTasks = db
165
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND category = ? AND priority = ?')
166
+ .all(testSessionId, 'task', 'high');
167
+ expect(highTasks).toHaveLength(1);
168
+ expect(highTasks[0].key).toBe('item1');
169
+ });
170
+ it('should return empty array when no matches', () => {
171
+ const items = db
172
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
173
+ .all(testSessionId, 'nonexistent');
174
+ expect(items).toHaveLength(0);
175
+ });
176
+ it('should get items from specific session', () => {
177
+ // Create another session
178
+ const otherSessionId = (0, uuid_1.v4)();
179
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(otherSessionId, 'Other Session');
180
+ db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), otherSessionId, 'other_item', 'other_value');
181
+ // Get from specific session
182
+ const items = db
183
+ .prepare('SELECT * FROM context_items WHERE session_id = ?')
184
+ .all(otherSessionId);
185
+ expect(items).toHaveLength(1);
186
+ expect(items[0].key).toBe('other_item');
187
+ });
188
+ });
189
+ describe('context_status', () => {
190
+ it('should show status with no items', () => {
191
+ const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(testSessionId);
192
+ const itemCount = db
193
+ .prepare('SELECT COUNT(*) as count FROM context_items WHERE session_id = ?')
194
+ .get(testSessionId);
195
+ const fileCount = db
196
+ .prepare('SELECT COUNT(*) as count FROM file_cache WHERE session_id = ?')
197
+ .get(testSessionId);
198
+ expect(session).toBeDefined();
199
+ expect(itemCount.count).toBe(0);
200
+ expect(fileCount.count).toBe(0);
201
+ });
202
+ it('should show complete status information', () => {
203
+ // Add some data
204
+ for (let i = 0; i < 5; i++) {
205
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, category) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, `key${i}`, `value${i}`, i % 2 === 0 ? 'task' : 'note');
206
+ }
207
+ for (let i = 0; i < 3; i++) {
208
+ db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, `/file${i}.txt`, `content${i}`, `hash${i}`);
209
+ }
210
+ // Get status
211
+ const itemCount = db
212
+ .prepare('SELECT COUNT(*) as count FROM context_items WHERE session_id = ?')
213
+ .get(testSessionId);
214
+ const fileCount = db
215
+ .prepare('SELECT COUNT(*) as count FROM file_cache WHERE session_id = ?')
216
+ .get(testSessionId);
217
+ const categoryBreakdown = db
218
+ .prepare(`SELECT category, COUNT(*) as count
219
+ FROM context_items
220
+ WHERE session_id = ?
221
+ GROUP BY category`)
222
+ .all(testSessionId);
223
+ expect(itemCount.count).toBe(5);
224
+ expect(fileCount.count).toBe(3);
225
+ expect(categoryBreakdown).toHaveLength(2);
226
+ expect(categoryBreakdown.find((c) => c.category === 'task')?.count).toBe(3);
227
+ expect(categoryBreakdown.find((c) => c.category === 'note')?.count).toBe(2);
228
+ });
229
+ it('should calculate database size', () => {
230
+ // Add substantial data
231
+ const largeContent = 'x'.repeat(1000);
232
+ for (let i = 0; i < 10; i++) {
233
+ db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, `large${i}`, largeContent);
234
+ }
235
+ // Calculate approximate size
236
+ const items = db
237
+ .prepare('SELECT LENGTH(value) as size FROM context_items WHERE session_id = ?')
238
+ .all(testSessionId);
239
+ const totalSize = items.reduce((sum, item) => sum + item.size, 0);
240
+ expect(totalSize).toBe(10000); // 10 items × 1000 chars
241
+ });
242
+ });
243
+ });