UNPKG

woaru

Version:

Universal Project Setup Autopilot - Analyze and automatically configure development tools for ANY programming language

356 lines 14.8 kB
import { EventEmitter } from 'events'; import fs from 'fs-extra'; import * as path from 'path'; import * as yaml from 'js-yaml'; import { CodeAnalyzer } from '../analyzer/CodeAnalyzer.js'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import { triggerHook, } from '../core/HookSystem.js'; // ES module compatibility const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export class ToolRecommendationEngine extends EventEmitter { toolDefinitions = new Map(); codeAnalyzer; toolsPath; constructor() { super(); this.codeAnalyzer = new CodeAnalyzer(); this.toolsPath = path.join(__dirname, '../../tools'); } async initialize() { await this.loadToolDefinitions(); } async loadToolDefinitions() { const toolFiles = await fs.readdir(this.toolsPath); for (const file of toolFiles) { if (file.endsWith('.yaml') || file.endsWith('.yml')) { try { const content = await fs.readFile(path.join(this.toolsPath, file), 'utf-8'); const definitions = yaml.load(content); Object.entries(definitions.tools).forEach(([name, def]) => { this.toolDefinitions.set(name, { ...def, name }); }); } catch (error) { console.error(`Failed to load tool definitions from ${file}:`, error); } } } } async getRecommendations(state) { const startTime = new Date(); // 🪝 HOOK: beforeToolExecution - KI-freundliche Regelwelt const beforeData = { toolName: 'tool-recommendation-engine', filePath: state.projectPath, command: 'generate-recommendations', timestamp: startTime, }; try { await triggerHook('beforeToolExecution', beforeData); } catch (hookError) { console.debug(`Hook error (beforeToolExecution recommendation): ${hookError}`); } try { const recommendations = []; // Get language and framework specific tools const relevantTools = this.getRelevantTools(state.language, state.frameworks); // Check each tool for (const tool of relevantTools) { if (state.detectedTools.has(tool.name)) { continue; // Tool already installed } const recommendation = await this.evaluateTool(tool, state); if (recommendation) { recommendations.push(recommendation); } } // Sort by priority const finalRecommendations = this.prioritizeRecommendations(recommendations); // 🪝 HOOK: afterToolExecution - KI-freundliche Regelwelt const afterData = { toolName: 'tool-recommendation-engine', filePath: state.projectPath, command: 'generate-recommendations', output: `Generated ${finalRecommendations.length} recommendations`, exitCode: 0, success: true, duration: new Date().getTime() - startTime.getTime(), timestamp: new Date(), }; try { await triggerHook('afterToolExecution', afterData); } catch (hookError) { console.debug(`Hook error (afterToolExecution recommendation): ${hookError}`); } return finalRecommendations; } catch (error) { // 🪝 HOOK: onError - KI-freundliche Regelwelt const errorData = { error: error instanceof Error ? error : new Error(String(error)), context: 'tool-recommendation-generation', filePath: state.projectPath, timestamp: new Date(), }; try { await triggerHook('onError', errorData); } catch (hookError) { console.debug(`Hook error (onError recommendation): ${hookError}`); } throw error; } } getRelevantTools(language, frameworks) { const relevant = []; this.toolDefinitions.forEach(tool => { // Check language match const langMatch = tool.languages.includes(language) || tool.languages.includes('*'); // Check framework match const frameworkMatch = !tool.frameworks || tool.frameworks.some(f => frameworks.includes(f)); if (langMatch && frameworkMatch && !tool.deprecated) { relevant.push(tool); } }); return relevant; } async evaluateTool(tool, state) { const evidence = []; let highestPriority = 'low'; let reason = ''; // Check detection patterns for (const pattern of tool.detectPatterns) { const detected = await this.checkPattern(pattern, state); if (detected.found) { evidence.push(...detected.evidence); // Update priority const patternPriority = pattern.severity || 'medium'; if (this.comparePriority(patternPriority, highestPriority) > 0) { highestPriority = patternPriority; } // Build reason if (pattern.message) { reason = pattern.message; } else { reason = this.buildReasonFromPattern(pattern, detected.evidence); } } } if (evidence.length === 0) { return null; } // Get setup command for current package manager const setupCommand = this.getSetupCommand(tool, state.projectPath); return { tool: tool.name, reason, priority: tool.priority || highestPriority, evidence, autoFixable: !!setupCommand, setupCommand, category: tool.category, }; } async checkPattern(pattern, state) { const evidence = []; switch (pattern.type) { case 'missing_config': { const configPath = path.join(state.projectPath, pattern.pattern); const exists = await fs.pathExists(configPath); if (!exists) { evidence.push({ file: pattern.pattern, pattern: 'missing_config', }); } break; } case 'code_smell': { // Use code analyzer to find patterns const insights = await this.codeAnalyzer.analyzeCodebase(state.projectPath, state.language); // Check if this tool would help with found issues insights.forEach((insight, toolName) => { if (toolName === pattern.pattern || (insight.patterns && insight.patterns.includes(pattern.pattern))) { evidence.push(...insight.evidence.map(e => ({ file: e.split(':')[0], line: parseInt(e.split(':')[1]) || undefined, snippet: e, pattern: pattern.pattern, }))); } }); break; } case 'missing_dependency': { // Check if dependency is installed if (!state.detectedTools.has(pattern.pattern)) { evidence.push({ file: 'package.json', pattern: `missing_dependency:${pattern.pattern}`, }); } break; } case 'file_pattern': { // Check for files matching pattern const files = await this.findFiles(state.projectPath, pattern.pattern); if (files.length > 0) { files.forEach(file => { evidence.push({ file, pattern: pattern.pattern, }); }); } break; } } return { found: evidence.length > 0, evidence }; } buildReasonFromPattern(pattern, evidence) { switch (pattern.type) { case 'missing_config': return `Configuration file ${pattern.pattern} not found`; case 'code_smell': { const count = evidence.length; return `Found ${count} ${pattern.pattern} patterns in your code`; } case 'missing_dependency': return `Dependency ${pattern.pattern} is recommended but not installed`; case 'file_pattern': return `Found files matching ${pattern.pattern} that could benefit from this tool`; default: return `Detected pattern: ${pattern.pattern}`; } } getSetupCommand(tool, projectPath) { // Detect package manager const packageManager = this.detectPackageManager(projectPath); const instruction = tool.setupInstructions.find(inst => inst.packageManager === packageManager); return instruction?.command; } detectPackageManager(projectPath) { // Simple detection - could be enhanced if (fs.existsSync(path.join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm'; if (fs.existsSync(path.join(projectPath, 'yarn.lock'))) return 'yarn'; if (fs.existsSync(path.join(projectPath, 'package-lock.json'))) return 'npm'; if (fs.existsSync(path.join(projectPath, 'requirements.txt'))) return 'pip'; if (fs.existsSync(path.join(projectPath, 'Cargo.toml'))) return 'cargo'; if (fs.existsSync(path.join(projectPath, 'go.mod'))) return 'go'; return 'npm'; // default } async findFiles(_projectPath, _pattern) { // Simplified file finding - would use glob in production const files = []; // Implementation would scan for files matching pattern return files; } prioritizeRecommendations(recommendations) { const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 }; return recommendations.sort((a, b) => { return priorityOrder[a.priority] - priorityOrder[b.priority]; }); } comparePriority(a, b) { const priorityValue = { critical: 4, high: 3, medium: 2, low: 1 }; return priorityValue[a] - priorityValue[b]; } async checkSingleFile(filePath, state) { const startTime = new Date(); // 🪝 HOOK: beforeToolExecution - KI-freundliche Regelwelt (Single File) const beforeData = { toolName: 'tool-recommendation-single-file', filePath: filePath, command: 'check-single-file', timestamp: startTime, }; try { await triggerHook('beforeToolExecution', beforeData); } catch (hookError) { console.debug(`Hook error (beforeToolExecution single file): ${hookError}`); } try { // Quick check for single file changes const recommendations = []; // Check if this file triggers any tool recommendations const relevantTools = this.getRelevantTools(state.language, state.frameworks); for (const tool of relevantTools) { if (state.detectedTools.has(tool.name)) continue; // Quick pattern check for this file only for (const pattern of tool.detectPatterns) { if (pattern.type === 'code_smell') { // Analyze just this file const fileContent = await fs.readFile(filePath, 'utf-8'); // Simple pattern matching - would be more sophisticated if (fileContent.includes(pattern.pattern)) { recommendations.push({ tool: tool.name, reason: `Found ${pattern.pattern} in ${path.basename(filePath)}`, priority: pattern.severity || 'medium', evidence: [ { file: filePath, pattern: pattern.pattern, }, ], autoFixable: true, setupCommand: this.getSetupCommand(tool, state.projectPath), category: tool.category, }); break; } } } } // 🪝 HOOK: afterToolExecution - KI-freundliche Regelwelt (Single File) const afterData = { toolName: 'tool-recommendation-single-file', filePath: filePath, command: 'check-single-file', output: `Checked ${path.basename(filePath)}, found ${recommendations.length} recommendations`, exitCode: 0, success: true, duration: new Date().getTime() - startTime.getTime(), timestamp: new Date(), }; try { await triggerHook('afterToolExecution', afterData); } catch (hookError) { console.debug(`Hook error (afterToolExecution single file): ${hookError}`); } return recommendations; } catch (error) { // 🪝 HOOK: onError - KI-freundliche Regelwelt (Single File) const errorData = { error: error instanceof Error ? error : new Error(String(error)), context: 'single-file-recommendation', filePath: filePath, timestamp: new Date(), }; try { await triggerHook('onError', errorData); } catch (hookError) { console.debug(`Hook error (onError single file): ${hookError}`); } throw error; } } } //# sourceMappingURL=ToolRecommendationEngine.js.map