mcp-memory-keeper 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +433 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1051 -0
  4. package/bin/mcp-memory-keeper +52 -0
  5. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  6. package/dist/__tests__/helpers/test-server.js +92 -0
  7. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  8. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  9. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  10. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  11. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  12. package/dist/__tests__/integration/channels.test.js +376 -0
  13. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  14. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  15. package/dist/__tests__/integration/context-operations.test.js +243 -0
  16. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  17. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  18. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  19. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  20. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  21. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  22. package/dist/__tests__/integration/contextSearch.test.js +938 -0
  23. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  24. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  25. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  26. package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
  27. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  28. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  29. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  30. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  31. package/dist/__tests__/integration/error-cases.test.js +407 -0
  32. package/dist/__tests__/integration/export-import.test.js +367 -0
  33. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  34. package/dist/__tests__/integration/file-operations.test.js +264 -0
  35. package/dist/__tests__/integration/git-integration.test.js +237 -0
  36. package/dist/__tests__/integration/index-tools.test.js +496 -0
  37. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  38. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  39. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  40. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  41. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  42. package/dist/__tests__/integration/migrations.test.js +528 -0
  43. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  44. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  45. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  46. package/dist/__tests__/integration/project-directory.test.js +283 -0
  47. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  48. package/dist/__tests__/integration/retention.test.js +513 -0
  49. package/dist/__tests__/integration/search.test.js +333 -0
  50. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  51. package/dist/__tests__/integration/server-initialization.test.js +307 -0
  52. package/dist/__tests__/integration/session-management.test.js +219 -0
  53. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  54. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  55. package/dist/__tests__/integration/summarization.test.js +308 -0
  56. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  57. package/dist/__tests__/security/input-validation.test.js +115 -0
  58. package/dist/__tests__/utils/agents.test.js +473 -0
  59. package/dist/__tests__/utils/database.test.js +177 -0
  60. package/dist/__tests__/utils/git.test.js +122 -0
  61. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  62. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  63. package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
  64. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  65. package/dist/__tests__/utils/validation.test.js +200 -0
  66. package/dist/__tests__/utils/vector-store.test.js +231 -0
  67. package/dist/handlers/contextWatchHandlers.js +206 -0
  68. package/dist/index.js +4310 -0
  69. package/dist/index.phase1.backup.js +410 -0
  70. package/dist/index.phase2.backup.js +704 -0
  71. package/dist/migrations/003_add_channels.js +174 -0
  72. package/dist/migrations/004_add_context_watch.js +151 -0
  73. package/dist/migrations/005_add_context_watch.js +98 -0
  74. package/dist/migrations/simplify-sharing.js +117 -0
  75. package/dist/repositories/BaseRepository.js +30 -0
  76. package/dist/repositories/CheckpointRepository.js +140 -0
  77. package/dist/repositories/ContextRepository.js +1873 -0
  78. package/dist/repositories/FileRepository.js +104 -0
  79. package/dist/repositories/RepositoryManager.js +62 -0
  80. package/dist/repositories/SessionRepository.js +66 -0
  81. package/dist/repositories/WatcherRepository.js +252 -0
  82. package/dist/repositories/index.js +15 -0
  83. package/dist/server.js +384 -0
  84. package/dist/test-helpers/database-helper.js +128 -0
  85. package/dist/types/entities.js +3 -0
  86. package/dist/utils/agents.js +791 -0
  87. package/dist/utils/channels.js +150 -0
  88. package/dist/utils/database.js +731 -0
  89. package/dist/utils/feature-flags.js +476 -0
  90. package/dist/utils/git.js +145 -0
  91. package/dist/utils/knowledge-graph.js +264 -0
  92. package/dist/utils/migrationHealthCheck.js +373 -0
  93. package/dist/utils/migrations.js +452 -0
  94. package/dist/utils/retention.js +460 -0
  95. package/dist/utils/timestamps.js +112 -0
  96. package/dist/utils/validation.js +296 -0
  97. package/dist/utils/vector-store.js +247 -0
  98. package/package.json +84 -0
@@ -0,0 +1,452 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MigrationManager = void 0;
4
+ const uuid_1 = require("uuid");
5
+ class MigrationManager {
6
+ db;
7
+ constructor(db) {
8
+ this.db = db;
9
+ this.initializeMigrationTables();
10
+ }
11
+ initializeMigrationTables() {
12
+ this.db.getDatabase().exec(`
13
+ CREATE TABLE IF NOT EXISTS migrations (
14
+ id TEXT PRIMARY KEY,
15
+ version TEXT UNIQUE NOT NULL,
16
+ name TEXT NOT NULL,
17
+ description TEXT,
18
+ up_sql TEXT NOT NULL,
19
+ down_sql TEXT,
20
+ dependencies TEXT, -- JSON array
21
+ requires_backup BOOLEAN DEFAULT false,
22
+ checksum TEXT,
23
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
24
+ applied_at TIMESTAMP,
25
+ rollback_at TIMESTAMP
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS migration_log (
29
+ id TEXT PRIMARY KEY,
30
+ migration_id TEXT NOT NULL,
31
+ version TEXT NOT NULL,
32
+ action TEXT NOT NULL, -- 'apply', 'rollback', 'backup'
33
+ success BOOLEAN NOT NULL,
34
+ errors TEXT, -- JSON array
35
+ warnings TEXT, -- JSON array
36
+ execution_time INTEGER, -- milliseconds
37
+ rows_affected INTEGER,
38
+ backup_path TEXT,
39
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
40
+ FOREIGN KEY (migration_id) REFERENCES migrations(id)
41
+ );
42
+
43
+ CREATE INDEX IF NOT EXISTS idx_migrations_version ON migrations(version);
44
+ CREATE INDEX IF NOT EXISTS idx_migrations_applied ON migrations(applied_at);
45
+ CREATE INDEX IF NOT EXISTS idx_migration_log_version ON migration_log(version);
46
+ CREATE INDEX IF NOT EXISTS idx_migration_log_timestamp ON migration_log(timestamp);
47
+ `);
48
+ }
49
+ createMigration(migration) {
50
+ const id = (0, uuid_1.v4)();
51
+ const now = new Date().toISOString();
52
+ // Calculate checksum
53
+ const checksum = this.calculateChecksum(migration.up + (migration.down || ''));
54
+ const _migrationWithDefaults = {
55
+ id,
56
+ createdAt: now,
57
+ checksum,
58
+ requiresBackup: false,
59
+ ...migration,
60
+ };
61
+ this.db
62
+ .getDatabase()
63
+ .prepare(`
64
+ INSERT INTO migrations (
65
+ id, version, name, description, up_sql, down_sql, dependencies,
66
+ requires_backup, checksum, created_at
67
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
68
+ `)
69
+ .run(id, migration.version, migration.name, migration.description, migration.up, migration.down, migration.dependencies ? JSON.stringify(migration.dependencies) : null, migration.requiresBackup ? 1 : 0, checksum, now);
70
+ return id;
71
+ }
72
+ getMigration(version) {
73
+ const row = this.db
74
+ .getDatabase()
75
+ .prepare(`
76
+ SELECT * FROM migrations WHERE version = ?
77
+ `)
78
+ .get(version);
79
+ if (!row)
80
+ return null;
81
+ return this.rowToMigration(row);
82
+ }
83
+ listMigrations(options = {}) {
84
+ let query = 'SELECT * FROM migrations WHERE 1=1';
85
+ const params = [];
86
+ if (options.applied === true) {
87
+ query += ' AND applied_at IS NOT NULL';
88
+ }
89
+ else if (options.applied === false) {
90
+ query += ' AND applied_at IS NULL';
91
+ }
92
+ if (options.pending === true) {
93
+ query += ' AND applied_at IS NULL';
94
+ }
95
+ else if (options.pending === false) {
96
+ query += ' AND applied_at IS NOT NULL';
97
+ }
98
+ query += ' ORDER BY version';
99
+ if (options.limit) {
100
+ query += ' LIMIT ?';
101
+ params.push(options.limit);
102
+ }
103
+ const rows = this.db
104
+ .getDatabase()
105
+ .prepare(query)
106
+ .all(...params);
107
+ return rows.map(row => this.rowToMigration(row));
108
+ }
109
+ getStatus() {
110
+ const allMigrations = this.listMigrations();
111
+ const appliedMigrations = allMigrations.filter(m => m.appliedAt);
112
+ const pendingMigrations = allMigrations.filter(m => !m.appliedAt);
113
+ const lastMigration = appliedMigrations.sort((a, b) => (b.appliedAt || '').localeCompare(a.appliedAt || ''))[0];
114
+ return {
115
+ currentVersion: lastMigration?.version || '0.0.0',
116
+ totalMigrations: allMigrations.length,
117
+ appliedMigrations: appliedMigrations.length,
118
+ pendingMigrations: pendingMigrations.length,
119
+ pending: pendingMigrations.map(m => ({
120
+ version: m.version,
121
+ name: m.name,
122
+ requiresBackup: m.requiresBackup || false,
123
+ })),
124
+ applied: appliedMigrations.map(m => ({
125
+ version: m.version,
126
+ name: m.name,
127
+ appliedAt: m.appliedAt,
128
+ })),
129
+ lastMigration: lastMigration
130
+ ? {
131
+ version: lastMigration.version,
132
+ name: lastMigration.name,
133
+ appliedAt: lastMigration.appliedAt,
134
+ }
135
+ : undefined,
136
+ };
137
+ }
138
+ async applyMigration(version, options = {}) {
139
+ const startTime = Date.now();
140
+ const migration = this.getMigration(version);
141
+ if (!migration) {
142
+ throw new Error(`Migration not found: ${version}`);
143
+ }
144
+ if (migration.appliedAt) {
145
+ throw new Error(`Migration ${version} is already applied`);
146
+ }
147
+ const result = {
148
+ migrationId: migration.id,
149
+ version: migration.version,
150
+ name: migration.name,
151
+ success: false,
152
+ errors: [],
153
+ warnings: [],
154
+ executionTime: 0,
155
+ timestamp: new Date().toISOString(),
156
+ };
157
+ try {
158
+ // Check dependencies
159
+ if (migration.dependencies) {
160
+ for (const depVersion of migration.dependencies) {
161
+ const dep = this.getMigration(depVersion);
162
+ if (!dep || !dep.appliedAt) {
163
+ result.errors.push(`Dependency not satisfied: ${depVersion}`);
164
+ result.success = false;
165
+ result.executionTime = Date.now() - startTime;
166
+ this.logMigration(result, 'apply', result.backupCreated);
167
+ return result;
168
+ }
169
+ }
170
+ }
171
+ // Validate migration SQL
172
+ try {
173
+ this.validateSQL(migration.up);
174
+ }
175
+ catch (error) {
176
+ result.errors.push(error.message);
177
+ result.success = false;
178
+ result.executionTime = Date.now() - startTime;
179
+ this.logMigration(result, 'apply', result.backupCreated);
180
+ return result;
181
+ }
182
+ // Create backup if required or requested
183
+ let backupPath;
184
+ if ((migration.requiresBackup || options.createBackup) && !options.dryRun) {
185
+ backupPath = await this.createBackup(version);
186
+ result.backupCreated = backupPath;
187
+ }
188
+ if (!options.dryRun) {
189
+ // Execute migration
190
+ const db = this.db.getDatabase();
191
+ // Begin transaction
192
+ db.exec('BEGIN TRANSACTION');
193
+ try {
194
+ // Execute the migration SQL
195
+ db.exec(migration.up);
196
+ result.rowsAffected = db.changes || 0;
197
+ // Mark migration as applied
198
+ db.prepare(`
199
+ UPDATE migrations
200
+ SET applied_at = CURRENT_TIMESTAMP
201
+ WHERE id = ?
202
+ `).run(migration.id);
203
+ // Commit transaction
204
+ db.exec('COMMIT');
205
+ result.success = true;
206
+ }
207
+ catch (error) {
208
+ // Rollback transaction
209
+ db.exec('ROLLBACK');
210
+ throw error;
211
+ }
212
+ }
213
+ else {
214
+ result.success = true;
215
+ result.warnings.push('Dry run - no changes applied');
216
+ }
217
+ }
218
+ catch (error) {
219
+ result.errors.push(error.message);
220
+ result.success = false;
221
+ }
222
+ result.executionTime = Date.now() - startTime;
223
+ // Log the result
224
+ this.logMigration(result, 'apply', result.backupCreated);
225
+ return result;
226
+ }
227
+ async rollbackMigration(version, options = {}) {
228
+ const startTime = Date.now();
229
+ const migration = this.getMigration(version);
230
+ if (!migration) {
231
+ throw new Error(`Migration not found: ${version}`);
232
+ }
233
+ if (!migration.appliedAt) {
234
+ throw new Error(`Migration ${version} is not applied`);
235
+ }
236
+ if (!migration.down) {
237
+ throw new Error(`Migration ${version} has no rollback SQL`);
238
+ }
239
+ const result = {
240
+ migrationId: migration.id,
241
+ version: migration.version,
242
+ name: migration.name,
243
+ success: false,
244
+ errors: [],
245
+ warnings: [],
246
+ executionTime: 0,
247
+ timestamp: new Date().toISOString(),
248
+ };
249
+ try {
250
+ // Create backup if requested
251
+ let backupPath;
252
+ if (options.createBackup && !options.dryRun) {
253
+ backupPath = await this.createBackup(version);
254
+ result.backupCreated = backupPath;
255
+ }
256
+ // Validate rollback SQL
257
+ this.validateSQL(migration.down);
258
+ if (!options.dryRun) {
259
+ // Execute rollback
260
+ const db = this.db.getDatabase();
261
+ // Begin transaction
262
+ db.exec('BEGIN TRANSACTION');
263
+ try {
264
+ // Execute the rollback SQL
265
+ db.exec(migration.down);
266
+ result.rowsAffected = db.changes || 0;
267
+ // Mark migration as rolled back
268
+ db.prepare(`
269
+ UPDATE migrations
270
+ SET applied_at = NULL, rollback_at = CURRENT_TIMESTAMP
271
+ WHERE id = ?
272
+ `).run(migration.id);
273
+ // Commit transaction
274
+ db.exec('COMMIT');
275
+ result.success = true;
276
+ }
277
+ catch (error) {
278
+ // Rollback transaction
279
+ db.exec('ROLLBACK');
280
+ throw error;
281
+ }
282
+ }
283
+ else {
284
+ result.success = true;
285
+ result.warnings.push('Dry run - no changes applied');
286
+ }
287
+ }
288
+ catch (error) {
289
+ result.errors.push(error.message);
290
+ result.success = false;
291
+ }
292
+ result.executionTime = Date.now() - startTime;
293
+ // Log the result
294
+ this.logMigration(result, 'rollback', result.backupCreated);
295
+ return result;
296
+ }
297
+ async applyAllPending(options = {}) {
298
+ const pendingMigrations = this.listMigrations({ pending: true });
299
+ const results = [];
300
+ for (const migration of pendingMigrations) {
301
+ try {
302
+ const result = await this.applyMigration(migration.version, {
303
+ dryRun: options.dryRun,
304
+ createBackup: options.createBackups,
305
+ });
306
+ results.push(result);
307
+ if (!result.success && options.stopOnError) {
308
+ break;
309
+ }
310
+ }
311
+ catch (error) {
312
+ const errorResult = {
313
+ migrationId: migration.id,
314
+ version: migration.version,
315
+ name: migration.name,
316
+ success: false,
317
+ errors: [error.message],
318
+ warnings: [],
319
+ executionTime: 0,
320
+ timestamp: new Date().toISOString(),
321
+ };
322
+ results.push(errorResult);
323
+ if (options.stopOnError) {
324
+ break;
325
+ }
326
+ }
327
+ }
328
+ return results;
329
+ }
330
+ getMigrationLog(version, limit = 50) {
331
+ const query = version
332
+ ? 'SELECT * FROM migration_log WHERE version = ? ORDER BY timestamp DESC LIMIT ?'
333
+ : 'SELECT * FROM migration_log ORDER BY timestamp DESC LIMIT ?';
334
+ const params = version ? [version, limit] : [limit];
335
+ return this.db
336
+ .getDatabase()
337
+ .prepare(query)
338
+ .all(...params);
339
+ }
340
+ async createBackup(version) {
341
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
342
+ const backupPath = `context_backup_${version}_${timestamp}.db`;
343
+ // For SQLite, we can create a backup by copying the database file
344
+ // In a more complex implementation, you might use SQLite backup API
345
+ const fs = require('fs');
346
+ fs.copyFileSync(this.db.getDatabase().name, backupPath);
347
+ return backupPath;
348
+ }
349
+ validateSQL(sql) {
350
+ // Basic SQL validation
351
+ if (!sql || sql.trim().length === 0) {
352
+ throw new Error('Empty SQL statement');
353
+ }
354
+ // Check for potentially dangerous operations
355
+ const dangerousPatterns = [
356
+ /DROP\s+DATABASE/i,
357
+ /DELETE\s+FROM\s+\w+\s*;?\s*$/i, // DELETE without WHERE
358
+ /TRUNCATE/i,
359
+ ];
360
+ for (const pattern of dangerousPatterns) {
361
+ if (pattern.test(sql)) {
362
+ throw new Error('Potentially dangerous SQL detected');
363
+ }
364
+ }
365
+ }
366
+ calculateChecksum(content) {
367
+ const crypto = require('crypto');
368
+ return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
369
+ }
370
+ rowToMigration(row) {
371
+ return {
372
+ id: row.id,
373
+ version: row.version,
374
+ name: row.name,
375
+ description: row.description,
376
+ up: row.up_sql,
377
+ down: row.down_sql,
378
+ dependencies: row.dependencies ? JSON.parse(row.dependencies) : undefined,
379
+ requiresBackup: Boolean(row.requires_backup),
380
+ checksum: row.checksum,
381
+ createdAt: row.created_at,
382
+ appliedAt: row.applied_at,
383
+ rollbackAt: row.rollback_at,
384
+ };
385
+ }
386
+ logMigration(result, action, backupPath) {
387
+ this.db
388
+ .getDatabase()
389
+ .prepare(`
390
+ INSERT INTO migration_log (
391
+ id, migration_id, version, action, success, errors, warnings,
392
+ execution_time, rows_affected, backup_path
393
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
394
+ `)
395
+ .run((0, uuid_1.v4)(), result.migrationId, result.version, action, result.success ? 1 : 0, result.errors.length > 0 ? JSON.stringify(result.errors) : null, result.warnings.length > 0 ? JSON.stringify(result.warnings) : null, result.executionTime, result.rowsAffected, backupPath);
396
+ }
397
+ // Predefined migrations for common schema updates
398
+ static getDefaultMigrations() {
399
+ return [
400
+ {
401
+ version: '1.0.0',
402
+ name: 'Add size column to context_items',
403
+ description: 'Add size tracking for context items',
404
+ up: `
405
+ ALTER TABLE context_items ADD COLUMN size INTEGER DEFAULT 0;
406
+ UPDATE context_items SET size = LENGTH(value) WHERE size = 0;
407
+ `,
408
+ down: `
409
+ ALTER TABLE context_items DROP COLUMN size;
410
+ `,
411
+ },
412
+ {
413
+ version: '1.1.0',
414
+ name: 'Create compressed_context table',
415
+ description: 'Support for context compression',
416
+ up: `
417
+ CREATE TABLE IF NOT EXISTS compressed_context (
418
+ id TEXT PRIMARY KEY,
419
+ session_id TEXT NOT NULL,
420
+ original_count INTEGER NOT NULL,
421
+ compressed_data TEXT NOT NULL,
422
+ compression_ratio REAL NOT NULL,
423
+ date_range_start TIMESTAMP NOT NULL,
424
+ date_range_end TIMESTAMP NOT NULL,
425
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
426
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
427
+ );
428
+ CREATE INDEX IF NOT EXISTS idx_compressed_session ON compressed_context(session_id);
429
+ `,
430
+ down: `
431
+ DROP TABLE IF EXISTS compressed_context;
432
+ `,
433
+ dependencies: ['1.0.0'],
434
+ },
435
+ {
436
+ version: '1.2.0',
437
+ name: 'Add metadata support',
438
+ description: 'Add metadata column for extensibility',
439
+ up: `
440
+ ALTER TABLE context_items ADD COLUMN metadata TEXT;
441
+ ALTER TABLE sessions ADD COLUMN metadata TEXT;
442
+ `,
443
+ down: `
444
+ ALTER TABLE context_items DROP COLUMN metadata;
445
+ ALTER TABLE sessions DROP COLUMN metadata;
446
+ `,
447
+ dependencies: ['1.1.0'],
448
+ },
449
+ ];
450
+ }
451
+ }
452
+ exports.MigrationManager = MigrationManager;