woaru
Version:
Universal Project Setup Autopilot - Analyze and automatically configure development tools for ANY programming language
356 lines • 14.8 kB
JavaScript
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