code-auditor-mcp 1.19.0 → 1.21.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.
Files changed (50) hide show
  1. package/dist/analyzers/schemaAnalyzer.d.ts +21 -0
  2. package/dist/analyzers/schemaAnalyzer.d.ts.map +1 -0
  3. package/dist/analyzers/schemaAnalyzer.js +405 -0
  4. package/dist/analyzers/schemaAnalyzer.js.map +1 -0
  5. package/dist/auditRunner.d.ts.map +1 -1
  6. package/dist/auditRunner.js +5 -1
  7. package/dist/auditRunner.js.map +1 -1
  8. package/dist/codeIndexDb.d.ts +98 -1
  9. package/dist/codeIndexDb.d.ts.map +1 -1
  10. package/dist/codeIndexDb.js +277 -0
  11. package/dist/codeIndexDb.js.map +1 -1
  12. package/dist/mcp-hybrid.d.ts +10 -0
  13. package/dist/mcp-hybrid.d.ts.map +1 -0
  14. package/dist/mcp-hybrid.js +41 -0
  15. package/dist/mcp-hybrid.js.map +1 -0
  16. package/dist/mcp-index.d.ts +11 -0
  17. package/dist/mcp-index.d.ts.map +1 -0
  18. package/dist/mcp-index.js +101 -0
  19. package/dist/mcp-index.js.map +1 -0
  20. package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
  21. package/dist/mcp-tools/workflowGuide.js +67 -10
  22. package/dist/mcp-tools/workflowGuide.js.map +1 -1
  23. package/dist/mcp-tools-shared.d.ts +46 -0
  24. package/dist/mcp-tools-shared.d.ts.map +1 -0
  25. package/dist/mcp-tools-shared.js +1161 -0
  26. package/dist/mcp-tools-shared.js.map +1 -0
  27. package/dist/mcp-ui-server.d.ts +27 -0
  28. package/dist/mcp-ui-server.d.ts.map +1 -0
  29. package/dist/mcp-ui-server.js +484 -0
  30. package/dist/mcp-ui-server.js.map +1 -0
  31. package/dist/mcp-ui-simple.d.ts +27 -0
  32. package/dist/mcp-ui-simple.d.ts.map +1 -0
  33. package/dist/mcp-ui-simple.js +425 -0
  34. package/dist/mcp-ui-simple.js.map +1 -0
  35. package/dist/mcp.js +122 -24
  36. package/dist/mcp.js.map +1 -1
  37. package/dist/services/CodeMapGenerator.d.ts +32 -0
  38. package/dist/services/CodeMapGenerator.d.ts.map +1 -1
  39. package/dist/services/CodeMapGenerator.js +169 -1
  40. package/dist/services/CodeMapGenerator.js.map +1 -1
  41. package/dist/services/SchemaParser.d.ts +89 -0
  42. package/dist/services/SchemaParser.d.ts.map +1 -0
  43. package/dist/services/SchemaParser.js +434 -0
  44. package/dist/services/SchemaParser.js.map +1 -0
  45. package/dist/types.d.ts +127 -0
  46. package/dist/types.d.ts.map +1 -1
  47. package/dist/types.js.map +1 -1
  48. package/examples/schema-example.json +364 -0
  49. package/examples/schema-example.yaml +137 -0
  50. package/package.json +9 -2
@@ -0,0 +1,1161 @@
1
+ /**
2
+ * Shared MCP Tools Logic
3
+ *
4
+ * This contains all the tool definitions and handlers that are shared
5
+ * between stdio and HTTP/UI MCP server implementations.
6
+ */
7
+ import { createAuditRunner } from './auditRunner.js';
8
+ import { searchFunctions, findDefinition, syncFileIndex } from './codeIndexService.js';
9
+ import { CodeMapGenerator } from './services/CodeMapGenerator.js';
10
+ import { analyzeDocumentation } from './analyzers/documentationAnalyzer.js';
11
+ import { CodeIndexDB } from './codeIndexDB.js';
12
+ import path from 'node:path';
13
+ import fs from 'node:fs/promises';
14
+ import chalk from 'chalk';
15
+ export const tools = [
16
+ // Core Audit Tools
17
+ {
18
+ name: 'audit',
19
+ description: 'Run a comprehensive code audit on files or directories, including React component analysis',
20
+ parameters: [
21
+ {
22
+ name: 'path',
23
+ type: 'string',
24
+ required: false,
25
+ description: 'The file or directory path to audit (defaults to current directory)',
26
+ default: process.cwd(),
27
+ },
28
+ {
29
+ name: 'analyzers',
30
+ type: 'array',
31
+ required: false,
32
+ description: 'List of analyzers to run (solid, dry, documentation, react, data-access)',
33
+ default: ['solid', 'dry', 'documentation', 'react', 'data-access'],
34
+ },
35
+ {
36
+ name: 'minSeverity',
37
+ type: 'string',
38
+ required: false,
39
+ description: 'Minimum severity level to report',
40
+ default: 'warning',
41
+ enum: ['info', 'warning', 'critical'],
42
+ },
43
+ {
44
+ name: 'indexFunctions',
45
+ type: 'boolean',
46
+ required: false,
47
+ description: 'Automatically index functions during audit',
48
+ default: true,
49
+ },
50
+ {
51
+ name: 'analyzerConfigs',
52
+ type: 'object',
53
+ required: false,
54
+ description: 'Analyzer-specific configuration overrides (e.g., SOLID thresholds, DRY settings)',
55
+ },
56
+ {
57
+ name: 'generateCodeMap',
58
+ type: 'boolean',
59
+ required: false,
60
+ description: 'Generate and return a human-readable code map as part of the audit results',
61
+ default: true,
62
+ },
63
+ ],
64
+ },
65
+ {
66
+ name: 'audit_health',
67
+ description: 'Quick health check of a codebase with key metrics',
68
+ parameters: [
69
+ {
70
+ name: 'path',
71
+ type: 'string',
72
+ required: false,
73
+ description: 'The directory path to check',
74
+ default: process.cwd(),
75
+ },
76
+ {
77
+ name: 'threshold',
78
+ type: 'number',
79
+ required: false,
80
+ description: 'Health score threshold (0-100) for pass/fail',
81
+ default: 70,
82
+ },
83
+ {
84
+ name: 'indexFunctions',
85
+ type: 'boolean',
86
+ required: false,
87
+ description: 'Automatically index functions during health check',
88
+ default: true,
89
+ },
90
+ {
91
+ name: 'analyzerConfigs',
92
+ type: 'object',
93
+ required: false,
94
+ description: 'Analyzer-specific configuration overrides (e.g., SOLID thresholds, DRY settings)',
95
+ },
96
+ {
97
+ name: 'generateCodeMap',
98
+ type: 'boolean',
99
+ required: false,
100
+ description: 'Generate and return a human-readable code map as part of the health check results',
101
+ default: true,
102
+ },
103
+ ],
104
+ },
105
+ // Code Index Tools
106
+ {
107
+ name: 'search_code',
108
+ description: 'Search indexed functions and React components with natural language queries. Supports operators: entity:component, component:functional|class|memo|forwardRef, hook:useState|useEffect|etc, prop:propName, dep:packageName, dependency:lodash, uses:express, calls:functionName, calledby:functionName, dependents-of:functionName, used-by:functionName, depends-on:module, imports-from:file, unused-imports, dead-imports, type:fileType, file:path, lang:language, complexity:1-10, jsdoc:true|false',
109
+ parameters: [
110
+ {
111
+ name: 'query',
112
+ type: 'string',
113
+ required: true,
114
+ description: 'Search query with natural language and/or operators. Examples: "Button component:functional", "entity:component hook:useState", "render prop:onClick", "dep:lodash", "calls:validateUser", "unused-imports", "dependents-of:authenticate"',
115
+ },
116
+ {
117
+ name: 'filters',
118
+ type: 'object',
119
+ required: false,
120
+ description: 'Optional filters (language, filePath, dependencies, componentType, entityType, searchMode). Set searchMode to "content" to search within function bodies, "metadata" for names/signatures only, or "both" for combined search',
121
+ },
122
+ {
123
+ name: 'limit',
124
+ type: 'number',
125
+ required: false,
126
+ description: 'Maximum results to return',
127
+ default: 50,
128
+ },
129
+ {
130
+ name: 'offset',
131
+ type: 'number',
132
+ required: false,
133
+ description: 'Offset for pagination',
134
+ default: 0,
135
+ },
136
+ ],
137
+ },
138
+ {
139
+ name: 'find_definition',
140
+ description: 'Find the exact definition of a specific function or React component',
141
+ parameters: [
142
+ {
143
+ name: 'name',
144
+ type: 'string',
145
+ required: true,
146
+ description: 'Function or component name to find',
147
+ },
148
+ {
149
+ name: 'filePath',
150
+ type: 'string',
151
+ required: false,
152
+ description: 'Optional file path to narrow search',
153
+ },
154
+ ],
155
+ },
156
+ {
157
+ name: 'sync_index',
158
+ description: 'Synchronize, cleanup, or reset the code index',
159
+ parameters: [
160
+ {
161
+ name: 'mode',
162
+ type: 'string',
163
+ required: false,
164
+ description: 'Operation mode: sync (update), cleanup (remove stale), or reset (clear all)',
165
+ default: 'sync',
166
+ enum: ['sync', 'cleanup', 'reset'],
167
+ },
168
+ {
169
+ name: 'path',
170
+ type: 'string',
171
+ required: false,
172
+ description: 'Optional specific path to sync',
173
+ },
174
+ ],
175
+ },
176
+ // AI Configuration Tool
177
+ {
178
+ name: 'generate_ai_config',
179
+ description: 'Generate configuration files for AI coding assistants',
180
+ parameters: [
181
+ {
182
+ name: 'tools',
183
+ type: 'array',
184
+ required: true,
185
+ description: 'AI tools to configure (cursor, continue, copilot, claude, zed, windsurf, cody, aider, cline, pearai)',
186
+ },
187
+ {
188
+ name: 'outputDir',
189
+ type: 'string',
190
+ required: false,
191
+ description: 'Output directory for configuration files',
192
+ default: '.',
193
+ },
194
+ ],
195
+ },
196
+ // Workflow Guide Tool
197
+ {
198
+ name: 'get_workflow_guide',
199
+ description: 'Get recommended workflows and best practices for using code auditor tools effectively',
200
+ parameters: [
201
+ {
202
+ name: 'scenario',
203
+ type: 'string',
204
+ required: false,
205
+ description: 'Specific scenario: initial-setup, react-development, code-review, find-patterns, maintenance. Leave empty to see all.',
206
+ },
207
+ ],
208
+ },
209
+ // Analyzer Configuration Tools
210
+ {
211
+ name: 'set_analyzer_config',
212
+ description: 'Set or update analyzer configuration that persists across audit runs',
213
+ parameters: [
214
+ {
215
+ name: 'analyzerName',
216
+ type: 'string',
217
+ required: true,
218
+ description: 'The analyzer to configure (solid, dry, security, etc.)',
219
+ },
220
+ {
221
+ name: 'config',
222
+ type: 'object',
223
+ required: true,
224
+ description: 'Configuration object for the analyzer (e.g., thresholds, rules)',
225
+ },
226
+ {
227
+ name: 'projectPath',
228
+ type: 'string',
229
+ required: false,
230
+ description: 'Optional project path for project-specific config (defaults to global)',
231
+ },
232
+ ],
233
+ },
234
+ {
235
+ name: 'get_analyzer_config',
236
+ description: 'Get current configuration for an analyzer',
237
+ parameters: [
238
+ {
239
+ name: 'analyzerName',
240
+ type: 'string',
241
+ required: false,
242
+ description: 'Specific analyzer name, or omit to get all configs',
243
+ },
244
+ {
245
+ name: 'projectPath',
246
+ type: 'string',
247
+ required: false,
248
+ description: 'Optional project path to get project-specific config',
249
+ },
250
+ ],
251
+ },
252
+ {
253
+ name: 'reset_analyzer_config',
254
+ description: 'Reset analyzer configuration to defaults',
255
+ parameters: [
256
+ {
257
+ name: 'analyzerName',
258
+ type: 'string',
259
+ required: false,
260
+ description: 'Specific analyzer to reset, or omit to reset all',
261
+ },
262
+ {
263
+ name: 'projectPath',
264
+ type: 'string',
265
+ required: false,
266
+ description: 'Optional project path to reset only project-specific config',
267
+ },
268
+ ],
269
+ },
270
+ {
271
+ name: 'get_code_map_section',
272
+ description: 'Retrieve a specific section of a previously generated code map',
273
+ parameters: [
274
+ {
275
+ name: 'mapId',
276
+ type: 'string',
277
+ required: true,
278
+ description: 'The map ID returned from a previous audit with code map generation',
279
+ },
280
+ {
281
+ name: 'sectionType',
282
+ type: 'string',
283
+ required: true,
284
+ description: 'The section type to retrieve (e.g., overview, files, dependencies, documentation)',
285
+ },
286
+ ],
287
+ },
288
+ {
289
+ name: 'list_code_map_sections',
290
+ description: 'List all available sections for a code map',
291
+ parameters: [
292
+ {
293
+ name: 'mapId',
294
+ type: 'string',
295
+ required: true,
296
+ description: 'The map ID returned from a previous audit',
297
+ },
298
+ ],
299
+ },
300
+ // Database Schema Management Tools
301
+ {
302
+ name: 'generate_schema_discovery_sql',
303
+ description: 'Generate SQL queries for LLMs to extract database schema information automatically',
304
+ parameters: [
305
+ {
306
+ name: 'databaseType',
307
+ type: 'string',
308
+ required: true,
309
+ enum: ['postgresql', 'mysql', 'sqlite', 'sqlserver', 'oracle'],
310
+ description: 'Type of database to generate queries for',
311
+ },
312
+ {
313
+ name: 'includeIndexes',
314
+ type: 'boolean',
315
+ required: false,
316
+ default: true,
317
+ description: 'Include queries to discover indexes',
318
+ },
319
+ {
320
+ name: 'includeConstraints',
321
+ type: 'boolean',
322
+ required: false,
323
+ default: true,
324
+ description: 'Include queries to discover foreign key constraints',
325
+ },
326
+ {
327
+ name: 'specificTables',
328
+ type: 'array',
329
+ required: false,
330
+ description: 'Limit discovery to specific table names (optional)',
331
+ },
332
+ ],
333
+ },
334
+ {
335
+ name: 'get_schemas',
336
+ description: 'List all loaded database schemas with their metadata',
337
+ parameters: [],
338
+ },
339
+ {
340
+ name: 'search_schema',
341
+ description: 'Search for tables, columns, or relationships in loaded schemas',
342
+ parameters: [
343
+ {
344
+ name: 'query',
345
+ type: 'string',
346
+ required: true,
347
+ description: 'Search query for table/column names or descriptions',
348
+ },
349
+ {
350
+ name: 'schemaId',
351
+ type: 'string',
352
+ required: false,
353
+ description: 'Limit search to specific schema ID',
354
+ },
355
+ {
356
+ name: 'searchType',
357
+ type: 'string',
358
+ required: false,
359
+ enum: ['tables', 'columns', 'relationships', 'all'],
360
+ default: 'all',
361
+ description: 'Type of schema elements to search',
362
+ },
363
+ ],
364
+ },
365
+ {
366
+ name: 'analyze_schema_usage',
367
+ description: 'Analyze how database tables are used in the codebase',
368
+ parameters: [
369
+ {
370
+ name: 'path',
371
+ type: 'string',
372
+ required: false,
373
+ default: '.',
374
+ description: 'Path to analyze for schema usage patterns',
375
+ },
376
+ {
377
+ name: 'schemaId',
378
+ type: 'string',
379
+ required: false,
380
+ description: 'Schema ID to analyze against',
381
+ },
382
+ {
383
+ name: 'includeUsagePatterns',
384
+ type: 'boolean',
385
+ required: false,
386
+ default: true,
387
+ description: 'Whether to include detailed usage patterns',
388
+ },
389
+ ],
390
+ },
391
+ {
392
+ name: 'find_table_usage',
393
+ description: 'Find all functions that interact with a specific database table',
394
+ parameters: [
395
+ {
396
+ name: 'tableName',
397
+ type: 'string',
398
+ required: true,
399
+ description: 'Name of the table to find usage for',
400
+ },
401
+ {
402
+ name: 'usageType',
403
+ type: 'string',
404
+ required: false,
405
+ enum: ['query', 'insert', 'update', 'delete', 'reference', 'all'],
406
+ default: 'all',
407
+ description: 'Type of table usage to find',
408
+ },
409
+ ],
410
+ },
411
+ {
412
+ name: 'validate_schema_consistency',
413
+ description: 'Validate schema consistency and find potential issues',
414
+ parameters: [
415
+ {
416
+ name: 'schemaId',
417
+ type: 'string',
418
+ required: false,
419
+ description: 'Schema ID to validate (validates all if not specified)',
420
+ },
421
+ {
422
+ name: 'checkCircularDeps',
423
+ type: 'boolean',
424
+ required: false,
425
+ default: true,
426
+ description: 'Check for circular dependencies',
427
+ },
428
+ {
429
+ name: 'checkNamingConventions',
430
+ type: 'boolean',
431
+ required: false,
432
+ default: true,
433
+ description: 'Check naming convention compliance',
434
+ },
435
+ ],
436
+ },
437
+ ];
438
+ // UI-specific tools (only available in UI mode)
439
+ export const uiTools = [
440
+ {
441
+ name: 'audit_dashboard',
442
+ description: 'Generates an interactive dashboard with detailed audit findings, code maps, and remediation options.',
443
+ parameters: [
444
+ {
445
+ name: 'path',
446
+ type: 'string',
447
+ required: false,
448
+ description: 'Path to audit',
449
+ default: '.',
450
+ },
451
+ {
452
+ name: 'analyzers',
453
+ type: 'array',
454
+ required: false,
455
+ description: 'Analyzers to run',
456
+ default: ['solid', 'dry', 'documentation', 'react', 'data-access'],
457
+ },
458
+ {
459
+ name: 'minSeverity',
460
+ type: 'string',
461
+ required: false,
462
+ description: 'Minimum severity level',
463
+ default: 'warning',
464
+ enum: ['info', 'warning', 'critical'],
465
+ },
466
+ ],
467
+ },
468
+ {
469
+ name: 'code_map_viewer',
470
+ description: 'Generates an interactive, navigable code map with file structure, complexity analysis, and documentation coverage.',
471
+ parameters: [
472
+ {
473
+ name: 'path',
474
+ type: 'string',
475
+ required: false,
476
+ description: 'Path to analyze',
477
+ default: '.',
478
+ },
479
+ ],
480
+ },
481
+ ];
482
+ /**
483
+ * Shared tool handler implementations
484
+ */
485
+ export class ToolHandlers {
486
+ static async handleAudit(args) {
487
+ const auditPath = path.resolve(args.path || process.cwd());
488
+ const indexFunctions = args.indexFunctions !== false; // Default true
489
+ const generateCodeMap = args.generateCodeMap !== false; // Default true
490
+ // Check if path is a file or directory
491
+ const stats = await fs.stat(auditPath).catch(() => null);
492
+ const isFile = stats?.isFile() || false;
493
+ // Get stored analyzer configs from database
494
+ const db = CodeIndexDB.getInstance();
495
+ await db.initialize();
496
+ const storedConfigs = await db.getAllAnalyzerConfigs(auditPath);
497
+ // Merge stored configs with any provided configs
498
+ const analyzerConfigs = {
499
+ ...storedConfigs,
500
+ ...(args.analyzerConfigs || {})
501
+ };
502
+ const options = {
503
+ projectRoot: isFile ? path.dirname(auditPath) : auditPath,
504
+ enabledAnalyzers: args.analyzers || ['solid', 'dry', 'documentation', 'react', 'data-access'],
505
+ minSeverity: (args.minSeverity || 'warning'),
506
+ verbose: false,
507
+ indexFunctions,
508
+ ...(isFile && { includePaths: [auditPath] }),
509
+ ...(Object.keys(analyzerConfigs).length > 0 && { analyzerConfigs }),
510
+ };
511
+ const runner = createAuditRunner(options);
512
+ const auditResult = await runner.run();
513
+ // Handle function indexing if enabled and functions were collected
514
+ let indexingResult = null;
515
+ if (indexFunctions && auditResult.metadata.fileToFunctionsMap) {
516
+ try {
517
+ const syncStats = { added: 0, updated: 0, removed: 0 };
518
+ // Sync each file's functions to handle additions, updates, and removals
519
+ for (const [filePath, functions] of Object.entries(auditResult.metadata.fileToFunctionsMap)) {
520
+ const fileStats = await syncFileIndex(filePath, functions);
521
+ syncStats.added += fileStats.added;
522
+ syncStats.updated += fileStats.updated;
523
+ syncStats.removed += fileStats.removed;
524
+ }
525
+ indexingResult = {
526
+ success: true,
527
+ registered: syncStats.added + syncStats.updated,
528
+ failed: 0,
529
+ syncStats
530
+ };
531
+ console.error(chalk.blue('[INFO]'), `Synced functions: ${syncStats.added} added, ${syncStats.updated} updated, ${syncStats.removed} removed`);
532
+ }
533
+ catch (error) {
534
+ console.error(chalk.yellow('[WARN]'), 'Failed to sync functions:', error);
535
+ }
536
+ }
537
+ // Generate code map if requested and functions were indexed
538
+ let codeMapResult = null;
539
+ if (generateCodeMap && indexingResult && indexingResult.success) {
540
+ try {
541
+ const mapGenerator = new CodeMapGenerator();
542
+ const mapOptions = {
543
+ includeComplexity: true,
544
+ includeDocumentation: true,
545
+ includeDependencies: true,
546
+ includeUsage: false,
547
+ groupByDirectory: true,
548
+ maxDepth: 10,
549
+ showUnusedImports: true,
550
+ minComplexity: 7,
551
+ };
552
+ // Generate documentation metrics
553
+ let documentation = undefined;
554
+ try {
555
+ const files = Object.keys(auditResult.metadata.fileToFunctionsMap || {});
556
+ if (files.length > 0) {
557
+ const docResult = await analyzeDocumentation(files);
558
+ documentation = docResult.metrics;
559
+ }
560
+ }
561
+ catch (docError) {
562
+ console.error(chalk.yellow('[WARN]'), 'Failed to analyze documentation:', docError);
563
+ }
564
+ // Use paginated code map generation
565
+ const paginatedResult = await mapGenerator.generatePaginatedCodeMap(auditPath, {
566
+ ...mapOptions,
567
+ includeDocumentation: !!documentation
568
+ });
569
+ codeMapResult = {
570
+ success: true,
571
+ mapId: paginatedResult.mapId,
572
+ summary: paginatedResult.summary,
573
+ quickPreview: paginatedResult.quickPreview,
574
+ sections: paginatedResult.summary.sectionsAvailable,
575
+ documentationCoverage: documentation?.coverageScore
576
+ };
577
+ console.error(chalk.blue('[INFO]'), `Generated paginated code map: ${paginatedResult.summary.stats.totalFiles} files, ${paginatedResult.summary.totalSections} sections`);
578
+ }
579
+ catch (error) {
580
+ console.error(chalk.yellow('[WARN]'), 'Failed to generate code map:', error);
581
+ codeMapResult = {
582
+ success: false,
583
+ error: error instanceof Error ? error.message : 'Failed to generate code map'
584
+ };
585
+ }
586
+ }
587
+ // Format for MCP
588
+ return {
589
+ summary: {
590
+ totalViolations: auditResult.summary.totalViolations,
591
+ criticalIssues: auditResult.summary.criticalIssues,
592
+ warnings: auditResult.summary.warnings,
593
+ suggestions: auditResult.summary.suggestions,
594
+ filesAnalyzed: auditResult.metadata.filesAnalyzed,
595
+ executionTime: auditResult.metadata.auditDuration,
596
+ healthScore: ToolHandlers.calculateHealthScore(auditResult),
597
+ },
598
+ violations: ToolHandlers.getAllViolations(auditResult).slice(0, 100), // Limit to first 100
599
+ recommendations: auditResult.recommendations,
600
+ ...(indexingResult && { functionIndexing: indexingResult }),
601
+ ...(codeMapResult && { codeMap: codeMapResult }),
602
+ };
603
+ }
604
+ static async handleAuditHealth(args) {
605
+ const auditPath = path.resolve(args.path || process.cwd());
606
+ const threshold = args.threshold || 70;
607
+ const indexFunctions = args.indexFunctions !== false; // Default true
608
+ const generateCodeMap = args.generateCodeMap !== false; // Default true
609
+ // Get stored analyzer configs from database
610
+ const db = CodeIndexDB.getInstance();
611
+ await db.initialize();
612
+ const storedConfigs = await db.getAllAnalyzerConfigs(auditPath);
613
+ // Merge stored configs with any provided configs
614
+ const analyzerConfigs = {
615
+ ...storedConfigs,
616
+ ...(args.analyzerConfigs || {})
617
+ };
618
+ const runner = createAuditRunner({
619
+ projectRoot: auditPath,
620
+ enabledAnalyzers: ['solid', 'dry', 'documentation', 'react', 'data-access'],
621
+ minSeverity: 'warning',
622
+ verbose: false,
623
+ indexFunctions,
624
+ ...(Object.keys(analyzerConfigs).length > 0 && { analyzerConfigs }),
625
+ });
626
+ const auditResult = await runner.run();
627
+ const healthScore = ToolHandlers.calculateHealthScore(auditResult);
628
+ // Handle function indexing if enabled and functions were collected
629
+ let indexingResult = null;
630
+ if (indexFunctions && auditResult.metadata.fileToFunctionsMap) {
631
+ try {
632
+ const syncStats = { added: 0, updated: 0, removed: 0 };
633
+ // Sync each file's functions to handle additions, updates, and removals
634
+ for (const [filePath, functions] of Object.entries(auditResult.metadata.fileToFunctionsMap)) {
635
+ const fileStats = await syncFileIndex(filePath, functions);
636
+ syncStats.added += fileStats.added;
637
+ syncStats.updated += fileStats.updated;
638
+ syncStats.removed += fileStats.removed;
639
+ }
640
+ indexingResult = {
641
+ success: true,
642
+ registered: syncStats.added + syncStats.updated,
643
+ failed: 0,
644
+ syncStats
645
+ };
646
+ console.error(chalk.blue('[INFO]'), `Synced functions: ${syncStats.added} added, ${syncStats.updated} updated, ${syncStats.removed} removed`);
647
+ }
648
+ catch (error) {
649
+ console.error(chalk.yellow('[WARN]'), 'Failed to sync functions:', error);
650
+ }
651
+ }
652
+ // Generate code map if requested and functions were indexed
653
+ let codeMapResult = null;
654
+ if (generateCodeMap && indexingResult && indexingResult.success) {
655
+ try {
656
+ const mapGenerator = new CodeMapGenerator();
657
+ const mapOptions = {
658
+ includeComplexity: true,
659
+ includeDocumentation: true,
660
+ includeDependencies: true,
661
+ includeUsage: false,
662
+ groupByDirectory: true,
663
+ maxDepth: 8, // Slightly smaller for health check
664
+ showUnusedImports: true,
665
+ minComplexity: 7,
666
+ };
667
+ // Generate documentation metrics
668
+ let documentation = undefined;
669
+ try {
670
+ const files = Object.keys(auditResult.metadata.fileToFunctionsMap || {});
671
+ if (files.length > 0) {
672
+ const docResult = await analyzeDocumentation(files);
673
+ documentation = docResult.metrics;
674
+ }
675
+ }
676
+ catch (docError) {
677
+ console.error(chalk.yellow('[WARN]'), 'Failed to analyze documentation:', docError);
678
+ }
679
+ // Use paginated code map generation for health check too
680
+ const paginatedResult = await mapGenerator.generatePaginatedCodeMap(auditPath, {
681
+ ...mapOptions,
682
+ includeDocumentation: !!documentation
683
+ });
684
+ codeMapResult = {
685
+ success: true,
686
+ mapId: paginatedResult.mapId,
687
+ summary: paginatedResult.summary,
688
+ quickPreview: paginatedResult.quickPreview,
689
+ sections: paginatedResult.summary.sectionsAvailable,
690
+ documentationCoverage: documentation?.coverageScore
691
+ };
692
+ console.error(chalk.blue('[INFO]'), `Generated paginated code map: ${paginatedResult.summary.stats.totalFiles} files, ${paginatedResult.summary.totalSections} sections`);
693
+ }
694
+ catch (error) {
695
+ console.error(chalk.yellow('[WARN]'), 'Failed to generate code map:', error);
696
+ codeMapResult = {
697
+ success: false,
698
+ error: error instanceof Error ? error.message : 'Failed to generate code map'
699
+ };
700
+ }
701
+ }
702
+ return {
703
+ healthScore,
704
+ threshold,
705
+ passed: healthScore >= threshold,
706
+ status: healthScore >= threshold ? 'healthy' : 'needs-attention',
707
+ metrics: {
708
+ filesAnalyzed: auditResult.metadata.filesAnalyzed,
709
+ totalViolations: auditResult.summary.totalViolations,
710
+ criticalViolations: auditResult.summary.criticalIssues,
711
+ warningViolations: auditResult.summary.warnings,
712
+ },
713
+ recommendation: ToolHandlers.getHealthRecommendation(healthScore, auditResult),
714
+ ...(indexingResult && { functionIndexing: indexingResult }),
715
+ ...(codeMapResult && { codeMap: codeMapResult }),
716
+ };
717
+ }
718
+ // Add all other tool handlers here following the same pattern...
719
+ // (I'll include key ones for brevity)
720
+ static async handleSearchCode(args) {
721
+ const query = args.query;
722
+ const filters = args.filters;
723
+ const limit = args.limit || 50;
724
+ const offset = args.offset || 0;
725
+ if (query !== undefined && typeof query !== 'string') {
726
+ throw new Error('query must be a string');
727
+ }
728
+ return await searchFunctions({
729
+ query,
730
+ filters,
731
+ limit,
732
+ offset
733
+ });
734
+ }
735
+ static async handleFindDefinition(args) {
736
+ const name = args.name;
737
+ const filePath = args.filePath;
738
+ if (!name || typeof name !== 'string') {
739
+ throw new Error('name must be a non-empty string');
740
+ }
741
+ const definition = await findDefinition(name, filePath);
742
+ return definition || { error: 'Function not found' };
743
+ }
744
+ // Schema Management Tool Handlers
745
+ static async handleGenerateSchemaDiscoverySQL(args) {
746
+ const databaseType = args.databaseType;
747
+ const includeIndexes = args.includeIndexes !== false;
748
+ const includeConstraints = args.includeConstraints !== false;
749
+ const specificTables = args.specificTables;
750
+ if (!databaseType || typeof databaseType !== 'string') {
751
+ throw new Error('databaseType must be a non-empty string');
752
+ }
753
+ const sqlQueries = ToolHandlers.generateSchemaDiscoveryQueries(databaseType, includeIndexes, includeConstraints, specificTables);
754
+ return {
755
+ databaseType,
756
+ queries: sqlQueries,
757
+ instructions: [
758
+ "Execute these SQL queries against your database",
759
+ "Copy the results and use create_schema_from_sql_result to import them",
760
+ "Each query discovers different aspects of your database schema",
761
+ "Run them in order and collect all results"
762
+ ],
763
+ nextStep: "Use create_schema_from_sql_result with the query results"
764
+ };
765
+ }
766
+ static async handleCreateSchemaFromSqlResult(args) {
767
+ const schemaName = args.schemaName;
768
+ const databaseType = args.databaseType;
769
+ const tablesData = args.tablesData;
770
+ const columnsData = args.columnsData;
771
+ const constraintsData = args.constraintsData;
772
+ if (!schemaName || !databaseType || !tablesData || !columnsData) {
773
+ throw new Error('schemaName, databaseType, tablesData, and columnsData are required');
774
+ }
775
+ try {
776
+ const schema = ToolHandlers.buildSchemaFromSqlData(schemaName, databaseType, tablesData, columnsData, constraintsData);
777
+ const db = CodeIndexDB.getInstance();
778
+ await db.initialize();
779
+ const schemaId = await db.storeSchema(schema);
780
+ return {
781
+ success: true,
782
+ schemaId,
783
+ schemaName: schema.name,
784
+ stats: {
785
+ databaseCount: schema.databases.length,
786
+ tableCount: schema.databases.reduce((acc, db) => acc + db.tables.length, 0),
787
+ columnCount: schema.databases.reduce((acc, db) => acc + db.tables.reduce((tAcc, table) => tAcc + table.columns.length, 0), 0)
788
+ },
789
+ message: `Schema '${schema.name}' created from SQL results`
790
+ };
791
+ }
792
+ catch (error) {
793
+ return {
794
+ success: false,
795
+ error: `Failed to create schema from SQL results: ${error instanceof Error ? error.message : 'Unknown error'}`
796
+ };
797
+ }
798
+ }
799
+ static async handleAddTableManually(args) {
800
+ const schemaName = args.schemaName;
801
+ const tableName = args.tableName;
802
+ const columns = args.columns;
803
+ const databaseType = args.databaseType || 'postgresql';
804
+ if (!schemaName || !tableName || !columns || !Array.isArray(columns)) {
805
+ throw new Error('schemaName, tableName, and columns array are required');
806
+ }
807
+ try {
808
+ const db = CodeIndexDB.getInstance();
809
+ await db.initialize();
810
+ // Get existing schema or create new one
811
+ const existingSchemas = await db.getAllSchemas();
812
+ let schema = existingSchemas.find(s => s.schema.name === schemaName)?.schema;
813
+ if (!schema) {
814
+ // Create new schema
815
+ schema = {
816
+ version: "1.0.0",
817
+ name: schemaName,
818
+ description: `Manually created schema: ${schemaName}`,
819
+ databases: [{
820
+ name: 'default',
821
+ type: databaseType,
822
+ tables: []
823
+ }]
824
+ };
825
+ }
826
+ // Add the table
827
+ const newTable = {
828
+ name: tableName,
829
+ type: 'table',
830
+ columns: columns.map(col => ({
831
+ name: col.name,
832
+ type: col.type,
833
+ nullable: col.nullable !== false,
834
+ primaryKey: col.primaryKey || false,
835
+ unique: col.unique || false,
836
+ indexed: col.indexed || false,
837
+ description: col.description
838
+ })),
839
+ references: [],
840
+ indexes: []
841
+ };
842
+ schema.databases[0].tables.push(newTable);
843
+ // Store updated schema
844
+ const schemaId = await db.storeSchema(schema);
845
+ return {
846
+ success: true,
847
+ schemaId,
848
+ tableName,
849
+ columnCount: columns.length,
850
+ message: `Table '${tableName}' added to schema '${schemaName}'`
851
+ };
852
+ }
853
+ catch (error) {
854
+ return {
855
+ success: false,
856
+ error: `Failed to add table: ${error instanceof Error ? error.message : 'Unknown error'}`
857
+ };
858
+ }
859
+ }
860
+ static async handleListSchemas(args) {
861
+ try {
862
+ const db = CodeIndexDB.getInstance();
863
+ await db.initialize();
864
+ const schemas = await db.getAllSchemas();
865
+ const stats = await db.getSchemaStats();
866
+ return {
867
+ schemas: schemas.map(s => ({
868
+ schemaId: s.schemaId,
869
+ name: s.schema.name,
870
+ description: s.schema.description,
871
+ databaseCount: s.schema.databases.length,
872
+ tableCount: s.metadata.tableCount,
873
+ relationshipCount: s.metadata.relationshipCount,
874
+ indexedAt: s.metadata.indexedAt
875
+ })),
876
+ totalStats: stats
877
+ };
878
+ }
879
+ catch (error) {
880
+ return {
881
+ success: false,
882
+ error: `Failed to list schemas: ${error instanceof Error ? error.message : 'Unknown error'}`
883
+ };
884
+ }
885
+ }
886
+ static async handleSearchSchemaElements(args) {
887
+ const query = args.query;
888
+ const elementType = args.elementType || 'all';
889
+ if (!query || typeof query !== 'string') {
890
+ throw new Error('query must be a non-empty string');
891
+ }
892
+ try {
893
+ const db = CodeIndexDB.getInstance();
894
+ await db.initialize();
895
+ const schemas = (await db.getAllSchemas()).map(s => s.schema);
896
+ const results = [];
897
+ const queryLower = query.toLowerCase();
898
+ for (const schema of schemas) {
899
+ for (const database of schema.databases) {
900
+ for (const table of database.tables) {
901
+ // Search tables
902
+ if ((elementType === 'all' || elementType === 'tables') &&
903
+ (table.name.toLowerCase().includes(queryLower) ||
904
+ table.description?.toLowerCase().includes(queryLower))) {
905
+ results.push({
906
+ type: 'table',
907
+ tableName: table.name,
908
+ databaseName: database.name,
909
+ schemaName: schema.name,
910
+ description: table.description,
911
+ columnCount: table.columns.length,
912
+ tags: table.tags
913
+ });
914
+ }
915
+ // Search columns
916
+ if (elementType === 'all' || elementType === 'columns') {
917
+ for (const column of table.columns) {
918
+ if (column.name.toLowerCase().includes(queryLower) ||
919
+ column.description?.toLowerCase().includes(queryLower) ||
920
+ column.type.toLowerCase().includes(queryLower)) {
921
+ results.push({
922
+ type: 'column',
923
+ columnName: column.name,
924
+ tableName: table.name,
925
+ databaseName: database.name,
926
+ schemaName: schema.name,
927
+ columnType: column.type,
928
+ description: column.description,
929
+ nullable: column.nullable,
930
+ primaryKey: column.primaryKey
931
+ });
932
+ }
933
+ }
934
+ }
935
+ }
936
+ }
937
+ }
938
+ return {
939
+ query,
940
+ elementType,
941
+ resultCount: results.length,
942
+ results
943
+ };
944
+ }
945
+ catch (error) {
946
+ return {
947
+ success: false,
948
+ error: `Failed to search schema elements: ${error instanceof Error ? error.message : 'Unknown error'}`
949
+ };
950
+ }
951
+ }
952
+ // Schema Helper Functions
953
+ static generateSchemaDiscoveryQueries(databaseType, includeIndexes, includeConstraints, specificTables) {
954
+ const queries = [];
955
+ const tableFilter = specificTables && specificTables.length > 0
956
+ ? `WHERE table_name IN (${specificTables.map(t => `'${t}'`).join(', ')})`
957
+ : '';
958
+ switch (databaseType.toLowerCase()) {
959
+ case 'postgresql':
960
+ queries.push({
961
+ name: 'tables',
962
+ sql: `SELECT table_name, table_type, table_schema
963
+ FROM information_schema.tables
964
+ WHERE table_schema NOT IN ('information_schema', 'pg_catalog') ${tableFilter}
965
+ ORDER BY table_schema, table_name;`,
966
+ description: 'Get all tables and views'
967
+ });
968
+ queries.push({
969
+ name: 'columns',
970
+ sql: `SELECT table_name, column_name, data_type, is_nullable, column_default,
971
+ character_maximum_length, numeric_precision, numeric_scale
972
+ FROM information_schema.columns
973
+ WHERE table_schema NOT IN ('information_schema', 'pg_catalog') ${tableFilter}
974
+ ORDER BY table_name, ordinal_position;`,
975
+ description: 'Get all columns with types and constraints'
976
+ });
977
+ if (includeConstraints) {
978
+ queries.push({
979
+ name: 'foreign_keys',
980
+ sql: `SELECT tc.table_name, kcu.column_name, ccu.table_name AS foreign_table_name,
981
+ ccu.column_name AS foreign_column_name, rc.delete_rule, rc.update_rule
982
+ FROM information_schema.table_constraints AS tc
983
+ JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
984
+ JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
985
+ JOIN information_schema.referential_constraints AS rc ON tc.constraint_name = rc.constraint_name
986
+ WHERE tc.constraint_type = 'FOREIGN KEY' ${tableFilter.replace('table_name', 'tc.table_name')}
987
+ ORDER BY tc.table_name, kcu.column_name;`,
988
+ description: 'Get foreign key relationships'
989
+ });
990
+ }
991
+ if (includeIndexes) {
992
+ queries.push({
993
+ name: 'indexes',
994
+ sql: `SELECT tablename, indexname, indexdef
995
+ FROM pg_indexes
996
+ WHERE schemaname NOT IN ('information_schema', 'pg_catalog') ${tableFilter.replace('table_name', 'tablename')}
997
+ ORDER BY tablename, indexname;`,
998
+ description: 'Get all indexes'
999
+ });
1000
+ }
1001
+ break;
1002
+ case 'mysql':
1003
+ queries.push({
1004
+ name: 'tables',
1005
+ sql: `SELECT table_name, table_type, table_schema
1006
+ FROM information_schema.tables
1007
+ WHERE table_schema = DATABASE() ${tableFilter}
1008
+ ORDER BY table_name;`,
1009
+ description: 'Get all tables and views'
1010
+ });
1011
+ queries.push({
1012
+ name: 'columns',
1013
+ sql: `SELECT table_name, column_name, data_type, is_nullable, column_default,
1014
+ character_maximum_length, numeric_precision, numeric_scale,
1015
+ column_key, extra
1016
+ FROM information_schema.columns
1017
+ WHERE table_schema = DATABASE() ${tableFilter}
1018
+ ORDER BY table_name, ordinal_position;`,
1019
+ description: 'Get all columns with types and constraints'
1020
+ });
1021
+ if (includeConstraints) {
1022
+ queries.push({
1023
+ name: 'foreign_keys',
1024
+ sql: `SELECT table_name, column_name, referenced_table_name, referenced_column_name,
1025
+ delete_rule, update_rule
1026
+ FROM information_schema.key_column_usage
1027
+ WHERE table_schema = DATABASE() AND referenced_table_name IS NOT NULL ${tableFilter}
1028
+ ORDER BY table_name, column_name;`,
1029
+ description: 'Get foreign key relationships'
1030
+ });
1031
+ }
1032
+ break;
1033
+ case 'sqlite':
1034
+ queries.push({
1035
+ name: 'tables',
1036
+ sql: `SELECT name as table_name, type as table_type
1037
+ FROM sqlite_master
1038
+ WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'
1039
+ ORDER BY name;`,
1040
+ description: 'Get all tables and views'
1041
+ });
1042
+ queries.push({
1043
+ name: 'table_info',
1044
+ sql: `-- Run this for each table: PRAGMA table_info(table_name);
1045
+ -- This will give you column information for each table`,
1046
+ description: 'Get column information (run PRAGMA table_info for each table)'
1047
+ });
1048
+ break;
1049
+ }
1050
+ return queries;
1051
+ }
1052
+ static buildSchemaFromSqlData(schemaName, databaseType, tablesData, columnsData, constraintsData) {
1053
+ // Build schema from SQL results
1054
+ const tables = [];
1055
+ const tableMap = new Map();
1056
+ // Process tables
1057
+ const tableRows = Array.isArray(tablesData) ? tablesData : tablesData.rows || [];
1058
+ for (const row of tableRows) {
1059
+ const tableName = row.table_name || row.TABLE_NAME;
1060
+ if (!tableMap.has(tableName)) {
1061
+ tableMap.set(tableName, {
1062
+ name: tableName,
1063
+ type: 'table',
1064
+ columns: [],
1065
+ references: [],
1066
+ indexes: []
1067
+ });
1068
+ }
1069
+ }
1070
+ // Process columns
1071
+ const columnRows = Array.isArray(columnsData) ? columnsData : columnsData.rows || [];
1072
+ for (const row of columnRows) {
1073
+ const tableName = row.table_name || row.TABLE_NAME;
1074
+ const table = tableMap.get(tableName);
1075
+ if (table) {
1076
+ table.columns.push({
1077
+ name: row.column_name || row.COLUMN_NAME,
1078
+ type: row.data_type || row.DATA_TYPE,
1079
+ nullable: (row.is_nullable || row.IS_NULLABLE) === 'YES',
1080
+ primaryKey: (row.column_key || row.COLUMN_KEY) === 'PRI',
1081
+ defaultValue: row.column_default || row.COLUMN_DEFAULT,
1082
+ length: row.character_maximum_length || row.CHARACTER_MAXIMUM_LENGTH,
1083
+ precision: row.numeric_precision || row.NUMERIC_PRECISION,
1084
+ scale: row.numeric_scale || row.NUMERIC_SCALE
1085
+ });
1086
+ }
1087
+ }
1088
+ // Process constraints if provided
1089
+ if (constraintsData) {
1090
+ const constraintRows = Array.isArray(constraintsData) ? constraintsData : constraintsData.rows || [];
1091
+ for (const row of constraintRows) {
1092
+ const tableName = row.table_name || row.TABLE_NAME;
1093
+ const table = tableMap.get(tableName);
1094
+ if (table) {
1095
+ table.references.push({
1096
+ foreignKey: row.column_name || row.COLUMN_NAME,
1097
+ referencedTable: row.foreign_table_name || row.REFERENCED_TABLE_NAME,
1098
+ referencedColumn: row.foreign_column_name || row.REFERENCED_COLUMN_NAME,
1099
+ onDelete: row.delete_rule || row.DELETE_RULE,
1100
+ onUpdate: row.update_rule || row.UPDATE_RULE
1101
+ });
1102
+ }
1103
+ }
1104
+ }
1105
+ return {
1106
+ version: "1.0.0",
1107
+ name: schemaName,
1108
+ description: `Schema discovered from ${databaseType} database`,
1109
+ databases: [{
1110
+ name: 'main',
1111
+ type: databaseType,
1112
+ tables: Array.from(tableMap.values())
1113
+ }],
1114
+ metadata: {
1115
+ createdAt: new Date().toISOString(),
1116
+ source: 'sql-discovery'
1117
+ }
1118
+ };
1119
+ }
1120
+ // Helper functions
1121
+ static getAllViolations(result) {
1122
+ const violations = [];
1123
+ for (const [analyzerName, analyzerResult] of Object.entries(result.analyzerResults)) {
1124
+ for (const violation of analyzerResult.violations) {
1125
+ violations.push({
1126
+ ...violation,
1127
+ analyzer: analyzerName,
1128
+ });
1129
+ }
1130
+ }
1131
+ return violations;
1132
+ }
1133
+ static calculateHealthScore(result) {
1134
+ const filesAnalyzed = result.metadata?.filesAnalyzed || 1;
1135
+ const critical = result.summary.criticalIssues || 0;
1136
+ const warnings = result.summary.warnings || 0;
1137
+ const suggestions = result.summary.suggestions || 0;
1138
+ const weights = {
1139
+ critical: 10,
1140
+ warning: 3,
1141
+ suggestion: 0.5
1142
+ };
1143
+ const weightedViolations = (critical * weights.critical) +
1144
+ (warnings * weights.warning) +
1145
+ (suggestions * weights.suggestion);
1146
+ const violationsPerFile = weightedViolations / filesAnalyzed;
1147
+ let score = 100 - (violationsPerFile * 2);
1148
+ return Math.max(0, Math.round(Math.min(100, score)));
1149
+ }
1150
+ static getHealthRecommendation(score, result) {
1151
+ if (score >= 90)
1152
+ return 'Excellent code health!';
1153
+ if (score >= 70)
1154
+ return 'Good code health with room for improvement';
1155
+ if (result.summary.criticalIssues > 0) {
1156
+ return `Fix ${result.summary.criticalIssues} critical violations first`;
1157
+ }
1158
+ return 'Code health needs attention - run detailed audit';
1159
+ }
1160
+ }
1161
+ //# sourceMappingURL=mcp-tools-shared.js.map