@smallironman/mcp-memory-keeper 0.12.2-fork1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +542 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1281 -0
  4. package/bin/mcp-memory-keeper +54 -0
  5. package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
  6. package/dist/__tests__/e2e/server-e2e.test.js +341 -0
  7. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  8. package/dist/__tests__/helpers/test-server.js +92 -0
  9. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  10. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  11. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  12. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  13. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  14. package/dist/__tests__/integration/channels.test.js +376 -0
  15. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  16. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  17. package/dist/__tests__/integration/context-operations.test.js +243 -0
  18. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  19. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  20. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  21. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  22. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  23. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  24. package/dist/__tests__/integration/contextSearch.test.js +1054 -0
  25. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  26. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  27. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  28. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  29. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  30. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  31. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  32. package/dist/__tests__/integration/error-cases.test.js +411 -0
  33. package/dist/__tests__/integration/export-import.test.js +367 -0
  34. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  35. package/dist/__tests__/integration/file-operations.test.js +264 -0
  36. package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
  37. package/dist/__tests__/integration/git-integration.test.js +241 -0
  38. package/dist/__tests__/integration/index-tools.test.js +496 -0
  39. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  40. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  41. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  42. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  43. package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
  44. package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
  45. package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
  46. package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
  47. package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
  48. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  49. package/dist/__tests__/integration/migrations.test.js +528 -0
  50. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  51. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  52. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  53. package/dist/__tests__/integration/project-directory.test.js +291 -0
  54. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  55. package/dist/__tests__/integration/retention.test.js +513 -0
  56. package/dist/__tests__/integration/search.test.js +333 -0
  57. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  58. package/dist/__tests__/integration/server-initialization.test.js +305 -0
  59. package/dist/__tests__/integration/session-management.test.js +219 -0
  60. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  61. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  62. package/dist/__tests__/integration/summarization.test.js +308 -0
  63. package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
  64. package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
  65. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  66. package/dist/__tests__/security/input-validation.test.js +115 -0
  67. package/dist/__tests__/utils/agents.test.js +473 -0
  68. package/dist/__tests__/utils/database.test.js +177 -0
  69. package/dist/__tests__/utils/git.test.js +122 -0
  70. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  71. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  72. package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
  73. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  74. package/dist/__tests__/utils/token-limits.test.js +225 -0
  75. package/dist/__tests__/utils/tool-profiles.test.js +374 -0
  76. package/dist/__tests__/utils/validation.test.js +200 -0
  77. package/dist/__tests__/utils/vector-store.test.js +231 -0
  78. package/dist/handlers/contextWatchHandlers.js +206 -0
  79. package/dist/index.js +4425 -0
  80. package/dist/migrations/003_add_channels.js +174 -0
  81. package/dist/migrations/004_add_context_watch.js +151 -0
  82. package/dist/migrations/005_add_context_watch.js +98 -0
  83. package/dist/migrations/simplify-sharing.js +117 -0
  84. package/dist/repositories/BaseRepository.js +30 -0
  85. package/dist/repositories/CheckpointRepository.js +140 -0
  86. package/dist/repositories/ContextRepository.js +2017 -0
  87. package/dist/repositories/FileRepository.js +104 -0
  88. package/dist/repositories/RepositoryManager.js +62 -0
  89. package/dist/repositories/SessionRepository.js +66 -0
  90. package/dist/repositories/WatcherRepository.js +252 -0
  91. package/dist/repositories/index.js +15 -0
  92. package/dist/test-helpers/database-helper.js +128 -0
  93. package/dist/types/entities.js +3 -0
  94. package/dist/utils/agents.js +791 -0
  95. package/dist/utils/channels.js +150 -0
  96. package/dist/utils/database.js +780 -0
  97. package/dist/utils/feature-flags.js +476 -0
  98. package/dist/utils/git.js +145 -0
  99. package/dist/utils/knowledge-graph.js +264 -0
  100. package/dist/utils/migrationHealthCheck.js +373 -0
  101. package/dist/utils/migrations.js +452 -0
  102. package/dist/utils/retention.js +460 -0
  103. package/dist/utils/timestamps.js +112 -0
  104. package/dist/utils/token-limits.js +350 -0
  105. package/dist/utils/tool-profiles.js +242 -0
  106. package/dist/utils/validation.js +296 -0
  107. package/dist/utils/vector-store.js +247 -0
  108. package/examples/config.json +31 -0
  109. package/examples/project-directory-setup.md +114 -0
  110. package/package.json +85 -0
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KnowledgeGraphManager = void 0;
4
+ const uuid_1 = require("uuid");
5
+ class KnowledgeGraphManager {
6
+ db;
7
+ constructor(db) {
8
+ this.db = db;
9
+ }
10
+ // Entity operations
11
+ createEntity(sessionId, type, name, attributes) {
12
+ const id = (0, uuid_1.v4)();
13
+ const stmt = this.db.prepare(`
14
+ INSERT INTO entities (id, session_id, type, name, attributes)
15
+ VALUES (?, ?, ?, ?, ?)
16
+ `);
17
+ stmt.run(id, sessionId, type, name, attributes ? JSON.stringify(attributes) : null);
18
+ return { id, session_id: sessionId, type, name, attributes };
19
+ }
20
+ findEntity(sessionId, name, type) {
21
+ let query = 'SELECT * FROM entities WHERE session_id = ? AND name = ?';
22
+ const params = [sessionId, name];
23
+ if (type) {
24
+ query += ' AND type = ?';
25
+ params.push(type);
26
+ }
27
+ const row = this.db.prepare(query).get(...params);
28
+ if (!row)
29
+ return null;
30
+ return {
31
+ ...row,
32
+ attributes: row.attributes ? JSON.parse(row.attributes) : undefined,
33
+ };
34
+ }
35
+ getEntitiesByType(sessionId, type) {
36
+ const rows = this.db
37
+ .prepare('SELECT * FROM entities WHERE session_id = ? AND type = ? ORDER BY created_at DESC')
38
+ .all(sessionId, type);
39
+ return rows.map(row => ({
40
+ ...row,
41
+ attributes: row.attributes ? JSON.parse(row.attributes) : undefined,
42
+ }));
43
+ }
44
+ // Relation operations
45
+ createRelation(sessionId, subjectId, predicate, objectId, confidence = 1.0) {
46
+ const id = (0, uuid_1.v4)();
47
+ const stmt = this.db.prepare(`
48
+ INSERT INTO relations (id, session_id, subject_id, predicate, object_id, confidence)
49
+ VALUES (?, ?, ?, ?, ?, ?)
50
+ `);
51
+ stmt.run(id, sessionId, subjectId, predicate, objectId, confidence);
52
+ return {
53
+ id,
54
+ session_id: sessionId,
55
+ subject_id: subjectId,
56
+ predicate,
57
+ object_id: objectId,
58
+ confidence,
59
+ };
60
+ }
61
+ getRelations(entityId, direction = 'both') {
62
+ let query = '';
63
+ const params = [];
64
+ if (direction === 'subject' || direction === 'both') {
65
+ query = 'SELECT * FROM relations WHERE subject_id = ?';
66
+ params.push(entityId);
67
+ }
68
+ if (direction === 'object') {
69
+ query = 'SELECT * FROM relations WHERE object_id = ?';
70
+ params.push(entityId);
71
+ }
72
+ else if (direction === 'both') {
73
+ query += ' UNION SELECT * FROM relations WHERE object_id = ?';
74
+ params.push(entityId);
75
+ }
76
+ return this.db.prepare(query).all(...params);
77
+ }
78
+ // Observation operations
79
+ addObservation(entityId, observation, source) {
80
+ const id = (0, uuid_1.v4)();
81
+ const stmt = this.db.prepare(`
82
+ INSERT INTO observations (id, entity_id, observation, source)
83
+ VALUES (?, ?, ?, ?)
84
+ `);
85
+ stmt.run(id, entityId, observation, source || null);
86
+ return { id, entity_id: entityId, observation, source };
87
+ }
88
+ getObservations(entityId) {
89
+ return this.db
90
+ .prepare('SELECT * FROM observations WHERE entity_id = ? ORDER BY timestamp DESC')
91
+ .all(entityId);
92
+ }
93
+ // Graph traversal
94
+ getConnectedEntities(entityId, maxDepth = 2) {
95
+ const visited = new Set();
96
+ const queue = [{ id: entityId, depth: 0 }];
97
+ while (queue.length > 0) {
98
+ const { id, depth } = queue.shift();
99
+ if (visited.has(id) || depth > maxDepth)
100
+ continue;
101
+ visited.add(id);
102
+ const relations = this.getRelations(id);
103
+ for (const rel of relations) {
104
+ const nextId = rel.subject_id === id ? rel.object_id : rel.subject_id;
105
+ if (!visited.has(nextId)) {
106
+ queue.push({ id: nextId, depth: depth + 1 });
107
+ }
108
+ }
109
+ }
110
+ return visited;
111
+ }
112
+ // Analysis operations
113
+ analyzeContext(sessionId, text) {
114
+ const entities = [];
115
+ const relations = [];
116
+ // Simple pattern matching for common entities
117
+ // Files - multiple patterns to catch various mentions
118
+ const filePatterns = [
119
+ /(?:file|module|component)\s+(?:called\s+)?["`']?([a-zA-Z0-9_\-./]+\.[a-zA-Z]+)["`']?/gi,
120
+ /(?:modified|updated|created)\s+(?:files?\s+)?([a-zA-Z0-9_\-./]+\.[a-zA-Z]+)/gi,
121
+ /([a-zA-Z0-9_\-./]+\.[a-zA-Z]+)\s+(?:which|that|file)/gi,
122
+ /(?:from|imports?)\s+([a-zA-Z0-9_\-./]+\.[a-zA-Z]+)/gi,
123
+ /([a-zA-Z0-9_\-./]+\.(?:ts|js|tsx|jsx|py|java|cpp|cs|rb|go))\b/gi,
124
+ ];
125
+ let match;
126
+ const foundFiles = new Set();
127
+ for (const pattern of filePatterns) {
128
+ pattern.lastIndex = 0; // Reset regex
129
+ while ((match = pattern.exec(text)) !== null) {
130
+ if (!foundFiles.has(match[1])) {
131
+ entities.push({ type: 'file', name: match[1], confidence: 0.9 });
132
+ foundFiles.add(match[1]);
133
+ }
134
+ }
135
+ }
136
+ // Functions/Methods - multiple patterns
137
+ const functionPatterns = [
138
+ /(?:function|method|def)\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
139
+ /(?:const|let|var)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(?:function|\()/g,
140
+ /([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*function/g,
141
+ /[Tt]he\s+(?:function|method)\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
142
+ ];
143
+ const foundFunctions = new Set();
144
+ for (const pattern of functionPatterns) {
145
+ pattern.lastIndex = 0;
146
+ while ((match = pattern.exec(text)) !== null) {
147
+ if (!foundFunctions.has(match[1])) {
148
+ entities.push({ type: 'function', name: match[1], confidence: 0.8 });
149
+ foundFunctions.add(match[1]);
150
+ }
151
+ }
152
+ }
153
+ // Classes/Interfaces/Services - case sensitive for better matching
154
+ const classPatterns = [
155
+ /(?:class|interface|type)\s+([A-Z][a-zA-Z0-9_]*)/g,
156
+ /([A-Z][a-zA-Z0-9_]*)\s+(?:class|interface|service|model|controller|repository)/gi,
157
+ /[Tt]he\s+([A-Z][a-zA-Z0-9_]*)\s+(?:class|interface|service)/g,
158
+ /([A-Z][a-zA-Z0-9_]*Service|[A-Z][a-zA-Z0-9_]*Controller|[A-Z][a-zA-Z0-9_]*Model|[A-Z][a-zA-Z0-9_]*Repository)\b/g,
159
+ ];
160
+ const foundClasses = new Set();
161
+ for (const pattern of classPatterns) {
162
+ pattern.lastIndex = 0;
163
+ while ((match = pattern.exec(text)) !== null) {
164
+ if (!foundClasses.has(match[1])) {
165
+ entities.push({ type: 'class', name: match[1], confidence: 0.8 });
166
+ foundClasses.add(match[1]);
167
+ }
168
+ }
169
+ }
170
+ // Simple relation extraction - expanded patterns
171
+ const relationPatterns = [
172
+ {
173
+ // eslint-disable-next-line no-useless-escape
174
+ pattern: /([a-zA-Z_][a-zA-Z0-9_]*)\s+(?:calls|invokes|uses)\s+([a-zA-Z_][a-zA-Z0-9_\.]*)/g,
175
+ predicate: 'calls',
176
+ },
177
+ {
178
+ pattern: /([a-zA-Z_][a-zA-Z0-9_]*)\s+(?:implements|extends)\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
179
+ predicate: 'implements',
180
+ },
181
+ {
182
+ pattern: /([a-zA-Z_][a-zA-Z0-9_]*)\s+(?:imports|requires)\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
183
+ predicate: 'imports',
184
+ },
185
+ {
186
+ pattern: /([a-zA-Z_][a-zA-Z0-9_]*)\s+(?:contains|has)\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
187
+ predicate: 'contains',
188
+ },
189
+ ];
190
+ for (const { pattern, predicate } of relationPatterns) {
191
+ pattern.lastIndex = 0;
192
+ while ((match = pattern.exec(text)) !== null) {
193
+ relations.push({
194
+ subject: match[1],
195
+ predicate,
196
+ object: match[2],
197
+ confidence: predicate === 'implements' ? 0.8 : 0.7,
198
+ });
199
+ }
200
+ }
201
+ return { entities, relations };
202
+ }
203
+ // Visualization support
204
+ getGraphData(sessionId, entityTypes) {
205
+ let entityQuery = 'SELECT * FROM entities WHERE session_id = ?';
206
+ const entityParams = [sessionId];
207
+ if (entityTypes && entityTypes.length > 0) {
208
+ entityQuery += ` AND type IN (${entityTypes.map(() => '?').join(',')})`;
209
+ entityParams.push(...entityTypes);
210
+ }
211
+ const entities = this.db.prepare(entityQuery).all(...entityParams);
212
+ const entityIds = entities.map(e => e.id);
213
+ if (entityIds.length === 0) {
214
+ return { nodes: [], edges: [] };
215
+ }
216
+ const relations = this.db
217
+ .prepare(`
218
+ SELECT * FROM relations
219
+ WHERE session_id = ?
220
+ AND subject_id IN (${entityIds.map(() => '?').join(',')})
221
+ AND object_id IN (${entityIds.map(() => '?').join(',')})
222
+ `)
223
+ .all(sessionId, ...entityIds, ...entityIds);
224
+ const nodes = entities.map(e => ({
225
+ id: e.id,
226
+ type: e.type,
227
+ name: e.name,
228
+ attributes: e.attributes ? JSON.parse(e.attributes) : undefined,
229
+ }));
230
+ const edges = relations.map(r => ({
231
+ source: r.subject_id,
232
+ target: r.object_id,
233
+ predicate: r.predicate,
234
+ confidence: r.confidence,
235
+ }));
236
+ return { nodes, edges };
237
+ }
238
+ // Cleanup operations
239
+ pruneOrphanedEntities(sessionId) {
240
+ // Find entities with no relations
241
+ const orphaned = this.db
242
+ .prepare(`
243
+ SELECT e.id FROM entities e
244
+ WHERE e.session_id = ?
245
+ AND NOT EXISTS (
246
+ SELECT 1 FROM relations r
247
+ WHERE r.subject_id = e.id OR r.object_id = e.id
248
+ )
249
+ AND NOT EXISTS (
250
+ SELECT 1 FROM observations o
251
+ WHERE o.entity_id = e.id
252
+ )
253
+ `)
254
+ .all(sessionId);
255
+ if (orphaned.length === 0)
256
+ return 0;
257
+ const ids = orphaned.map(o => o.id);
258
+ this.db
259
+ .prepare(`DELETE FROM entities WHERE id IN (${ids.map(() => '?').join(',')})`)
260
+ .run(...ids);
261
+ return orphaned.length;
262
+ }
263
+ }
264
+ exports.KnowledgeGraphManager = KnowledgeGraphManager;
@@ -0,0 +1,373 @@
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
+ exports.MigrationHealthCheck = void 0;
37
+ exports.runMigrationHealthCheckCLI = runMigrationHealthCheckCLI;
38
+ const database_1 = require("./database");
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ class MigrationHealthCheck {
42
+ db;
43
+ dbManager;
44
+ constructor(dbManager) {
45
+ this.db = dbManager.getDatabase();
46
+ this.dbManager = dbManager;
47
+ }
48
+ /**
49
+ * Get all tables from the database
50
+ */
51
+ getAllTables() {
52
+ const tables = this.db
53
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
54
+ .all();
55
+ return tables.map(t => t.name);
56
+ }
57
+ /**
58
+ * Get columns for a specific table from the actual database
59
+ */
60
+ getTableColumns(tableName) {
61
+ const columns = this.db.prepare(`PRAGMA table_info(${tableName})`).all();
62
+ return columns.map(col => ({
63
+ name: col.name,
64
+ type: col.type,
65
+ notNull: col.notnull === 1,
66
+ defaultValue: col.dflt_value,
67
+ pk: col.pk === 1,
68
+ }));
69
+ }
70
+ /**
71
+ * Dynamically discover the expected schema from SQL create statements in database.ts
72
+ */
73
+ discoverExpectedSchema() {
74
+ const schemaMap = new Map();
75
+ // Try to find database.ts or database.js
76
+ let dbFilePath = path.join(__dirname, 'database.ts');
77
+ let dbContent;
78
+ try {
79
+ // First try TypeScript source
80
+ dbContent = fs.readFileSync(dbFilePath, 'utf-8');
81
+ }
82
+ catch (_error) {
83
+ // If TypeScript not found, try JavaScript (compiled)
84
+ try {
85
+ dbFilePath = path.join(__dirname, 'database.js');
86
+ dbContent = fs.readFileSync(dbFilePath, 'utf-8');
87
+ }
88
+ catch (_error2) {
89
+ // If neither found, return hardcoded critical columns as fallback
90
+ return this.getFallbackSchema();
91
+ }
92
+ }
93
+ // Extract the createTables method content
94
+ const createTablesMatch = dbContent.match(/createTables\(\)[\s\S]*?this\.db\.exec\(`([\s\S]*?)`\);/);
95
+ if (!createTablesMatch) {
96
+ console.warn('Could not find createTables method in database.ts');
97
+ return schemaMap;
98
+ }
99
+ const sqlContent = createTablesMatch[1];
100
+ // Parse CREATE TABLE statements
101
+ const tableRegex = /CREATE TABLE IF NOT EXISTS (\w+)\s*\(([\s\S]*?)\)(?:\s*;|(?=\s*CREATE))/g;
102
+ let match;
103
+ while ((match = tableRegex.exec(sqlContent)) !== null) {
104
+ const tableName = match[1];
105
+ const columnsContent = match[2];
106
+ // Extract column definitions
107
+ const columnMap = new Map();
108
+ // First, remove all newlines and extra spaces to handle multi-line definitions
109
+ const cleanedContent = columnsContent.replace(/\n\s*/g, ' ').trim();
110
+ // Split by comma but be careful with constraints
111
+ const parts = cleanedContent.split(/,(?![^(]*\))/);
112
+ for (const part of parts) {
113
+ const line = part.trim();
114
+ // Skip constraint lines
115
+ if (!line ||
116
+ line.startsWith('FOREIGN KEY') ||
117
+ line.startsWith('UNIQUE') ||
118
+ line.startsWith('PRIMARY KEY') ||
119
+ line.startsWith('CHECK') ||
120
+ line.startsWith('--')) {
121
+ continue;
122
+ }
123
+ // Extract column name and full definition
124
+ const columnMatch = line.match(/^(\w+)\s+(.+)$/);
125
+ if (columnMatch) {
126
+ const columnName = columnMatch[1];
127
+ const columnDef = columnMatch[2].trim();
128
+ columnMap.set(columnName, columnDef);
129
+ }
130
+ }
131
+ schemaMap.set(tableName, columnMap);
132
+ }
133
+ return schemaMap;
134
+ }
135
+ /**
136
+ * Fallback schema definition for when we can't parse the source files
137
+ */
138
+ getFallbackSchema() {
139
+ const schemaMap = new Map();
140
+ // Define critical columns that we know should exist
141
+ const sessionsColumns = new Map([
142
+ ['id', 'TEXT PRIMARY KEY'],
143
+ ['name', 'TEXT'],
144
+ ['description', 'TEXT'],
145
+ ['branch', 'TEXT'],
146
+ ['working_directory', 'TEXT'],
147
+ ['parent_id', 'TEXT'],
148
+ ['created_at', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP'],
149
+ ['updated_at', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP'],
150
+ ]);
151
+ const contextItemsColumns = new Map([
152
+ ['id', 'TEXT PRIMARY KEY'],
153
+ ['session_id', 'TEXT NOT NULL'],
154
+ ['key', 'TEXT NOT NULL'],
155
+ ['value', 'TEXT NOT NULL'],
156
+ ['category', 'TEXT'],
157
+ ['priority', "TEXT DEFAULT 'normal'"],
158
+ ['metadata', 'TEXT'],
159
+ ['size', 'INTEGER DEFAULT 0'],
160
+ ['shared', 'BOOLEAN DEFAULT 0'],
161
+ ['shared_with_sessions', 'TEXT'],
162
+ ['created_at', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP'],
163
+ ['updated_at', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP'],
164
+ ]);
165
+ const fileCacheColumns = new Map([
166
+ ['id', 'TEXT PRIMARY KEY'],
167
+ ['session_id', 'TEXT NOT NULL'],
168
+ ['file_path', 'TEXT NOT NULL'],
169
+ ['content', 'TEXT'],
170
+ ['hash', 'TEXT'],
171
+ ['size', 'INTEGER DEFAULT 0'],
172
+ ['last_read', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP'],
173
+ ['updated_at', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP'],
174
+ ]);
175
+ schemaMap.set('sessions', sessionsColumns);
176
+ schemaMap.set('context_items', contextItemsColumns);
177
+ schemaMap.set('file_cache', fileCacheColumns);
178
+ return schemaMap;
179
+ }
180
+ /**
181
+ * Generate SQLite-safe ALTER TABLE statement
182
+ */
183
+ generateSafeAlterStatement(tableName, columnName, columnDef) {
184
+ // SQLite doesn't support CURRENT_TIMESTAMP as default in ALTER TABLE
185
+ // Replace with a static timestamp
186
+ let safeColumnDef = columnDef;
187
+ if (columnDef.includes('CURRENT_TIMESTAMP')) {
188
+ const currentTimestamp = new Date().toISOString();
189
+ safeColumnDef = columnDef.replace(/DEFAULT CURRENT_TIMESTAMP/g, `DEFAULT '${currentTimestamp}'`);
190
+ }
191
+ return `ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${safeColumnDef}`;
192
+ }
193
+ /**
194
+ * Check for missing columns by comparing actual DB with expected schema
195
+ */
196
+ checkMissingColumns() {
197
+ const issues = [];
198
+ const expectedSchema = this.discoverExpectedSchema();
199
+ const existingTables = this.getAllTables();
200
+ for (const [tableName, expectedColumns] of expectedSchema) {
201
+ if (!existingTables.includes(tableName)) {
202
+ continue; // Skip if table doesn't exist yet
203
+ }
204
+ const actualColumns = this.getTableColumns(tableName);
205
+ const actualColumnNames = new Set(actualColumns.map(c => c.name));
206
+ for (const [columnName, columnDef] of expectedColumns) {
207
+ if (!actualColumnNames.has(columnName)) {
208
+ issues.push({
209
+ table: tableName,
210
+ issue: `Missing column '${columnName}'`,
211
+ severity: 'error',
212
+ fix: this.generateSafeAlterStatement(tableName, columnName, columnDef),
213
+ });
214
+ }
215
+ }
216
+ }
217
+ return issues;
218
+ }
219
+ /**
220
+ * Run all health checks
221
+ */
222
+ runHealthCheck() {
223
+ const missingColumns = this.checkMissingColumns();
224
+ const allIssues = [...missingColumns];
225
+ const errors = allIssues.filter(i => i.severity === 'error');
226
+ const warnings = allIssues.filter(i => i.severity === 'warning');
227
+ const canAutoFix = errors.length > 0 && errors.every(e => e.fix);
228
+ const summary = `Found ${errors.length} errors and ${warnings.length} warnings. ${canAutoFix ? 'Auto-fix available.' : 'Database schema is healthy.'}`;
229
+ return {
230
+ issues: allIssues,
231
+ canAutoFix,
232
+ summary,
233
+ };
234
+ }
235
+ /**
236
+ * Attempt to auto-fix migration issues
237
+ */
238
+ autoFixIssues(issues) {
239
+ const fixed = [];
240
+ const failed = [];
241
+ // Group fixes by table to run in transaction
242
+ const fixesByTable = new Map();
243
+ for (const issue of issues) {
244
+ if (issue.fix && issue.severity === 'error') {
245
+ const _table = issue.table;
246
+ if (!fixesByTable.has(_table)) {
247
+ fixesByTable.set(_table, []);
248
+ }
249
+ fixesByTable.get(_table).push(issue);
250
+ }
251
+ }
252
+ // Apply fixes table by table
253
+ for (const [_table, tableIssues] of fixesByTable) {
254
+ try {
255
+ this.db.transaction(() => {
256
+ for (const issue of tableIssues) {
257
+ this.db.exec(issue.fix);
258
+ fixed.push(`Fixed: ${issue.table} - ${issue.issue}`);
259
+ }
260
+ })();
261
+ }
262
+ catch (error) {
263
+ for (const issue of tableIssues) {
264
+ failed.push(`Failed to fix ${issue.table} - ${issue.issue}: ${error}`);
265
+ }
266
+ }
267
+ }
268
+ return { fixed, failed };
269
+ }
270
+ /**
271
+ * Generate a detailed migration report
272
+ */
273
+ generateReport() {
274
+ const healthCheck = this.runHealthCheck();
275
+ const report = [
276
+ '=== MCP Memory Keeper Migration Health Check ===',
277
+ '',
278
+ `Summary: ${healthCheck.summary}`,
279
+ '',
280
+ ];
281
+ if (healthCheck.issues.length === 0) {
282
+ report.push('āœ… All database columns are up to date!');
283
+ }
284
+ else {
285
+ report.push('Issues found:');
286
+ report.push('');
287
+ for (const issue of healthCheck.issues) {
288
+ const icon = issue.severity === 'error' ? 'āŒ' : 'āš ļø';
289
+ report.push(`${icon} [${issue.severity.toUpperCase()}] ${issue.table}: ${issue.issue}`);
290
+ if (issue.fix) {
291
+ report.push(` Fix: ${issue.fix}`);
292
+ }
293
+ report.push('');
294
+ }
295
+ if (healthCheck.canAutoFix) {
296
+ report.push('');
297
+ report.push('šŸ’” Auto-fix is available. The server will apply fixes automatically on startup.');
298
+ }
299
+ }
300
+ return report.join('\n');
301
+ }
302
+ /**
303
+ * Run automatic migration fix if needed
304
+ */
305
+ runAutoFix() {
306
+ const healthCheck = this.runHealthCheck();
307
+ if (healthCheck.issues.length === 0) {
308
+ return true;
309
+ }
310
+ // Only log in non-test environments
311
+ const isTest = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID;
312
+ if (!isTest) {
313
+ // eslint-disable-next-line no-console
314
+ console.log('[MCP Memory Keeper] Running database migration health check...');
315
+ // eslint-disable-next-line no-console
316
+ console.log(this.generateReport());
317
+ }
318
+ if (healthCheck.canAutoFix) {
319
+ if (!isTest) {
320
+ // eslint-disable-next-line no-console
321
+ console.log('[MCP Memory Keeper] Applying automatic fixes...');
322
+ }
323
+ const { fixed, failed } = this.autoFixIssues(healthCheck.issues);
324
+ if (!isTest) {
325
+ // eslint-disable-next-line no-console
326
+ fixed.forEach(f => console.log(`āœ… ${f}`));
327
+ failed.forEach(f => console.error(`āŒ ${f}`));
328
+ }
329
+ if (failed.length === 0) {
330
+ if (!isTest) {
331
+ // eslint-disable-next-line no-console
332
+ console.log('[MCP Memory Keeper] All migrations applied successfully!');
333
+ }
334
+ return true;
335
+ }
336
+ else {
337
+ if (!isTest) {
338
+ console.error('[MCP Memory Keeper] Some migrations failed. Manual intervention may be required.');
339
+ }
340
+ return false;
341
+ }
342
+ }
343
+ return false;
344
+ }
345
+ }
346
+ exports.MigrationHealthCheck = MigrationHealthCheck;
347
+ /**
348
+ * Standalone function to run migration health check from CLI
349
+ */
350
+ async function runMigrationHealthCheckCLI(dbPath, autoFix = false) {
351
+ const dbManager = new database_1.DatabaseManager({ filename: dbPath });
352
+ const healthCheck = new MigrationHealthCheck(dbManager);
353
+ // eslint-disable-next-line no-console
354
+ console.log(healthCheck.generateReport());
355
+ if (autoFix) {
356
+ const issues = healthCheck.runHealthCheck().issues;
357
+ const fixableIssues = issues.filter(i => i.severity === 'error' && i.fix);
358
+ if (fixableIssues.length > 0) {
359
+ // eslint-disable-next-line no-console
360
+ console.log('\nAttempting auto-fix...');
361
+ const { fixed, failed } = healthCheck.autoFixIssues(fixableIssues);
362
+ // eslint-disable-next-line no-console
363
+ fixed.forEach(f => console.log(`āœ… ${f}`));
364
+ // eslint-disable-next-line no-console
365
+ failed.forEach(f => console.log(`āŒ ${f}`));
366
+ if (fixed.length > 0) {
367
+ // eslint-disable-next-line no-console
368
+ console.log('\nāœ… Auto-fix completed. Please restart the MCP server.');
369
+ }
370
+ }
371
+ }
372
+ dbManager.close();
373
+ }