ai-mind-map 1.1.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/LICENSE +21 -0
- package/README.md +554 -0
- package/dist/change-tracker/change-log.d.ts +160 -0
- package/dist/change-tracker/change-log.d.ts.map +1 -0
- package/dist/change-tracker/change-log.js +507 -0
- package/dist/change-tracker/change-log.js.map +1 -0
- package/dist/change-tracker/diff-engine.d.ts +149 -0
- package/dist/change-tracker/diff-engine.d.ts.map +1 -0
- package/dist/change-tracker/diff-engine.js +530 -0
- package/dist/change-tracker/diff-engine.js.map +1 -0
- package/dist/change-tracker/watcher.d.ts +137 -0
- package/dist/change-tracker/watcher.d.ts.map +1 -0
- package/dist/change-tracker/watcher.js +300 -0
- package/dist/change-tracker/watcher.js.map +1 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +937 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +222 -0
- package/dist/config.js.map +1 -0
- package/dist/context/compressor.d.ts +49 -0
- package/dist/context/compressor.d.ts.map +1 -0
- package/dist/context/compressor.js +769 -0
- package/dist/context/compressor.js.map +1 -0
- package/dist/context/progressive-disclosure.d.ts +71 -0
- package/dist/context/progressive-disclosure.d.ts.map +1 -0
- package/dist/context/progressive-disclosure.js +470 -0
- package/dist/context/progressive-disclosure.js.map +1 -0
- package/dist/context/token-budget.d.ts +121 -0
- package/dist/context/token-budget.d.ts.map +1 -0
- package/dist/context/token-budget.js +282 -0
- package/dist/context/token-budget.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +944 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +66 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +946 -0
- package/dist/install.js.map +1 -0
- package/dist/knowledge-graph/architecture.d.ts +213 -0
- package/dist/knowledge-graph/architecture.d.ts.map +1 -0
- package/dist/knowledge-graph/architecture.js +585 -0
- package/dist/knowledge-graph/architecture.js.map +1 -0
- package/dist/knowledge-graph/cypher.d.ts +113 -0
- package/dist/knowledge-graph/cypher.d.ts.map +1 -0
- package/dist/knowledge-graph/cypher.js +1051 -0
- package/dist/knowledge-graph/cypher.js.map +1 -0
- package/dist/knowledge-graph/dead-code.d.ts +121 -0
- package/dist/knowledge-graph/dead-code.d.ts.map +1 -0
- package/dist/knowledge-graph/dead-code.js +331 -0
- package/dist/knowledge-graph/dead-code.js.map +1 -0
- package/dist/knowledge-graph/flow-analyzer.d.ts +167 -0
- package/dist/knowledge-graph/flow-analyzer.d.ts.map +1 -0
- package/dist/knowledge-graph/flow-analyzer.js +739 -0
- package/dist/knowledge-graph/flow-analyzer.js.map +1 -0
- package/dist/knowledge-graph/graph.d.ts +291 -0
- package/dist/knowledge-graph/graph.d.ts.map +1 -0
- package/dist/knowledge-graph/graph.js +978 -0
- package/dist/knowledge-graph/graph.js.map +1 -0
- package/dist/knowledge-graph/index.d.ts +17 -0
- package/dist/knowledge-graph/index.d.ts.map +1 -0
- package/dist/knowledge-graph/index.js +14 -0
- package/dist/knowledge-graph/index.js.map +1 -0
- package/dist/knowledge-graph/indexer.d.ts +112 -0
- package/dist/knowledge-graph/indexer.d.ts.map +1 -0
- package/dist/knowledge-graph/indexer.js +506 -0
- package/dist/knowledge-graph/indexer.js.map +1 -0
- package/dist/knowledge-graph/pagerank.d.ts +141 -0
- package/dist/knowledge-graph/pagerank.d.ts.map +1 -0
- package/dist/knowledge-graph/pagerank.js +493 -0
- package/dist/knowledge-graph/pagerank.js.map +1 -0
- package/dist/knowledge-graph/parser.d.ts +55 -0
- package/dist/knowledge-graph/parser.d.ts.map +1 -0
- package/dist/knowledge-graph/parser.js +1090 -0
- package/dist/knowledge-graph/parser.js.map +1 -0
- package/dist/knowledge-graph/snapshot.d.ts +107 -0
- package/dist/knowledge-graph/snapshot.d.ts.map +1 -0
- package/dist/knowledge-graph/snapshot.js +435 -0
- package/dist/knowledge-graph/snapshot.js.map +1 -0
- package/dist/memory/decision-log.d.ts +151 -0
- package/dist/memory/decision-log.d.ts.map +1 -0
- package/dist/memory/decision-log.js +482 -0
- package/dist/memory/decision-log.js.map +1 -0
- package/dist/memory/persistent-memory.d.ts +182 -0
- package/dist/memory/persistent-memory.d.ts.map +1 -0
- package/dist/memory/persistent-memory.js +579 -0
- package/dist/memory/persistent-memory.js.map +1 -0
- package/dist/memory/session-memory.d.ts +165 -0
- package/dist/memory/session-memory.d.ts.map +1 -0
- package/dist/memory/session-memory.js +382 -0
- package/dist/memory/session-memory.js.map +1 -0
- package/dist/stress-test.d.ts +10 -0
- package/dist/stress-test.d.ts.map +1 -0
- package/dist/stress-test.js +258 -0
- package/dist/stress-test.js.map +1 -0
- package/dist/tools/advanced-tools.d.ts +32 -0
- package/dist/tools/advanced-tools.d.ts.map +1 -0
- package/dist/tools/advanced-tools.js +480 -0
- package/dist/tools/advanced-tools.js.map +1 -0
- package/dist/tools/change-tools.d.ts +76 -0
- package/dist/tools/change-tools.d.ts.map +1 -0
- package/dist/tools/change-tools.js +93 -0
- package/dist/tools/change-tools.js.map +1 -0
- package/dist/tools/context-tools.d.ts +68 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +141 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/debug-tools.d.ts +25 -0
- package/dist/tools/debug-tools.d.ts.map +1 -0
- package/dist/tools/debug-tools.js +286 -0
- package/dist/tools/debug-tools.js.map +1 -0
- package/dist/tools/evolving-tools.d.ts +23 -0
- package/dist/tools/evolving-tools.d.ts.map +1 -0
- package/dist/tools/evolving-tools.js +207 -0
- package/dist/tools/evolving-tools.js.map +1 -0
- package/dist/tools/flow-tools.d.ts +24 -0
- package/dist/tools/flow-tools.d.ts.map +1 -0
- package/dist/tools/flow-tools.js +265 -0
- package/dist/tools/flow-tools.js.map +1 -0
- package/dist/tools/graph-tools.d.ts +71 -0
- package/dist/tools/graph-tools.d.ts.map +1 -0
- package/dist/tools/graph-tools.js +165 -0
- package/dist/tools/graph-tools.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +62 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +195 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/smart-tools.d.ts +23 -0
- package/dist/tools/smart-tools.d.ts.map +1 -0
- package/dist/tools/smart-tools.js +482 -0
- package/dist/tools/smart-tools.js.map +1 -0
- package/dist/tools/snapshot-tools.d.ts +19 -0
- package/dist/tools/snapshot-tools.d.ts.map +1 -0
- package/dist/tools/snapshot-tools.js +149 -0
- package/dist/tools/snapshot-tools.js.map +1 -0
- package/dist/types.d.ts +181 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +45 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +142 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/token-counter.d.ts +51 -0
- package/dist/utils/token-counter.d.ts.map +1 -0
- package/dist/utils/token-counter.js +181 -0
- package/dist/utils/token-counter.js.map +1 -0
- package/install.ps1 +321 -0
- package/install.sh +345 -0
- package/package.json +94 -0
- package/setup.bat +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AI Mind Map β MCP Server Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Creates the MCP server with stdio transport, registers all tools,
|
|
6
|
+
* initialises ALL real subsystems (knowledge graph, change tracker,
|
|
7
|
+
* persistent memory, context engine), and handles graceful shutdown.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ai-mind-map [--project-root <path>] [--db-path <path>] [--log-level debug|info|warn|error]
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, mkdirSync, statSync } from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import process from 'node:process';
|
|
15
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
16
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
17
|
+
import Database from 'better-sqlite3';
|
|
18
|
+
import { loadConfig, parseCliArgs } from './config.js';
|
|
19
|
+
// ββ Knowledge Graph βββββββββββββββββββββββββββββββββββββββββββ
|
|
20
|
+
import { KnowledgeGraph } from './knowledge-graph/graph.js';
|
|
21
|
+
// Note: parser.ts exports functions (parseFile, parseFiles, etc.), not a class
|
|
22
|
+
import { Indexer } from './knowledge-graph/indexer.js';
|
|
23
|
+
import { PageRankEngine } from './knowledge-graph/pagerank.js';
|
|
24
|
+
// ββ Change Tracker ββββββββββββββββββββββββββββββββββββββββββββ
|
|
25
|
+
import { FileWatcher } from './change-tracker/watcher.js';
|
|
26
|
+
import { DiffEngine } from './change-tracker/diff-engine.js';
|
|
27
|
+
import { ChangeLog } from './change-tracker/change-log.js';
|
|
28
|
+
// ββ Memory ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
29
|
+
import { SessionMemory } from './memory/session-memory.js';
|
|
30
|
+
import { PersistentMemory } from './memory/persistent-memory.js';
|
|
31
|
+
import { DecisionLog } from './memory/decision-log.js';
|
|
32
|
+
// ββ Context Engine ββββββββββββββββββββββββββββββββββββββββββββ
|
|
33
|
+
// compressor.ts exports functions: compress, detectContentType
|
|
34
|
+
import { compress as compressContent } from './context/compressor.js';
|
|
35
|
+
// progressive-disclosure.ts exports function: buildContextPackage
|
|
36
|
+
import { buildContextPackage } from './context/progressive-disclosure.js';
|
|
37
|
+
import { estimateTokens } from './utils/token-counter.js';
|
|
38
|
+
// ββ Tools βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
39
|
+
import { registerGraphTools } from './tools/graph-tools.js';
|
|
40
|
+
import { registerChangeTools } from './tools/change-tools.js';
|
|
41
|
+
import { registerMemoryTools } from './tools/memory-tools.js';
|
|
42
|
+
import { registerContextTools } from './tools/context-tools.js';
|
|
43
|
+
import { registerDebugTools } from './tools/debug-tools.js';
|
|
44
|
+
import { registerFlowTools } from './tools/flow-tools.js';
|
|
45
|
+
import { registerSnapshotTools } from './tools/snapshot-tools.js';
|
|
46
|
+
import { registerSmartTools } from './tools/smart-tools.js';
|
|
47
|
+
import { registerEvolvingTools } from './tools/evolving-tools.js';
|
|
48
|
+
// ============================================================
|
|
49
|
+
// Logger β writes to stderr so MCP stdio is uncontaminated
|
|
50
|
+
// ============================================================
|
|
51
|
+
const LOG_LEVELS = {
|
|
52
|
+
debug: 0,
|
|
53
|
+
info: 1,
|
|
54
|
+
warn: 2,
|
|
55
|
+
error: 3,
|
|
56
|
+
};
|
|
57
|
+
let currentLogLevel = LOG_LEVELS.info;
|
|
58
|
+
function setLogLevel(level) {
|
|
59
|
+
currentLogLevel = LOG_LEVELS[level];
|
|
60
|
+
}
|
|
61
|
+
function log(level, message, ...extra) {
|
|
62
|
+
if (LOG_LEVELS[level] < currentLogLevel)
|
|
63
|
+
return;
|
|
64
|
+
const ts = new Date().toISOString();
|
|
65
|
+
const prefix = `[${ts}] [${level.toUpperCase()}]`;
|
|
66
|
+
if (extra.length > 0) {
|
|
67
|
+
process.stderr.write(`${prefix} ${message} ${JSON.stringify(extra)}\n`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
process.stderr.write(`${prefix} ${message}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// ============================================================
|
|
74
|
+
// Adapters β Bridge real implementations to tool interfaces
|
|
75
|
+
// ============================================================
|
|
76
|
+
/**
|
|
77
|
+
* Creates an adapter that satisfies IKnowledgeGraph from the real
|
|
78
|
+
* KnowledgeGraph and PageRankEngine classes.
|
|
79
|
+
*
|
|
80
|
+
* Key API mappings:
|
|
81
|
+
* - graph.search(query, limit) β FTS5 search, no type filter param
|
|
82
|
+
* - graph.getProjectOverview() β returns Map<string, GraphNode[]>, no args
|
|
83
|
+
* - graph.findCallers(nodeId) / graph.findCallees(nodeId) β single nodeId arg
|
|
84
|
+
* - graph.getNodesByName(name) β returns GraphNode[]
|
|
85
|
+
* - graph.getFileStructure(filePath) β returns GraphNode[]
|
|
86
|
+
*/
|
|
87
|
+
function createGraphAdapter(graph, pagerank) {
|
|
88
|
+
return {
|
|
89
|
+
search: (query, type, limit) => {
|
|
90
|
+
const maxResults = limit ?? 20;
|
|
91
|
+
let results = graph.search(query, maxResults);
|
|
92
|
+
// Expand with learned search aliases
|
|
93
|
+
try {
|
|
94
|
+
const aliases = graph.getLearnedSearchAliases();
|
|
95
|
+
const lowerQuery = query.toLowerCase();
|
|
96
|
+
for (const alias of aliases) {
|
|
97
|
+
if (alias.term.toLowerCase() === lowerQuery) {
|
|
98
|
+
for (const alt of alias.aliases) {
|
|
99
|
+
if (results.length >= maxResults)
|
|
100
|
+
break;
|
|
101
|
+
const aliasResults = graph.search(alt, Math.max(3, maxResults - results.length));
|
|
102
|
+
const existingIds = new Set(results.map(r => r.id));
|
|
103
|
+
for (const r of aliasResults) {
|
|
104
|
+
if (!existingIds.has(r.id) && results.length < maxResults) {
|
|
105
|
+
results.push(r);
|
|
106
|
+
existingIds.add(r.id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Touch the alias to track usage
|
|
111
|
+
try {
|
|
112
|
+
graph.touchLearnedRule(alias.id);
|
|
113
|
+
}
|
|
114
|
+
catch { }
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Learned rules table might not exist
|
|
121
|
+
}
|
|
122
|
+
if (type) {
|
|
123
|
+
return results.filter((n) => n.type === type);
|
|
124
|
+
}
|
|
125
|
+
return results;
|
|
126
|
+
},
|
|
127
|
+
getStructure: (depth) => {
|
|
128
|
+
// graph.getProjectOverview() returns Map<string, GraphNode[]>
|
|
129
|
+
// We convert it to the shape expected by IKnowledgeGraph.getStructure
|
|
130
|
+
const overview = graph.getProjectOverview();
|
|
131
|
+
const files = [];
|
|
132
|
+
for (const [filePath, nodes] of overview) {
|
|
133
|
+
files.push({
|
|
134
|
+
path: filePath,
|
|
135
|
+
symbols: nodes.map(n => ({
|
|
136
|
+
name: n.name,
|
|
137
|
+
type: n.type,
|
|
138
|
+
signature: n.signature,
|
|
139
|
+
})),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// depth is accepted but getProjectOverview doesn't take a depth arg;
|
|
143
|
+
// we can slice results if needed, but return all for now.
|
|
144
|
+
return { files };
|
|
145
|
+
},
|
|
146
|
+
traceDependencies: (symbolName, direction, depth) => {
|
|
147
|
+
const nodes = graph.getNodesByName(symbolName);
|
|
148
|
+
if (nodes.length === 0) {
|
|
149
|
+
return { root: symbolName, direction, depth, nodes: [], edges: [] };
|
|
150
|
+
}
|
|
151
|
+
const rootNode = nodes[0];
|
|
152
|
+
let traced = [];
|
|
153
|
+
// findCallers(nodeId) and findCallees(nodeId) each take a single string arg
|
|
154
|
+
if (direction === 'callers' || direction === 'both') {
|
|
155
|
+
traced = traced.concat(graph.findCallers(rootNode.id));
|
|
156
|
+
}
|
|
157
|
+
if (direction === 'callees' || direction === 'both') {
|
|
158
|
+
traced = traced.concat(graph.findCallees(rootNode.id));
|
|
159
|
+
}
|
|
160
|
+
// Deduplicate
|
|
161
|
+
const seen = new Set();
|
|
162
|
+
const unique = traced.filter(n => {
|
|
163
|
+
if (seen.has(n.id))
|
|
164
|
+
return false;
|
|
165
|
+
seen.add(n.id);
|
|
166
|
+
return true;
|
|
167
|
+
});
|
|
168
|
+
// Collect relevant edges
|
|
169
|
+
const edgeSet = [];
|
|
170
|
+
for (const n of unique) {
|
|
171
|
+
const outEdges = graph.getOutEdges(n.id);
|
|
172
|
+
const inEdges = graph.getInEdges(n.id);
|
|
173
|
+
edgeSet.push(...outEdges, ...inEdges);
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
root: symbolName,
|
|
177
|
+
direction,
|
|
178
|
+
depth,
|
|
179
|
+
nodes: unique,
|
|
180
|
+
edges: edgeSet,
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
getSignature: (symbolName, filePath) => {
|
|
184
|
+
const nodes = graph.getNodesByName(symbolName);
|
|
185
|
+
let match;
|
|
186
|
+
if (filePath) {
|
|
187
|
+
match = nodes.find((n) => n.filePath === filePath);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
match = nodes[0];
|
|
191
|
+
}
|
|
192
|
+
if (!match)
|
|
193
|
+
return null;
|
|
194
|
+
// Return the shape expected by IKnowledgeGraph.getSignature
|
|
195
|
+
return {
|
|
196
|
+
node: match,
|
|
197
|
+
parameters: match.parameters,
|
|
198
|
+
returnType: match.returnType,
|
|
199
|
+
docComment: match.docComment,
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
findReferences: (symbolName) => {
|
|
203
|
+
const nodes = graph.getNodesByName(symbolName);
|
|
204
|
+
if (nodes.length === 0) {
|
|
205
|
+
return { symbol: symbolName, references: [] };
|
|
206
|
+
}
|
|
207
|
+
// Find all callers of the first matching node as "references"
|
|
208
|
+
const callers = graph.findCallers(nodes[0].id);
|
|
209
|
+
return {
|
|
210
|
+
symbol: symbolName,
|
|
211
|
+
references: callers.map((n) => ({
|
|
212
|
+
filePath: n.filePath,
|
|
213
|
+
line: n.startLine,
|
|
214
|
+
context: n.signature,
|
|
215
|
+
})),
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
getFileMap: (filePath) => {
|
|
219
|
+
const nodes = graph.getFileStructure(filePath);
|
|
220
|
+
if (!nodes || nodes.length === 0)
|
|
221
|
+
return null;
|
|
222
|
+
return {
|
|
223
|
+
filePath,
|
|
224
|
+
symbols: nodes
|
|
225
|
+
.filter(n => n.type !== 'file')
|
|
226
|
+
.map(n => ({
|
|
227
|
+
name: n.name,
|
|
228
|
+
type: n.type,
|
|
229
|
+
signature: n.signature,
|
|
230
|
+
startLine: n.startLine,
|
|
231
|
+
endLine: n.endLine,
|
|
232
|
+
visibility: n.visibility,
|
|
233
|
+
isExported: n.isExported,
|
|
234
|
+
})),
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Creates an adapter that satisfies IChangeTracker from the real
|
|
241
|
+
* DiffEngine and ChangeLog classes.
|
|
242
|
+
*
|
|
243
|
+
* Key API mappings:
|
|
244
|
+
* - changeLog.getLatestSession() β returns ChangeSession | null
|
|
245
|
+
* - changeLog.queryChanges(options) β options has `since` (timestamp), not `afterTimestamp`
|
|
246
|
+
* - changeLog.generateSessionSummary(sessionId) β returns string
|
|
247
|
+
* - changeLog.recordChange(change) β records a FileChange
|
|
248
|
+
*/
|
|
249
|
+
function createChangeAdapter(diffEngine, changeLog, graph) {
|
|
250
|
+
return {
|
|
251
|
+
getChanges: (since) => {
|
|
252
|
+
try {
|
|
253
|
+
const latestSession = changeLog.getLatestSession();
|
|
254
|
+
const sinceTimestamp = since === 'last_session'
|
|
255
|
+
? (latestSession?.endedAt ?? Date.now() - 86400000)
|
|
256
|
+
: since === 'today'
|
|
257
|
+
? new Date().setHours(0, 0, 0, 0)
|
|
258
|
+
: since === 'this_week'
|
|
259
|
+
? Date.now() - 7 * 86400000
|
|
260
|
+
: new Date(since).getTime();
|
|
261
|
+
// ChangeLog.queryChanges uses ChangeQueryOptions with `since` field (timestamp)
|
|
262
|
+
const changes = changeLog.queryChanges({
|
|
263
|
+
since: sinceTimestamp,
|
|
264
|
+
});
|
|
265
|
+
return {
|
|
266
|
+
since,
|
|
267
|
+
resolvedTimestamp: sinceTimestamp,
|
|
268
|
+
changes,
|
|
269
|
+
totalFilesChanged: new Set(changes.map(c => c.filePath)).size,
|
|
270
|
+
totalLinesAdded: changes.reduce((sum, c) => sum + c.linesAdded, 0),
|
|
271
|
+
totalLinesRemoved: changes.reduce((sum, c) => sum + c.linesRemoved, 0),
|
|
272
|
+
summary: changes.length > 0
|
|
273
|
+
? changes.map(c => `- ${c.filePath}: ${c.summary}`).join('\n')
|
|
274
|
+
: 'No changes found since the specified time.',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return {
|
|
279
|
+
since,
|
|
280
|
+
resolvedTimestamp: Date.now(),
|
|
281
|
+
changes: [],
|
|
282
|
+
totalFilesChanged: 0,
|
|
283
|
+
totalLinesAdded: 0,
|
|
284
|
+
totalLinesRemoved: 0,
|
|
285
|
+
summary: 'Unable to retrieve changes.',
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
getSessionDiff: () => {
|
|
290
|
+
try {
|
|
291
|
+
const latestSession = changeLog.getLatestSession();
|
|
292
|
+
if (!latestSession) {
|
|
293
|
+
return {
|
|
294
|
+
previousSession: null,
|
|
295
|
+
changes: [],
|
|
296
|
+
affectedSymbols: [],
|
|
297
|
+
summary: 'No previous session found.',
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const changes = changeLog.queryChanges({
|
|
301
|
+
since: latestSession.endedAt ?? latestSession.startedAt,
|
|
302
|
+
});
|
|
303
|
+
const symbols = changes.flatMap(c => c.symbolsAffected);
|
|
304
|
+
return {
|
|
305
|
+
previousSession: latestSession,
|
|
306
|
+
changes,
|
|
307
|
+
affectedSymbols: [...new Set(symbols)],
|
|
308
|
+
summary: changeLog.generateSessionSummary(latestSession.sessionId),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return {
|
|
313
|
+
previousSession: null,
|
|
314
|
+
changes: [],
|
|
315
|
+
affectedSymbols: [],
|
|
316
|
+
summary: 'Unable to compute session diff.',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
analyseImpact: (params) => {
|
|
321
|
+
const target = params.filePath ?? params.symbolName ?? 'unknown';
|
|
322
|
+
// Try to find the node and compute blast radius
|
|
323
|
+
let directlyAffected = [];
|
|
324
|
+
let transitivelyAffected = [];
|
|
325
|
+
let riskLevel = 'low';
|
|
326
|
+
try {
|
|
327
|
+
if (params.symbolName) {
|
|
328
|
+
const nodes = graph.getNodesByName(params.symbolName);
|
|
329
|
+
if (nodes.length > 0) {
|
|
330
|
+
const rootNode = nodes[0];
|
|
331
|
+
const callers = graph.findCallers(rootNode.id);
|
|
332
|
+
directlyAffected = callers.map(n => ({
|
|
333
|
+
node: n,
|
|
334
|
+
relationship: 'calls',
|
|
335
|
+
}));
|
|
336
|
+
const blastNodes = graph.blastRadius(rootNode.id, 3);
|
|
337
|
+
transitivelyAffected = blastNodes.map((n, idx) => ({
|
|
338
|
+
node: n,
|
|
339
|
+
depth: Math.min(idx + 1, 3),
|
|
340
|
+
}));
|
|
341
|
+
const total = directlyAffected.length + transitivelyAffected.length;
|
|
342
|
+
riskLevel = total > 20 ? 'high' : total > 5 ? 'medium' : 'low';
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else if (params.filePath) {
|
|
346
|
+
const fileNodes = graph.getFileStructure(params.filePath);
|
|
347
|
+
for (const node of fileNodes) {
|
|
348
|
+
if (node.type === 'file')
|
|
349
|
+
continue;
|
|
350
|
+
const callers = graph.findCallers(node.id);
|
|
351
|
+
for (const caller of callers) {
|
|
352
|
+
directlyAffected.push({ node: caller, relationship: 'calls' });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
riskLevel = directlyAffected.length > 10 ? 'high'
|
|
356
|
+
: directlyAffected.length > 3 ? 'medium' : 'low';
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// Fall through with empty arrays
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
target,
|
|
364
|
+
directlyAffected,
|
|
365
|
+
transitivelyAffected,
|
|
366
|
+
riskLevel,
|
|
367
|
+
summary: directlyAffected.length > 0
|
|
368
|
+
? `${target}: ${directlyAffected.length} directly affected, ${transitivelyAffected.length} transitively affected. Risk: ${riskLevel}.`
|
|
369
|
+
: `${target}: No dependents found. Use mindmap_trace_dependencies for full dependency chain.`,
|
|
370
|
+
};
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Creates an adapter that satisfies IMemoryStore from PersistentMemory,
|
|
376
|
+
* DecisionLog, and SessionMemory.
|
|
377
|
+
*
|
|
378
|
+
* Key API mappings:
|
|
379
|
+
* - persistentMemory.queryMemories(query) β query uses MemoryQuery shape
|
|
380
|
+
* - persistentMemory.createMemory(input) β input is CreateMemoryInput
|
|
381
|
+
* - persistentMemory.getStats() β returns MemoryStats
|
|
382
|
+
* - decisionLog.queryDecisions(query) β query uses DecisionQuery
|
|
383
|
+
* - decisionLog.createDecision(input) β returns { decision, conflicts }
|
|
384
|
+
* - decisionLog.getActiveDecisions() β returns Decision[]
|
|
385
|
+
* - sessionMemory.listRecentSessions(limit) β returns SessionListItem[]
|
|
386
|
+
*/
|
|
387
|
+
function createMemoryAdapter(persistentMemory, decisionLog, sessionMemory) {
|
|
388
|
+
return {
|
|
389
|
+
recall: (query, category, limit) => {
|
|
390
|
+
return persistentMemory.queryMemories({
|
|
391
|
+
text: query,
|
|
392
|
+
categories: category ? [category] : undefined,
|
|
393
|
+
limit: limit ?? 10,
|
|
394
|
+
});
|
|
395
|
+
},
|
|
396
|
+
remember: (params) => {
|
|
397
|
+
return persistentMemory.createMemory({
|
|
398
|
+
category: params.category,
|
|
399
|
+
content: params.content,
|
|
400
|
+
importance: params.importance,
|
|
401
|
+
tags: params.tags,
|
|
402
|
+
relatedFiles: params.relatedFiles,
|
|
403
|
+
sessionId: params.sessionId,
|
|
404
|
+
source: params.source,
|
|
405
|
+
});
|
|
406
|
+
},
|
|
407
|
+
getDecisions: (params) => {
|
|
408
|
+
if (params.query) {
|
|
409
|
+
// Use DecisionLog.queryDecisions with text search
|
|
410
|
+
const results = decisionLog.queryDecisions({
|
|
411
|
+
text: params.query,
|
|
412
|
+
status: params.status === 'all' ? undefined : (params.status ?? 'active'),
|
|
413
|
+
});
|
|
414
|
+
return results;
|
|
415
|
+
}
|
|
416
|
+
if (!params.status || params.status === 'active') {
|
|
417
|
+
return decisionLog.getActiveDecisions();
|
|
418
|
+
}
|
|
419
|
+
// 'all' β query with no filters
|
|
420
|
+
return decisionLog.queryDecisions({});
|
|
421
|
+
},
|
|
422
|
+
decide: (params) => {
|
|
423
|
+
// DecisionLog.createDecision returns { decision, conflicts }
|
|
424
|
+
const result = decisionLog.createDecision({
|
|
425
|
+
title: params.title,
|
|
426
|
+
description: params.description,
|
|
427
|
+
rationale: params.rationale,
|
|
428
|
+
alternatives: params.alternatives,
|
|
429
|
+
consequences: params.consequences,
|
|
430
|
+
relatedFiles: params.relatedFiles,
|
|
431
|
+
tags: params.tags,
|
|
432
|
+
decidedBy: params.decidedBy,
|
|
433
|
+
});
|
|
434
|
+
return result.decision;
|
|
435
|
+
},
|
|
436
|
+
getSessionSummaries: (count) => {
|
|
437
|
+
// sessionMemory.listRecentSessions returns SessionListItem[]
|
|
438
|
+
// We need to return SessionSummary[], so we adapt
|
|
439
|
+
const sessions = sessionMemory.listRecentSessions(count);
|
|
440
|
+
return sessions.map(s => {
|
|
441
|
+
// Try to get full session details if available
|
|
442
|
+
const full = sessionMemory.getSession(s.sessionId);
|
|
443
|
+
if (full) {
|
|
444
|
+
return {
|
|
445
|
+
sessionId: full.sessionId,
|
|
446
|
+
startedAt: full.startedAt,
|
|
447
|
+
endedAt: full.endedAt,
|
|
448
|
+
tasksCompleted: full.tasksCompleted,
|
|
449
|
+
filesModified: full.filesModified,
|
|
450
|
+
decisionseMade: full.decisionseMade,
|
|
451
|
+
memoriesCreated: full.memoriesCreated,
|
|
452
|
+
tokensSaved: full.tokensSaved,
|
|
453
|
+
summary: full.summary,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
// Fallback: return minimal SessionSummary from SessionListItem
|
|
457
|
+
return {
|
|
458
|
+
sessionId: s.sessionId,
|
|
459
|
+
startedAt: s.startedAt,
|
|
460
|
+
endedAt: s.endedAt ?? Date.now(),
|
|
461
|
+
tasksCompleted: [],
|
|
462
|
+
filesModified: [],
|
|
463
|
+
decisionseMade: [],
|
|
464
|
+
memoriesCreated: 0,
|
|
465
|
+
tokensSaved: 0,
|
|
466
|
+
summary: s.summary,
|
|
467
|
+
};
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Creates an adapter that satisfies ISessionProvider.
|
|
474
|
+
* sessionMemory.getCurrentSessionId() returns string | null.
|
|
475
|
+
* The interface expects string, so we provide a fallback.
|
|
476
|
+
*/
|
|
477
|
+
function createSessionAdapter(sessionMemory) {
|
|
478
|
+
return {
|
|
479
|
+
currentSessionId: () => {
|
|
480
|
+
return sessionMemory.getCurrentSessionId() ?? 'no-session';
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Creates an adapter that satisfies IContextEngine.
|
|
486
|
+
*
|
|
487
|
+
* Key API mappings:
|
|
488
|
+
* - compress(text, level, contentType) β module-level function from compressor.ts
|
|
489
|
+
* - buildContextPackage(...) β module-level function from progressive-disclosure.ts
|
|
490
|
+
*/
|
|
491
|
+
function createContextAdapter(graph, persistentMemory, decisionLog, changeLog, config) {
|
|
492
|
+
return {
|
|
493
|
+
getContext: (params) => {
|
|
494
|
+
try {
|
|
495
|
+
// Build ProjectInfo for Tier 1
|
|
496
|
+
const overview = graph.getProjectOverview();
|
|
497
|
+
const stats = graph.getStats();
|
|
498
|
+
const directoryTree = Array.from(overview.keys())
|
|
499
|
+
.slice(0, 20)
|
|
500
|
+
.map(f => ` ${f}`)
|
|
501
|
+
.join('\n');
|
|
502
|
+
const projectInfo = {
|
|
503
|
+
name: path.basename(config.projectRoot),
|
|
504
|
+
description: params.taskDescription,
|
|
505
|
+
techStack: Object.keys(stats.languageBreakdown),
|
|
506
|
+
directoryTree,
|
|
507
|
+
conventions: [],
|
|
508
|
+
currentTask: params.taskDescription,
|
|
509
|
+
};
|
|
510
|
+
// Build Tier 2 data
|
|
511
|
+
const tier2Data = {};
|
|
512
|
+
if (params.includeMemories) {
|
|
513
|
+
tier2Data.memories = persistentMemory.queryMemories({
|
|
514
|
+
text: params.taskDescription,
|
|
515
|
+
limit: 5,
|
|
516
|
+
});
|
|
517
|
+
tier2Data.decisions = decisionLog.getActiveDecisions();
|
|
518
|
+
}
|
|
519
|
+
if (params.includeChanges) {
|
|
520
|
+
tier2Data.recentChanges = changeLog.queryChanges({
|
|
521
|
+
limit: 10,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
// Search graph for relevant nodes
|
|
525
|
+
const graphNodes = graph.search(params.taskDescription, 10);
|
|
526
|
+
if (graphNodes.length > 0) {
|
|
527
|
+
tier2Data.graphNodes = graphNodes;
|
|
528
|
+
}
|
|
529
|
+
// Build context package
|
|
530
|
+
const pkg = buildContextPackage(projectInfo, tier2Data, {}, // Tier 3 data β empty for initial load
|
|
531
|
+
config.tokenBudgets, params.taskDescription);
|
|
532
|
+
return pkg;
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
return {
|
|
536
|
+
tier1: 'Project context loading failed.',
|
|
537
|
+
tier2: '',
|
|
538
|
+
tier3: '',
|
|
539
|
+
totalTokens: 10,
|
|
540
|
+
tokensSaved: 0,
|
|
541
|
+
breakdown: [],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
compress: (params) => {
|
|
546
|
+
// compress(text, level, contentType?) from compressor.ts
|
|
547
|
+
// Returns CompressionResult { compressed, originalTokens, compressedTokens, ratio, contentType, level }
|
|
548
|
+
const result = compressContent(params.content, params.level, params.contentType);
|
|
549
|
+
return {
|
|
550
|
+
original: params.content,
|
|
551
|
+
compressed: result.compressed,
|
|
552
|
+
originalTokens: result.originalTokens,
|
|
553
|
+
compressedTokens: result.compressedTokens,
|
|
554
|
+
ratio: result.ratio,
|
|
555
|
+
contentType: result.contentType,
|
|
556
|
+
};
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Creates an adapter that satisfies IIndexer.
|
|
562
|
+
*
|
|
563
|
+
* Key API mappings:
|
|
564
|
+
* - indexer.fullIndex(onProgress?) β returns Promise<IndexStats>
|
|
565
|
+
* IndexStats has: filesScanned, filesParsed, filesSkipped, filesDeleted,
|
|
566
|
+
* nodesCreated, edgesCreated, parseErrors, durationMs, languages
|
|
567
|
+
* - graph.getStats() β returns { totalNodes, totalEdges, totalFiles, nodesByType, edgesByType, languageBreakdown }
|
|
568
|
+
* - persistentMemory.getStats() β returns MemoryStats
|
|
569
|
+
* - changeLog.getStats(topN?) β returns ChangeLogStats
|
|
570
|
+
*/
|
|
571
|
+
function createIndexerAdapter(indexer, graph, persistentMemory, decisionLog, changeLog, config) {
|
|
572
|
+
return {
|
|
573
|
+
reindex: async () => {
|
|
574
|
+
const startTime = Date.now();
|
|
575
|
+
try {
|
|
576
|
+
const result = await indexer.fullIndex();
|
|
577
|
+
return {
|
|
578
|
+
filesScanned: result.filesScanned,
|
|
579
|
+
filesIndexed: result.filesParsed,
|
|
580
|
+
nodesCreated: result.nodesCreated,
|
|
581
|
+
edgesCreated: result.edgesCreated,
|
|
582
|
+
durationMs: result.durationMs,
|
|
583
|
+
errors: result.parseErrors > 0
|
|
584
|
+
? [`${result.parseErrors} parse errors encountered`]
|
|
585
|
+
: [],
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
catch (err) {
|
|
589
|
+
return {
|
|
590
|
+
filesScanned: 0,
|
|
591
|
+
filesIndexed: 0,
|
|
592
|
+
nodesCreated: 0,
|
|
593
|
+
edgesCreated: 0,
|
|
594
|
+
durationMs: Date.now() - startTime,
|
|
595
|
+
errors: [err instanceof Error ? err.message : String(err)],
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
getStats: () => {
|
|
600
|
+
let dbSize = 0;
|
|
601
|
+
try {
|
|
602
|
+
if (existsSync(config.dbPath)) {
|
|
603
|
+
dbSize = statSync(config.dbPath).size;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
catch {
|
|
607
|
+
// ignore
|
|
608
|
+
}
|
|
609
|
+
const graphStats = graph.getStats();
|
|
610
|
+
const memoryStats = persistentMemory.getStats();
|
|
611
|
+
const changeStats = changeLog.getStats();
|
|
612
|
+
// DecisionLog doesn't have a count() method; use queryDecisions
|
|
613
|
+
const allDecisions = decisionLog.queryDecisions({});
|
|
614
|
+
return {
|
|
615
|
+
indexedFiles: graphStats.totalFiles,
|
|
616
|
+
totalNodes: graphStats.totalNodes,
|
|
617
|
+
totalEdges: graphStats.totalEdges,
|
|
618
|
+
totalMemories: memoryStats.totalMemories,
|
|
619
|
+
totalDecisions: allDecisions.length,
|
|
620
|
+
totalChangesTracked: changeStats.totalChanges,
|
|
621
|
+
lastIndexedAt: null, // Graph doesn't track this directly
|
|
622
|
+
lastChangeAt: null,
|
|
623
|
+
dbSizeBytes: dbSize,
|
|
624
|
+
languageBreakdown: graphStats.languageBreakdown,
|
|
625
|
+
tokensSavedEstimate: graphStats.totalNodes * 500,
|
|
626
|
+
};
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
// ============================================================
|
|
631
|
+
// Ensure database directory exists
|
|
632
|
+
// ============================================================
|
|
633
|
+
function ensureDbDirectory(dbPath) {
|
|
634
|
+
const dir = path.dirname(dbPath);
|
|
635
|
+
if (!existsSync(dir)) {
|
|
636
|
+
mkdirSync(dir, { recursive: true });
|
|
637
|
+
log('info', `Created database directory: ${dir}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
// ============================================================
|
|
641
|
+
// Main entry point
|
|
642
|
+
// ============================================================
|
|
643
|
+
async function main() {
|
|
644
|
+
// ββ 1. Parse CLI & load config ββββββββββββββββββββββββββββββ
|
|
645
|
+
let cliArgs;
|
|
646
|
+
try {
|
|
647
|
+
cliArgs = parseCliArgs();
|
|
648
|
+
}
|
|
649
|
+
catch (err) {
|
|
650
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
651
|
+
process.stderr.write(`Error parsing CLI arguments: ${msg}\n`);
|
|
652
|
+
process.exit(1);
|
|
653
|
+
}
|
|
654
|
+
setLogLevel(cliArgs.logLevel);
|
|
655
|
+
log('info', 'π§ AI Mind Map MCP Server startingβ¦');
|
|
656
|
+
let config;
|
|
657
|
+
try {
|
|
658
|
+
config = await loadConfig(cliArgs);
|
|
659
|
+
log('info', `Project root: ${config.projectRoot}`);
|
|
660
|
+
log('info', `Database path: ${config.dbPath}`);
|
|
661
|
+
log('debug', 'Loaded config', config);
|
|
662
|
+
}
|
|
663
|
+
catch (err) {
|
|
664
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
665
|
+
log('error', `Failed to load configuration: ${msg}`);
|
|
666
|
+
process.exit(1);
|
|
667
|
+
}
|
|
668
|
+
// ββ 2. Initialise database directory ββββββββββββββββββββββββ
|
|
669
|
+
try {
|
|
670
|
+
ensureDbDirectory(config.dbPath);
|
|
671
|
+
}
|
|
672
|
+
catch (err) {
|
|
673
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
674
|
+
log('error', `Failed to create DB directory: ${msg}`);
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
// ββ 3. Initialise SQLite database ββββββββββββββββββββββββββ
|
|
678
|
+
// KnowledgeGraph manages its own db connection, but ChangeLog,
|
|
679
|
+
// SessionMemory, PersistentMemory, and DecisionLog each need
|
|
680
|
+
// a shared Database instance for their tables.
|
|
681
|
+
log('info', 'Initialising databaseβ¦');
|
|
682
|
+
let sharedDb;
|
|
683
|
+
try {
|
|
684
|
+
sharedDb = new Database(config.dbPath);
|
|
685
|
+
sharedDb.pragma('journal_mode = WAL');
|
|
686
|
+
sharedDb.pragma('foreign_keys = ON');
|
|
687
|
+
sharedDb.pragma('busy_timeout = 5000');
|
|
688
|
+
log('info', 'Database initialized with WAL mode');
|
|
689
|
+
}
|
|
690
|
+
catch (err) {
|
|
691
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
692
|
+
log('error', `Failed to initialize database: ${msg}`);
|
|
693
|
+
process.exit(1);
|
|
694
|
+
}
|
|
695
|
+
// ββ 4. Initialise real subsystems ββββββββββββββββββββββββββ
|
|
696
|
+
log('info', 'Initialising subsystemsβ¦');
|
|
697
|
+
// Knowledge Graph β constructor takes dbPath string
|
|
698
|
+
const graph = new KnowledgeGraph(config.dbPath);
|
|
699
|
+
// Indexer β constructor is Indexer(graph, config)
|
|
700
|
+
const indexer = new Indexer(graph, config);
|
|
701
|
+
// PageRankEngine β constructor is PageRankEngine(graph, config?)
|
|
702
|
+
const pagerank = new PageRankEngine(graph);
|
|
703
|
+
log('info', 'β
Knowledge Graph initialized');
|
|
704
|
+
// Change Tracker
|
|
705
|
+
// ChangeLog constructor takes ChangeLogConfig: { dbPath, retentionDays?, defaultSearchLimit? }
|
|
706
|
+
const changeLog = new ChangeLog({ dbPath: config.dbPath });
|
|
707
|
+
const diffEngine = new DiffEngine(config.projectRoot);
|
|
708
|
+
let watcher = null;
|
|
709
|
+
// SessionMemory β must be created before watcher so it's available in the handler
|
|
710
|
+
// SessionMemory constructor takes Database.Database instance
|
|
711
|
+
const sessionMemory = new SessionMemory(sharedDb);
|
|
712
|
+
const sessionId = sessionMemory.startSession();
|
|
713
|
+
log('info', `Session started: ${sessionId}`);
|
|
714
|
+
if (config.watchEnabled) {
|
|
715
|
+
// FileWatcher constructor: config with { projectRoot, watchDebounceMs?, maxFileSize?, ignore? }
|
|
716
|
+
watcher = new FileWatcher({
|
|
717
|
+
projectRoot: config.projectRoot,
|
|
718
|
+
ignore: config.ignore,
|
|
719
|
+
watchDebounceMs: config.watchDebounceMs,
|
|
720
|
+
maxFileSize: config.maxFileSize,
|
|
721
|
+
});
|
|
722
|
+
// FileWatcher emits 'changes' with WatcherEvent[]
|
|
723
|
+
watcher.on('changes', async (events) => {
|
|
724
|
+
log('debug', `File watcher detected ${events.length} changes`);
|
|
725
|
+
for (const event of events) {
|
|
726
|
+
try {
|
|
727
|
+
if (event.changeType === 'deleted') {
|
|
728
|
+
indexer.removeFile(event.filePath);
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
await indexer.indexFile(event.filePath);
|
|
732
|
+
}
|
|
733
|
+
const currentSessionId = sessionMemory.getCurrentSessionId() ?? 'no-session';
|
|
734
|
+
changeLog.recordChange({
|
|
735
|
+
filePath: event.filePath,
|
|
736
|
+
changeType: event.changeType,
|
|
737
|
+
summary: `File ${event.changeType}: ${path.basename(event.filePath)}`,
|
|
738
|
+
symbolsAffected: [],
|
|
739
|
+
linesAdded: 0,
|
|
740
|
+
linesRemoved: 0,
|
|
741
|
+
timestamp: event.timestamp,
|
|
742
|
+
sessionId: currentSessionId,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
catch (err) {
|
|
746
|
+
log('warn', `Failed to process file change: ${event.filePath}`, err);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
// Invalidate PageRank cache when graph changes
|
|
750
|
+
pagerank.invalidateCache();
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
log('info', `β
Change Tracker initialized (watcher: ${config.watchEnabled ? 'enabled' : 'disabled'})`);
|
|
754
|
+
// Memory
|
|
755
|
+
// PersistentMemory constructor: (db, config?) where config is Pick<MindMapConfig['memory'], 'decayRate' | 'maxMemories' | 'importanceThreshold'>
|
|
756
|
+
const persistentMemory = new PersistentMemory(sharedDb, {
|
|
757
|
+
decayRate: config.memory.decayRate,
|
|
758
|
+
maxMemories: config.memory.maxMemories,
|
|
759
|
+
importanceThreshold: config.memory.importanceThreshold,
|
|
760
|
+
});
|
|
761
|
+
// DecisionLog constructor: (db, config?) where config is Pick<MindMapConfig['memory'], 'maxDecisions'>
|
|
762
|
+
const decisionLog = new DecisionLog(sharedDb, {
|
|
763
|
+
maxDecisions: config.memory.maxDecisions,
|
|
764
|
+
});
|
|
765
|
+
log('info', `β
Memory initialized (session: ${sessionMemory.getCurrentSessionId()})`);
|
|
766
|
+
// Context Engine β no class instances needed; uses module-level functions
|
|
767
|
+
log('info', 'β
Context Engine initialized');
|
|
768
|
+
// ββ 5. Build adapters ββββββββββββββββββββββββββββββββββββββ
|
|
769
|
+
const graphAdapter = createGraphAdapter(graph, pagerank);
|
|
770
|
+
const changeAdapter = createChangeAdapter(diffEngine, changeLog, graph);
|
|
771
|
+
const memoryAdapter = createMemoryAdapter(persistentMemory, decisionLog, sessionMemory);
|
|
772
|
+
const sessionAdapter = createSessionAdapter(sessionMemory);
|
|
773
|
+
const contextAdapter = createContextAdapter(graph, persistentMemory, decisionLog, changeLog, config);
|
|
774
|
+
const indexerAdapter = createIndexerAdapter(indexer, graph, persistentMemory, decisionLog, changeLog, config);
|
|
775
|
+
// Token estimator using the exported estimateTokens function
|
|
776
|
+
const tokenEstimator = {
|
|
777
|
+
estimate: (text) => estimateTokens(text),
|
|
778
|
+
};
|
|
779
|
+
// ββ 6. Create MCP server ββββββββββββββββββββββββββββββββββ
|
|
780
|
+
const server = new McpServer({
|
|
781
|
+
name: 'ai-mind-map',
|
|
782
|
+
version: '1.0.0',
|
|
783
|
+
});
|
|
784
|
+
// ββ 7. Register all tools βββββββββββββββββββββββββββββββββ
|
|
785
|
+
log('info', 'Registering MCP toolsβ¦');
|
|
786
|
+
registerGraphTools(server, graphAdapter, tokenEstimator);
|
|
787
|
+
log('debug', 'Registered graph tools (6)');
|
|
788
|
+
registerChangeTools(server, changeAdapter, tokenEstimator);
|
|
789
|
+
log('debug', 'Registered change tools (3)');
|
|
790
|
+
registerMemoryTools(server, memoryAdapter, sessionAdapter, tokenEstimator);
|
|
791
|
+
log('debug', 'Registered memory tools (5)');
|
|
792
|
+
registerContextTools(server, contextAdapter, indexerAdapter, tokenEstimator);
|
|
793
|
+
log('debug', 'Registered context tools (4)');
|
|
794
|
+
registerDebugTools(server, graph, config, tokenEstimator);
|
|
795
|
+
log('debug', 'Registered debug tools (3)');
|
|
796
|
+
registerFlowTools(server, graph, config, tokenEstimator);
|
|
797
|
+
log('debug', 'Registered flow tools (4)');
|
|
798
|
+
registerSnapshotTools(server, graph, config, tokenEstimator);
|
|
799
|
+
log('debug', 'Registered snapshot tools (3)');
|
|
800
|
+
registerSmartTools(server, graph, config, tokenEstimator);
|
|
801
|
+
log('debug', 'Registered smart tools (3)');
|
|
802
|
+
registerEvolvingTools(server, graph, config, tokenEstimator);
|
|
803
|
+
log('debug', 'Registered evolving tools (3)');
|
|
804
|
+
log('info', 'π§ All 41 MCP tools registered:');
|
|
805
|
+
log('info', ' Graph: mindmap_search, mindmap_get_structure, mindmap_trace_dependencies, mindmap_get_signature, mindmap_find_references, mindmap_get_file_map');
|
|
806
|
+
log('info', ' Changes: mindmap_what_changed, mindmap_session_diff, mindmap_impact_analysis');
|
|
807
|
+
log('info', ' Memory: mindmap_recall, mindmap_remember, mindmap_get_decisions, mindmap_decide, mindmap_session_summary');
|
|
808
|
+
log('info', ' Context: mindmap_get_context, mindmap_compress, mindmap_reindex, mindmap_status');
|
|
809
|
+
log('info', ' Debug: mindmap_debug_changes, mindmap_file_before, mindmap_file_history');
|
|
810
|
+
log('info', ' Flow: mindmap_trace_flow, mindmap_interaction_map, mindmap_classify_file, mindmap_layer_overview');
|
|
811
|
+
log('info', ' Snapshot: mindmap_project_map, mindmap_change_delta, mindmap_session_start β');
|
|
812
|
+
log('info', ' Advanced: mindmap_query_graph, mindmap_dead_code, mindmap_architecture, mindmap_get_code_snippet, mindmap_search_code, mindmap_list_projects, mindmap_health');
|
|
813
|
+
log('info', ' Smart: mindmap_explain β, mindmap_git_changes β, mindmap_smart_search β');
|
|
814
|
+
log('info', ' Evolving: mindmap_teach β, mindmap_get_learned, mindmap_forget');
|
|
815
|
+
// ββ 8. Auto-index on first run βββββββββββββββββββββββββββββ
|
|
816
|
+
const stats = graph.getStats();
|
|
817
|
+
if (stats.totalNodes === 0) {
|
|
818
|
+
log('info', 'π No existing index found. Running initial codebase indexingβ¦');
|
|
819
|
+
try {
|
|
820
|
+
const result = await indexer.fullIndex();
|
|
821
|
+
log('info', `β
Initial index complete: ${result.filesParsed} files, ${result.nodesCreated} nodes, ${result.edgesCreated} edges`);
|
|
822
|
+
if (result.parseErrors > 0) {
|
|
823
|
+
log('warn', `β οΈ ${result.parseErrors} parse errors (non-fatal)`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
catch (err) {
|
|
827
|
+
log('warn', `β οΈ Initial indexing failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
log('info', `π Existing index found: ${stats.totalNodes} nodes. Running incremental updateβ¦`);
|
|
832
|
+
try {
|
|
833
|
+
const result = await indexer.incrementalIndex();
|
|
834
|
+
log('info', `β
Incremental update: ${result.filesParsed} files reindexed`);
|
|
835
|
+
}
|
|
836
|
+
catch (err) {
|
|
837
|
+
log('warn', `β οΈ Incremental update failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
// ββ 9. Start file watcher ββββββββββββββββββββββββββββββββββ
|
|
841
|
+
if (watcher) {
|
|
842
|
+
try {
|
|
843
|
+
await watcher.start();
|
|
844
|
+
log('info', 'ποΈ File watcher started');
|
|
845
|
+
}
|
|
846
|
+
catch (err) {
|
|
847
|
+
log('warn', `β οΈ File watcher failed to start: ${err instanceof Error ? err.message : String(err)}`);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
// ββ 10. Graceful shutdown ββββββββββββββββββββββββββββββββββ
|
|
851
|
+
let shuttingDown = false;
|
|
852
|
+
async function shutdown(signal) {
|
|
853
|
+
if (shuttingDown)
|
|
854
|
+
return;
|
|
855
|
+
shuttingDown = true;
|
|
856
|
+
log('info', `Received ${signal}, shutting down gracefullyβ¦`);
|
|
857
|
+
try {
|
|
858
|
+
// Stop file watcher
|
|
859
|
+
if (watcher) {
|
|
860
|
+
await watcher.stop();
|
|
861
|
+
log('debug', 'File watcher stopped');
|
|
862
|
+
}
|
|
863
|
+
// End current session
|
|
864
|
+
try {
|
|
865
|
+
sessionMemory.endSession();
|
|
866
|
+
log('debug', 'Session ended');
|
|
867
|
+
}
|
|
868
|
+
catch {
|
|
869
|
+
// Ignore session end errors
|
|
870
|
+
}
|
|
871
|
+
// Apply memory decay
|
|
872
|
+
try {
|
|
873
|
+
persistentMemory.applyDecay();
|
|
874
|
+
log('debug', 'Memory decay applied');
|
|
875
|
+
}
|
|
876
|
+
catch {
|
|
877
|
+
// Ignore decay errors
|
|
878
|
+
}
|
|
879
|
+
// Close change log database
|
|
880
|
+
try {
|
|
881
|
+
changeLog.close();
|
|
882
|
+
log('debug', 'Change log closed');
|
|
883
|
+
}
|
|
884
|
+
catch {
|
|
885
|
+
// Ignore close errors
|
|
886
|
+
}
|
|
887
|
+
// Close graph database
|
|
888
|
+
try {
|
|
889
|
+
graph.close();
|
|
890
|
+
log('debug', 'Knowledge graph closed');
|
|
891
|
+
}
|
|
892
|
+
catch {
|
|
893
|
+
// Ignore close errors
|
|
894
|
+
}
|
|
895
|
+
// Close shared database
|
|
896
|
+
try {
|
|
897
|
+
sharedDb.close();
|
|
898
|
+
log('debug', 'Shared database closed');
|
|
899
|
+
}
|
|
900
|
+
catch {
|
|
901
|
+
// Ignore close errors
|
|
902
|
+
}
|
|
903
|
+
log('info', 'β
Cleanup complete. Goodbye!');
|
|
904
|
+
}
|
|
905
|
+
catch (err) {
|
|
906
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
907
|
+
log('error', `Error during shutdown: ${msg}`);
|
|
908
|
+
}
|
|
909
|
+
process.exit(0);
|
|
910
|
+
}
|
|
911
|
+
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
912
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
913
|
+
// Handle uncaught errors gracefully
|
|
914
|
+
process.on('uncaughtException', (err) => {
|
|
915
|
+
log('error', `Uncaught exception: ${err.message}`, err.stack);
|
|
916
|
+
void shutdown('uncaughtException');
|
|
917
|
+
});
|
|
918
|
+
process.on('unhandledRejection', (reason) => {
|
|
919
|
+
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
920
|
+
log('error', `Unhandled rejection: ${msg}`);
|
|
921
|
+
});
|
|
922
|
+
// ββ 11. Connect transport and start serving ββββββββββββββββ
|
|
923
|
+
log('info', 'Connecting stdio transportβ¦');
|
|
924
|
+
try {
|
|
925
|
+
const transport = new StdioServerTransport();
|
|
926
|
+
await server.connect(transport);
|
|
927
|
+
log('info', 'π§ AI Mind Map MCP Server is LIVE. Waiting for requestsβ¦');
|
|
928
|
+
log('info', ` Project: ${config.projectRoot}`);
|
|
929
|
+
log('info', ` Database: ${config.dbPath}`);
|
|
930
|
+
log('info', ` Session: ${sessionMemory.getCurrentSessionId()}`);
|
|
931
|
+
}
|
|
932
|
+
catch (err) {
|
|
933
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
934
|
+
log('error', `Failed to start MCP server: ${msg}`);
|
|
935
|
+
process.exit(1);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
// ββ Kick off ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
939
|
+
main().catch((err) => {
|
|
940
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
941
|
+
process.stderr.write(`Fatal: ${msg}\n`);
|
|
942
|
+
process.exit(1);
|
|
943
|
+
});
|
|
944
|
+
//# sourceMappingURL=index.js.map
|