UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

971 lines (821 loc) 28.2 kB
import { z } from 'zod'; import { BaseTool } from './base-tool.js'; import { promises as fs, existsSync } from 'fs'; import { join, extname, dirname, basename } from 'path'; import { exec } from 'child_process'; import { promisify } from 'util'; import { JsonValue, JsonObject, AnalysisResult, ToolArguments, ToolResult } from '../types.js'; const execAsync = promisify(exec); // Analysis result interfaces interface ComplexityResult { file: string; cyclomaticComplexity: number; cognitiveComplexity: number; maintainabilityIndex: number; linesOfCode: number; nestingDepth: number; error?: string; } interface DependencyAnalysis { packageInfo: JsonObject | null; dependencies: Record<string, string>; devDependencies: Record<string, string>; importGraph: Record<string, string[]>; unusedDependencies: string[]; outdatedDependencies: Array<{ name: string; current: string; latest: string; }>; } interface StructureAnalysis { directories: Set<string>; fileTypes: Record<string, number>; totalFiles: number; totalDirectories: number; averageFileSize: number; largestFiles: Array<{ file: string; size: number }>; deepestNesting: number; } interface QualityIssue { type: 'error' | 'warning' | 'info' | 'todo' | 'fixme' | 'deprecated'; severity: 'critical' | 'high' | 'medium' | 'low'; message: string; file: string; line: number; } interface QualityMetrics { overallScore: number; maintainabilityScore: number; readabilityScore: number; testCoverage: number; issues: QualityIssue[]; suggestions: string[]; } /** * Comprehensive Code Analysis Tool */ export class CodeAnalysisTool extends BaseTool { constructor(private agentContext: { workingDirectory: string }) { const parameters = z.object({ analysis: z.enum([ 'complexity', 'dependencies', 'structure', 'patterns', 'quality', 'security', 'performance', 'documentation', 'tests', 'refactor', ]), files: z .union([z.string(), z.array(z.string())]) .optional() .describe('Specific files to analyze'), includePatterns: z .array(z.string()) .optional() .describe('File patterns to include (e.g., ["*.ts", "*.js"])'), excludePatterns: z.array(z.string()).optional().describe('Patterns to exclude'), language: z .string() .optional() .describe('Programming language (auto-detect if not specified)'), depth: z.enum(['surface', 'detailed', 'comprehensive']).optional().default('detailed'), }); super({ name: 'analyzeCode', description: 'Comprehensive code analysis: complexity, dependencies, structure, patterns, quality', category: 'Code Analysis', parameters, }); } async execute(args: z.infer<typeof this.definition.parameters>): Promise<any> { try { switch (args.analysis) { case 'complexity': return await this.analyzeComplexity(args); case 'dependencies': return await this.analyzeDependencies(args); case 'structure': return await this.analyzeStructure(args); case 'patterns': return await this.analyzePatterns(args); case 'quality': return await this.analyzeQuality(args); case 'security': return await this.analyzeSecurity(args); case 'performance': return await this.analyzePerformance(args); case 'documentation': return await this.analyzeDocumentation(args); case 'tests': return await this.analyzeTests(args); case 'refactor': return await this.analyzeRefactorOpportunities(args); default: return { error: `Unknown analysis type: ${args.analysis}` }; } } catch (error) { return { error: `Code analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private async analyzeComplexity(args: Record<string, unknown>): Promise<Record<string, unknown>> { const files = await this.getFilesToAnalyze(args); const results = []; for (const file of files) { try { const content = await fs.readFile(file, 'utf-8'); const analysis = this.calculateComplexity(content, file); results.push({ file, ...analysis }); } catch (error) { results.push({ file, error: error instanceof Error ? error.message : 'Unknown error', }); } } return { analysis: 'complexity', results, summary: this.summarizeComplexity(results), }; } private calculateComplexity(content: string, file: string): Record<string, unknown> { const lines = content.split('\n'); const ext = extname(file); // Basic metrics const totalLines = lines.length; const codeLines = lines.filter( line => line.trim() && !line.trim().startsWith('//') && !line.trim().startsWith('*') && !line.trim().startsWith('/*') ).length; // Cyclomatic complexity indicators const complexityKeywords = [ 'if', 'else', 'while', 'for', 'switch', 'case', 'catch', 'try', '&&', '||', '?', ':', 'forEach', 'map', 'filter', 'reduce', ]; let cyclomaticComplexity = 1; // Base complexity const regex = new RegExp(`\\b(${complexityKeywords.join('|')})\\b`, 'g'); const matches = content.match(regex); if (matches) { cyclomaticComplexity += matches.length; } // Function/method count const functionRegex = /function\s+\w+|const\s+\w+\s*=\s*\(|\w+\s*:\s*\(|class\s+\w+/g; const functions = content.match(functionRegex) || []; // Nesting level const maxNesting = this.calculateMaxNesting(content); return { metrics: { totalLines, codeLines, cyclomaticComplexity, functions: functions.length, maxNesting, complexityPerFunction: functions.length > 0 ? cyclomaticComplexity / functions.length : 0, }, score: this.calculateComplexityScore(cyclomaticComplexity, totalLines, maxNesting), recommendations: this.getComplexityRecommendations( cyclomaticComplexity, maxNesting, functions.length ), }; } private calculateMaxNesting(content: string): number { let maxNesting = 0; let currentNesting = 0; for (const char of content) { if (char === '{') { currentNesting++; maxNesting = Math.max(maxNesting, currentNesting); } else if (char === '}') { currentNesting--; } } return maxNesting; } private async analyzeDependencies( args: Record<string, unknown> ): Promise<Record<string, unknown>> { try { const packageJsonPath = join(this.agentContext.workingDirectory, 'package.json'); let packageInfo = null; if (existsSync(packageJsonPath)) { const packageContent = await fs.readFile(packageJsonPath, 'utf-8'); packageInfo = JSON.parse(packageContent); } // Analyze import statements in code files const files = await this.getFilesToAnalyze(args); const importAnalysis = await this.analyzeImports(files); return { analysis: 'dependencies', packageDependencies: packageInfo ? { dependencies: Object.keys(packageInfo.dependencies || {}), devDependencies: Object.keys(packageInfo.devDependencies || {}), peerDependencies: Object.keys(packageInfo.peerDependencies || {}), totalDeps: Object.keys({ ...packageInfo.dependencies, ...packageInfo.devDependencies, }).length, } : null, codeImports: importAnalysis, recommendations: this.getDependencyRecommendations(packageInfo, importAnalysis), }; } catch (error) { return { error: `Dependency analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private async analyzeImports(files: string[]): Promise<any> { const imports = new Map<string, Set<string>>(); const localImports: string[] = []; const externalImports: string[] = []; for (const file of files) { try { const content = await fs.readFile(file, 'utf-8'); const fileImports = this.extractImports(content); imports.set(file, new Set(fileImports)); for (const imp of fileImports) { if (imp.startsWith('.') || imp.startsWith('/')) { localImports.push(imp); } else { externalImports.push(imp); } } } catch (error) { // Skip files that can't be read } } return { byFile: Object.fromEntries( Array.from(imports.entries()).map(([file, imps]) => [file, Array.from(imps)]) ), summary: { totalFiles: files.length, localImports: [...new Set(localImports)], externalImports: [...new Set(externalImports)], mostImported: this.getMostImported(Array.from(imports.values())), }, }; } private extractImports(content: string): string[] { const imports: string[] = []; // ES6 imports const es6Regex = /import\s+.*?\s+from\s+['"](.*?)['"];?/g; let match; while ((match = es6Regex.exec(content)) !== null) { imports.push(match[1]); } // CommonJS requires const cjsRegex = /require\s*\(\s*['"](.*?)['"];?\s*\)/g; while ((match = cjsRegex.exec(content)) !== null) { imports.push(match[1]); } // Dynamic imports const dynamicRegex = /import\s*\(\s*['"](.*?)['"];?\s*\)/g; while ((match = dynamicRegex.exec(content)) !== null) { imports.push(match[1]); } return imports; } private async analyzeStructure(args: Record<string, unknown>): Promise<Record<string, unknown>> { const files = await this.getFilesToAnalyze(args); const structure = { directories: new Set<string>(), fileTypes: new Map<string, number>(), totalFiles: files.length, largestFiles: [] as any[], deepestNesting: 0, }; const fileSizes: any[] = []; for (const file of files) { try { const stats = await fs.stat(file); const dir = dirname(file); const ext = extname(file) || 'no-extension'; structure.directories.add(dir); structure.fileTypes.set(ext, (structure.fileTypes.get(ext) || 0) + 1); const depth = file.split('/').length - 1; structure.deepestNesting = Math.max(structure.deepestNesting, depth); fileSizes.push({ file, size: stats.size, lines: 0, // Will be calculated if needed }); } catch (error) { // Skip files that can't be accessed } } // Sort and get largest files fileSizes.sort((a, b) => b.size - a.size); structure.largestFiles = fileSizes.slice(0, 10); return { analysis: 'structure', structure: { ...structure, directories: Array.from(structure.directories), fileTypes: Object.fromEntries(structure.fileTypes), }, recommendations: this.getStructureRecommendations(structure), }; } private async analyzeQuality(args: Record<string, unknown>): Promise<Record<string, unknown>> { const files = await this.getFilesToAnalyze(args); const qualityMetrics = { overallScore: 0, issues: [] as QualityIssue[], suggestions: [] as string[], }; for (const file of files) { try { const content = await fs.readFile(file, 'utf-8'); const fileQuality = await this.assessFileQuality(content, file); qualityMetrics.issues.push(...fileQuality.issues); qualityMetrics.suggestions.push(...fileQuality.suggestions); } catch (error) { qualityMetrics.issues.push({ file, type: 'error', severity: 'high', line: 0, message: `Could not analyze file: ${error instanceof Error ? error.message : 'Unknown error'}`, }); } } qualityMetrics.overallScore = this.calculateQualityScore(qualityMetrics.issues); return { analysis: 'quality', metrics: qualityMetrics, recommendations: this.getQualityRecommendations(qualityMetrics), }; } private async assessFileQuality(content: string, file: string): Promise<any> { const issues: any[] = []; const suggestions: string[] = []; const lines = content.split('\n'); // Check for common quality issues // Long lines lines.forEach((line, index) => { if (line.length > 120) { issues.push({ file, line: index + 1, type: 'long_line', message: `Line too long (${line.length} characters)`, }); } }); // TODO comments const todoRegex = /\/\/\s*TODO|\/\*\s*TODO|#\s*TODO/gi; let match; while ((match = todoRegex.exec(content)) !== null) { const lineNum = content.substring(0, match.index).split('\n').length; issues.push({ file, line: lineNum, type: 'todo', message: 'TODO comment found', }); } // Console.log statements (in production code) const consoleRegex = /console\.(log|debug|info|warn|error)/g; while ((match = consoleRegex.exec(content)) !== null) { const lineNum = content.substring(0, match.index).split('\n').length; issues.push({ file, line: lineNum, type: 'console_log', message: 'Console statement found (consider using proper logging)', }); } // Large functions (rough heuristic) const functionRegex = /function\s+\w+\s*\([^)]*\)\s*\{|const\s+\w+\s*=\s*\([^)]*\)\s*=>\s*\{/g; while ((match = functionRegex.exec(content)) !== null) { const startIndex = match.index; const endIndex = this.findClosingBrace(content, startIndex + match[0].length - 1); if (endIndex !== -1) { const functionContent = content.substring(startIndex, endIndex); const functionLines = functionContent.split('\n').length; if (functionLines > 50) { const lineNum = content.substring(0, startIndex).split('\n').length; issues.push({ file, line: lineNum, type: 'large_function', message: `Function is very large (${functionLines} lines)`, }); } } } // Missing error handling if (content.includes('async ') && !content.includes('try') && !content.includes('catch')) { suggestions.push('Consider adding error handling for async operations'); } return { issues, suggestions }; } private findClosingBrace(content: string, startIndex: number): number { let braceCount = 1; for (let i = startIndex + 1; i < content.length; i++) { if (content[i] === '{') braceCount++; else if (content[i] === '}') braceCount--; if (braceCount === 0) return i; } return -1; } private async getFilesToAnalyze(args: Record<string, unknown>): Promise<string[]> { if (args.files) { if (Array.isArray(args.files)) { return args.files.filter((f): f is string => typeof f === 'string'); } else if (typeof args.files === 'string') { return [args.files]; } } // Default file patterns for code analysis const defaultPatterns = [ '**/*.ts', '**/*.js', '**/*.jsx', '**/*.tsx', '**/*.py', '**/*.java', '**/*.cpp', '**/*.c', ]; const patterns = Array.isArray(args.includePatterns) ? args.includePatterns.filter((p): p is string => typeof p === 'string') : defaultPatterns; // Use glob to find files (simplified implementation) const files: string[] = []; try { const { glob } = await import('glob'); for (const pattern of patterns) { const matches = await glob(pattern, { cwd: this.agentContext.workingDirectory, ignore: args.excludePatterns || ['node_modules/**', 'dist/**', 'build/**', '.git/**'], }); files.push(...matches); } } catch (error) { // Fallback to reading directory return []; } return [...new Set(files)]; } // Helper methods for calculations and recommendations private calculateComplexityScore(complexity: number, lines: number, nesting: number): number { const complexityScore = Math.max(0, 100 - complexity * 2); const lineScore = Math.max(0, 100 - lines / 10); const nestingScore = Math.max(0, 100 - nesting * 10); return Math.round((complexityScore + lineScore + nestingScore) / 3); } private getComplexityRecommendations( complexity: number, nesting: number, functions: number ): string[] { const recommendations: string[] = []; if (complexity > 15) { recommendations.push('Consider breaking down complex functions'); } if (nesting > 4) { recommendations.push('Reduce nesting levels for better readability'); } if (functions === 0) { recommendations.push('Consider organizing code into functions'); } return recommendations; } private summarizeComplexity(results: Array<Record<string, unknown>>): Record<string, unknown> { const validResults = results.filter(r => !r.error); if (validResults.length === 0) { return { error: 'No files could be analyzed' }; } const totalComplexity = validResults.reduce( (sum, r) => sum + (r.metrics && typeof r.metrics === 'object' && 'cyclomaticComplexity' in r.metrics && typeof r.metrics.cyclomaticComplexity === 'number' ? r.metrics.cyclomaticComplexity : 0), 0 ); const avgComplexity = totalComplexity / validResults.length; const maxComplexity = Math.max( ...validResults.map(r => r.metrics && typeof r.metrics === 'object' && 'cyclomaticComplexity' in r.metrics && typeof r.metrics.cyclomaticComplexity === 'number' ? r.metrics.cyclomaticComplexity : 0 ) ); return { filesAnalyzed: validResults.length, averageComplexity: Math.round(avgComplexity * 100) / 100, maxComplexity, overallScore: Math.round( validResults.reduce( (sum, r) => sum + (r.score && typeof r.score === 'number' ? r.score : 0), 0 ) / validResults.length ), }; } private getDependencyRecommendations(packageInfo: any, importAnalysis: any): string[] { const recommendations: string[] = []; if (packageInfo && packageInfo.dependencies) { const depCount = Object.keys(packageInfo.dependencies).length; if (depCount > 50) { recommendations.push('Consider reducing the number of dependencies'); } } if ( importAnalysis.summary.externalImports.length > importAnalysis.summary.localImports.length * 2 ) { recommendations.push('High external dependency usage - consider code organization'); } return recommendations; } private getMostImported(importSets: Set<string>[]): string[] { const importCounts = new Map<string, number>(); for (const importSet of importSets) { for (const imp of importSet) { importCounts.set(imp, (importCounts.get(imp) || 0) + 1); } } return Array.from(importCounts.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([imp]) => imp); } private getStructureRecommendations(structure: any): string[] { const recommendations: string[] = []; if (structure.deepestNesting > 5) { recommendations.push('Consider flattening the directory structure'); } if (structure.totalFiles > 100 && structure.directories.size < 5) { recommendations.push('Consider organizing files into more directories'); } return recommendations; } private calculateQualityScore(issues: any[]): number { const severityWeights = { critical: -20, high: -10, medium: -5, low: -2, info: -1, }; let score = 100; for (const issue of issues) { const weight = severityWeights[issue.severity as keyof typeof severityWeights] || -1; score += weight; } return Math.max(0, score); } private getQualityRecommendations(metrics: any): string[] { const recommendations: string[] = []; if (metrics.overallScore < 70) { recommendations.push('Code quality needs improvement'); } const todoCount = metrics.issues.filter((i: any) => i.type === 'todo').length; if (todoCount > 10) { recommendations.push('Consider addressing TODO comments'); } return recommendations; } // Placeholder implementations for remaining analysis types private async analyzePatterns(args: any): Promise<any> { return { analysis: 'patterns', message: 'Pattern analysis implementation in progress' }; } private async analyzeSecurity(args: any): Promise<any> { return { analysis: 'security', message: 'Security analysis implementation in progress' }; } private async analyzePerformance(args: any): Promise<any> { return { analysis: 'performance', message: 'Performance analysis implementation in progress' }; } private async analyzeDocumentation(args: any): Promise<any> { return { analysis: 'documentation', message: 'Documentation analysis implementation in progress', }; } private async analyzeTests(args: any): Promise<any> { return { analysis: 'tests', message: 'Test analysis implementation in progress' }; } private async analyzeRefactorOpportunities(args: any): Promise<any> { return { analysis: 'refactor', message: 'Refactor analysis implementation in progress' }; } } /** * Code Generation Tool */ export class CodeGenerationTool extends BaseTool { constructor(private agentContext: { workingDirectory: string }) { const parameters = z.object({ task: z.enum([ 'function', 'class', 'component', 'module', 'test', 'documentation', 'api', 'schema', 'config', 'script', ]), language: z.string().describe('Programming language (e.g., typescript, javascript, python)'), name: z.string().describe('Name of the item to generate'), description: z.string().describe('Description of what to generate'), requirements: z.array(z.string()).optional().describe('Specific requirements'), style: z .enum(['functional', 'object-oriented', 'minimal', 'comprehensive']) .optional() .default('comprehensive'), dependencies: z.array(z.string()).optional().describe('Dependencies to include'), outputPath: z.string().optional().describe('Where to save the generated code'), }); super({ name: 'generateCode', description: 'Generate code: functions, classes, components, tests, documentation, APIs', category: 'Code Generation', parameters, }); } async execute(args: z.infer<typeof this.definition.parameters>): Promise<any> { try { switch (args.task) { case 'function': return this.generateFunction(args); case 'class': return this.generateClass(args); case 'component': return this.generateComponent(args); case 'module': return this.generateModule(args); case 'test': return this.generateTest(args); case 'documentation': return this.generateDocumentation(args); case 'api': return this.generateAPI(args); case 'schema': return this.generateSchema(args); case 'config': return this.generateConfig(args); case 'script': return this.generateScript(args); default: return { error: `Unknown generation task: ${args.task}` }; } } catch (error) { return { error: `Code generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private generateFunction(args: any): any { const templates = { typescript: this.generateTypeScriptFunction(args), javascript: this.generateJavaScriptFunction(args), python: this.generatePythonFunction(args), }; const code = templates[args.language as keyof typeof templates] || templates.typescript; return { task: 'function', name: args.name, language: args.language, code, description: args.description, suggestions: this.getFunctionSuggestions(args), }; } private generateTypeScriptFunction(args: any): string { const params = args.requirements?.join(', ') || 'params: any'; return `/** * ${args.description} */ export function ${args.name}(${params}): any { // TODO: Implement ${args.description.toLowerCase()} try { // Implementation goes here return result; } catch (error) { throw new Error(\`Failed to ${args.name}: \${error instanceof Error ? error.message : 'Unknown error'}\`); } }`; } private generateJavaScriptFunction(args: any): string { const params = args.requirements?.join(', ') || 'params'; return `/** * ${args.description} */ function ${args.name}(${params}) { // TODO: Implement ${args.description.toLowerCase()} try { // Implementation goes here return result; } catch (error) { throw new Error(\`Failed to ${args.name}: \${error.message}\`); } } module.exports = { ${args.name} };`; } private generatePythonFunction(args: any): string { const params = args.requirements?.join(', ') || 'params'; return `def ${args.name}(${params}): """ ${args.description} Args: ${params}: Parameters for the function Returns: Result of the operation Raises: Exception: If the operation fails """ try: # TODO: Implement ${args.description.toLowerCase()} # Implementation goes here return result except Exception as error: raise Exception(f"Failed to ${args.name}: {str(error)}")`; } private generateClass(args: any): any { // Similar pattern for class generation return { task: 'class', name: args.name, language: args.language, code: `// Generated ${args.name} class\n// TODO: Implement class structure`, description: args.description, }; } // Placeholder implementations for other generation types private generateComponent(args: any): any { return { task: 'component', message: 'Component generation implementation in progress' }; } private generateModule(args: any): any { return { task: 'module', message: 'Module generation implementation in progress' }; } private generateTest(args: any): any { return { task: 'test', message: 'Test generation implementation in progress' }; } private generateDocumentation(args: any): any { return { task: 'documentation', message: 'Documentation generation implementation in progress', }; } private generateAPI(args: any): any { return { task: 'api', message: 'API generation implementation in progress' }; } private generateSchema(args: any): any { return { task: 'schema', message: 'Schema generation implementation in progress' }; } private generateConfig(args: any): any { return { task: 'config', message: 'Config generation implementation in progress' }; } private generateScript(args: any): any { return { task: 'script', message: 'Script generation implementation in progress' }; } private getFunctionSuggestions(args: any): string[] { return [ 'Add input validation', 'Include error handling', 'Add unit tests', 'Document parameters and return values', ]; } }