UNPKG

code-auditor-mcp

Version:

Multi-language code quality auditor with MCP server - Analyze TypeScript, JavaScript, and Go code for SOLID principles, DRY violations, security patterns, and more

268 lines 9.93 kB
/** * Audit Runner (Functional) * Main orchestrator for running code audits */ import { discoverFiles } from './utils/fileDiscovery.js'; import { loadConfig } from './config/configLoader.js'; import { generateReport } from './reporting/reportGenerator.js'; import { extractFunctionsFromFile } from './functionScanner.js'; // Import analyzer definitions // Use compatibility layers for refactored analyzers import { createSOLIDAnalyzer } from './adapters/solidAnalyzerCompat.js'; import { dryAnalyzer } from './analyzers/dryAnalyzerCompat.js'; // import { securityAnalyzer } from './analyzers/securityAnalyzer.js'; // import { componentAnalyzer } from './analyzers/componentAnalyzer.js'; import { dataAccessAnalyzer } from './analyzers/dataAccessAnalyzerCompat.js'; import { reactAnalyzer } from './analyzers/reactAnalyzer.js'; // Keep legacy React analyzer import { documentationAnalyzer } from './analyzers/documentationAnalyzerCompat.js'; import { schemaAnalyzer } from './analyzers/schemaAnalyzerCompat.js'; /** * Default analyzer registry */ const DEFAULT_ANALYZERS = { 'solid': createSOLIDAnalyzer(), 'dry': dryAnalyzer, // 'security': securityAnalyzer, // 'component': componentAnalyzer, 'data-access': dataAccessAnalyzer, 'react': reactAnalyzer, 'documentation': documentationAnalyzer, 'schema': schemaAnalyzer }; /** * Create an audit runner with the given options */ export function createAuditRunner(options = {}) { const analyzerRegistry = { ...DEFAULT_ANALYZERS }; /** * Register a custom analyzer */ function registerAnalyzer(analyzer) { analyzerRegistry[analyzer.name] = analyzer; } /** * Load configuration from file */ async function loadConfiguration(configPath) { const config = await loadConfig({ configPath }); return { ...options, ...config }; } /** * Run the audit */ async function run(runOptions) { const mergedOptions = { ...options, ...runOptions }; const startTime = Date.now(); // Report progress reportProgress(mergedOptions, { phase: 'discovery', message: 'Discovering files...' }); // Discover files const files = await discoverProjectFiles(mergedOptions); // Collect functions if enabled let collectedFunctions = []; const fileToFunctionsMap = new Map(); // Track functions per file for sync if (mergedOptions.indexFunctions) { reportProgress(mergedOptions, { phase: 'function-indexing', message: 'Collecting functions from files...' }); // Collect functions from TypeScript/JavaScript files // Note: Go files will be indexed by the Universal SOLID analyzer directly const scriptFiles = files.filter(f => f.endsWith('.ts') || f.endsWith('.tsx') || f.endsWith('.js') || f.endsWith('.jsx')); for (let i = 0; i < scriptFiles.length; i++) { try { const fileFunctions = await extractFunctionsFromFile(scriptFiles[i], { unusedImportsConfig: mergedOptions.unusedImportsConfig }); collectedFunctions.push(...fileFunctions); fileToFunctionsMap.set(scriptFiles[i], fileFunctions); // Store for sync reportProgress(mergedOptions, { phase: 'function-indexing', current: i + 1, total: scriptFiles.length, message: `Collected ${fileFunctions.length} items from ${scriptFiles[i]}` }); } catch (error) { // Log error but continue with other files console.warn(`Failed to extract functions from ${scriptFiles[i]}:`, error); } } } // Run analyzers const analyzerResults = {}; const enabledAnalyzers = getEnabledAnalyzers(mergedOptions, analyzerRegistry); console.log('[DEBUG] Enabled analyzers:', enabledAnalyzers); console.log('[DEBUG] Available analyzers in registry:', Object.keys(analyzerRegistry)); for (const analyzerName of enabledAnalyzers) { const analyzer = analyzerRegistry[analyzerName]; console.log(`[DEBUG] Processing analyzer: ${analyzerName}, found: ${!!analyzer}`); if (!analyzer) { console.warn(`Unknown analyzer: ${analyzerName}`); continue; } reportProgress(mergedOptions, { phase: 'analysis', analyzer: analyzerName, message: `Running ${analyzerName} analyzer...` }); console.log(`[DEBUG] About to call ${analyzerName}.analyze()`); console.log(`[DEBUG] Analyzer object:`, analyzer); console.log(`[DEBUG] Analyzer.analyze type:`, typeof analyzer.analyze); console.log(`[DEBUG] Files to analyze:`, files); console.log(`[DEBUG] Config for analyzer:`, mergedOptions.analyzerConfigs?.[analyzerName] || {}); try { const result = await analyzer.analyze(files, mergedOptions.analyzerConfigs?.[analyzerName] || {}, mergedOptions, (progress) => { reportProgress(mergedOptions, { phase: 'analysis', analyzer: analyzerName, current: progress.current, total: progress.total, message: `Analyzing ${progress.file}...` }); }); console.log(`[DEBUG] ${analyzerName}.analyze() returned:`, result); analyzerResults[analyzerName] = result; } catch (error) { reportError(mergedOptions, error, `${analyzerName} analyzer`); analyzerResults[analyzerName] = { violations: [], filesProcessed: 0, executionTime: 0, errors: [{ file: 'analyzer', error: error.message }] }; } } // Generate summary const summary = generateSummary(analyzerResults); // Create result const result = { timestamp: new Date(), summary, analyzerResults, recommendations: [], metadata: { auditDuration: Date.now() - startTime, filesAnalyzed: files.length, analyzersRun: enabledAnalyzers, configUsed: mergedOptions, ...(collectedFunctions.length > 0 && { collectedFunctions, fileToFunctionsMap: Object.fromEntries(fileToFunctionsMap) }) } }; // Report completion reportProgress(mergedOptions, { phase: 'reporting', message: 'Generating reports...' }); return result; } /** * Generate report in specified format */ async function generateReportForResult(result, format) { return generateReport(result, format); } return { registerAnalyzer, loadConfiguration, run, generateReport: generateReportForResult }; } /** * Discover files to analyze */ async function discoverProjectFiles(options) { const rootDir = options.projectRoot || process.cwd(); return discoverFiles(rootDir, { includePaths: options.includePaths, excludePaths: options.excludePaths, extensions: options.fileExtensions, // Use override if provided excludeDirs: undefined // This will use DEFAULT_EXCLUDED_DIRS which includes node_modules }); } /** * Get list of enabled analyzers */ function getEnabledAnalyzers(options, registry) { if (options.enabledAnalyzers && options.enabledAnalyzers.length > 0) { return options.enabledAnalyzers; } return Object.keys(registry); } /** * Generate audit summary */ function generateSummary(analyzerResults) { let totalViolations = 0; let criticalIssues = 0; let warnings = 0; let suggestions = 0; const violationsByCategory = {}; for (const [analyzer, result] of Object.entries(analyzerResults)) { for (const violation of result.violations) { totalViolations++; switch (violation.severity) { case 'critical': criticalIssues++; break; case 'warning': warnings++; break; case 'suggestion': suggestions++; break; } const category = violation.type || analyzer; violationsByCategory[category] = (violationsByCategory[category] || 0) + 1; } } return { totalFiles: 0, totalViolations, criticalIssues, warnings, suggestions, violationsByCategory, topIssues: [] }; } /** * Report progress */ function reportProgress(options, progress) { if (options.progressCallback) { options.progressCallback({ current: 0, total: 0, analyzer: '', ...progress }); } } /** * Report error */ function reportError(options, error, context) { if (options.errorCallback) { options.errorCallback(error, context); } else { console.error(`Error in ${context}:`, error); } } /** * Run audit with default runner (convenience function) */ export async function runAudit(options) { const runner = createAuditRunner(options); return runner.run(); } //# sourceMappingURL=auditRunner.js.map