mcp-memory-keeper 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +433 -0
- package/LICENSE +21 -0
- package/README.md +1051 -0
- package/bin/mcp-memory-keeper +52 -0
- package/dist/__tests__/helpers/database-test-helper.js +160 -0
- package/dist/__tests__/helpers/test-server.js +92 -0
- package/dist/__tests__/integration/advanced-features.test.js +614 -0
- package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
- package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
- package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
- package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
- package/dist/__tests__/integration/channels.test.js +376 -0
- package/dist/__tests__/integration/checkpoint.test.js +251 -0
- package/dist/__tests__/integration/concurrent-access.test.js +190 -0
- package/dist/__tests__/integration/context-operations.test.js +243 -0
- package/dist/__tests__/integration/contextDiff.test.js +852 -0
- package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
- package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
- package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
- package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
- package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
- package/dist/__tests__/integration/contextSearch.test.js +938 -0
- package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
- package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
- package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
- package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
- package/dist/__tests__/integration/database-initialization.test.js +134 -0
- package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
- package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
- package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
- package/dist/__tests__/integration/error-cases.test.js +407 -0
- package/dist/__tests__/integration/export-import.test.js +367 -0
- package/dist/__tests__/integration/feature-flags.test.js +542 -0
- package/dist/__tests__/integration/file-operations.test.js +264 -0
- package/dist/__tests__/integration/git-integration.test.js +237 -0
- package/dist/__tests__/integration/index-tools.test.js +496 -0
- package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
- package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
- package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
- package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
- package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
- package/dist/__tests__/integration/migrations.test.js +528 -0
- package/dist/__tests__/integration/multi-agent.test.js +546 -0
- package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
- package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
- package/dist/__tests__/integration/project-directory.test.js +283 -0
- package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
- package/dist/__tests__/integration/retention.test.js +513 -0
- package/dist/__tests__/integration/search.test.js +333 -0
- package/dist/__tests__/integration/semantic-search.test.js +266 -0
- package/dist/__tests__/integration/server-initialization.test.js +307 -0
- package/dist/__tests__/integration/session-management.test.js +219 -0
- package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
- package/dist/__tests__/integration/smart-compaction.test.js +230 -0
- package/dist/__tests__/integration/summarization.test.js +308 -0
- package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
- package/dist/__tests__/security/input-validation.test.js +115 -0
- package/dist/__tests__/utils/agents.test.js +473 -0
- package/dist/__tests__/utils/database.test.js +177 -0
- package/dist/__tests__/utils/git.test.js +122 -0
- package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
- package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
- package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/validation.test.js +200 -0
- package/dist/__tests__/utils/vector-store.test.js +231 -0
- package/dist/handlers/contextWatchHandlers.js +206 -0
- package/dist/index.js +4310 -0
- package/dist/index.phase1.backup.js +410 -0
- package/dist/index.phase2.backup.js +704 -0
- package/dist/migrations/003_add_channels.js +174 -0
- package/dist/migrations/004_add_context_watch.js +151 -0
- package/dist/migrations/005_add_context_watch.js +98 -0
- package/dist/migrations/simplify-sharing.js +117 -0
- package/dist/repositories/BaseRepository.js +30 -0
- package/dist/repositories/CheckpointRepository.js +140 -0
- package/dist/repositories/ContextRepository.js +1873 -0
- package/dist/repositories/FileRepository.js +104 -0
- package/dist/repositories/RepositoryManager.js +62 -0
- package/dist/repositories/SessionRepository.js +66 -0
- package/dist/repositories/WatcherRepository.js +252 -0
- package/dist/repositories/index.js +15 -0
- package/dist/server.js +384 -0
- package/dist/test-helpers/database-helper.js +128 -0
- package/dist/types/entities.js +3 -0
- package/dist/utils/agents.js +791 -0
- package/dist/utils/channels.js +150 -0
- package/dist/utils/database.js +731 -0
- package/dist/utils/feature-flags.js +476 -0
- package/dist/utils/git.js +145 -0
- package/dist/utils/knowledge-graph.js +264 -0
- package/dist/utils/migrationHealthCheck.js +373 -0
- package/dist/utils/migrations.js +452 -0
- package/dist/utils/retention.js +460 -0
- package/dist/utils/timestamps.js +112 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/package.json +84 -0
|
@@ -0,0 +1,614 @@
|
|
|
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('Advanced Features Integration Tests', () => {
|
|
42
|
+
let dbManager;
|
|
43
|
+
let tempDbPath;
|
|
44
|
+
let db;
|
|
45
|
+
let testSessionId;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
tempDbPath = path.join(os.tmpdir(), `test-advanced-${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 all necessary tables including Phase 4.4 enhancements
|
|
55
|
+
db.exec(`
|
|
56
|
+
-- Sessions table with parent_id for branching
|
|
57
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
58
|
+
id TEXT PRIMARY KEY,
|
|
59
|
+
name TEXT,
|
|
60
|
+
description TEXT,
|
|
61
|
+
branch TEXT,
|
|
62
|
+
parent_id TEXT,
|
|
63
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
64
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
65
|
+
FOREIGN KEY (parent_id) REFERENCES sessions(id)
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
-- Context items table
|
|
69
|
+
CREATE TABLE IF NOT EXISTS context_items (
|
|
70
|
+
id TEXT PRIMARY KEY,
|
|
71
|
+
session_id TEXT NOT NULL,
|
|
72
|
+
key TEXT NOT NULL,
|
|
73
|
+
value TEXT NOT NULL,
|
|
74
|
+
category TEXT,
|
|
75
|
+
priority TEXT DEFAULT 'normal',
|
|
76
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
77
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id),
|
|
78
|
+
UNIQUE(session_id, key)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
-- File cache table
|
|
82
|
+
CREATE TABLE IF NOT EXISTS file_cache (
|
|
83
|
+
id TEXT PRIMARY KEY,
|
|
84
|
+
session_id TEXT NOT NULL,
|
|
85
|
+
file_path TEXT NOT NULL,
|
|
86
|
+
content TEXT,
|
|
87
|
+
hash TEXT,
|
|
88
|
+
last_read TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
89
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id),
|
|
90
|
+
UNIQUE(session_id, file_path)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
-- Journal entries table
|
|
94
|
+
CREATE TABLE IF NOT EXISTS journal_entries (
|
|
95
|
+
id TEXT PRIMARY KEY,
|
|
96
|
+
session_id TEXT NOT NULL,
|
|
97
|
+
entry TEXT NOT NULL,
|
|
98
|
+
tags TEXT,
|
|
99
|
+
mood TEXT,
|
|
100
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
101
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
-- Compressed context table
|
|
105
|
+
CREATE TABLE IF NOT EXISTS compressed_context (
|
|
106
|
+
id TEXT PRIMARY KEY,
|
|
107
|
+
session_id TEXT NOT NULL,
|
|
108
|
+
original_count INTEGER NOT NULL,
|
|
109
|
+
compressed_data TEXT NOT NULL,
|
|
110
|
+
compression_ratio REAL NOT NULL,
|
|
111
|
+
date_range_start TIMESTAMP,
|
|
112
|
+
date_range_end TIMESTAMP,
|
|
113
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
114
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
-- Cross-tool integration events
|
|
118
|
+
CREATE TABLE IF NOT EXISTS tool_events (
|
|
119
|
+
id TEXT PRIMARY KEY,
|
|
120
|
+
session_id TEXT NOT NULL,
|
|
121
|
+
tool_name TEXT NOT NULL,
|
|
122
|
+
event_type TEXT NOT NULL,
|
|
123
|
+
data TEXT,
|
|
124
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
125
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
126
|
+
);
|
|
127
|
+
`);
|
|
128
|
+
// Create test session
|
|
129
|
+
testSessionId = (0, uuid_1.v4)();
|
|
130
|
+
db.prepare('INSERT INTO sessions (id, name, description) VALUES (?, ?, ?)').run(testSessionId, 'Advanced Features Test', 'Testing Phase 4.4 features');
|
|
131
|
+
});
|
|
132
|
+
afterEach(() => {
|
|
133
|
+
dbManager.close();
|
|
134
|
+
try {
|
|
135
|
+
fs.unlinkSync(tempDbPath);
|
|
136
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
137
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
138
|
+
}
|
|
139
|
+
catch (_e) {
|
|
140
|
+
// Ignore
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
describe('Session Branching', () => {
|
|
144
|
+
beforeEach(() => {
|
|
145
|
+
// Add test data to source session
|
|
146
|
+
const items = [
|
|
147
|
+
{ key: 'task_1', value: 'High priority task', category: 'task', priority: 'high' },
|
|
148
|
+
{ key: 'task_2', value: 'Normal priority task', category: 'task', priority: 'normal' },
|
|
149
|
+
{
|
|
150
|
+
key: 'decision_1',
|
|
151
|
+
value: 'Architecture decision',
|
|
152
|
+
category: 'decision',
|
|
153
|
+
priority: 'high',
|
|
154
|
+
},
|
|
155
|
+
{ key: 'note_1', value: 'Implementation note', category: 'note', priority: 'normal' },
|
|
156
|
+
];
|
|
157
|
+
for (const item of items) {
|
|
158
|
+
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);
|
|
159
|
+
}
|
|
160
|
+
// Add file cache
|
|
161
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), testSessionId, '/test/file.js', 'console.log("test");', 'abc123');
|
|
162
|
+
});
|
|
163
|
+
it('should create shallow branch with only high priority items', () => {
|
|
164
|
+
// Create branch
|
|
165
|
+
const branchName = 'feature-branch';
|
|
166
|
+
const branchId = (0, uuid_1.v4)();
|
|
167
|
+
db.prepare(`
|
|
168
|
+
INSERT INTO sessions (id, name, description, parent_id)
|
|
169
|
+
VALUES (?, ?, ?, ?)
|
|
170
|
+
`).run(branchId, branchName, `Branch of test session`, testSessionId);
|
|
171
|
+
// Copy high priority items
|
|
172
|
+
const highPriorityItems = db
|
|
173
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND priority = ?')
|
|
174
|
+
.all(testSessionId, 'high');
|
|
175
|
+
expect(highPriorityItems.length).toBe(2);
|
|
176
|
+
for (const item of highPriorityItems) {
|
|
177
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), branchId, item.key, item.value, item.category, item.priority);
|
|
178
|
+
}
|
|
179
|
+
// Verify branch has only high priority items
|
|
180
|
+
const branchItems = db
|
|
181
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
182
|
+
.all(branchId);
|
|
183
|
+
expect(branchItems.length).toBe(2);
|
|
184
|
+
expect(branchItems.every((item) => item.priority === 'high')).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
it('should create deep branch with all items and files', () => {
|
|
187
|
+
const branchName = 'full-branch';
|
|
188
|
+
const branchId = (0, uuid_1.v4)();
|
|
189
|
+
db.prepare(`
|
|
190
|
+
INSERT INTO sessions (id, name, description, parent_id)
|
|
191
|
+
VALUES (?, ?, ?, ?)
|
|
192
|
+
`).run(branchId, branchName, `Branch of test session`, testSessionId);
|
|
193
|
+
// Copy all items
|
|
194
|
+
const allItems = db
|
|
195
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
196
|
+
.all(testSessionId);
|
|
197
|
+
for (const item of allItems) {
|
|
198
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority) VALUES (?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), branchId, item.key, item.value, item.category, item.priority);
|
|
199
|
+
}
|
|
200
|
+
// Copy files
|
|
201
|
+
const files = db
|
|
202
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ?')
|
|
203
|
+
.all(testSessionId);
|
|
204
|
+
for (const file of files) {
|
|
205
|
+
db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash) VALUES (?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), branchId, file.file_path, file.content, file.hash);
|
|
206
|
+
}
|
|
207
|
+
// Verify
|
|
208
|
+
const branchItems = db
|
|
209
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
210
|
+
.all(branchId);
|
|
211
|
+
const branchFiles = db
|
|
212
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ?')
|
|
213
|
+
.all(branchId);
|
|
214
|
+
expect(branchItems.length).toBe(4);
|
|
215
|
+
expect(branchFiles.length).toBe(1);
|
|
216
|
+
});
|
|
217
|
+
it('should track parent-child relationship', () => {
|
|
218
|
+
const branchId = (0, uuid_1.v4)();
|
|
219
|
+
db.prepare(`
|
|
220
|
+
INSERT INTO sessions (id, name, parent_id)
|
|
221
|
+
VALUES (?, ?, ?)
|
|
222
|
+
`).run(branchId, 'child-branch', testSessionId);
|
|
223
|
+
const branch = db.prepare('SELECT * FROM sessions WHERE id = ?').get(branchId);
|
|
224
|
+
expect(branch.parent_id).toBe(testSessionId);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
describe('Session Merging', () => {
|
|
228
|
+
let sourceSessionId;
|
|
229
|
+
let targetSessionId;
|
|
230
|
+
beforeEach(() => {
|
|
231
|
+
// Create source and target sessions
|
|
232
|
+
sourceSessionId = (0, uuid_1.v4)();
|
|
233
|
+
targetSessionId = (0, uuid_1.v4)();
|
|
234
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(sourceSessionId, 'Source Session');
|
|
235
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(targetSessionId, 'Target Session');
|
|
236
|
+
// Add items to source
|
|
237
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), sourceSessionId, 'unique_item', 'Only in source');
|
|
238
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), sourceSessionId, 'shared_item', 'Source version');
|
|
239
|
+
// Add items to target
|
|
240
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), targetSessionId, 'shared_item', 'Target version');
|
|
241
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), targetSessionId, 'target_only', 'Only in target');
|
|
242
|
+
});
|
|
243
|
+
it('should merge with keep_current conflict resolution', () => {
|
|
244
|
+
const sourceItems = db
|
|
245
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
246
|
+
.all(sourceSessionId);
|
|
247
|
+
for (const item of sourceItems) {
|
|
248
|
+
const existing = db
|
|
249
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
250
|
+
.get(targetSessionId, item.key);
|
|
251
|
+
if (!existing) {
|
|
252
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), targetSessionId, item.key, item.value);
|
|
253
|
+
}
|
|
254
|
+
// keep_current means we don't update existing items
|
|
255
|
+
}
|
|
256
|
+
const finalItems = db
|
|
257
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
258
|
+
.all(targetSessionId);
|
|
259
|
+
const sharedItem = finalItems.find((i) => i.key === 'shared_item');
|
|
260
|
+
expect(finalItems.length).toBe(3);
|
|
261
|
+
expect(sharedItem.value).toBe('Target version'); // Kept current
|
|
262
|
+
});
|
|
263
|
+
it('should merge with keep_source conflict resolution', () => {
|
|
264
|
+
const sourceItems = db
|
|
265
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
266
|
+
.all(sourceSessionId);
|
|
267
|
+
for (const item of sourceItems) {
|
|
268
|
+
const existing = db
|
|
269
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
270
|
+
.get(targetSessionId, item.key);
|
|
271
|
+
if (existing) {
|
|
272
|
+
db.prepare('UPDATE context_items SET value = ? WHERE session_id = ? AND key = ?').run(item.value, targetSessionId, item.key);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), targetSessionId, item.key, item.value);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const finalItems = db
|
|
279
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
280
|
+
.all(targetSessionId);
|
|
281
|
+
const sharedItem = finalItems.find((i) => i.key === 'shared_item');
|
|
282
|
+
expect(finalItems.length).toBe(3);
|
|
283
|
+
expect(sharedItem.value).toBe('Source version'); // Replaced with source
|
|
284
|
+
});
|
|
285
|
+
it('should merge with keep_newest conflict resolution', () => {
|
|
286
|
+
// First, create the target item with an older timestamp
|
|
287
|
+
const oldDate = new Date();
|
|
288
|
+
oldDate.setHours(oldDate.getHours() - 1); // 1 hour ago
|
|
289
|
+
// Update target item to be older
|
|
290
|
+
db.prepare('UPDATE context_items SET created_at = ? WHERE session_id = ? AND key = ?').run(oldDate.toISOString(), targetSessionId, 'shared_item');
|
|
291
|
+
// Source item has current timestamp, so it's newer
|
|
292
|
+
const sourceItems = db
|
|
293
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
294
|
+
.all(sourceSessionId);
|
|
295
|
+
for (const item of sourceItems) {
|
|
296
|
+
const existing = db
|
|
297
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
298
|
+
.get(targetSessionId, item.key);
|
|
299
|
+
if (existing) {
|
|
300
|
+
if (new Date(item.created_at) > new Date(existing.created_at)) {
|
|
301
|
+
db.prepare('UPDATE context_items SET value = ? WHERE session_id = ? AND key = ?').run(item.value, targetSessionId, item.key);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value) VALUES (?, ?, ?, ?)').run((0, uuid_1.v4)(), targetSessionId, item.key, item.value);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const finalItems = db
|
|
309
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
310
|
+
.all(targetSessionId);
|
|
311
|
+
const sharedItem = finalItems.find((i) => i.key === 'shared_item');
|
|
312
|
+
expect(finalItems.length).toBe(3);
|
|
313
|
+
expect(sharedItem.value).toBe('Source version'); // Source was newer
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
describe('Journal Entries', () => {
|
|
317
|
+
it('should create journal entry with tags and mood', () => {
|
|
318
|
+
const entry = 'Had a productive day working on the authentication module';
|
|
319
|
+
const tags = ['productivity', 'authentication', 'backend'];
|
|
320
|
+
const mood = 'accomplished';
|
|
321
|
+
const id = (0, uuid_1.v4)();
|
|
322
|
+
db.prepare(`
|
|
323
|
+
INSERT INTO journal_entries (id, session_id, entry, tags, mood)
|
|
324
|
+
VALUES (?, ?, ?, ?, ?)
|
|
325
|
+
`).run(id, testSessionId, entry, JSON.stringify(tags), mood);
|
|
326
|
+
const saved = db.prepare('SELECT * FROM journal_entries WHERE id = ?').get(id);
|
|
327
|
+
expect(saved.entry).toBe(entry);
|
|
328
|
+
expect(JSON.parse(saved.tags)).toEqual(tags);
|
|
329
|
+
expect(saved.mood).toBe(mood);
|
|
330
|
+
});
|
|
331
|
+
it('should retrieve journal entries by session', () => {
|
|
332
|
+
// Add multiple entries
|
|
333
|
+
const entries = [
|
|
334
|
+
{ entry: 'Morning standup went well', mood: 'positive', tags: ['meeting'] },
|
|
335
|
+
{ entry: 'Debugging session was challenging', mood: 'frustrated', tags: ['debugging'] },
|
|
336
|
+
{ entry: 'Fixed the bug!', mood: 'excited', tags: ['debugging', 'success'] },
|
|
337
|
+
];
|
|
338
|
+
for (const e of entries) {
|
|
339
|
+
db.prepare(`
|
|
340
|
+
INSERT INTO journal_entries (id, session_id, entry, tags, mood)
|
|
341
|
+
VALUES (?, ?, ?, ?, ?)
|
|
342
|
+
`).run((0, uuid_1.v4)(), testSessionId, e.entry, JSON.stringify(e.tags), e.mood);
|
|
343
|
+
}
|
|
344
|
+
const journalEntries = db
|
|
345
|
+
.prepare('SELECT * FROM journal_entries WHERE session_id = ? ORDER BY created_at')
|
|
346
|
+
.all(testSessionId);
|
|
347
|
+
expect(journalEntries.length).toBe(3);
|
|
348
|
+
expect(journalEntries[0].entry).toBe('Morning standup went well');
|
|
349
|
+
expect(journalEntries[2].mood).toBe('excited');
|
|
350
|
+
});
|
|
351
|
+
it('should filter journal entries by date range', () => {
|
|
352
|
+
const yesterday = new Date();
|
|
353
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
354
|
+
const tomorrow = new Date();
|
|
355
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
356
|
+
// Add entry
|
|
357
|
+
db.prepare(`
|
|
358
|
+
INSERT INTO journal_entries (id, session_id, entry)
|
|
359
|
+
VALUES (?, ?, ?)
|
|
360
|
+
`).run((0, uuid_1.v4)(), testSessionId, 'Today entry');
|
|
361
|
+
const entries = db
|
|
362
|
+
.prepare('SELECT * FROM journal_entries WHERE session_id = ? AND created_at >= ? AND created_at <= ?')
|
|
363
|
+
.all(testSessionId, yesterday.toISOString(), tomorrow.toISOString());
|
|
364
|
+
expect(entries.length).toBe(1);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
describe('Timeline Generation', () => {
|
|
368
|
+
beforeEach(() => {
|
|
369
|
+
// Create items across different dates
|
|
370
|
+
const dates = [
|
|
371
|
+
new Date('2024-01-15T10:00:00'),
|
|
372
|
+
new Date('2024-01-15T14:00:00'),
|
|
373
|
+
new Date('2024-01-16T09:00:00'),
|
|
374
|
+
new Date('2024-01-16T11:00:00'),
|
|
375
|
+
new Date('2024-01-16T15:00:00'),
|
|
376
|
+
];
|
|
377
|
+
const categories = ['task', 'decision', 'task', 'progress', 'note'];
|
|
378
|
+
dates.forEach((date, index) => {
|
|
379
|
+
db.prepare(`
|
|
380
|
+
INSERT INTO context_items (id, session_id, key, value, category, created_at)
|
|
381
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
382
|
+
`).run((0, uuid_1.v4)(), testSessionId, `item_${index}`, `Item ${index}`, categories[index], date.toISOString());
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
it('should group timeline by day', () => {
|
|
386
|
+
const timeline = db
|
|
387
|
+
.prepare(`
|
|
388
|
+
SELECT
|
|
389
|
+
strftime('%Y-%m-%d', created_at) as date,
|
|
390
|
+
COUNT(*) as count,
|
|
391
|
+
category
|
|
392
|
+
FROM context_items
|
|
393
|
+
WHERE session_id = ?
|
|
394
|
+
GROUP BY date, category
|
|
395
|
+
ORDER BY date
|
|
396
|
+
`)
|
|
397
|
+
.all(testSessionId);
|
|
398
|
+
const dateGroups = {};
|
|
399
|
+
for (const item of timeline) {
|
|
400
|
+
if (!dateGroups[item.date]) {
|
|
401
|
+
dateGroups[item.date] = { total: 0, categories: {} };
|
|
402
|
+
}
|
|
403
|
+
dateGroups[item.date].categories[item.category] = item.count;
|
|
404
|
+
dateGroups[item.date].total += item.count;
|
|
405
|
+
}
|
|
406
|
+
expect(Object.keys(dateGroups).length).toBe(2); // 2 different days
|
|
407
|
+
expect(dateGroups['2024-01-15'].total).toBe(2);
|
|
408
|
+
expect(dateGroups['2024-01-16'].total).toBe(3);
|
|
409
|
+
});
|
|
410
|
+
it('should group timeline by hour', () => {
|
|
411
|
+
const timeline = db
|
|
412
|
+
.prepare(`
|
|
413
|
+
SELECT
|
|
414
|
+
strftime('%Y-%m-%d', created_at) as date,
|
|
415
|
+
strftime('%H', created_at) as hour,
|
|
416
|
+
COUNT(*) as count
|
|
417
|
+
FROM context_items
|
|
418
|
+
WHERE session_id = ?
|
|
419
|
+
GROUP BY date, hour
|
|
420
|
+
ORDER BY date, hour
|
|
421
|
+
`)
|
|
422
|
+
.all(testSessionId);
|
|
423
|
+
expect(timeline.length).toBe(5); // 5 different hours
|
|
424
|
+
// Just verify we have different hours, don't check specific values due to timezone
|
|
425
|
+
const hours = timeline.map((t) => parseInt(t.hour));
|
|
426
|
+
expect(new Set(hours).size).toBe(5); // All hours are different
|
|
427
|
+
});
|
|
428
|
+
it('should include journal entries in timeline', () => {
|
|
429
|
+
// Add journal entry
|
|
430
|
+
db.prepare(`
|
|
431
|
+
INSERT INTO journal_entries (id, session_id, entry, created_at)
|
|
432
|
+
VALUES (?, ?, ?, ?)
|
|
433
|
+
`).run((0, uuid_1.v4)(), testSessionId, 'Daily reflection', '2024-01-15T20:00:00');
|
|
434
|
+
const journals = db
|
|
435
|
+
.prepare('SELECT * FROM journal_entries WHERE session_id = ? ORDER BY created_at')
|
|
436
|
+
.all(testSessionId);
|
|
437
|
+
expect(journals.length).toBe(1);
|
|
438
|
+
expect(journals[0].entry).toBe('Daily reflection');
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
describe('Progressive Compression', () => {
|
|
442
|
+
beforeEach(() => {
|
|
443
|
+
// Create old and new items
|
|
444
|
+
const oldDate = new Date();
|
|
445
|
+
oldDate.setDate(oldDate.getDate() - 30);
|
|
446
|
+
const items = [
|
|
447
|
+
{
|
|
448
|
+
key: 'old_task_1',
|
|
449
|
+
value: 'Old task 1',
|
|
450
|
+
created_at: oldDate.toISOString(),
|
|
451
|
+
category: 'task',
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
key: 'old_task_2',
|
|
455
|
+
value: 'Old task 2',
|
|
456
|
+
created_at: oldDate.toISOString(),
|
|
457
|
+
category: 'task',
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
key: 'old_decision',
|
|
461
|
+
value: 'Old decision',
|
|
462
|
+
created_at: oldDate.toISOString(),
|
|
463
|
+
category: 'decision',
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
key: 'recent_task',
|
|
467
|
+
value: 'Recent task',
|
|
468
|
+
created_at: new Date().toISOString(),
|
|
469
|
+
category: 'task',
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
key: 'preserve_me',
|
|
473
|
+
value: 'Important decision',
|
|
474
|
+
created_at: oldDate.toISOString(),
|
|
475
|
+
category: 'critical',
|
|
476
|
+
},
|
|
477
|
+
];
|
|
478
|
+
for (const item of items) {
|
|
479
|
+
db.prepare(`
|
|
480
|
+
INSERT INTO context_items (id, session_id, key, value, category, created_at)
|
|
481
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
482
|
+
`).run((0, uuid_1.v4)(), testSessionId, item.key, item.value, item.category, item.created_at);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
it('should compress old items', () => {
|
|
486
|
+
const cutoffDate = new Date();
|
|
487
|
+
cutoffDate.setDate(cutoffDate.getDate() - 7);
|
|
488
|
+
const itemsToCompress = db
|
|
489
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND created_at < ?')
|
|
490
|
+
.all(testSessionId, cutoffDate.toISOString());
|
|
491
|
+
expect(itemsToCompress.length).toBe(4); // All old items
|
|
492
|
+
// Group by category
|
|
493
|
+
const categoryGroups = {};
|
|
494
|
+
for (const item of itemsToCompress) {
|
|
495
|
+
const category = item.category || 'uncategorized';
|
|
496
|
+
if (!categoryGroups[category]) {
|
|
497
|
+
categoryGroups[category] = [];
|
|
498
|
+
}
|
|
499
|
+
categoryGroups[category].push(item);
|
|
500
|
+
}
|
|
501
|
+
expect(Object.keys(categoryGroups).length).toBe(3); // task, decision, critical
|
|
502
|
+
expect(categoryGroups.task.length).toBe(2);
|
|
503
|
+
});
|
|
504
|
+
it('should preserve specified categories', () => {
|
|
505
|
+
const cutoffDate = new Date();
|
|
506
|
+
cutoffDate.setDate(cutoffDate.getDate() - 7);
|
|
507
|
+
const preserveCategories = ['critical', 'decision'];
|
|
508
|
+
const query = `
|
|
509
|
+
SELECT * FROM context_items
|
|
510
|
+
WHERE session_id = ?
|
|
511
|
+
AND created_at < ?
|
|
512
|
+
AND category NOT IN (${preserveCategories.map(() => '?').join(',')})
|
|
513
|
+
`;
|
|
514
|
+
const itemsToCompress = db
|
|
515
|
+
.prepare(query)
|
|
516
|
+
.all(testSessionId, cutoffDate.toISOString(), ...preserveCategories);
|
|
517
|
+
expect(itemsToCompress.length).toBe(2); // Only old tasks
|
|
518
|
+
expect(itemsToCompress.every((item) => item.category === 'task')).toBe(true);
|
|
519
|
+
});
|
|
520
|
+
it('should calculate compression ratio', () => {
|
|
521
|
+
const items = db
|
|
522
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
523
|
+
.all(testSessionId);
|
|
524
|
+
const compressed = {
|
|
525
|
+
categories: {
|
|
526
|
+
task: { count: 3, samples: [] },
|
|
527
|
+
decision: { count: 1, samples: [] },
|
|
528
|
+
critical: { count: 1, samples: [] },
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
const originalSize = JSON.stringify(items).length;
|
|
532
|
+
const compressedSize = JSON.stringify(compressed).length;
|
|
533
|
+
const compressionRatio = 1 - compressedSize / originalSize;
|
|
534
|
+
expect(compressionRatio).toBeGreaterThan(0.5); // Should achieve >50% compression
|
|
535
|
+
});
|
|
536
|
+
it('should store compressed data', () => {
|
|
537
|
+
const compressedData = JSON.stringify({
|
|
538
|
+
categories: { task: { count: 2 } },
|
|
539
|
+
});
|
|
540
|
+
const id = (0, uuid_1.v4)();
|
|
541
|
+
const now = new Date().toISOString();
|
|
542
|
+
db.prepare(`
|
|
543
|
+
INSERT INTO compressed_context (id, session_id, original_count, compressed_data, compression_ratio, date_range_start, date_range_end)
|
|
544
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
545
|
+
`).run(id, testSessionId, 2, compressedData, 0.75, now, now);
|
|
546
|
+
const saved = db.prepare('SELECT * FROM compressed_context WHERE id = ?').get(id);
|
|
547
|
+
expect(saved.original_count).toBe(2);
|
|
548
|
+
expect(saved.compression_ratio).toBe(0.75);
|
|
549
|
+
expect(JSON.parse(saved.compressed_data).categories.task.count).toBe(2);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
describe('Cross-Tool Integration', () => {
|
|
553
|
+
it('should record tool events', () => {
|
|
554
|
+
const toolName = 'code-analyzer';
|
|
555
|
+
const eventType = 'analysis-complete';
|
|
556
|
+
const data = { files: 10, issues: 3 };
|
|
557
|
+
const id = (0, uuid_1.v4)();
|
|
558
|
+
db.prepare(`
|
|
559
|
+
INSERT INTO tool_events (id, session_id, tool_name, event_type, data)
|
|
560
|
+
VALUES (?, ?, ?, ?, ?)
|
|
561
|
+
`).run(id, testSessionId, toolName, eventType, JSON.stringify(data));
|
|
562
|
+
const saved = db.prepare('SELECT * FROM tool_events WHERE id = ?').get(id);
|
|
563
|
+
expect(saved.tool_name).toBe(toolName);
|
|
564
|
+
expect(saved.event_type).toBe(eventType);
|
|
565
|
+
expect(JSON.parse(saved.data)).toEqual(data);
|
|
566
|
+
});
|
|
567
|
+
it('should create context item for important events', () => {
|
|
568
|
+
const toolName = 'security-scanner';
|
|
569
|
+
const eventType = 'vulnerability-found';
|
|
570
|
+
const data = {
|
|
571
|
+
severity: 'high',
|
|
572
|
+
file: 'auth.js',
|
|
573
|
+
important: true,
|
|
574
|
+
};
|
|
575
|
+
// Record event
|
|
576
|
+
const eventId = (0, uuid_1.v4)();
|
|
577
|
+
db.prepare(`
|
|
578
|
+
INSERT INTO tool_events (id, session_id, tool_name, event_type, data)
|
|
579
|
+
VALUES (?, ?, ?, ?, ?)
|
|
580
|
+
`).run(eventId, testSessionId, toolName, eventType, JSON.stringify(data));
|
|
581
|
+
// Create context item for important event
|
|
582
|
+
db.prepare(`
|
|
583
|
+
INSERT INTO context_items (id, session_id, key, value, category, priority)
|
|
584
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
585
|
+
`).run((0, uuid_1.v4)(), testSessionId, `${toolName}_${eventType}_${Date.now()}`, `Tool event: ${toolName} - ${eventType}: ${JSON.stringify(data)}`, 'tool_event', 'high');
|
|
586
|
+
const contextItems = db
|
|
587
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND category = ?')
|
|
588
|
+
.all(testSessionId, 'tool_event');
|
|
589
|
+
expect(contextItems.length).toBe(1);
|
|
590
|
+
expect(contextItems[0].priority).toBe('high');
|
|
591
|
+
expect(contextItems[0].value).toContain('vulnerability-found');
|
|
592
|
+
});
|
|
593
|
+
it('should handle events from multiple tools', () => {
|
|
594
|
+
const events = [
|
|
595
|
+
{ tool: 'linter', type: 'scan-complete', data: { warnings: 5 } },
|
|
596
|
+
{ tool: 'test-runner', type: 'tests-passed', data: { total: 100, passed: 100 } },
|
|
597
|
+
{ tool: 'build-tool', type: 'build-success', data: { duration: '45s' } },
|
|
598
|
+
];
|
|
599
|
+
for (const event of events) {
|
|
600
|
+
db.prepare(`
|
|
601
|
+
INSERT INTO tool_events (id, session_id, tool_name, event_type, data)
|
|
602
|
+
VALUES (?, ?, ?, ?, ?)
|
|
603
|
+
`).run((0, uuid_1.v4)(), testSessionId, event.tool, event.type, JSON.stringify(event.data));
|
|
604
|
+
}
|
|
605
|
+
const allEvents = db
|
|
606
|
+
.prepare('SELECT * FROM tool_events WHERE session_id = ?')
|
|
607
|
+
.all(testSessionId);
|
|
608
|
+
expect(allEvents.length).toBe(3);
|
|
609
|
+
expect(allEvents.map((e) => e.tool_name)).toContain('linter');
|
|
610
|
+
expect(allEvents.map((e) => e.tool_name)).toContain('test-runner');
|
|
611
|
+
expect(allEvents.map((e) => e.tool_name)).toContain('build-tool');
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
});
|