@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,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
|
+
}
|