ai-mind-map 1.6.1 → 1.6.2

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/dist/index.js CHANGED
@@ -1,1372 +1,2 @@
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
- import { syncSharedContext } from './memory/shared-sync.js';
33
- // ── Context Engine ────────────────────────────────────────────
34
- // compressor.ts exports functions: compress, detectContentType
35
- import { compress as compressContent } from './context/compressor.js';
36
- // progressive-disclosure.ts exports function: buildContextPackage
37
- import { buildContextPackage } from './context/progressive-disclosure.js';
38
- import { estimateTokens } from './utils/token-counter.js';
39
- // ── Tools ─────────────────────────────────────────────────────
40
- import { registerGraphTools } from './tools/graph-tools.js';
41
- import { registerChangeTools } from './tools/change-tools.js';
42
- import { registerMemoryTools } from './tools/memory-tools.js';
43
- import { registerContextTools } from './tools/context-tools.js';
44
- import { registerDebugTools } from './tools/debug-tools.js';
45
- import { registerFlowTools } from './tools/flow-tools.js';
46
- import { registerSnapshotTools } from './tools/snapshot-tools.js';
47
- import { registerSmartTools } from './tools/smart-tools.js';
48
- import { registerEvolvingTools } from './tools/evolving-tools.js';
49
- import { registerAdvancedTools } from './tools/advanced-tools.js';
50
- import { registerSemanticTools } from './tools/semantic-tools.js';
51
- import { SemanticSearchEngine } from './knowledge-graph/semantic-search.js';
52
- import { ChangelogEngine } from './knowledge-graph/changelog.js';
53
- import { registerSessionTools } from './tools/session-tools.js';
54
- import { registerDigestTools } from './tools/digest-tools.js';
55
- // ============================================================
56
- // Logger — writes to stderr so MCP stdio is uncontaminated
57
- // ============================================================
58
- const LOG_LEVELS = {
59
- debug: 0,
60
- info: 1,
61
- warn: 2,
62
- error: 3,
63
- };
64
- let currentLogLevel = LOG_LEVELS.info;
65
- function setLogLevel(level) {
66
- currentLogLevel = LOG_LEVELS[level];
67
- }
68
- function log(level, message, ...extra) {
69
- if (LOG_LEVELS[level] < currentLogLevel)
70
- return;
71
- const ts = new Date().toISOString();
72
- const prefix = `[${ts}] [${level.toUpperCase()}]`;
73
- if (extra.length > 0) {
74
- process.stderr.write(`${prefix} ${message} ${JSON.stringify(extra)}\n`);
75
- }
76
- else {
77
- process.stderr.write(`${prefix} ${message}\n`);
78
- }
79
- }
80
- // ============================================================
81
- // Adapters — Bridge real implementations to tool interfaces
82
- // ============================================================
83
- /**
84
- * Creates an adapter that satisfies IKnowledgeGraph from the real
85
- * KnowledgeGraph and PageRankEngine classes.
86
- *
87
- * Key API mappings:
88
- * - graph.search(query, limit) — FTS5 search, no type filter param
89
- * - graph.getProjectOverview() — returns Map<string, GraphNode[]>, no args
90
- * - graph.findCallers(nodeId) / graph.findCallees(nodeId) — single nodeId arg
91
- * - graph.getNodesByName(name) — returns GraphNode[]
92
- * - graph.getFileStructure(filePath) — returns GraphNode[]
93
- */
94
- function createGraphAdapter(graph, pagerank) {
95
- return {
96
- search: (query, type, limit) => {
97
- const maxResults = limit ?? 20;
98
- let results = graph.search(query, maxResults);
99
- // Expand with learned search aliases
100
- try {
101
- const aliases = graph.getLearnedSearchAliases();
102
- const lowerQuery = query.toLowerCase();
103
- for (const alias of aliases) {
104
- if (alias.term.toLowerCase() === lowerQuery) {
105
- for (const alt of alias.aliases) {
106
- if (results.length >= maxResults)
107
- break;
108
- const aliasResults = graph.search(alt, Math.max(3, maxResults - results.length));
109
- const existingIds = new Set(results.map(r => r.id));
110
- for (const r of aliasResults) {
111
- if (!existingIds.has(r.id) && results.length < maxResults) {
112
- results.push(r);
113
- existingIds.add(r.id);
114
- }
115
- }
116
- }
117
- // Touch the alias to track usage
118
- try {
119
- graph.touchLearnedRule(alias.id);
120
- }
121
- catch { }
122
- break;
123
- }
124
- }
125
- }
126
- catch {
127
- // Learned rules table might not exist
128
- }
129
- if (type) {
130
- return results.filter((n) => n.type === type);
131
- }
132
- return results;
133
- },
134
- getStructure: (depth) => {
135
- // graph.getProjectOverview() returns Map<string, GraphNode[]>
136
- // We convert it to the shape expected by IKnowledgeGraph.getStructure
137
- const overview = graph.getProjectOverview();
138
- const files = [];
139
- for (const [filePath, nodes] of overview) {
140
- files.push({
141
- path: filePath,
142
- symbols: nodes.map(n => ({
143
- name: n.name,
144
- type: n.type,
145
- signature: n.signature,
146
- })),
147
- });
148
- }
149
- // depth is accepted but getProjectOverview doesn't take a depth arg;
150
- // we can slice results if needed, but return all for now.
151
- return { files };
152
- },
153
- traceDependencies: (symbolName, direction, depth) => {
154
- const nodes = graph.getNodesByName(symbolName);
155
- if (nodes.length === 0) {
156
- return { root: symbolName, direction, depth, nodes: [], edges: [] };
157
- }
158
- const rootNode = nodes[0];
159
- let traced = [];
160
- // findCallers(nodeId) and findCallees(nodeId) each take a single string arg
161
- if (direction === 'callers' || direction === 'both') {
162
- traced = traced.concat(graph.findCallers(rootNode.id));
163
- }
164
- if (direction === 'callees' || direction === 'both') {
165
- traced = traced.concat(graph.findCallees(rootNode.id));
166
- }
167
- // Deduplicate
168
- const seen = new Set();
169
- const unique = traced.filter(n => {
170
- if (seen.has(n.id))
171
- return false;
172
- seen.add(n.id);
173
- return true;
174
- });
175
- // Collect relevant edges
176
- const edgeSet = [];
177
- for (const n of unique) {
178
- const outEdges = graph.getOutEdges(n.id);
179
- const inEdges = graph.getInEdges(n.id);
180
- edgeSet.push(...outEdges, ...inEdges);
181
- }
182
- return {
183
- root: symbolName,
184
- direction,
185
- depth,
186
- nodes: unique,
187
- edges: edgeSet,
188
- };
189
- },
190
- getSignature: (symbolName, filePath) => {
191
- const nodes = graph.getNodesByName(symbolName);
192
- let match;
193
- if (filePath) {
194
- match = nodes.find((n) => n.filePath === filePath);
195
- }
196
- else {
197
- match = nodes[0];
198
- }
199
- if (!match)
200
- return null;
201
- // Return the shape expected by IKnowledgeGraph.getSignature
202
- return {
203
- node: match,
204
- parameters: match.parameters,
205
- returnType: match.returnType,
206
- docComment: match.docComment,
207
- };
208
- },
209
- findReferences: (symbolName) => {
210
- const nodes = graph.getNodesByName(symbolName);
211
- if (nodes.length === 0) {
212
- return { symbol: symbolName, references: [] };
213
- }
214
- // Find all callers of the first matching node as "references"
215
- const callers = graph.findCallers(nodes[0].id);
216
- return {
217
- symbol: symbolName,
218
- references: callers.map((n) => ({
219
- filePath: n.filePath,
220
- line: n.startLine,
221
- context: n.signature,
222
- })),
223
- };
224
- },
225
- getFileMap: (filePath) => {
226
- const nodes = graph.getFileStructure(filePath);
227
- if (!nodes || nodes.length === 0)
228
- return null;
229
- return {
230
- filePath,
231
- symbols: nodes
232
- .filter(n => n.type !== 'file')
233
- .map(n => ({
234
- name: n.name,
235
- type: n.type,
236
- signature: n.signature,
237
- startLine: n.startLine,
238
- endLine: n.endLine,
239
- visibility: n.visibility,
240
- isExported: n.isExported,
241
- })),
242
- };
243
- },
244
- };
245
- }
246
- /**
247
- * Creates an adapter that satisfies IChangeTracker from the real
248
- * DiffEngine and ChangeLog classes.
249
- *
250
- * Key API mappings:
251
- * - changeLog.getLatestSession() — returns ChangeSession | null
252
- * - changeLog.queryChanges(options) — options has `since` (timestamp), not `afterTimestamp`
253
- * - changeLog.generateSessionSummary(sessionId) — returns string
254
- * - changeLog.recordChange(change) — records a FileChange
255
- */
256
- function createChangeAdapter(diffEngine, changeLog, graph) {
257
- return {
258
- getChanges: (since) => {
259
- try {
260
- const latestSession = changeLog.getLatestSession();
261
- const sinceTimestamp = since === 'last_session'
262
- ? (latestSession?.endedAt ?? Date.now() - 86400000)
263
- : since === 'today'
264
- ? new Date().setHours(0, 0, 0, 0)
265
- : since === 'this_week'
266
- ? Date.now() - 7 * 86400000
267
- : new Date(since).getTime();
268
- // ChangeLog.queryChanges uses ChangeQueryOptions with `since` field (timestamp)
269
- const changes = changeLog.queryChanges({
270
- since: sinceTimestamp,
271
- });
272
- return {
273
- since,
274
- resolvedTimestamp: sinceTimestamp,
275
- changes,
276
- totalFilesChanged: new Set(changes.map(c => c.filePath)).size,
277
- totalLinesAdded: changes.reduce((sum, c) => sum + c.linesAdded, 0),
278
- totalLinesRemoved: changes.reduce((sum, c) => sum + c.linesRemoved, 0),
279
- summary: changes.length > 0
280
- ? changes.map(c => `- ${c.filePath}: ${c.summary}`).join('\n')
281
- : 'No changes found since the specified time.',
282
- };
283
- }
284
- catch {
285
- return {
286
- since,
287
- resolvedTimestamp: Date.now(),
288
- changes: [],
289
- totalFilesChanged: 0,
290
- totalLinesAdded: 0,
291
- totalLinesRemoved: 0,
292
- summary: 'Unable to retrieve changes.',
293
- };
294
- }
295
- },
296
- getSessionDiff: () => {
297
- try {
298
- const latestSession = changeLog.getLatestSession();
299
- if (!latestSession) {
300
- return {
301
- previousSession: null,
302
- changes: [],
303
- affectedSymbols: [],
304
- summary: 'No previous session found.',
305
- };
306
- }
307
- const changes = changeLog.queryChanges({
308
- since: latestSession.endedAt ?? latestSession.startedAt,
309
- });
310
- const symbols = changes.flatMap(c => c.symbolsAffected);
311
- return {
312
- previousSession: latestSession,
313
- changes,
314
- affectedSymbols: [...new Set(symbols)],
315
- summary: changeLog.generateSessionSummary(latestSession.sessionId),
316
- };
317
- }
318
- catch {
319
- return {
320
- previousSession: null,
321
- changes: [],
322
- affectedSymbols: [],
323
- summary: 'Unable to compute session diff.',
324
- };
325
- }
326
- },
327
- analyseImpact: (params) => {
328
- const target = params.filePath ?? params.symbolName ?? 'unknown';
329
- // Try to find the node and compute blast radius
330
- let directlyAffected = [];
331
- let transitivelyAffected = [];
332
- let riskLevel = 'low';
333
- try {
334
- if (params.symbolName) {
335
- const nodes = graph.getNodesByName(params.symbolName);
336
- if (nodes.length > 0) {
337
- const rootNode = nodes[0];
338
- const callers = graph.findCallers(rootNode.id);
339
- directlyAffected = callers.map(n => ({
340
- node: n,
341
- relationship: 'calls',
342
- }));
343
- const blastNodes = graph.blastRadius(rootNode.id, 3);
344
- transitivelyAffected = blastNodes.map((n, idx) => ({
345
- node: n,
346
- depth: Math.min(idx + 1, 3),
347
- }));
348
- const total = directlyAffected.length + transitivelyAffected.length;
349
- riskLevel = total > 20 ? 'high' : total > 5 ? 'medium' : 'low';
350
- }
351
- }
352
- else if (params.filePath) {
353
- const fileNodes = graph.getFileStructure(params.filePath);
354
- for (const node of fileNodes) {
355
- if (node.type === 'file')
356
- continue;
357
- const callers = graph.findCallers(node.id);
358
- for (const caller of callers) {
359
- directlyAffected.push({ node: caller, relationship: 'calls' });
360
- }
361
- }
362
- riskLevel = directlyAffected.length > 10 ? 'high'
363
- : directlyAffected.length > 3 ? 'medium' : 'low';
364
- }
365
- }
366
- catch {
367
- // Fall through with empty arrays
368
- }
369
- return {
370
- target,
371
- directlyAffected,
372
- transitivelyAffected,
373
- riskLevel,
374
- summary: directlyAffected.length > 0
375
- ? `${target}: ${directlyAffected.length} directly affected, ${transitivelyAffected.length} transitively affected. Risk: ${riskLevel}.`
376
- : `${target}: No dependents found. Use mindmap_trace_dependencies for full dependency chain.`,
377
- };
378
- },
379
- };
380
- }
381
- /**
382
- * Creates an adapter that satisfies IMemoryStore from PersistentMemory,
383
- * DecisionLog, and SessionMemory.
384
- *
385
- * Key API mappings:
386
- * - persistentMemory.queryMemories(query) — query uses MemoryQuery shape
387
- * - persistentMemory.createMemory(input) — input is CreateMemoryInput
388
- * - persistentMemory.getStats() — returns MemoryStats
389
- * - decisionLog.queryDecisions(query) — query uses DecisionQuery
390
- * - decisionLog.createDecision(input) — returns { decision, conflicts }
391
- * - decisionLog.getActiveDecisions() — returns Decision[]
392
- * - sessionMemory.listRecentSessions(limit) — returns SessionListItem[]
393
- */
394
- function createMemoryAdapter(persistentMemory, decisionLog, sessionMemory) {
395
- return {
396
- recall: (query, category, limit) => {
397
- return persistentMemory.queryMemories({
398
- text: query,
399
- categories: category ? [category] : undefined,
400
- limit: limit ?? 10,
401
- });
402
- },
403
- remember: (params) => {
404
- return persistentMemory.createMemory({
405
- category: params.category,
406
- content: params.content,
407
- importance: params.importance,
408
- tags: params.tags,
409
- relatedFiles: params.relatedFiles,
410
- sessionId: params.sessionId,
411
- source: params.source,
412
- });
413
- },
414
- getDecisions: (params) => {
415
- if (params.query) {
416
- // Use DecisionLog.queryDecisions with text search
417
- const results = decisionLog.queryDecisions({
418
- text: params.query,
419
- status: params.status === 'all' ? undefined : (params.status ?? 'active'),
420
- });
421
- return results;
422
- }
423
- if (!params.status || params.status === 'active') {
424
- return decisionLog.getActiveDecisions();
425
- }
426
- // 'all' — query with no filters
427
- return decisionLog.queryDecisions({});
428
- },
429
- decide: (params) => {
430
- // DecisionLog.createDecision returns { decision, conflicts }
431
- const result = decisionLog.createDecision({
432
- title: params.title,
433
- description: params.description,
434
- rationale: params.rationale,
435
- alternatives: params.alternatives,
436
- consequences: params.consequences,
437
- relatedFiles: params.relatedFiles,
438
- tags: params.tags,
439
- decidedBy: params.decidedBy,
440
- });
441
- return result.decision;
442
- },
443
- getSessionSummaries: (count) => {
444
- // sessionMemory.listRecentSessions returns SessionListItem[]
445
- // We need to return SessionSummary[], so we adapt
446
- const sessions = sessionMemory.listRecentSessions(count);
447
- return sessions.map(s => {
448
- // Try to get full session details if available
449
- const full = sessionMemory.getSession(s.sessionId);
450
- if (full) {
451
- return {
452
- sessionId: full.sessionId,
453
- startedAt: full.startedAt,
454
- endedAt: full.endedAt,
455
- tasksCompleted: full.tasksCompleted,
456
- filesModified: full.filesModified,
457
- decisionseMade: full.decisionseMade,
458
- memoriesCreated: full.memoriesCreated,
459
- tokensSaved: full.tokensSaved,
460
- summary: full.summary,
461
- };
462
- }
463
- // Fallback: return minimal SessionSummary from SessionListItem
464
- return {
465
- sessionId: s.sessionId,
466
- startedAt: s.startedAt,
467
- endedAt: s.endedAt ?? Date.now(),
468
- tasksCompleted: [],
469
- filesModified: [],
470
- decisionseMade: [],
471
- memoriesCreated: 0,
472
- tokensSaved: 0,
473
- summary: s.summary,
474
- };
475
- });
476
- },
477
- };
478
- }
479
- /**
480
- * Creates an adapter that satisfies ISessionProvider.
481
- * sessionMemory.getCurrentSessionId() returns string | null.
482
- * The interface expects string, so we provide a fallback.
483
- */
484
- function createSessionAdapter(sessionMemory) {
485
- return {
486
- currentSessionId: () => {
487
- return sessionMemory.getCurrentSessionId() ?? 'no-session';
488
- },
489
- };
490
- }
491
- /**
492
- * Creates an adapter that satisfies IContextEngine.
493
- *
494
- * Key API mappings:
495
- * - compress(text, level, contentType) — module-level function from compressor.ts
496
- * - buildContextPackage(...) — module-level function from progressive-disclosure.ts
497
- */
498
- function createContextAdapter(graph, persistentMemory, decisionLog, changeLog, config) {
499
- return {
500
- getContext: (params) => {
501
- try {
502
- // Build ProjectInfo for Tier 1
503
- const overview = graph.getProjectOverview();
504
- const stats = graph.getStats();
505
- const directoryTree = Array.from(overview.keys())
506
- .slice(0, 20)
507
- .map(f => ` ${f}`)
508
- .join('\n');
509
- const projectInfo = {
510
- name: path.basename(config.projectRoot),
511
- description: params.taskDescription,
512
- techStack: Object.keys(stats.languageBreakdown),
513
- directoryTree,
514
- conventions: [],
515
- currentTask: params.taskDescription,
516
- };
517
- // Build Tier 2 data
518
- const tier2Data = {};
519
- if (params.includeMemories) {
520
- tier2Data.memories = persistentMemory.queryMemories({
521
- text: params.taskDescription,
522
- limit: 5,
523
- });
524
- tier2Data.decisions = decisionLog.getActiveDecisions();
525
- }
526
- if (params.includeChanges) {
527
- tier2Data.recentChanges = changeLog.queryChanges({
528
- limit: 10,
529
- });
530
- }
531
- // Search graph for relevant nodes
532
- const graphNodes = graph.search(params.taskDescription, 10);
533
- if (graphNodes.length > 0) {
534
- tier2Data.graphNodes = graphNodes;
535
- }
536
- // Build context package
537
- const pkg = buildContextPackage(projectInfo, tier2Data, {}, // Tier 3 data — empty for initial load
538
- config.tokenBudgets, params.taskDescription);
539
- return pkg;
540
- }
541
- catch {
542
- return {
543
- tier1: 'Project context loading failed.',
544
- tier2: '',
545
- tier3: '',
546
- totalTokens: 10,
547
- tokensSaved: 0,
548
- breakdown: [],
549
- };
550
- }
551
- },
552
- compress: (params) => {
553
- // compress(text, level, contentType?) from compressor.ts
554
- // Returns CompressionResult { compressed, originalTokens, compressedTokens, ratio, contentType, level }
555
- const result = compressContent(params.content, params.level, params.contentType);
556
- return {
557
- original: params.content,
558
- compressed: result.compressed,
559
- originalTokens: result.originalTokens,
560
- compressedTokens: result.compressedTokens,
561
- ratio: result.ratio,
562
- contentType: result.contentType,
563
- };
564
- },
565
- };
566
- }
567
- /**
568
- * Creates an adapter that satisfies IIndexer.
569
- *
570
- * Key API mappings:
571
- * - indexer.fullIndex(onProgress?) — returns Promise<IndexStats>
572
- * IndexStats has: filesScanned, filesParsed, filesSkipped, filesDeleted,
573
- * nodesCreated, edgesCreated, parseErrors, durationMs, languages
574
- * - graph.getStats() — returns { totalNodes, totalEdges, totalFiles, nodesByType, edgesByType, languageBreakdown }
575
- * - persistentMemory.getStats() — returns MemoryStats
576
- * - changeLog.getStats(topN?) — returns ChangeLogStats
577
- */
578
- function createIndexerAdapter(indexer, graph, persistentMemory, decisionLog, changeLog, config, watcher) {
579
- return {
580
- reindex: async () => {
581
- const startTime = Date.now();
582
- try {
583
- const result = await indexer.fullIndex();
584
- return {
585
- filesScanned: result.filesScanned,
586
- filesIndexed: result.filesParsed,
587
- nodesCreated: result.nodesCreated,
588
- edgesCreated: result.edgesCreated,
589
- durationMs: result.durationMs,
590
- errors: result.parseErrors > 0
591
- ? [`${result.parseErrors} parse errors encountered`]
592
- : [],
593
- };
594
- }
595
- catch (err) {
596
- return {
597
- filesScanned: 0,
598
- filesIndexed: 0,
599
- nodesCreated: 0,
600
- edgesCreated: 0,
601
- durationMs: Date.now() - startTime,
602
- errors: [err instanceof Error ? err.message : String(err)],
603
- };
604
- }
605
- },
606
- reindexProject: async (projectPath) => {
607
- const startTime = Date.now();
608
- const resolvedPath = path.resolve(projectPath);
609
- try {
610
- // Re-target the indexer to the new project
611
- indexer.setProjectRoot(resolvedPath);
612
- log('info', `📁 Re-targeted to project: ${resolvedPath}`);
613
- // Also watch the new project directory for changes
614
- if (watcher) {
615
- watcher.addRoot(resolvedPath);
616
- log('info', `👁️ File watcher now also watching: ${resolvedPath}`);
617
- }
618
- // Run full index on the new project (don't clear — multi-project)
619
- const result = await indexer.fullIndex();
620
- return {
621
- filesScanned: result.filesScanned,
622
- filesIndexed: result.filesParsed,
623
- nodesCreated: result.nodesCreated,
624
- edgesCreated: result.edgesCreated,
625
- durationMs: result.durationMs,
626
- errors: result.parseErrors > 0
627
- ? [`${result.parseErrors} parse errors encountered`]
628
- : [],
629
- projectRoot: resolvedPath,
630
- };
631
- }
632
- catch (err) {
633
- return {
634
- filesScanned: 0,
635
- filesIndexed: 0,
636
- nodesCreated: 0,
637
- edgesCreated: 0,
638
- durationMs: Date.now() - startTime,
639
- errors: [err instanceof Error ? err.message : String(err)],
640
- projectRoot: resolvedPath,
641
- };
642
- }
643
- },
644
- getStats: () => {
645
- let dbSize = 0;
646
- try {
647
- if (existsSync(config.dbPath)) {
648
- dbSize = statSync(config.dbPath).size;
649
- }
650
- }
651
- catch {
652
- // ignore
653
- }
654
- const graphStats = graph.getStats();
655
- const memoryStats = persistentMemory.getStats();
656
- const changeStats = changeLog.getStats();
657
- // DecisionLog doesn't have a count() method; use queryDecisions
658
- const allDecisions = decisionLog.queryDecisions({});
659
- return {
660
- projectRoot: config.projectRoot,
661
- indexedFiles: graphStats.totalFiles,
662
- totalNodes: graphStats.totalNodes,
663
- totalEdges: graphStats.totalEdges,
664
- totalMemories: memoryStats.totalMemories,
665
- totalDecisions: allDecisions.length,
666
- totalChangesTracked: changeStats.totalChanges,
667
- lastIndexedAt: null, // Graph doesn't track this directly
668
- lastChangeAt: null,
669
- dbSizeBytes: dbSize,
670
- languageBreakdown: graphStats.languageBreakdown,
671
- tokensSavedEstimate: graphStats.totalNodes * 500,
672
- };
673
- },
674
- };
675
- }
676
- // ============================================================
677
- // Ensure database directory exists
678
- // ============================================================
679
- function ensureDbDirectory(dbPath) {
680
- const dir = path.dirname(dbPath);
681
- if (!existsSync(dir)) {
682
- mkdirSync(dir, { recursive: true });
683
- log('info', `Created database directory: ${dir}`);
684
- }
685
- }
686
- // ============================================================
687
- // Session Token Tracker
688
- // ============================================================
689
- /**
690
- * Tracks cumulative token usage across all tool calls in a session.
691
- * Every tool response is enriched with `_sessionTokens` metadata
692
- * so AI agents always know their token footprint.
693
- */
694
- class SessionTokenTracker {
695
- totalToolCalls = 0;
696
- totalOutputTokens = 0;
697
- totalInputTokens = 0;
698
- totalTokensSaved = 0;
699
- startTime = Date.now();
700
- /** Record a tool call with its input and output token counts. */
701
- record(inputTokens, outputTokens, tokensSaved) {
702
- this.totalToolCalls++;
703
- this.totalInputTokens += inputTokens;
704
- this.totalOutputTokens += outputTokens;
705
- this.totalTokensSaved += tokensSaved;
706
- }
707
- /** Get a summary object to include in every tool response. */
708
- getSummary() {
709
- return {
710
- totalToolCalls: this.totalToolCalls,
711
- totalInputTokens: this.totalInputTokens,
712
- totalOutputTokens: this.totalOutputTokens,
713
- totalTokensSaved: this.totalTokensSaved,
714
- sessionDurationMs: Date.now() - this.startTime,
715
- };
716
- }
717
- }
718
- /**
719
- * Intercept an MCP tool response to inject session token metadata.
720
- *
721
- * Parses the JSON text content, adds `_sessionTokens` and optionally
722
- * `_hint` fields, then re-serialises. This works because all our tools
723
- * return `{ content: [{ type: 'text', text: JSON.stringify(result) }] }`.
724
- */
725
- function enrichToolResponse(response, tracker, estimator, graphNodeCount, getProjectInfo) {
726
- if (!response?.content?.[0]?.text)
727
- return response;
728
- try {
729
- const result = JSON.parse(response.content[0].text);
730
- // Estimate tokens for this response
731
- const outputTokens = estimator.estimate(response.content[0].text);
732
- const tokensSaved = result.tokensSaved ?? 0;
733
- // Record in tracker
734
- tracker.record(0, outputTokens, tokensSaved);
735
- // Always add session token metadata
736
- result._sessionTokens = tracker.getSummary();
737
- // Always add project metadata
738
- result._project = getProjectInfo();
739
- // If result is empty/failed AND graph has no nodes, add auto-index hint
740
- const nodeCount = graphNodeCount();
741
- if (nodeCount === 0) {
742
- const isEmpty = result.success === true && (result.data === null ||
743
- (Array.isArray(result.data) && result.data.length === 0) ||
744
- (typeof result.data === 'object' && result.data !== null && Object.keys(result.data).length === 0));
745
- const isFail = result.success === false;
746
- if (isEmpty || isFail) {
747
- result._hint = '⚠️ No codebase index found. Call mindmap_reindex with the projectPath parameter ' +
748
- 'set to the root directory of the user\'s project (e.g. "E:\\\\myproject"). ' +
749
- 'This is a one-time operation that takes ~10-30 seconds.';
750
- }
751
- }
752
- response.content[0].text = JSON.stringify(result);
753
- }
754
- catch {
755
- // If parsing fails, return original response unchanged
756
- }
757
- return response;
758
- }
759
- // ============================================================
760
- // Main entry point
761
- // ============================================================
762
- async function main() {
763
- // ── 1. Parse CLI & load config ──────────────────────────────
764
- let cliArgs;
765
- try {
766
- cliArgs = parseCliArgs();
767
- }
768
- catch (err) {
769
- const msg = err instanceof Error ? err.message : String(err);
770
- process.stderr.write(`Error parsing CLI arguments: ${msg}\n`);
771
- process.exit(1);
772
- }
773
- setLogLevel(cliArgs.logLevel);
774
- log('info', '🧠 AI Mind Map MCP Server starting…');
775
- let config;
776
- try {
777
- config = await loadConfig(cliArgs);
778
- log('info', `Project root: ${config.projectRoot}`);
779
- log('info', `Database path: ${config.dbPath}`);
780
- log('debug', 'Loaded config', config);
781
- }
782
- catch (err) {
783
- const msg = err instanceof Error ? err.message : String(err);
784
- log('error', `Failed to load configuration: ${msg}`);
785
- process.exit(1);
786
- }
787
- // ── 2. Initialise database directory ────────────────────────
788
- try {
789
- ensureDbDirectory(config.dbPath);
790
- }
791
- catch (err) {
792
- const msg = err instanceof Error ? err.message : String(err);
793
- log('error', `Failed to create DB directory: ${msg}`);
794
- process.exit(1);
795
- }
796
- // ── 3. Initialise SQLite database ──────────────────────────
797
- // KnowledgeGraph manages its own db connection, but ChangeLog,
798
- // SessionMemory, PersistentMemory, and DecisionLog each need
799
- // a shared Database instance for their tables.
800
- log('info', 'Initialising database…');
801
- let sharedDb;
802
- try {
803
- sharedDb = new Database(config.dbPath);
804
- sharedDb.pragma('journal_mode = WAL');
805
- sharedDb.pragma('foreign_keys = ON');
806
- sharedDb.pragma('busy_timeout = 5000');
807
- log('info', 'Database initialized with WAL mode');
808
- }
809
- catch (err) {
810
- const msg = err instanceof Error ? err.message : String(err);
811
- log('error', `Failed to initialize database: ${msg}`);
812
- process.exit(1);
813
- }
814
- // ── 4. Initialise real subsystems ──────────────────────────
815
- log('info', 'Initialising subsystems…');
816
- // Knowledge Graph — constructor takes dbPath string
817
- const graph = new KnowledgeGraph(config.dbPath);
818
- // Indexer — constructor is Indexer(graph, config)
819
- const indexer = new Indexer(graph, config);
820
- // PageRankEngine — constructor is PageRankEngine(graph, config?)
821
- const pagerank = new PageRankEngine(graph);
822
- // Changelog Engine — node-level change tracking (v1.4.0)
823
- const changelogEngine = new ChangelogEngine(graph.getDb());
824
- indexer.setChangelog(changelogEngine);
825
- log('info', '✅ Knowledge Graph initialized (with changelog engine)');
826
- // Change Tracker
827
- // ChangeLog constructor takes ChangeLogConfig: { dbPath, retentionDays?, defaultSearchLimit? }
828
- const changeLog = new ChangeLog({ dbPath: config.dbPath });
829
- const diffEngine = new DiffEngine(config.projectRoot);
830
- let watcher = null;
831
- // SessionMemory — must be created before watcher so it's available in the handler
832
- // SessionMemory constructor takes Database.Database instance
833
- const sessionMemory = new SessionMemory(sharedDb);
834
- const sessionId = sessionMemory.startSession();
835
- log('info', `Session started: ${sessionId}`);
836
- if (config.watchEnabled && !config.memoryOnly) {
837
- // FileWatcher constructor: config with { projectRoot, watchDebounceMs?, maxFileSize?, ignore? }
838
- watcher = new FileWatcher({
839
- projectRoot: config.projectRoot,
840
- ignore: config.ignore,
841
- watchDebounceMs: config.watchDebounceMs,
842
- maxFileSize: config.maxFileSize,
843
- });
844
- // FileWatcher emits 'changes' with WatcherEvent[]
845
- watcher.on('changes', async (events) => {
846
- log('debug', `File watcher detected ${events.length} changes`);
847
- for (const event of events) {
848
- try {
849
- if (event.changeType === 'deleted') {
850
- indexer.removeFile(event.filePath);
851
- }
852
- else {
853
- await indexer.indexFile(event.filePath);
854
- }
855
- const currentSessionId = sessionMemory.getCurrentSessionId() ?? 'no-session';
856
- changeLog.recordChange({
857
- filePath: event.filePath,
858
- changeType: event.changeType,
859
- summary: `File ${event.changeType}: ${path.basename(event.filePath)}`,
860
- symbolsAffected: [],
861
- linesAdded: 0,
862
- linesRemoved: 0,
863
- timestamp: event.timestamp,
864
- sessionId: currentSessionId,
865
- });
866
- }
867
- catch (err) {
868
- log('warn', `Failed to process file change: ${event.filePath}`, err);
869
- }
870
- }
871
- // Invalidate PageRank cache when graph changes
872
- pagerank.invalidateCache();
873
- });
874
- }
875
- log('info', `✅ Change Tracker initialized (watcher: ${config.watchEnabled ? 'enabled' : 'disabled'})`);
876
- // Memory
877
- // PersistentMemory constructor: (db, config?) where config is Pick<MindMapConfig['memory'], 'decayRate' | 'maxMemories' | 'importanceThreshold'>
878
- const persistentMemory = new PersistentMemory(sharedDb, {
879
- decayRate: config.memory.decayRate,
880
- maxMemories: config.memory.maxMemories,
881
- importanceThreshold: config.memory.importanceThreshold,
882
- });
883
- // DecisionLog constructor: (db, config?) where config is Pick<MindMapConfig['memory'], 'maxDecisions'>
884
- const decisionLog = new DecisionLog(sharedDb, {
885
- maxDecisions: config.memory.maxDecisions,
886
- });
887
- log('info', `✅ Memory initialized (session: ${sessionMemory.getCurrentSessionId()})`);
888
- // Apply memory decay on startup
889
- try {
890
- const decayed = persistentMemory.applyDecay();
891
- log('info', `Applied time-based memory decay at startup (${decayed} memories decayed)`);
892
- }
893
- catch (err) {
894
- log('warn', 'Failed to apply memory decay at startup', err);
895
- }
896
- // Context Engine — no class instances needed; uses module-level functions
897
- log('info', '✅ Context Engine initialized');
898
- // ── 5. Build adapters ──────────────────────────────────────
899
- const graphAdapter = createGraphAdapter(graph, pagerank);
900
- const changeAdapter = createChangeAdapter(diffEngine, changeLog, graph);
901
- const memoryAdapter = createMemoryAdapter(persistentMemory, decisionLog, sessionMemory);
902
- const sessionAdapter = createSessionAdapter(sessionMemory);
903
- const contextAdapter = createContextAdapter(graph, persistentMemory, decisionLog, changeLog, config);
904
- const indexerAdapter = createIndexerAdapter(indexer, graph, persistentMemory, decisionLog, changeLog, config, watcher);
905
- // Token estimator using the exported estimateTokens function
906
- const tokenEstimator = {
907
- estimate: (text) => estimateTokens(text),
908
- };
909
- // ── 6. Create MCP server ──────────────────────────────────
910
- const server = new McpServer({
911
- name: 'ai-mind-map',
912
- version: '1.6.1',
913
- }, {
914
- instructions: [
915
- '# AI Mind Map — Code Memory Engine',
916
- '',
917
- 'You have access to AI Mind Map, a persistent code memory system that saves you from re-reading files and losing context between sessions.',
918
- '',
919
- '## 🚀 FIRST CALL (every new conversation):',
920
- 'Call `mindmap_session_resume` FIRST. It returns:',
921
- '- What the previous AI worked on',
922
- '- What code changed since then (function-level diffs)',
923
- '- Project structure + tech stack',
924
- '- Hot files (most frequently changed)',
925
- 'This ONE call replaces reading 10+ files.',
926
- '',
927
- '## 📋 Tool Selection Guide:',
928
- '',
929
- '### When you need to UNDERSTAND the project:',
930
- '- `mindmap_digest` → Full project summary in <2000 tokens',
931
- '- `mindmap_architecture` → Layers, patterns, component overview',
932
- '- `mindmap_file_digest` → Understand a file WITHOUT reading it',
933
- '',
934
- '### When you need to FIND code:',
935
- '- `mindmap_smart_search` → Search by name/concept (best for most lookups)',
936
- '- `mindmap_semantic_search` → Search by meaning ("authentication", "error handling")',
937
- '- `mindmap_search_code` → Grep-like text search in code bodies',
938
- '- `mindmap_trace_dependencies` → Who calls X? What does X call?',
939
- '',
940
- '### When you need to READ code:',
941
- '- `mindmap_explain` → Get EVERYTHING about a symbol: signature, callers, callees, doc',
942
- '- `mindmap_get_code_snippet` → Read actual source code for a function/class',
943
- '- `mindmap_get_file_map` → All symbols in a file with signatures + line ranges',
944
- '',
945
- '### When you need to know WHAT CHANGED:',
946
- '- `mindmap_changelog` → Symbol-level diffs (added/modified/deleted functions)',
947
- '- `mindmap_git_changes` → Git-aware changes with symbol mapping',
948
- '- `mindmap_verify` → Check if your cached knowledge is still valid',
949
- '- `mindmap_hotspots` → Most frequently changed files + symbols',
950
- '',
951
- '### When you need to REMEMBER:',
952
- '- `mindmap_remember` → Save a fact/convention for future sessions',
953
- '- `mindmap_recall` → Retrieve relevant memories for current task',
954
- '- `mindmap_decide` → Record architectural decisions with rationale',
955
- '',
956
- '### When you finish work:',
957
- '- `mindmap_session_end` → Save summary so next AI can resume instantly',
958
- '',
959
- '### After editing code:',
960
- '- `mindmap_verify_changes` → Verify your edits at the symbol level WITHOUT re-reading files',
961
- '',
962
- '## ⚡ Token-Saving Rules:',
963
- '1. ALWAYS call `mindmap_session_resume` first — never start blind',
964
- '2. Use `mindmap_file_digest` BEFORE reading a full file — you may not need the full file',
965
- '3. Use `mindmap_verify_changes` after editing to verify changes — do NOT re-read whole files',
966
- '4. Use `mindmap_changelog` instead of re-reading files to see what changed',
967
- '5. Call `mindmap_session_end` when done — save context for next session',
968
- '',
969
- '## 🔄 Project Indexing:',
970
- 'Every response includes `_project.root` showing which directory is currently indexed.',
971
- 'If `_project.totalNodes` is 0, you MUST call `mindmap_reindex` with `projectPath` set to',
972
- 'the user\'s project directory (e.g. their workspace root). This creates the knowledge graph.',
973
- 'Example: `mindmap_reindex({ projectPath: "/home/user/my-project" })`',
974
- 'You can also reindex to switch between projects — the graph supports multiple projects.',
975
- '',
976
- '## 📊 Token Tracking:',
977
- 'Every response includes `_sessionTokens` with cumulative usage.',
978
- ].join('\n'),
979
- });
980
- // ── 7. Register all tools ─────────────────────────────────
981
- log('info', 'Registering MCP tools…');
982
- // ── 7.0 Token tracking middleware ──────────────────────────
983
- // Wrap every tool handler to inject session token metadata
984
- const tokenTracker = new SessionTokenTracker();
985
- const originalToolFn = server.tool.bind(server);
986
- // Override server.tool to wrap every handler with token tracking
987
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
988
- server.tool = function (...args) {
989
- // server.tool(name, description, schema, handler) or server.tool(name, description, handler)
990
- const lastArgIdx = args.length - 1;
991
- const originalHandler = args[lastArgIdx];
992
- if (typeof originalHandler === 'function') {
993
- args[lastArgIdx] = async (...handlerArgs) => {
994
- // Estimate input tokens from args
995
- const inputStr = JSON.stringify(handlerArgs);
996
- const inputTokens = tokenEstimator.estimate(inputStr);
997
- tokenTracker.totalInputTokens += inputTokens;
998
- // Call original handler
999
- const response = await originalHandler(...handlerArgs);
1000
- // Enrich response with token tracking + project metadata
1001
- return enrichToolResponse(response, tokenTracker, tokenEstimator, () => graph.getStats().totalNodes, () => {
1002
- const s = graph.getStats();
1003
- return { root: config.projectRoot, indexedFiles: s.totalFiles, totalNodes: s.totalNodes };
1004
- });
1005
- };
1006
- }
1007
- return originalToolFn.apply(server, args);
1008
- };
1009
- registerGraphTools(server, graphAdapter, tokenEstimator);
1010
- log('debug', 'Registered graph tools (6)');
1011
- registerChangeTools(server, changeAdapter, tokenEstimator);
1012
- log('debug', 'Registered change tools (3)');
1013
- registerMemoryTools(server, memoryAdapter, sessionAdapter, tokenEstimator);
1014
- log('debug', 'Registered memory tools (5)');
1015
- registerContextTools(server, contextAdapter, indexerAdapter, tokenEstimator);
1016
- log('debug', 'Registered context tools (4)');
1017
- registerDebugTools(server, graph, config, tokenEstimator);
1018
- log('debug', 'Registered debug tools (3)');
1019
- registerFlowTools(server, graph, config, tokenEstimator);
1020
- log('debug', 'Registered flow tools (4)');
1021
- registerSnapshotTools(server, graph, config, tokenEstimator);
1022
- log('debug', 'Registered snapshot tools (3)');
1023
- // Initialize semantic search engine
1024
- const semanticEngine = new SemanticSearchEngine(graph.getDb());
1025
- log('debug', 'Initialized semantic search engine');
1026
- registerSmartTools(server, graph, config, tokenEstimator, semanticEngine);
1027
- log('debug', 'Registered smart tools (3)');
1028
- registerEvolvingTools(server, graph, config, tokenEstimator);
1029
- log('debug', 'Registered evolving tools (3)');
1030
- registerAdvancedTools(server, graph, config, tokenEstimator, semanticEngine);
1031
- log('debug', 'Registered advanced tools (7)');
1032
- registerSemanticTools(server, graph, semanticEngine, config, tokenEstimator);
1033
- log('debug', 'Registered semantic search tools (3)');
1034
- // Session, Changelog & Digest tools (v1.4.0)
1035
- registerSessionTools(server, graph, changelogEngine, config, tokenEstimator);
1036
- log('debug', 'Registered session tools (5)');
1037
- registerDigestTools(server, graph, changelogEngine, config, tokenEstimator);
1038
- log('debug', 'Registered digest tools (3)');
1039
- // ── mindmap_sync_shared_context ─────────────────────────────
1040
- server.tool('mindmap_sync_shared_context', 'Synchronise local memories, decisions, and learned rules with the team-shared `.mindmap-shared.json` file. ' +
1041
- 'Performs a bidirectional sync to import new conventions/decisions and export local updates.', {}, async () => {
1042
- try {
1043
- const syncStats = await syncSharedContext(config, graph, persistentMemory, decisionLog);
1044
- return {
1045
- content: [
1046
- {
1047
- type: 'text',
1048
- text: JSON.stringify({
1049
- success: true,
1050
- message: 'Bidirectional synchronization completed successfully.',
1051
- stats: syncStats
1052
- })
1053
- }
1054
- ]
1055
- };
1056
- }
1057
- catch (err) {
1058
- const msg = err instanceof Error ? err.message : String(err);
1059
- return {
1060
- content: [
1061
- {
1062
- type: 'text',
1063
- text: JSON.stringify({
1064
- success: false,
1065
- message: `Synchronization failed: ${msg}`
1066
- })
1067
- }
1068
- ]
1069
- };
1070
- }
1071
- });
1072
- log('debug', 'Registered shared context sync tool');
1073
- log('info', '🔧 All MCP tools registered:');
1074
- log('info', ' Graph: mindmap_search, mindmap_get_structure, mindmap_trace_dependencies, mindmap_get_signature, mindmap_find_references, mindmap_get_file_map');
1075
- log('info', ' Changes: mindmap_what_changed, mindmap_session_diff, mindmap_impact_analysis');
1076
- log('info', ' Memory: mindmap_recall, mindmap_remember, mindmap_get_decisions, mindmap_decide, mindmap_session_summary, mindmap_sync_shared_context');
1077
- log('info', ' Context: mindmap_get_context, mindmap_compress, mindmap_reindex, mindmap_status');
1078
- log('info', ' Debug: mindmap_debug_changes, mindmap_file_before, mindmap_file_history');
1079
- log('info', ' Flow: mindmap_trace_flow, mindmap_interaction_map, mindmap_classify_file, mindmap_layer_overview');
1080
- log('info', ' Snapshot: mindmap_project_map, mindmap_change_delta, mindmap_session_kickoff ⭐');
1081
- log('info', ' Advanced: mindmap_query_graph, mindmap_dead_code, mindmap_architecture, mindmap_get_code_snippet, mindmap_search_code, mindmap_list_projects, mindmap_health');
1082
- log('info', ' Smart: mindmap_explain ⭐, mindmap_git_changes ⭐, mindmap_smart_search ⭐');
1083
- log('info', ' Evolving: mindmap_teach ⭐, mindmap_get_learned, mindmap_forget');
1084
- log('info', ' Semantic: mindmap_semantic_search ⭐, mindmap_semantic_stats, mindmap_synonyms');
1085
- log('info', ' Session: mindmap_session_start 🆕, mindmap_session_resume 🔥🆕, mindmap_session_end, mindmap_changelog 🆕, mindmap_hotspots, mindmap_verify_changes 🆕');
1086
- log('info', ' Digest: mindmap_digest ⭐, mindmap_file_digest ⭐, mindmap_verify');
1087
- // ── 7.3 Register MCP Prompts ──────────────────────────────
1088
- // These are interactive workflow templates that AI agents can request
1089
- server.prompt('start_session', 'Recommended first prompt for any AI coding session. Calls mindmap_session_resume and returns a complete project briefing.', async () => {
1090
- // Auto-start session
1091
- const sessionId = changelogEngine.ensureSession('ai-agent');
1092
- const lastSession = changelogEngine.getLastSession();
1093
- const stats = graph.getStats();
1094
- const since = lastSession?.endedAt || lastSession?.startedAt || (Date.now() - 24 * 3600_000);
1095
- const changes = changelogEngine.getChangesSince(since);
1096
- const lines = [
1097
- '# Session Briefing',
1098
- '',
1099
- `**Project**: ${path.basename(config.projectRoot)}`,
1100
- `**Files**: ${stats.totalFiles} | **Symbols**: ${stats.totalNodes} | **Relationships**: ${stats.totalEdges}`,
1101
- `**Languages**: ${Object.entries(stats.languageBreakdown).map(([l, c]) => `${l}(${c})`).join(', ')}`,
1102
- '',
1103
- ];
1104
- if (lastSession) {
1105
- lines.push('## Previous Session', `- **Agent**: ${lastSession.agentName}`, `- **Task**: ${lastSession.taskDescription || 'Not specified'}`, `- **Summary**: ${lastSession.summary || 'No summary'}`, `- **Files modified**: ${lastSession.filesModified.length}`, '');
1106
- }
1107
- if (changes.totalChanges > 0) {
1108
- lines.push(`## Changes Since Last Session (${changes.sinceLabel})`, `${changes.filesChanged} files, ${changes.totalChanges} symbol changes:`);
1109
- for (const f of changes.files.slice(0, 8)) {
1110
- const rel = path.relative(config.projectRoot, f.filePath).replace(/\\/g, '/');
1111
- const parts = [];
1112
- if (f.added.length > 0)
1113
- parts.push(`+${f.added.length}`);
1114
- if (f.modified.length > 0)
1115
- parts.push(`~${f.modified.length}`);
1116
- if (f.deleted.length > 0)
1117
- parts.push(`-${f.deleted.length}`);
1118
- lines.push(` ${rel}: ${parts.join(', ')}`);
1119
- }
1120
- lines.push('');
1121
- }
1122
- lines.push('## What to do next', '- Use `mindmap_smart_search` to find specific code', '- Use `mindmap_explain` to understand a symbol', '- Use `mindmap_changelog` for detailed change diffs', '- Use `mindmap_session_end` when done');
1123
- return {
1124
- messages: [{
1125
- role: 'user',
1126
- content: { type: 'text', text: lines.join('\n') },
1127
- }],
1128
- };
1129
- });
1130
- server.prompt('tool_guide', 'Complete guide to all AI Mind Map tools — when to use each one, organized by task.', async () => ({
1131
- messages: [{
1132
- role: 'user',
1133
- content: {
1134
- type: 'text',
1135
- text: [
1136
- '# AI Mind Map — Complete Tool Guide',
1137
- '',
1138
- '## 🚀 Session Lifecycle (use these to avoid re-reading code)',
1139
- '| Tool | When to Use |',
1140
- '|------|------------|',
1141
- '| `mindmap_session_resume` | **FIRST call every conversation** — returns project context + changes |',
1142
- '| `mindmap_session_kickoff` | Full preamble: project map + change delta + memories in ONE call |',
1143
- '| `mindmap_session_start` | Start tracking a new task (records agent name + task) |',
1144
- '| `mindmap_session_end` | Save summary for next AI session |',
1145
- '',
1146
- '## 🔍 Finding Code (instead of grep/reading files)',
1147
- '| Tool | When to Use |',
1148
- '|------|------------|',
1149
- '| `mindmap_smart_search` | Search by function/class name — returns full context |',
1150
- '| `mindmap_semantic_search` | Search by concept ("error handling", "auth") |',
1151
- '| `mindmap_search_code` | Grep-like text search in code bodies |',
1152
- '| `mindmap_find_references` | Find all usages of a symbol |',
1153
- '| `mindmap_trace_dependencies` | Who calls X? What does X call? |',
1154
- '',
1155
- '## 📖 Reading Code (without reading full files)',
1156
- '| Tool | When to Use |',
1157
- '|------|------------|',
1158
- '| `mindmap_explain` | Get EVERYTHING about a symbol in one call |',
1159
- '| `mindmap_get_code_snippet` | Read actual source for a function |',
1160
- '| `mindmap_file_digest` | Understand a file without reading it |',
1161
- '| `mindmap_get_file_map` | All symbols in a file with signatures |',
1162
- '| `mindmap_get_signature` | Just the signature (cheapest read) |',
1163
- '',
1164
- '## 📊 Understanding the Project',
1165
- '| Tool | When to Use |',
1166
- '|------|------------|',
1167
- '| `mindmap_digest` | Full project summary in <2000 tokens |',
1168
- '| `mindmap_architecture` | Architecture layers + patterns |',
1169
- '| `mindmap_project_map` | Complete project map |',
1170
- '',
1171
- '## 🔄 Change Tracking',
1172
- '| Tool | When to Use |',
1173
- '|------|------------|',
1174
- '| `mindmap_changelog` | Symbol-level diffs since a time |',
1175
- '| `mindmap_git_changes` | Git-aware change detection |',
1176
- '| `mindmap_verify` | Check if cached code is still valid |',
1177
- '| `mindmap_hotspots` | Most frequently changed files |',
1178
- '',
1179
- '## 🧠 Memory & Decisions',
1180
- '| Tool | When to Use |',
1181
- '|------|------------|',
1182
- '| `mindmap_remember` | Save important facts for future |',
1183
- '| `mindmap_recall` | Retrieve relevant memories |',
1184
- '| `mindmap_decide` | Record architectural decisions |',
1185
- '| `mindmap_teach` | Teach persistent rules |',
1186
- ].join('\n'),
1187
- },
1188
- }],
1189
- }));
1190
- log('debug', 'Registered 2 MCP prompts (start_session, tool_guide)');
1191
- // ── 7.5 Auto-sync shared context on startup ────────────────
1192
- if (config.autoSyncSharedContext) {
1193
- log('info', '🔄 Auto-syncing shared context…');
1194
- try {
1195
- const syncStats = await syncSharedContext(config, graph, persistentMemory, decisionLog);
1196
- log('info', `✅ Shared context sync complete: ` +
1197
- `Imported: ${syncStats.memoriesImported} memories, ${syncStats.decisionsImported} decisions, ${syncStats.rulesImported} rules. ` +
1198
- `Exported: ${syncStats.memoriesExported} memories, ${syncStats.decisionsExported} decisions, ${syncStats.rulesExported} rules.`);
1199
- }
1200
- catch (err) {
1201
- log('warn', `⚠️ Auto-sync of shared context failed: ${err instanceof Error ? err.message : String(err)}`);
1202
- }
1203
- }
1204
- // ── 8. Smart auto-index (only if projectRoot looks like a real project) ──
1205
- if (config.memoryOnly) {
1206
- log('info', '🧠 Running in memoryOnly mode. Bypassing codebase parsing and indexing.');
1207
- }
1208
- else {
1209
- // Check if projectRoot is a real project (not an IDE install directory)
1210
- const projectMarkers = [
1211
- '.git', 'package.json', 'build.gradle', 'build.gradle.kts',
1212
- 'Cargo.toml', 'go.mod', 'pom.xml', 'CMakeLists.txt',
1213
- 'Makefile', '.project', 'setup.py', 'pyproject.toml',
1214
- 'pubspec.yaml', 'Gemfile', '*.sln', '*.csproj',
1215
- ];
1216
- const isRealProject = projectMarkers.some(marker => existsSync(path.join(config.projectRoot, marker)));
1217
- if (!isRealProject) {
1218
- log('info', `⚠️ Project root "${config.projectRoot}" does not look like a code project (no .git, package.json, etc.).`);
1219
- log('info', ' Skipping auto-index. The AI agent will be prompted to call mindmap_reindex with the correct project path.');
1220
- }
1221
- else {
1222
- const stats = graph.getStats();
1223
- if (stats.totalNodes === 0) {
1224
- log('info', `📋 Real project detected at: ${config.projectRoot}. Running initial indexing…`);
1225
- try {
1226
- const result = await indexer.fullIndex();
1227
- log('info', `✅ Initial index complete: ${result.filesParsed} files, ${result.nodesCreated} nodes, ${result.edgesCreated} edges`);
1228
- if (result.parseErrors > 0) {
1229
- log('warn', `⚠️ ${result.parseErrors} parse errors (non-fatal)`);
1230
- }
1231
- }
1232
- catch (err) {
1233
- log('warn', `⚠️ Initial indexing failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
1234
- }
1235
- }
1236
- else {
1237
- log('info', `📋 Existing index found: ${stats.totalNodes} nodes. Running incremental update…`);
1238
- try {
1239
- const result = await indexer.incrementalIndex();
1240
- log('info', `✅ Incremental update: ${result.filesParsed} files reindexed`);
1241
- }
1242
- catch (err) {
1243
- log('warn', `⚠️ Incremental update failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
1244
- }
1245
- }
1246
- }
1247
- // Build semantic search TF-IDF index from existing graph nodes (if any)
1248
- try {
1249
- const allNodes = graph.getAllNodes();
1250
- const nonFileNodes = allNodes.filter(n => n.type !== 'file');
1251
- if (nonFileNodes.length > 0) {
1252
- semanticEngine.indexNodes(nonFileNodes.map(n => ({
1253
- id: n.id,
1254
- name: n.name,
1255
- qualifiedName: n.qualifiedName,
1256
- signature: n.signature,
1257
- docComment: n.docComment,
1258
- filePath: n.filePath,
1259
- })));
1260
- semanticEngine.rebuildIDF();
1261
- log('info', `🧠 Semantic index built: ${nonFileNodes.length} symbols indexed`);
1262
- }
1263
- }
1264
- catch (err) {
1265
- log('warn', `⚠️ Semantic index build failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
1266
- }
1267
- }
1268
- // ── 9. Start file watcher ──────────────────────────────────
1269
- if (watcher && !config.memoryOnly) {
1270
- try {
1271
- await watcher.start();
1272
- log('info', '👁️ File watcher started');
1273
- }
1274
- catch (err) {
1275
- log('warn', `⚠️ File watcher failed to start: ${err instanceof Error ? err.message : String(err)}`);
1276
- }
1277
- }
1278
- // ── 10. Graceful shutdown ──────────────────────────────────
1279
- let shuttingDown = false;
1280
- async function shutdown(signal) {
1281
- if (shuttingDown)
1282
- return;
1283
- shuttingDown = true;
1284
- log('info', `Received ${signal}, shutting down gracefully…`);
1285
- try {
1286
- // Stop file watcher
1287
- if (watcher) {
1288
- await watcher.stop();
1289
- log('debug', 'File watcher stopped');
1290
- }
1291
- // End current session
1292
- try {
1293
- sessionMemory.endSession();
1294
- log('debug', 'Session ended');
1295
- }
1296
- catch {
1297
- // Ignore session end errors
1298
- }
1299
- // Apply memory decay
1300
- try {
1301
- persistentMemory.applyDecay();
1302
- log('debug', 'Memory decay applied');
1303
- }
1304
- catch {
1305
- // Ignore decay errors
1306
- }
1307
- // Close change log database
1308
- try {
1309
- changeLog.close();
1310
- log('debug', 'Change log closed');
1311
- }
1312
- catch {
1313
- // Ignore close errors
1314
- }
1315
- // Close graph database
1316
- try {
1317
- graph.close();
1318
- log('debug', 'Knowledge graph closed');
1319
- }
1320
- catch {
1321
- // Ignore close errors
1322
- }
1323
- // Close shared database
1324
- try {
1325
- sharedDb.close();
1326
- log('debug', 'Shared database closed');
1327
- }
1328
- catch {
1329
- // Ignore close errors
1330
- }
1331
- log('info', '✅ Cleanup complete. Goodbye!');
1332
- }
1333
- catch (err) {
1334
- const msg = err instanceof Error ? err.message : String(err);
1335
- log('error', `Error during shutdown: ${msg}`);
1336
- }
1337
- process.exit(0);
1338
- }
1339
- process.on('SIGINT', () => void shutdown('SIGINT'));
1340
- process.on('SIGTERM', () => void shutdown('SIGTERM'));
1341
- // Handle uncaught errors gracefully
1342
- process.on('uncaughtException', (err) => {
1343
- log('error', `Uncaught exception: ${err.message}`, err.stack);
1344
- void shutdown('uncaughtException');
1345
- });
1346
- process.on('unhandledRejection', (reason) => {
1347
- const msg = reason instanceof Error ? reason.message : String(reason);
1348
- log('error', `Unhandled rejection: ${msg}`);
1349
- });
1350
- // ── 11. Connect transport and start serving ────────────────
1351
- log('info', 'Connecting stdio transport…');
1352
- try {
1353
- const transport = new StdioServerTransport();
1354
- await server.connect(transport);
1355
- log('info', '🧠 AI Mind Map MCP Server is LIVE. Waiting for requests…');
1356
- log('info', ` Project: ${config.projectRoot}`);
1357
- log('info', ` Database: ${config.dbPath}`);
1358
- log('info', ` Session: ${sessionMemory.getCurrentSessionId()}`);
1359
- }
1360
- catch (err) {
1361
- const msg = err instanceof Error ? err.message : String(err);
1362
- log('error', `Failed to start MCP server: ${msg}`);
1363
- process.exit(1);
1364
- }
1365
- }
1366
- // ── Kick off ────────────────────────────────────────────────
1367
- main().catch((err) => {
1368
- const msg = err instanceof Error ? err.message : String(err);
1369
- process.stderr.write(`Fatal: ${msg}\n`);
1370
- process.exit(1);
1371
- });
1
+ export {};
1372
2
  //# sourceMappingURL=index.js.map