@claude-flow/plugin-code-intelligence 3.0.0-alpha.1

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.
@@ -0,0 +1,794 @@
1
+ /**
2
+ * Code Intelligence Plugin - MCP Tools
3
+ *
4
+ * Implements 5 MCP tools for advanced code analysis:
5
+ * 1. code/semantic-search - Find semantically similar code patterns
6
+ * 2. code/architecture-analyze - Analyze codebase architecture
7
+ * 3. code/refactor-impact - Predict refactoring impact using GNN
8
+ * 4. code/split-suggest - Suggest module splits using MinCut
9
+ * 5. code/learn-patterns - Learn patterns from code history
10
+ *
11
+ * Based on ADR-035: Advanced Code Intelligence Plugin
12
+ *
13
+ * @module v3/plugins/code-intelligence/mcp-tools
14
+ */
15
+ import path from 'path';
16
+ import { SemanticSearchInputSchema, ArchitectureAnalyzeInputSchema, RefactorImpactInputSchema, SplitSuggestInputSchema, LearnPatternsInputSchema, CodeIntelligenceError, CodeIntelligenceErrorCodes, maskSecrets, } from './types.js';
17
+ import { createGNNBridge } from './bridges/gnn-bridge.js';
18
+ import { createMinCutBridge } from './bridges/mincut-bridge.js';
19
+ // ============================================================================
20
+ // Security Utilities
21
+ // ============================================================================
22
+ /**
23
+ * Validate path for security
24
+ */
25
+ function validatePath(userPath, allowedRoots) {
26
+ const normalized = path.normalize(userPath);
27
+ // Check for path traversal
28
+ if (normalized.includes('..')) {
29
+ throw new CodeIntelligenceError(CodeIntelligenceErrorCodes.PATH_TRAVERSAL, 'Path traversal detected', { path: userPath });
30
+ }
31
+ // Check against allowed roots
32
+ const resolved = path.resolve(normalized);
33
+ const isAllowed = allowedRoots.some(root => {
34
+ const resolvedRoot = path.resolve(root);
35
+ return resolved.startsWith(resolvedRoot);
36
+ });
37
+ if (!isAllowed && allowedRoots.length > 0 && !allowedRoots.includes('.')) {
38
+ throw new CodeIntelligenceError(CodeIntelligenceErrorCodes.PATH_TRAVERSAL, 'Path outside allowed roots', { path: userPath, allowedRoots });
39
+ }
40
+ return normalized;
41
+ }
42
+ /**
43
+ * Check if path is sensitive
44
+ */
45
+ function isSensitivePath(filePath, blockedPatterns) {
46
+ return blockedPatterns.some(pattern => pattern.test(filePath));
47
+ }
48
+ // ============================================================================
49
+ // Semantic Search Tool
50
+ // ============================================================================
51
+ /**
52
+ * MCP Tool: code/semantic-search
53
+ *
54
+ * Search for semantically similar code patterns
55
+ */
56
+ export const semanticSearchTool = {
57
+ name: 'code/semantic-search',
58
+ description: 'Search for semantically similar code patterns',
59
+ category: 'code-intelligence',
60
+ version: '3.0.0-alpha.1',
61
+ inputSchema: SemanticSearchInputSchema,
62
+ handler: async (input, context) => {
63
+ const startTime = Date.now();
64
+ try {
65
+ const validated = SemanticSearchInputSchema.parse(input);
66
+ // Validate paths
67
+ const paths = validated.scope?.paths?.map(p => validatePath(p, context.config.allowedRoots)) ?? ['.'];
68
+ // Filter out sensitive files
69
+ const safePaths = paths.filter(p => !isSensitivePath(p, context.config.blockedPatterns));
70
+ // Initialize GNN bridge for semantic embeddings
71
+ const gnn = context.bridges.gnn;
72
+ if (!gnn.isInitialized()) {
73
+ await gnn.initialize();
74
+ }
75
+ // Perform search (simplified - in production would use vector index)
76
+ const results = await performSemanticSearch(validated.query, safePaths, validated.searchType, validated.topK, validated.scope?.languages, validated.scope?.excludeTests ?? false, context);
77
+ // Mask secrets in results
78
+ if (context.config.maskSecrets) {
79
+ for (const result of results) {
80
+ result.snippet = maskSecrets(result.snippet);
81
+ result.context = maskSecrets(result.context);
82
+ }
83
+ }
84
+ const result = {
85
+ success: true,
86
+ query: validated.query,
87
+ searchType: validated.searchType,
88
+ results,
89
+ totalMatches: results.length,
90
+ scope: {
91
+ paths: safePaths,
92
+ languages: validated.scope?.languages,
93
+ excludeTests: validated.scope?.excludeTests,
94
+ },
95
+ durationMs: Date.now() - startTime,
96
+ };
97
+ return {
98
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
99
+ data: result,
100
+ };
101
+ }
102
+ catch (error) {
103
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
104
+ return {
105
+ content: [{
106
+ type: 'text',
107
+ text: JSON.stringify({
108
+ success: false,
109
+ error: errorMessage,
110
+ durationMs: Date.now() - startTime,
111
+ }, null, 2),
112
+ }],
113
+ };
114
+ }
115
+ },
116
+ };
117
+ // ============================================================================
118
+ // Architecture Analyze Tool
119
+ // ============================================================================
120
+ /**
121
+ * MCP Tool: code/architecture-analyze
122
+ *
123
+ * Analyze codebase architecture and detect drift
124
+ */
125
+ export const architectureAnalyzeTool = {
126
+ name: 'code/architecture-analyze',
127
+ description: 'Analyze codebase architecture and detect drift',
128
+ category: 'code-intelligence',
129
+ version: '3.0.0-alpha.1',
130
+ inputSchema: ArchitectureAnalyzeInputSchema,
131
+ handler: async (input, context) => {
132
+ const startTime = Date.now();
133
+ try {
134
+ const validated = ArchitectureAnalyzeInputSchema.parse(input);
135
+ // Validate root path
136
+ const rootPath = validatePath(validated.rootPath, context.config.allowedRoots);
137
+ // Initialize GNN bridge
138
+ const gnn = context.bridges.gnn;
139
+ if (!gnn.isInitialized()) {
140
+ await gnn.initialize();
141
+ }
142
+ // Determine analyses to perform
143
+ const analyses = validated.analysis ?? [
144
+ 'dependency_graph',
145
+ 'circular_deps',
146
+ 'component_coupling',
147
+ ];
148
+ // Build dependency graph
149
+ const files = await getFilesInPath(rootPath);
150
+ const safeFiles = files.filter(f => !isSensitivePath(f, context.config.blockedPatterns));
151
+ const dependencyGraph = await gnn.buildCodeGraph(safeFiles, true);
152
+ // Perform requested analyses
153
+ const result = {
154
+ success: true,
155
+ rootPath,
156
+ analyses: analyses,
157
+ dependencyGraph: analyses.includes('dependency_graph') ? dependencyGraph : undefined,
158
+ layerViolations: analyses.includes('layer_violations')
159
+ ? detectLayerViolations(dependencyGraph, validated.layers)
160
+ : undefined,
161
+ circularDeps: analyses.includes('circular_deps')
162
+ ? detectCircularDeps(dependencyGraph)
163
+ : undefined,
164
+ couplingMetrics: analyses.includes('component_coupling')
165
+ ? calculateCouplingMetrics(dependencyGraph)
166
+ : undefined,
167
+ cohesionMetrics: analyses.includes('module_cohesion')
168
+ ? calculateCohesionMetrics(dependencyGraph)
169
+ : undefined,
170
+ deadCode: analyses.includes('dead_code')
171
+ ? findDeadCode(dependencyGraph)
172
+ : undefined,
173
+ apiSurface: analyses.includes('api_surface')
174
+ ? analyzeAPISurface(dependencyGraph)
175
+ : undefined,
176
+ drift: analyses.includes('architectural_drift') && validated.baseline
177
+ ? await detectDrift(dependencyGraph, validated.baseline)
178
+ : undefined,
179
+ summary: {
180
+ totalFiles: dependencyGraph.nodes.length,
181
+ totalModules: countModules(dependencyGraph),
182
+ healthScore: calculateHealthScore(dependencyGraph),
183
+ issues: 0,
184
+ warnings: 0,
185
+ },
186
+ durationMs: Date.now() - startTime,
187
+ };
188
+ return {
189
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
190
+ data: result,
191
+ };
192
+ }
193
+ catch (error) {
194
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
195
+ return {
196
+ content: [{
197
+ type: 'text',
198
+ text: JSON.stringify({
199
+ success: false,
200
+ error: errorMessage,
201
+ durationMs: Date.now() - startTime,
202
+ }, null, 2),
203
+ }],
204
+ };
205
+ }
206
+ },
207
+ };
208
+ // ============================================================================
209
+ // Refactor Impact Tool
210
+ // ============================================================================
211
+ /**
212
+ * MCP Tool: code/refactor-impact
213
+ *
214
+ * Analyze impact of proposed code changes using GNN
215
+ */
216
+ export const refactorImpactTool = {
217
+ name: 'code/refactor-impact',
218
+ description: 'Analyze impact of proposed code changes using GNN',
219
+ category: 'code-intelligence',
220
+ version: '3.0.0-alpha.1',
221
+ inputSchema: RefactorImpactInputSchema,
222
+ handler: async (input, context) => {
223
+ const startTime = Date.now();
224
+ try {
225
+ const validated = RefactorImpactInputSchema.parse(input);
226
+ // Validate file paths
227
+ for (const change of validated.changes) {
228
+ validatePath(change.file, context.config.allowedRoots);
229
+ }
230
+ // Initialize GNN bridge
231
+ const gnn = context.bridges.gnn;
232
+ if (!gnn.isInitialized()) {
233
+ await gnn.initialize();
234
+ }
235
+ // Get affected files
236
+ const changedFiles = validated.changes.map(c => c.file);
237
+ // Build graph
238
+ const allFiles = await getAllRelatedFiles(changedFiles);
239
+ const safeFiles = allFiles.filter(f => !isSensitivePath(f, context.config.blockedPatterns));
240
+ const graph = await gnn.buildCodeGraph(safeFiles, true);
241
+ // Predict impact using GNN propagation
242
+ const impactScores = await gnn.predictImpact(graph, changedFiles, validated.depth);
243
+ // Build file impacts
244
+ const impactedFiles = [];
245
+ for (const [filePath, score] of impactScores) {
246
+ if (score > 0.1) {
247
+ impactedFiles.push({
248
+ filePath,
249
+ impactType: changedFiles.includes(filePath) ? 'direct' :
250
+ score > 0.5 ? 'indirect' : 'transitive',
251
+ requiresChange: changedFiles.includes(filePath) || score > 0.7,
252
+ changesNeeded: getChangesNeeded(filePath, validated.changes),
253
+ risk: score > 0.8 ? 'high' : score > 0.5 ? 'medium' : 'low',
254
+ testsAffected: validated.includeTests
255
+ ? getAffectedTests(filePath, graph)
256
+ : [],
257
+ });
258
+ }
259
+ }
260
+ // Sort by impact
261
+ impactedFiles.sort((a, b) => {
262
+ const aScore = impactScores.get(a.filePath) ?? 0;
263
+ const bScore = impactScores.get(b.filePath) ?? 0;
264
+ return bScore - aScore;
265
+ });
266
+ const result = {
267
+ success: true,
268
+ changes: validated.changes.map(c => ({
269
+ file: c.file,
270
+ type: c.type,
271
+ details: c.details ?? {},
272
+ })),
273
+ impactedFiles,
274
+ summary: {
275
+ directlyAffected: impactedFiles.filter(f => f.impactType === 'direct').length,
276
+ indirectlyAffected: impactedFiles.filter(f => f.impactType !== 'direct').length,
277
+ testsAffected: new Set(impactedFiles.flatMap(f => f.testsAffected)).size,
278
+ totalRisk: impactedFiles.some(f => f.risk === 'high') ? 'high' :
279
+ impactedFiles.some(f => f.risk === 'medium') ? 'medium' : 'low',
280
+ },
281
+ suggestedOrder: getSuggestedOrder(impactedFiles, graph),
282
+ breakingChanges: findBreakingChanges(validated.changes, graph),
283
+ durationMs: Date.now() - startTime,
284
+ };
285
+ return {
286
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
287
+ data: result,
288
+ };
289
+ }
290
+ catch (error) {
291
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
292
+ return {
293
+ content: [{
294
+ type: 'text',
295
+ text: JSON.stringify({
296
+ success: false,
297
+ error: errorMessage,
298
+ durationMs: Date.now() - startTime,
299
+ }, null, 2),
300
+ }],
301
+ };
302
+ }
303
+ },
304
+ };
305
+ // ============================================================================
306
+ // Split Suggest Tool
307
+ // ============================================================================
308
+ /**
309
+ * MCP Tool: code/split-suggest
310
+ *
311
+ * Suggest optimal code splitting using MinCut algorithm
312
+ */
313
+ export const splitSuggestTool = {
314
+ name: 'code/split-suggest',
315
+ description: 'Suggest optimal code splitting using MinCut algorithm',
316
+ category: 'code-intelligence',
317
+ version: '3.0.0-alpha.1',
318
+ inputSchema: SplitSuggestInputSchema,
319
+ handler: async (input, context) => {
320
+ const startTime = Date.now();
321
+ try {
322
+ const validated = SplitSuggestInputSchema.parse(input);
323
+ // Validate path
324
+ const targetPath = validatePath(validated.targetPath, context.config.allowedRoots);
325
+ // Initialize bridges
326
+ const gnn = context.bridges.gnn;
327
+ const mincut = context.bridges.mincut;
328
+ if (!gnn.isInitialized())
329
+ await gnn.initialize();
330
+ if (!mincut.isInitialized())
331
+ await mincut.initialize();
332
+ // Get files
333
+ const files = await getFilesInPath(targetPath);
334
+ const safeFiles = files.filter(f => !isSensitivePath(f, context.config.blockedPatterns));
335
+ // Build graph
336
+ const graph = await gnn.buildCodeGraph(safeFiles, true);
337
+ // Determine number of modules
338
+ const targetModules = validated.targetModules ??
339
+ Math.max(2, Math.ceil(Math.sqrt(graph.nodes.length / 5)));
340
+ // Find optimal cuts
341
+ const partition = await mincut.findOptimalCuts(graph, targetModules, validated.constraints ?? {});
342
+ // Build suggested modules
343
+ const modules = buildSuggestedModules(graph, partition, validated.strategy);
344
+ // Calculate cut edges
345
+ const cutEdges = [];
346
+ for (const edge of graph.edges) {
347
+ const fromPart = partition.get(edge.from);
348
+ const toPart = partition.get(edge.to);
349
+ if (fromPart !== undefined && toPart !== undefined && fromPart !== toPart) {
350
+ cutEdges.push({
351
+ from: edge.from,
352
+ to: edge.to,
353
+ weight: edge.weight,
354
+ });
355
+ }
356
+ }
357
+ // Calculate quality metrics
358
+ const totalCutWeight = cutEdges.reduce((sum, e) => sum + e.weight, 0);
359
+ const avgCohesion = modules.reduce((sum, m) => sum + m.cohesion, 0) / modules.length;
360
+ const avgCoupling = modules.reduce((sum, m) => sum + m.coupling, 0) / modules.length;
361
+ const sizes = modules.map(m => m.loc);
362
+ const balanceScore = 1 - (Math.max(...sizes) - Math.min(...sizes)) /
363
+ (Math.max(...sizes) + 1);
364
+ const result = {
365
+ success: true,
366
+ targetPath,
367
+ strategy: validated.strategy,
368
+ modules,
369
+ cutEdges,
370
+ quality: {
371
+ totalCutWeight,
372
+ avgCohesion,
373
+ avgCoupling,
374
+ balanceScore,
375
+ },
376
+ migrationSteps: generateMigrationSteps(modules, cutEdges),
377
+ durationMs: Date.now() - startTime,
378
+ };
379
+ return {
380
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
381
+ data: result,
382
+ };
383
+ }
384
+ catch (error) {
385
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
386
+ return {
387
+ content: [{
388
+ type: 'text',
389
+ text: JSON.stringify({
390
+ success: false,
391
+ error: errorMessage,
392
+ durationMs: Date.now() - startTime,
393
+ }, null, 2),
394
+ }],
395
+ };
396
+ }
397
+ },
398
+ };
399
+ // ============================================================================
400
+ // Learn Patterns Tool
401
+ // ============================================================================
402
+ /**
403
+ * MCP Tool: code/learn-patterns
404
+ *
405
+ * Learn recurring patterns from code changes using SONA
406
+ */
407
+ export const learnPatternsTool = {
408
+ name: 'code/learn-patterns',
409
+ description: 'Learn recurring patterns from code changes using SONA',
410
+ category: 'code-intelligence',
411
+ version: '3.0.0-alpha.1',
412
+ inputSchema: LearnPatternsInputSchema,
413
+ handler: async (input, context) => {
414
+ const startTime = Date.now();
415
+ try {
416
+ const validated = LearnPatternsInputSchema.parse(input);
417
+ // Analyze git history
418
+ const scope = validated.scope ?? { gitRange: 'HEAD~100..HEAD' };
419
+ const patternTypes = validated.patternTypes ?? [
420
+ 'bug_patterns',
421
+ 'refactor_patterns',
422
+ ];
423
+ // Learn patterns from commits (simplified)
424
+ const patterns = await learnPatternsFromHistory(scope, patternTypes, validated.minOccurrences, context);
425
+ // Generate recommendations
426
+ const recommendations = generateRecommendations(patterns);
427
+ const result = {
428
+ success: true,
429
+ scope,
430
+ patternTypes,
431
+ patterns,
432
+ summary: {
433
+ commitsAnalyzed: 100, // Simplified
434
+ filesAnalyzed: patterns.reduce((sum, p) => sum + p.files.length, 0),
435
+ patternsFound: patterns.length,
436
+ byType: patternTypes.reduce((acc, type) => {
437
+ acc[type] = patterns.filter(p => p.type === type).length;
438
+ return acc;
439
+ }, {}),
440
+ },
441
+ recommendations,
442
+ durationMs: Date.now() - startTime,
443
+ };
444
+ return {
445
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
446
+ data: result,
447
+ };
448
+ }
449
+ catch (error) {
450
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
451
+ return {
452
+ content: [{
453
+ type: 'text',
454
+ text: JSON.stringify({
455
+ success: false,
456
+ error: errorMessage,
457
+ durationMs: Date.now() - startTime,
458
+ }, null, 2),
459
+ }],
460
+ };
461
+ }
462
+ },
463
+ };
464
+ // ============================================================================
465
+ // Helper Functions
466
+ // ============================================================================
467
+ async function performSemanticSearch(_query, _paths, _searchType, topK, _languages, _excludeTests, _context) {
468
+ // Simplified implementation
469
+ const results = [];
470
+ // In production, would use vector index
471
+ return results.slice(0, topK);
472
+ }
473
+ async function getFilesInPath(_rootPath) {
474
+ // Simplified - in production would use glob
475
+ return [];
476
+ }
477
+ async function getAllRelatedFiles(changedFiles) {
478
+ // Simplified - would traverse dependencies
479
+ return changedFiles;
480
+ }
481
+ function detectLayerViolations(graph, layers) {
482
+ const violations = [];
483
+ if (!layers)
484
+ return violations;
485
+ // Build layer lookup
486
+ const nodeLayer = new Map();
487
+ for (const [layer, patterns] of Object.entries(layers)) {
488
+ for (const pattern of patterns) {
489
+ for (const node of graph.nodes) {
490
+ if (node.id.includes(pattern)) {
491
+ nodeLayer.set(node.id, layer);
492
+ }
493
+ }
494
+ }
495
+ }
496
+ // Check edges for violations
497
+ for (const edge of graph.edges) {
498
+ const fromLayer = nodeLayer.get(edge.from);
499
+ const toLayer = nodeLayer.get(edge.to);
500
+ if (fromLayer && toLayer && fromLayer !== toLayer) {
501
+ // Simplified check - in production would check layer order
502
+ violations.push({
503
+ source: edge.from,
504
+ target: edge.to,
505
+ sourceLayer: fromLayer,
506
+ targetLayer: toLayer,
507
+ violationType: 'cross',
508
+ severity: 'medium',
509
+ suggestedFix: `Move ${edge.from} or ${edge.to} to appropriate layer`,
510
+ });
511
+ }
512
+ }
513
+ return violations;
514
+ }
515
+ function detectCircularDeps(graph) {
516
+ const cycles = [];
517
+ // Build adjacency list
518
+ const adj = new Map();
519
+ for (const node of graph.nodes) {
520
+ adj.set(node.id, []);
521
+ }
522
+ for (const edge of graph.edges) {
523
+ adj.get(edge.from)?.push(edge.to);
524
+ }
525
+ // DFS for cycle detection
526
+ const visited = new Set();
527
+ const recStack = new Set();
528
+ const findCycle = (node, path) => {
529
+ visited.add(node);
530
+ recStack.add(node);
531
+ for (const neighbor of adj.get(node) ?? []) {
532
+ if (recStack.has(neighbor)) {
533
+ // Found cycle
534
+ const cycleStart = path.indexOf(neighbor);
535
+ if (cycleStart >= 0) {
536
+ cycles.push({
537
+ cycle: [...path.slice(cycleStart), neighbor],
538
+ length: path.length - cycleStart + 1,
539
+ severity: path.length - cycleStart > 3 ? 'high' : 'medium',
540
+ suggestedBreakPoint: neighbor,
541
+ });
542
+ }
543
+ }
544
+ else if (!visited.has(neighbor)) {
545
+ findCycle(neighbor, [...path, neighbor]);
546
+ }
547
+ }
548
+ recStack.delete(node);
549
+ };
550
+ for (const node of graph.nodes) {
551
+ if (!visited.has(node.id)) {
552
+ findCycle(node.id, [node.id]);
553
+ }
554
+ }
555
+ return cycles;
556
+ }
557
+ function calculateCouplingMetrics(graph) {
558
+ const metrics = [];
559
+ for (const node of graph.nodes) {
560
+ const afferent = graph.edges.filter(e => e.to === node.id).length;
561
+ const efferent = graph.edges.filter(e => e.from === node.id).length;
562
+ const instability = (afferent + efferent) > 0
563
+ ? efferent / (afferent + efferent)
564
+ : 0;
565
+ metrics.push({
566
+ componentId: node.id,
567
+ afferentCoupling: afferent,
568
+ efferentCoupling: efferent,
569
+ instability,
570
+ abstractness: 0.5, // Simplified
571
+ distanceFromMain: Math.abs(instability - 0.5),
572
+ inZoneOfPain: instability < 0.3 && false, // Simplified
573
+ inZoneOfUselessness: instability > 0.7 && false, // Simplified
574
+ });
575
+ }
576
+ return metrics;
577
+ }
578
+ function calculateCohesionMetrics(_graph) {
579
+ return [];
580
+ }
581
+ function findDeadCode(graph) {
582
+ const deadCode = [];
583
+ // Find nodes with no incoming edges and not exported
584
+ const hasIncoming = new Set(graph.edges.map(e => e.to));
585
+ for (const node of graph.nodes) {
586
+ if (!hasIncoming.has(node.id) && node.type === 'function') {
587
+ deadCode.push({
588
+ filePath: node.id,
589
+ symbol: node.label,
590
+ symbolType: 'function',
591
+ lineNumber: 1,
592
+ confidence: 0.7,
593
+ reason: 'No references found',
594
+ isExported: false,
595
+ });
596
+ }
597
+ }
598
+ return deadCode;
599
+ }
600
+ function analyzeAPISurface(_graph) {
601
+ return [];
602
+ }
603
+ async function detectDrift(_graph, _baseline) {
604
+ return [];
605
+ }
606
+ function countModules(graph) {
607
+ const dirs = new Set();
608
+ for (const node of graph.nodes) {
609
+ const parts = node.id.split('/');
610
+ if (parts.length > 1) {
611
+ dirs.add(parts.slice(0, -1).join('/'));
612
+ }
613
+ }
614
+ return dirs.size;
615
+ }
616
+ function calculateHealthScore(graph) {
617
+ // Simplified scoring
618
+ const nodeCount = graph.nodes.length;
619
+ const edgeCount = graph.edges.length;
620
+ if (nodeCount === 0)
621
+ return 100;
622
+ const avgDegree = edgeCount / nodeCount;
623
+ const idealDegree = 3;
624
+ return Math.max(0, 100 - Math.abs(avgDegree - idealDegree) * 10);
625
+ }
626
+ function getChangesNeeded(filePath, changes) {
627
+ const changesNeeded = [];
628
+ for (const change of changes) {
629
+ if (change.file === filePath) {
630
+ changesNeeded.push(`Apply ${change.type}`);
631
+ }
632
+ }
633
+ return changesNeeded;
634
+ }
635
+ function getAffectedTests(filePath, graph) {
636
+ const tests = [];
637
+ for (const edge of graph.edges) {
638
+ if (edge.from === filePath && edge.to.includes('.test')) {
639
+ tests.push(edge.to);
640
+ }
641
+ }
642
+ return tests;
643
+ }
644
+ function getSuggestedOrder(impactedFiles, _graph) {
645
+ // Order by dependencies
646
+ return impactedFiles
647
+ .filter(f => f.requiresChange)
648
+ .sort((a, b) => {
649
+ const aRisk = a.risk === 'high' ? 3 : a.risk === 'medium' ? 2 : 1;
650
+ const bRisk = b.risk === 'high' ? 3 : b.risk === 'medium' ? 2 : 1;
651
+ return bRisk - aRisk;
652
+ })
653
+ .map(f => f.filePath);
654
+ }
655
+ function findBreakingChanges(changes, graph) {
656
+ const breakingChanges = [];
657
+ for (const change of changes) {
658
+ if (change.type === 'delete') {
659
+ const dependents = graph.edges.filter(e => e.to === change.file);
660
+ if (dependents.length > 0) {
661
+ breakingChanges.push(`Deleting ${change.file} breaks ${dependents.length} dependents`);
662
+ }
663
+ }
664
+ }
665
+ return breakingChanges;
666
+ }
667
+ function buildSuggestedModules(graph, partition, _strategy) {
668
+ const modules = [];
669
+ const partitionGroups = new Map();
670
+ for (const [nodeId, partNum] of partition) {
671
+ if (!partitionGroups.has(partNum)) {
672
+ partitionGroups.set(partNum, []);
673
+ }
674
+ partitionGroups.get(partNum)?.push(nodeId);
675
+ }
676
+ for (const [partNum, files] of partitionGroups) {
677
+ // Calculate cohesion (internal edges / possible internal edges)
678
+ const internalEdges = graph.edges.filter(e => partition.get(e.from) === partNum && partition.get(e.to) === partNum).length;
679
+ const possibleEdges = files.length * (files.length - 1);
680
+ const cohesion = possibleEdges > 0 ? internalEdges / possibleEdges : 1;
681
+ // Calculate coupling (external edges)
682
+ const externalEdges = graph.edges.filter(e => (partition.get(e.from) === partNum) !== (partition.get(e.to) === partNum)).length;
683
+ const coupling = externalEdges / Math.max(files.length, 1);
684
+ // Get dependencies on other modules
685
+ const dependencies = new Set();
686
+ for (const edge of graph.edges) {
687
+ if (partition.get(edge.from) === partNum && partition.get(edge.to) !== partNum) {
688
+ const depModule = partition.get(edge.to);
689
+ if (depModule !== undefined) {
690
+ dependencies.add(`module-${depModule}`);
691
+ }
692
+ }
693
+ }
694
+ modules.push({
695
+ name: `module-${partNum}`,
696
+ files,
697
+ loc: files.length * 100, // Simplified
698
+ cohesion,
699
+ coupling,
700
+ publicApi: [], // Simplified
701
+ dependencies: Array.from(dependencies),
702
+ });
703
+ }
704
+ return modules;
705
+ }
706
+ function generateMigrationSteps(modules, cutEdges) {
707
+ const steps = [];
708
+ steps.push(`1. Create ${modules.length} new module directories`);
709
+ steps.push(`2. Move files to their respective modules`);
710
+ steps.push(`3. Update ${cutEdges.length} cross-module imports`);
711
+ steps.push(`4. Define public APIs for each module`);
712
+ steps.push(`5. Run tests to verify no regressions`);
713
+ return steps;
714
+ }
715
+ async function learnPatternsFromHistory(_scope, _patternTypes, _minOccurrences, _context) {
716
+ // Simplified - in production would analyze git history
717
+ return [
718
+ {
719
+ id: 'pattern-1',
720
+ type: 'refactor_patterns',
721
+ description: 'Convert callbacks to async/await',
722
+ codeBefore: 'function(callback) { ... }',
723
+ codeAfter: 'async function() { ... }',
724
+ occurrences: 5,
725
+ authors: ['developer1'],
726
+ files: ['src/utils.ts'],
727
+ confidence: 0.85,
728
+ impact: 'positive',
729
+ suggestedAction: 'Consider modernizing callback-based code to async/await',
730
+ },
731
+ ];
732
+ }
733
+ function generateRecommendations(patterns) {
734
+ const recommendations = [];
735
+ for (const pattern of patterns) {
736
+ if (pattern.suggestedAction) {
737
+ recommendations.push(pattern.suggestedAction);
738
+ }
739
+ }
740
+ return recommendations;
741
+ }
742
+ // ============================================================================
743
+ // Tool Registry
744
+ // ============================================================================
745
+ /**
746
+ * All Code Intelligence MCP Tools
747
+ */
748
+ export const codeIntelligenceTools = [
749
+ semanticSearchTool,
750
+ architectureAnalyzeTool,
751
+ refactorImpactTool,
752
+ splitSuggestTool,
753
+ learnPatternsTool,
754
+ ];
755
+ /**
756
+ * Tool name to handler map
757
+ */
758
+ export const toolHandlers = new Map([
759
+ ['code/semantic-search', semanticSearchTool.handler],
760
+ ['code/architecture-analyze', architectureAnalyzeTool.handler],
761
+ ['code/refactor-impact', refactorImpactTool.handler],
762
+ ['code/split-suggest', splitSuggestTool.handler],
763
+ ['code/learn-patterns', learnPatternsTool.handler],
764
+ ]);
765
+ /**
766
+ * Create tool context with bridges
767
+ */
768
+ export function createToolContext(config) {
769
+ const store = new Map();
770
+ const defaultBlockedPatterns = [
771
+ /\.env$/,
772
+ /\.git\/config$/,
773
+ /credentials/i,
774
+ /secrets?\./i,
775
+ /\.pem$/,
776
+ /\.key$/,
777
+ /id_rsa/i,
778
+ ];
779
+ return {
780
+ get: (key) => store.get(key),
781
+ set: (key, value) => { store.set(key, value); },
782
+ bridges: {
783
+ gnn: createGNNBridge(),
784
+ mincut: createMinCutBridge(),
785
+ },
786
+ config: {
787
+ allowedRoots: config?.allowedRoots ?? ['.'],
788
+ blockedPatterns: config?.blockedPatterns ?? defaultBlockedPatterns,
789
+ maskSecrets: config?.maskSecrets ?? true,
790
+ },
791
+ };
792
+ }
793
+ export default codeIntelligenceTools;
794
+ //# sourceMappingURL=mcp-tools.js.map