@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.
- package/CHANGELOG.md +542 -0
- package/LICENSE +21 -0
- package/README.md +1281 -0
- package/bin/mcp-memory-keeper +54 -0
- package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
- package/dist/__tests__/e2e/server-e2e.test.js +341 -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 +1054 -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/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 +411 -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/filterBySessionId.test.js +251 -0
- package/dist/__tests__/integration/git-integration.test.js +241 -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/issue24-final-fix.test.js +241 -0
- package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
- package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
- package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
- package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -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 +291 -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 +305 -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/tokenLimitEnforcement.test.js +134 -0
- package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -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 +192 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/token-limits.test.js +225 -0
- package/dist/__tests__/utils/tool-profiles.test.js +374 -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 +4425 -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 +2017 -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/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 +780 -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/token-limits.js +350 -0
- package/dist/utils/tool-profiles.js +242 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/examples/config.json +31 -0
- package/examples/project-directory-setup.md +114 -0
- 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
|
+
});
|