@smallironman/mcp-memory-keeper 0.12.2-fork1

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 (110) hide show
  1. package/CHANGELOG.md +542 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1281 -0
  4. package/bin/mcp-memory-keeper +54 -0
  5. package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
  6. package/dist/__tests__/e2e/server-e2e.test.js +341 -0
  7. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  8. package/dist/__tests__/helpers/test-server.js +92 -0
  9. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  10. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  11. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  12. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  13. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  14. package/dist/__tests__/integration/channels.test.js +376 -0
  15. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  16. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  17. package/dist/__tests__/integration/context-operations.test.js +243 -0
  18. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  19. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  20. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  21. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  22. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  23. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  24. package/dist/__tests__/integration/contextSearch.test.js +1054 -0
  25. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  26. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  27. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  28. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  29. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  30. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  31. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  32. package/dist/__tests__/integration/error-cases.test.js +411 -0
  33. package/dist/__tests__/integration/export-import.test.js +367 -0
  34. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  35. package/dist/__tests__/integration/file-operations.test.js +264 -0
  36. package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
  37. package/dist/__tests__/integration/git-integration.test.js +241 -0
  38. package/dist/__tests__/integration/index-tools.test.js +496 -0
  39. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  40. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  41. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  42. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  43. package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
  44. package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
  45. package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
  46. package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
  47. package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
  48. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  49. package/dist/__tests__/integration/migrations.test.js +528 -0
  50. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  51. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  52. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  53. package/dist/__tests__/integration/project-directory.test.js +291 -0
  54. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  55. package/dist/__tests__/integration/retention.test.js +513 -0
  56. package/dist/__tests__/integration/search.test.js +333 -0
  57. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  58. package/dist/__tests__/integration/server-initialization.test.js +305 -0
  59. package/dist/__tests__/integration/session-management.test.js +219 -0
  60. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  61. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  62. package/dist/__tests__/integration/summarization.test.js +308 -0
  63. package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
  64. package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
  65. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  66. package/dist/__tests__/security/input-validation.test.js +115 -0
  67. package/dist/__tests__/utils/agents.test.js +473 -0
  68. package/dist/__tests__/utils/database.test.js +177 -0
  69. package/dist/__tests__/utils/git.test.js +122 -0
  70. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  71. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  72. package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
  73. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  74. package/dist/__tests__/utils/token-limits.test.js +225 -0
  75. package/dist/__tests__/utils/tool-profiles.test.js +374 -0
  76. package/dist/__tests__/utils/validation.test.js +200 -0
  77. package/dist/__tests__/utils/vector-store.test.js +231 -0
  78. package/dist/handlers/contextWatchHandlers.js +206 -0
  79. package/dist/index.js +4425 -0
  80. package/dist/migrations/003_add_channels.js +174 -0
  81. package/dist/migrations/004_add_context_watch.js +151 -0
  82. package/dist/migrations/005_add_context_watch.js +98 -0
  83. package/dist/migrations/simplify-sharing.js +117 -0
  84. package/dist/repositories/BaseRepository.js +30 -0
  85. package/dist/repositories/CheckpointRepository.js +140 -0
  86. package/dist/repositories/ContextRepository.js +2017 -0
  87. package/dist/repositories/FileRepository.js +104 -0
  88. package/dist/repositories/RepositoryManager.js +62 -0
  89. package/dist/repositories/SessionRepository.js +66 -0
  90. package/dist/repositories/WatcherRepository.js +252 -0
  91. package/dist/repositories/index.js +15 -0
  92. package/dist/test-helpers/database-helper.js +128 -0
  93. package/dist/types/entities.js +3 -0
  94. package/dist/utils/agents.js +791 -0
  95. package/dist/utils/channels.js +150 -0
  96. package/dist/utils/database.js +780 -0
  97. package/dist/utils/feature-flags.js +476 -0
  98. package/dist/utils/git.js +145 -0
  99. package/dist/utils/knowledge-graph.js +264 -0
  100. package/dist/utils/migrationHealthCheck.js +373 -0
  101. package/dist/utils/migrations.js +452 -0
  102. package/dist/utils/retention.js +460 -0
  103. package/dist/utils/timestamps.js +112 -0
  104. package/dist/utils/token-limits.js +350 -0
  105. package/dist/utils/tool-profiles.js +242 -0
  106. package/dist/utils/validation.js +296 -0
  107. package/dist/utils/vector-store.js +247 -0
  108. package/examples/config.json +31 -0
  109. package/examples/project-directory-setup.md +114 -0
  110. package/package.json +85 -0
@@ -0,0 +1,346 @@
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 RepositoryManager_1 = require("../../repositories/RepositoryManager");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ describe('Simplified Sharing Model Tests', () => {
41
+ let dbManager;
42
+ let repositories;
43
+ const testDbPath = path.join(__dirname, `test-simplified-sharing-${Date.now()}.db`);
44
+ beforeEach(() => {
45
+ // Clean up any existing test database
46
+ if (fs.existsSync(testDbPath)) {
47
+ fs.unlinkSync(testDbPath);
48
+ }
49
+ dbManager = new database_1.DatabaseManager({ filename: testDbPath });
50
+ repositories = new RepositoryManager_1.RepositoryManager(dbManager);
51
+ });
52
+ afterEach(() => {
53
+ dbManager.close();
54
+ // Clean up test database
55
+ if (fs.existsSync(testDbPath)) {
56
+ fs.unlinkSync(testDbPath);
57
+ }
58
+ if (fs.existsSync(`${testDbPath}-wal`)) {
59
+ fs.unlinkSync(`${testDbPath}-wal`);
60
+ }
61
+ if (fs.existsSync(`${testDbPath}-shm`)) {
62
+ fs.unlinkSync(`${testDbPath}-shm`);
63
+ }
64
+ });
65
+ describe('Default Sharing Behavior', () => {
66
+ let session1Id;
67
+ let session2Id;
68
+ let session3Id;
69
+ beforeEach(() => {
70
+ // Create test sessions
71
+ const session1 = repositories.sessions.create({ name: 'Session 1' });
72
+ const session2 = repositories.sessions.create({ name: 'Session 2' });
73
+ const session3 = repositories.sessions.create({ name: 'Session 3' });
74
+ session1Id = session1.id;
75
+ session2Id = session2.id;
76
+ session3Id = session3.id;
77
+ });
78
+ it('should make items accessible across sessions by default', () => {
79
+ // Create item in session 1 without private flag
80
+ const _item = repositories.contexts.save(session1Id, {
81
+ key: 'shared_by_default',
82
+ value: 'This should be accessible from all sessions',
83
+ category: 'test',
84
+ });
85
+ // Should be accessible from session 2
86
+ const accessibleItem = repositories.contexts.getAccessibleByKey(session2Id, 'shared_by_default');
87
+ expect(accessibleItem).toBeTruthy();
88
+ expect(accessibleItem?.value).toBe('This should be accessible from all sessions');
89
+ // Should be accessible from session 3
90
+ const accessibleItem3 = repositories.contexts.getAccessibleByKey(session3Id, 'shared_by_default');
91
+ expect(accessibleItem3).toBeTruthy();
92
+ expect(accessibleItem3?.value).toBe('This should be accessible from all sessions');
93
+ // Should be accessible when listing from any session
94
+ const session2Items = repositories.contexts.getAccessibleItems(session2Id);
95
+ expect(session2Items.some(i => i.key === 'shared_by_default')).toBe(true);
96
+ });
97
+ it('should make private items only accessible from creating session', () => {
98
+ // Create private item in session 1
99
+ const _privateItem = repositories.contexts.save(session1Id, {
100
+ key: 'private_notes',
101
+ value: 'Session 1 private thoughts',
102
+ category: 'note',
103
+ isPrivate: true,
104
+ });
105
+ // Should be accessible from session 1
106
+ const fromSession1 = repositories.contexts.getAccessibleByKey(session1Id, 'private_notes');
107
+ expect(fromSession1).toBeTruthy();
108
+ expect(fromSession1?.value).toBe('Session 1 private thoughts');
109
+ // Should NOT be accessible from session 2
110
+ const fromSession2 = repositories.contexts.getAccessibleByKey(session2Id, 'private_notes');
111
+ expect(fromSession2).toBeNull();
112
+ // Should NOT appear in session 2's list
113
+ const session2Items = repositories.contexts.getAccessibleItems(session2Id);
114
+ expect(session2Items.some(i => i.key === 'private_notes')).toBe(false);
115
+ // Should appear in session 1's list
116
+ const session1Items = repositories.contexts.getAccessibleItems(session1Id);
117
+ expect(session1Items.some(i => i.key === 'private_notes')).toBe(true);
118
+ });
119
+ it('should handle mixed public and private items correctly', () => {
120
+ // Create a mix of public and private items
121
+ repositories.contexts.save(session1Id, {
122
+ key: 'public_standard',
123
+ value: 'Team coding standard',
124
+ category: 'standard',
125
+ });
126
+ repositories.contexts.save(session1Id, {
127
+ key: 'private_debug',
128
+ value: 'Debugging notes',
129
+ category: 'note',
130
+ isPrivate: true,
131
+ });
132
+ repositories.contexts.save(session2Id, {
133
+ key: 'another_public',
134
+ value: 'Another shared item',
135
+ category: 'info',
136
+ });
137
+ repositories.contexts.save(session2Id, {
138
+ key: 'session2_private',
139
+ value: 'Session 2 private',
140
+ category: 'note',
141
+ isPrivate: true,
142
+ });
143
+ // Session 1 should see: its private + all public
144
+ const session1Items = repositories.contexts.getAccessibleItems(session1Id);
145
+ expect(session1Items).toHaveLength(3); // public_standard, private_debug, another_public
146
+ expect(session1Items.some(i => i.key === 'public_standard')).toBe(true);
147
+ expect(session1Items.some(i => i.key === 'private_debug')).toBe(true);
148
+ expect(session1Items.some(i => i.key === 'another_public')).toBe(true);
149
+ expect(session1Items.some(i => i.key === 'session2_private')).toBe(false);
150
+ // Session 2 should see: its private + all public
151
+ const session2Items = repositories.contexts.getAccessibleItems(session2Id);
152
+ expect(session2Items).toHaveLength(3); // public_standard, another_public, session2_private
153
+ expect(session2Items.some(i => i.key === 'public_standard')).toBe(true);
154
+ expect(session2Items.some(i => i.key === 'private_debug')).toBe(false);
155
+ expect(session2Items.some(i => i.key === 'another_public')).toBe(true);
156
+ expect(session2Items.some(i => i.key === 'session2_private')).toBe(true);
157
+ // Session 3 should only see public items
158
+ const session3Items = repositories.contexts.getAccessibleItems(session3Id);
159
+ expect(session3Items).toHaveLength(2); // public_standard, another_public
160
+ expect(session3Items.some(i => i.key === 'public_standard')).toBe(true);
161
+ expect(session3Items.some(i => i.key === 'another_public')).toBe(true);
162
+ });
163
+ });
164
+ describe('Search Functionality', () => {
165
+ let session1Id;
166
+ let session2Id;
167
+ beforeEach(() => {
168
+ const session1 = repositories.sessions.create({ name: 'Session 1' });
169
+ const session2 = repositories.sessions.create({ name: 'Session 2' });
170
+ session1Id = session1.id;
171
+ session2Id = session2.id;
172
+ // Create test data
173
+ repositories.contexts.save(session1Id, {
174
+ key: 'public_auth_pattern',
175
+ value: 'Use JWT for authentication across the app',
176
+ category: 'pattern',
177
+ });
178
+ repositories.contexts.save(session1Id, {
179
+ key: 'private_auth_notes',
180
+ value: 'Authentication debugging notes - token expires too fast',
181
+ category: 'debug',
182
+ isPrivate: true,
183
+ });
184
+ repositories.contexts.save(session2Id, {
185
+ key: 'auth_config',
186
+ value: 'Authentication configuration for production',
187
+ category: 'config',
188
+ });
189
+ repositories.contexts.save(session2Id, {
190
+ key: 'private_password',
191
+ value: 'Temporary password for testing auth',
192
+ category: 'secret',
193
+ isPrivate: true,
194
+ });
195
+ });
196
+ it('should search public items by default', () => {
197
+ // Search without specifying session - should only return public items
198
+ const results = repositories.contexts.search('auth');
199
+ expect(results).toHaveLength(2); // Only public items
200
+ expect(results.some(r => r.key === 'public_auth_pattern')).toBe(true);
201
+ expect(results.some(r => r.key === 'auth_config')).toBe(true);
202
+ expect(results.some(r => r.key === 'private_auth_notes')).toBe(false);
203
+ expect(results.some(r => r.key === 'private_password')).toBe(false);
204
+ });
205
+ it('should include private items when searching with includePrivate from owning session', () => {
206
+ // Search from session 1 with includePrivate
207
+ const results = repositories.contexts.search('auth', session1Id, true);
208
+ expect(results).toHaveLength(3); // public items + session1's private
209
+ expect(results.some(r => r.key === 'public_auth_pattern')).toBe(true);
210
+ expect(results.some(r => r.key === 'auth_config')).toBe(true);
211
+ expect(results.some(r => r.key === 'private_auth_notes')).toBe(true);
212
+ expect(results.some(r => r.key === 'private_password')).toBe(false);
213
+ });
214
+ it('should search across all sessions correctly', () => {
215
+ // Search across all sessions from session 1
216
+ const results = repositories.contexts.searchAcrossSessions('auth', session1Id);
217
+ expect(results).toHaveLength(3); // All public + session1's private
218
+ expect(results.some(r => r.key === 'public_auth_pattern')).toBe(true);
219
+ expect(results.some(r => r.key === 'auth_config')).toBe(true);
220
+ expect(results.some(r => r.key === 'private_auth_notes')).toBe(true);
221
+ expect(results.some(r => r.key === 'private_password')).toBe(false);
222
+ // Search across all sessions without current session - only public
223
+ const publicOnlyResults = repositories.contexts.searchAcrossSessions('auth');
224
+ expect(publicOnlyResults).toHaveLength(2);
225
+ expect(publicOnlyResults.every(r => !r.key.includes('private'))).toBe(true);
226
+ });
227
+ });
228
+ describe('Category Filtering', () => {
229
+ let sessionId;
230
+ beforeEach(() => {
231
+ const session = repositories.sessions.create({ name: 'Test Session' });
232
+ sessionId = session.id;
233
+ // Create items with different categories and privacy
234
+ repositories.contexts.save(sessionId, {
235
+ key: 'task1',
236
+ value: 'Public task',
237
+ category: 'task',
238
+ });
239
+ repositories.contexts.save(sessionId, {
240
+ key: 'task2',
241
+ value: 'Private task',
242
+ category: 'task',
243
+ isPrivate: true,
244
+ });
245
+ repositories.contexts.save(sessionId, {
246
+ key: 'note1',
247
+ value: 'Public note',
248
+ category: 'note',
249
+ });
250
+ });
251
+ it('should filter by category while respecting privacy', () => {
252
+ // Get tasks from the owning session
253
+ const tasksFromOwner = repositories.contexts.getAccessibleItems(sessionId, {
254
+ category: 'task',
255
+ });
256
+ expect(tasksFromOwner).toHaveLength(2); // Both public and private
257
+ // Get tasks from another session
258
+ const otherSession = repositories.sessions.create({ name: 'Other Session' });
259
+ const tasksFromOther = repositories.contexts.getAccessibleItems(otherSession.id, {
260
+ category: 'task',
261
+ });
262
+ expect(tasksFromOther).toHaveLength(1); // Only public
263
+ expect(tasksFromOther[0].key).toBe('task1');
264
+ });
265
+ });
266
+ describe('Key Uniqueness with Privacy', () => {
267
+ it('should handle same key in different sessions correctly', () => {
268
+ const session1 = repositories.sessions.create({ name: 'Session 1' });
269
+ const session2 = repositories.sessions.create({ name: 'Session 2' });
270
+ // Create items with same key in different sessions
271
+ repositories.contexts.save(session1.id, {
272
+ key: 'config',
273
+ value: 'Session 1 config',
274
+ category: 'config',
275
+ });
276
+ repositories.contexts.save(session2.id, {
277
+ key: 'config',
278
+ value: 'Session 2 config',
279
+ category: 'config',
280
+ });
281
+ // When accessing by key, should get the most relevant one
282
+ const fromSession1 = repositories.contexts.getAccessibleByKey(session1.id, 'config');
283
+ expect(fromSession1?.value).toBe('Session 1 config'); // Should prefer own session's item
284
+ const fromSession2 = repositories.contexts.getAccessibleByKey(session2.id, 'config');
285
+ expect(fromSession2?.value).toBe('Session 2 config'); // Should prefer own session's item
286
+ // Create a third session to test which public item it gets
287
+ const session3 = repositories.sessions.create({ name: 'Session 3' });
288
+ const fromSession3 = repositories.contexts.getAccessibleByKey(session3.id, 'config');
289
+ expect(fromSession3).toBeTruthy(); // Should get one of them (latest by created_at)
290
+ });
291
+ it('should handle private vs public items with same key', () => {
292
+ const session1 = repositories.sessions.create({ name: 'Session 1' });
293
+ const session2 = repositories.sessions.create({ name: 'Session 2' });
294
+ // Session 1 creates a private item
295
+ repositories.contexts.save(session1.id, {
296
+ key: 'secret',
297
+ value: 'Session 1 private secret',
298
+ isPrivate: true,
299
+ });
300
+ // Session 2 creates a public item with same key
301
+ repositories.contexts.save(session2.id, {
302
+ key: 'secret',
303
+ value: 'Session 2 public secret',
304
+ isPrivate: false,
305
+ });
306
+ // Session 1 should see its own private item
307
+ const fromSession1 = repositories.contexts.getAccessibleByKey(session1.id, 'secret');
308
+ expect(fromSession1?.value).toBe('Session 1 private secret');
309
+ // Session 2 should see its own public item
310
+ const fromSession2 = repositories.contexts.getAccessibleByKey(session2.id, 'secret');
311
+ expect(fromSession2?.value).toBe('Session 2 public secret');
312
+ // Session 3 should only see the public item
313
+ const session3 = repositories.sessions.create({ name: 'Session 3' });
314
+ const fromSession3 = repositories.contexts.getAccessibleByKey(session3.id, 'secret');
315
+ expect(fromSession3?.value).toBe('Session 2 public secret');
316
+ });
317
+ });
318
+ describe('Edge Cases', () => {
319
+ it('should handle null/undefined privacy flag as public', () => {
320
+ const session = repositories.sessions.create({ name: 'Test Session' });
321
+ // Save without specifying isPrivate
322
+ const _item = repositories.contexts.save(session.id, {
323
+ key: 'default_privacy',
324
+ value: 'Should be public',
325
+ });
326
+ // Verify it's public (is_private = 0)
327
+ expect(_item.is_private).toBe(0);
328
+ // Should be accessible from other sessions
329
+ const otherSession = repositories.sessions.create({ name: 'Other Session' });
330
+ const accessible = repositories.contexts.getAccessibleByKey(otherSession.id, 'default_privacy');
331
+ expect(accessible).toBeTruthy();
332
+ });
333
+ it('should handle empty results gracefully', () => {
334
+ const session = repositories.sessions.create({ name: 'Test Session' });
335
+ // Search for non-existent items
336
+ const searchResults = repositories.contexts.search('nonexistent', session.id);
337
+ expect(searchResults).toHaveLength(0);
338
+ // Get by non-existent key
339
+ const byKey = repositories.contexts.getAccessibleByKey(session.id, 'nonexistent');
340
+ expect(byKey).toBeNull();
341
+ // Get items from empty session
342
+ const items = repositories.contexts.getAccessibleItems(session.id);
343
+ expect(items).toHaveLength(0);
344
+ });
345
+ });
346
+ });
@@ -0,0 +1,230 @@
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 git_1 = require("../../utils/git");
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('Smart Compaction Integration Tests', () => {
43
+ let dbManager;
44
+ let _gitOps;
45
+ let tempDbPath;
46
+ let tempRepoPath;
47
+ let db;
48
+ beforeEach(() => {
49
+ tempDbPath = path.join(os.tmpdir(), `test-compaction-${Date.now()}.db`);
50
+ tempRepoPath = path.join(os.tmpdir(), `test-repo-${Date.now()}`);
51
+ dbManager = new database_1.DatabaseManager({
52
+ filename: tempDbPath,
53
+ maxSize: 10 * 1024 * 1024,
54
+ walMode: true,
55
+ });
56
+ db = dbManager.getDatabase();
57
+ // Create a mock git repo
58
+ fs.mkdirSync(tempRepoPath, { recursive: true });
59
+ _gitOps = new git_1.GitOperations(tempRepoPath);
60
+ });
61
+ afterEach(() => {
62
+ dbManager.close();
63
+ try {
64
+ fs.unlinkSync(tempDbPath);
65
+ fs.unlinkSync(`${tempDbPath}-wal`);
66
+ fs.unlinkSync(`${tempDbPath}-shm`);
67
+ fs.rmSync(tempRepoPath, { recursive: true, force: true });
68
+ }
69
+ catch (_e) {
70
+ // Ignore
71
+ }
72
+ });
73
+ describe('context_prepare_compaction', () => {
74
+ it('should identify critical context items', () => {
75
+ const sessionId = (0, uuid_1.v4)();
76
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test Session');
77
+ // Add various priority items
78
+ const items = [
79
+ { key: 'critical1', value: 'Critical task', priority: 'critical', category: 'task' },
80
+ { key: 'high1', value: 'High priority', priority: 'high', category: 'decision' },
81
+ { key: 'normal1', value: 'Normal item', priority: 'normal', category: 'note' },
82
+ { key: 'low1', value: 'Low priority', priority: 'low', category: 'reference' },
83
+ ];
84
+ items.forEach(item => {
85
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, priority, category) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, item.key, item.value, item.priority, item.category);
86
+ });
87
+ // Get critical items (critical + high priority)
88
+ const criticalItems = db
89
+ .prepare(`SELECT * FROM context_items
90
+ WHERE session_id = ?
91
+ AND priority IN ('critical', 'high')
92
+ ORDER BY
93
+ CASE priority
94
+ WHEN 'critical' THEN 1
95
+ WHEN 'high' THEN 2
96
+ END`)
97
+ .all(sessionId);
98
+ expect(criticalItems).toHaveLength(2);
99
+ expect(criticalItems[0].key).toBe('critical1');
100
+ expect(criticalItems[1].key).toBe('high1');
101
+ });
102
+ it('should include recently modified files', () => {
103
+ const sessionId = (0, uuid_1.v4)();
104
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Test Session');
105
+ // Add file cache entries with different timestamps
106
+ const now = new Date();
107
+ const files = [
108
+ { path: '/recent.ts', time: now },
109
+ { path: '/older.ts', time: new Date(now.getTime() - 3600000) }, // 1 hour ago
110
+ { path: '/oldest.ts', time: new Date(now.getTime() - 86400000) }, // 1 day ago
111
+ ];
112
+ files.forEach(file => {
113
+ db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash, last_read) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, file.path, 'content', 'hash', file.time.toISOString());
114
+ });
115
+ // Get recently modified files (last 2 hours)
116
+ const twoHoursAgo = new Date(now.getTime() - 7200000);
117
+ const recentFiles = db
118
+ .prepare(`SELECT * FROM file_cache
119
+ WHERE session_id = ?
120
+ AND datetime(last_read) > datetime(?)
121
+ ORDER BY last_read DESC`)
122
+ .all(sessionId, twoHoursAgo.toISOString());
123
+ expect(recentFiles).toHaveLength(2);
124
+ expect(recentFiles[0].file_path).toBe('/recent.ts');
125
+ expect(recentFiles[1].file_path).toBe('/older.ts');
126
+ });
127
+ it('should automatically create checkpoint before compaction', () => {
128
+ const sessionId = (0, uuid_1.v4)();
129
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Compaction Test');
130
+ // Add context items
131
+ for (let i = 0; i < 5; i++) {
132
+ db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, `key${i}`, `value${i}`);
133
+ }
134
+ // Simulate checkpoint creation
135
+ const checkpointId = (0, uuid_1.v4)();
136
+ db.prepare('INSERT INTO checkpoints (id, session_id, name, description) VALUES (?, ?, ?, ?)').run(checkpointId, sessionId, 'Pre-compaction checkpoint', 'Automatic checkpoint before context compaction');
137
+ // Link all items to checkpoint
138
+ const items = db
139
+ .prepare('SELECT id FROM context_items WHERE session_id = ?')
140
+ .all(sessionId);
141
+ items.forEach((item) => {
142
+ db.prepare('INSERT INTO checkpoint_items (id, checkpoint_id, context_item_id) VALUES (?, ?, ?)').run((0, uuid_1.v4)(), checkpointId, item.id);
143
+ });
144
+ // Verify checkpoint was created
145
+ const checkpoint = db
146
+ .prepare('SELECT * FROM checkpoints WHERE id = ?')
147
+ .get(checkpointId);
148
+ expect(checkpoint).toBeDefined();
149
+ expect(checkpoint.name).toContain('Pre-compaction');
150
+ // Verify all items are backed up
151
+ const backedUpCount = db
152
+ .prepare('SELECT COUNT(*) as count FROM checkpoint_items WHERE checkpoint_id = ?')
153
+ .get(checkpointId);
154
+ expect(backedUpCount.count).toBe(5);
155
+ });
156
+ it('should generate smart summary with categories', () => {
157
+ const sessionId = (0, uuid_1.v4)();
158
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Summary Test');
159
+ // Add items in different categories
160
+ const categories = ['task', 'decision', 'note', 'reference'];
161
+ categories.forEach(category => {
162
+ for (let i = 0; i < 3; i++) {
163
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, category) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, `${category}_${i}`, `${category} content ${i}`, category);
164
+ }
165
+ });
166
+ // Generate summary by category
167
+ const summary = categories.map(category => {
168
+ const items = db
169
+ .prepare('SELECT * FROM context_items WHERE session_id = ? AND category = ?')
170
+ .all(sessionId, category);
171
+ return {
172
+ category,
173
+ count: items.length,
174
+ items: items.map((i) => ({ key: i.key, value: i.value })),
175
+ };
176
+ });
177
+ expect(summary).toHaveLength(4);
178
+ expect(summary[0].category).toBe('task');
179
+ expect(summary[0].count).toBe(3);
180
+ expect(summary[1].category).toBe('decision');
181
+ expect(summary[1].count).toBe(3);
182
+ });
183
+ it('should identify next steps from tasks', () => {
184
+ const sessionId = (0, uuid_1.v4)();
185
+ db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sessionId, 'Next Steps Test');
186
+ // Add tasks with different priorities
187
+ const tasks = [
188
+ {
189
+ key: 'task_complete',
190
+ value: '[COMPLETED] Completed task',
191
+ category: 'task',
192
+ priority: 'low',
193
+ },
194
+ {
195
+ key: 'task_pending',
196
+ value: '[PENDING] Pending task',
197
+ category: 'task',
198
+ priority: 'normal',
199
+ },
200
+ {
201
+ key: 'task_inprogress',
202
+ value: '[IN PROGRESS] In progress task',
203
+ category: 'task',
204
+ priority: 'high',
205
+ },
206
+ {
207
+ key: 'task_blocked',
208
+ value: '[BLOCKED] Blocked task - waiting for review',
209
+ category: 'task',
210
+ priority: 'high',
211
+ },
212
+ ];
213
+ tasks.forEach(task => {
214
+ db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), sessionId, task.key, task.value, task.category, task.priority);
215
+ });
216
+ // Get high priority tasks as next steps
217
+ const nextSteps = db
218
+ .prepare(`SELECT * FROM context_items
219
+ WHERE session_id = ?
220
+ AND category = 'task'
221
+ AND priority = 'high'
222
+ ORDER BY created_at DESC`)
223
+ .all(sessionId);
224
+ expect(nextSteps).toHaveLength(2);
225
+ // Both high priority tasks should be included
226
+ expect(nextSteps.map((t) => t.key)).toContain('task_blocked');
227
+ expect(nextSteps.map((t) => t.key)).toContain('task_inprogress');
228
+ });
229
+ });
230
+ });