UNPKG

woaru

Version:

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

652 lines 29.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.WOARUSupervisor = void 0; const events_1 = require("events"); const path = __importStar(require("path")); const StateManager_1 = require("./StateManager"); const FileWatcher_1 = require("./FileWatcher"); const NotificationManager_1 = require("./NotificationManager"); const ToolRecommendationEngine_1 = require("./ToolRecommendationEngine"); const ProjectAnalyzer_1 = require("../analyzer/ProjectAnalyzer"); const LanguageDetector_1 = require("../analyzer/LanguageDetector"); const QualityRunner_1 = require("../quality/QualityRunner"); const ProductionReadinessAuditor_1 = require("../auditor/ProductionReadinessAuditor"); const ToolsDatabaseManager_1 = require("../database/ToolsDatabaseManager"); class WOARUSupervisor extends events_1.EventEmitter { constructor(projectPath, config = {}) { super(); this.isRunning = false; this.lastRecommendationCheck = new Date(); this.lastSecurityCheck = new Date(); this.cleanupHandlers = []; this.lastSecurityFindings = []; this.projectPath = path.resolve(projectPath); this.config = this.mergeConfig(config); // Initialize components this.stateManager = new StateManager_1.StateManager(this.projectPath); this.fileWatcher = new FileWatcher_1.FileWatcher(this.projectPath); this.notificationManager = new NotificationManager_1.NotificationManager(this.config.notifications); this.toolEngine = new ToolRecommendationEngine_1.ToolRecommendationEngine(); this.projectAnalyzer = new ProjectAnalyzer_1.ProjectAnalyzer(); this.languageDetector = new LanguageDetector_1.LanguageDetector(); this.qualityRunner = new QualityRunner_1.QualityRunner(this.notificationManager); this.productionAuditor = new ProductionReadinessAuditor_1.ProductionReadinessAuditor(this.projectPath); this.databaseManager = new ToolsDatabaseManager_1.ToolsDatabaseManager(); this.setupEventListeners(); } mergeConfig(config) { return { autoFix: false, autoSetup: false, notifications: { terminal: true, desktop: false, webhook: undefined, ...config.notifications, }, ignoreTools: [], watchPatterns: ['**/*'], ignorePatterns: [ '**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/.next/**', ], dashboard: false, ...config, }; } setupEventListeners() { // File watcher events const handleBatchChanges = (changes) => { this.handleFileChanges(changes); }; this.fileWatcher.on('batch_changes', handleBatchChanges); this.cleanupHandlers.push(() => this.fileWatcher.off('batch_changes', handleBatchChanges)); const handleCriticalFileChange = (change) => { this.handleCriticalFileChange(change); }; this.fileWatcher.on('critical_file_change', handleCriticalFileChange); this.cleanupHandlers.push(() => this.fileWatcher.off('critical_file_change', handleCriticalFileChange)); const handleFileWatcherError = (error) => { this.notificationManager.showError(`File watcher error: ${error.message}`); }; this.fileWatcher.on('error', handleFileWatcherError); this.cleanupHandlers.push(() => this.fileWatcher.off('error', handleFileWatcherError)); // State manager events const handleLanguageChanged = (language) => { this.notificationManager.showProgress(`Language detected: ${language}`); this.checkRecommendations(); }; this.stateManager.on('language_changed', handleLanguageChanged); this.cleanupHandlers.push(() => this.stateManager.off('language_changed', handleLanguageChanged)); const handleToolDetected = (tool) => { this.notificationManager.showSuccess(`Tool detected: ${tool}`); }; this.stateManager.on('tool_detected', handleToolDetected); this.cleanupHandlers.push(() => this.stateManager.off('tool_detected', handleToolDetected)); const handleCriticalIssues = (issues) => { this.notificationManager.notifyIssues(issues); }; this.stateManager.on('critical_issues', handleCriticalIssues); this.cleanupHandlers.push(() => this.stateManager.off('critical_issues', handleCriticalIssues)); const handleHealthScoreUpdated = (score) => { if (this.config.dashboard) { this.notificationManager.showHealthScore(score); } }; this.stateManager.on('health_score_updated', handleHealthScoreUpdated); this.cleanupHandlers.push(() => this.stateManager.off('health_score_updated', handleHealthScoreUpdated)); } async start() { if (this.isRunning) { throw new Error('Supervisor is already running'); } try { this.notificationManager.showProgress('Starting WOARU Supervisor...'); // Initialize components await this.toolEngine.initialize(); await this.stateManager.load(); // Start background database updates this.databaseManager.startBackgroundUpdates().catch(() => { // Silently ignore database update errors during startup }); // Perform initial analysis await this.performInitialAnalysis(); // Start file watching (non-blocking) this.fileWatcher.start(); // Start auto-save this.stateManager.startAutoSave(); // Start periodic recommendation checks this.startRecommendationChecks(); // Start security monitoring this.startSecurityMonitoring(); this.isRunning = true; // Show success immediately - file watcher will notify when ready this.notificationManager.showSuccess(`WOARU Supervisor started for ${path.basename(this.projectPath)}`); this.emit('started'); // Run initial checks asynchronously (don't block startup) this.checkRecommendations().catch(error => this.notificationManager.showError(`Initial recommendations failed: ${error}`)); this.runProductionAudit().catch(error => this.notificationManager.showError(`Initial production audit failed: ${error}`)); } catch (error) { this.notificationManager.showError(`Failed to start supervisor: ${error}`); throw error; } } async stop() { if (!this.isRunning) { return; } try { // Stop all intervals this.stopRecommendationChecks(); this.stopSecurityMonitoring(); // Stop components this.fileWatcher.stop(); this.stateManager.stopAutoSave(); // Clean up event listeners this.cleanupEventListeners(); // Save final state await this.stateManager.save(); // Destroy state manager await this.stateManager.destroy(); this.isRunning = false; this.notificationManager.showSuccess('WOARU Supervisor stopped'); this.emit('stopped'); } catch (error) { this.notificationManager.showError(`Error during supervisor shutdown: ${error}`); throw error; } } async performInitialAnalysis() { this.notificationManager.showProgress('Analyzing project...'); try { // Detect language and frameworks const language = await this.languageDetector.detectPrimaryLanguage(this.projectPath); const frameworks = await this.languageDetector.detectFrameworks(this.projectPath, language); // Analyze project structure const analysis = await this.projectAnalyzer.analyzeProject(this.projectPath); // Update state this.stateManager.updateLanguage(language); this.stateManager.updateFrameworks(frameworks); // Detect existing tools this.detectExistingTools(analysis); this.notificationManager.showSuccess(`Project analyzed: ${language} ${frameworks.length > 0 ? `(${frameworks.join(', ')})` : ''}`); } catch (error) { this.notificationManager.showError(`Analysis failed: ${error}`); } } detectExistingTools(analysis) { // Check package.json for tools const toolPatterns = { eslint: /eslint/, prettier: /prettier/, jest: /jest/, typescript: /typescript/, husky: /husky/, black: /black/, ruff: /ruff/, pytest: /pytest/, clippy: /clippy/, 'cargo-audit': /cargo-audit/, }; const allDeps = [ ...(analysis.dependencies || []), ...(analysis.devDependencies || []), ].join(' '); Object.entries(toolPatterns).forEach(([tool, pattern]) => { if (pattern.test(allDeps)) { this.stateManager.addDetectedTool(tool); } }); // Check for config files const configTools = { '.eslintrc': 'eslint', '.prettierrc': 'prettier', 'jest.config': 'jest', 'tsconfig.json': 'typescript', '.husky': 'husky', 'pyproject.toml': 'black', 'mypy.ini': 'mypy', 'rustfmt.toml': 'rustfmt', }; (analysis.configFiles || []).forEach((file) => { Object.entries(configTools).forEach(([pattern, tool]) => { if (file.includes(pattern)) { this.stateManager.addDetectedTool(tool); } }); }); } async handleFileChanges(changes) { // Apply changes to state changes.forEach(change => { this.stateManager.applyFileChange(change); }); // Check for package definition changes for security scanning const packageFiles = [ 'package.json', 'package-lock.json', 'pyproject.toml', 'requirements.txt', 'Gemfile', 'go.mod', ]; const hasPackageChanges = changes.some(change => packageFiles.some(pkgFile => change.path.endsWith(pkgFile))); // Check for new recommendations based on changes const fileSpecificRecommendations = []; for (const change of changes) { if (change.type === 'change') { // Run immediate quality checks await this.qualityRunner.runChecksOnFileChange(path.join(this.projectPath, change.path)); // Get recommendations for the file const recommendations = await this.toolEngine.checkSingleFile(path.join(this.projectPath, change.path), this.stateManager.getState()); fileSpecificRecommendations.push(...recommendations); } } if (fileSpecificRecommendations.length > 0) { await this.notificationManager.notifyRecommendations(fileSpecificRecommendations); } // Run security check asynchronously if package files changed or critical code files const securityTriggerFiles = [ ...packageFiles, '.env', '.env.example', 'docker-compose.yml', 'Dockerfile', ]; const hasSecurityTriggers = changes.some(change => securityTriggerFiles.some(secFile => change.path.endsWith(secFile)) || change.path.includes('auth') || change.path.includes('secret') || change.path.includes('key') || change.path.includes('password')); if (hasPackageChanges || hasSecurityTriggers) { this.runBackgroundSecurityCheck(changes.map(c => path.join(this.projectPath, c.path))).catch(error => { console.error('Background security check failed:', error); }); } // Note: Periodic checks are now handled by dedicated interval } async handleCriticalFileChange(change) { this.notificationManager.showProgress(`Critical file changed: ${change.path}`); // Immediate re-analysis for critical files if (change.path === 'package.json' || change.path.includes('config')) { await this.performInitialAnalysis(); // Run production-readiness audit on package.json changes await this.runProductionAudit(); } } async checkRecommendations() { try { const state = this.stateManager.getState(); const recommendations = await this.toolEngine.getRecommendations(state); // Filter out ignored tools const filteredRecommendations = recommendations.filter(rec => !this.config.ignoreTools.includes(rec.tool)); if (filteredRecommendations.length > 0) { await this.notificationManager.notifyRecommendations(filteredRecommendations); // Auto-setup if enabled if (this.config.autoSetup) { await this.autoSetupTools(filteredRecommendations); } } this.lastRecommendationCheck = new Date(); } catch (error) { this.notificationManager.showError(`Recommendation check failed: ${error}`); } } async autoSetupTools(recommendations) { const autoFixable = recommendations.filter(r => r.autoFixable && r.setupCommand); if (autoFixable.length === 0) return; this.notificationManager.showProgress(`Auto-setting up ${autoFixable.length} tools...`); for (const rec of autoFixable) { try { if (rec.setupCommand) { // Would execute setup command here this.notificationManager.showProgress(`Setting up ${rec.tool}...`); // await exec(rec.setupCommand, { cwd: this.projectPath }); this.stateManager.addDetectedTool(rec.tool); this.notificationManager.showSuccess(`${rec.tool} setup completed`); } } catch (error) { this.notificationManager.showError(`Failed to setup ${rec.tool}: ${error}`); } } } // Public methods getStatus() { return { isRunning: this.isRunning, state: this.stateManager.getState(), watchedFiles: this.fileWatcher.getWatchedFileCount(), config: this.config, }; } async getCurrentRecommendations() { const state = this.stateManager.getState(); const recommendations = await this.toolEngine.getRecommendations(state); return recommendations.filter(rec => !this.config.ignoreTools.includes(rec.tool)); } addIgnoredTool(tool) { if (!this.config.ignoreTools.includes(tool)) { this.config.ignoreTools.push(tool); this.notificationManager.showSuccess(`Tool ${tool} added to ignore list`); } } removeIgnoredTool(tool) { const index = this.config.ignoreTools.indexOf(tool); if (index !== -1) { this.config.ignoreTools.splice(index, 1); this.notificationManager.showSuccess(`Tool ${tool} removed from ignore list`); } } startRecommendationChecks() { // Start periodic recommendation checks every 5 minutes this.recommendationInterval = setInterval(async () => { try { await this.checkRecommendations(); } catch (error) { this.notificationManager.showError(`Periodic recommendation check failed: ${error}`); } }, 5 * 60 * 1000); } stopRecommendationChecks() { if (this.recommendationInterval) { clearInterval(this.recommendationInterval); this.recommendationInterval = undefined; } } startSecurityMonitoring() { // Start periodic security checks every 15 minutes this.securityInterval = setInterval(async () => { try { await this.runPeriodicSecurityCheck(); } catch (error) { this.notificationManager.showError(`Periodic security check failed: ${error}`); } }, 15 * 60 * 1000 // 15 minutes ); } stopSecurityMonitoring() { if (this.securityInterval) { clearInterval(this.securityInterval); this.securityInterval = undefined; } } cleanupEventListeners() { // Execute all cleanup handlers this.cleanupHandlers.forEach(cleanup => { try { cleanup(); } catch (error) { console.warn('Error during event listener cleanup:', error); } }); this.cleanupHandlers = []; // Remove all listeners from this instance this.removeAllListeners(); } async runProductionAudit() { try { const state = this.stateManager.getState(); // Determine project type based on dependencies const auditConfig = { language: state.language, frameworks: state.frameworks, projectType: this.determineProjectType(state), }; const audits = await this.productionAuditor.auditProject(auditConfig); if (audits.length > 0) { await this.notificationManager.notifyProductionAudits(audits); } } catch (error) { this.notificationManager.showError(`Production audit failed: ${error}`); } } determineProjectType(state) { const frameworks = state.frameworks; // Check for frontend frameworks const frontendFrameworks = ['react', 'vue', 'angular', 'next', 'nuxt']; const hasFrontend = frameworks.some(f => frontendFrameworks.includes(f)); // Check for backend frameworks const backendFrameworks = [ 'express', 'fastify', 'koa', 'django', 'flask', 'spring', ]; const hasBackend = frameworks.some(f => backendFrameworks.includes(f)); if (hasFrontend && hasBackend) return 'fullstack'; if (hasFrontend) return 'frontend'; if (hasBackend) return 'backend'; // Check if it's a CLI tool if (state.detectedTools.has('commander') || state.detectedTools.has('yargs')) { return 'cli'; } // Default to library if no specific type detected return 'library'; } async destroy() { await this.stop(); } /** * Runs comprehensive security check in the background when files change */ async runBackgroundSecurityCheck(changedFiles) { this.notificationManager.showProgress('🔒 Running security scan on updated dependencies...'); try { // Get list of files for security check const filesToCheck = changedFiles || (await this.getAllProjectFiles()); // Run comprehensive security checks (Snyk + Gitleaks) const securityResults = await this.qualityRunner.runSecurityChecksForReview(filesToCheck); // Store findings for comparison in future checks const allFindings = []; securityResults.forEach(result => { allFindings.push(...result.findings); }); // Process results and show alerts for critical/high vulnerabilities let hasCriticalIssues = false; let criticalCount = 0; let highCount = 0; let secretsFound = 0; securityResults.forEach(result => { criticalCount += result.summary.critical; highCount += result.summary.high; if (result.summary.critical > 0 || result.summary.high > 0) { hasCriticalIssues = true; } // Count secrets found by Gitleaks if (result.tool === 'gitleaks') { secretsFound += result.findings.length; } // Show specific critical and high findings const criticalFindings = result.findings.filter(f => f.severity === 'critical'); const highFindings = result.findings.filter(f => f.severity === 'high'); // Show critical findings individually criticalFindings.forEach(finding => { let description = ''; let recommendation = finding.recommendation; if (finding.package) { description = `Package: ${finding.package}@${finding.version || 'unknown'}`; if (finding.fixedIn) { description += ' (Fix available)'; recommendation = recommendation || `Update to ${finding.fixedIn}`; } } else if (finding.file) { description = `File: ${finding.file}${finding.line ? `:${finding.line}` : ''}`; } this.notificationManager.showSecurityAlert('critical', `🚨 CRITICAL [${finding.tool.toUpperCase()}]: ${finding.title}`, description, recommendation); }); // Show high findings (summarized if many) if (highFindings.length > 0 && highFindings.length <= 3) { highFindings.forEach(finding => { let description = ''; if (finding.package) { description = `Package: ${finding.package}@${finding.version || 'unknown'}`; } else if (finding.file) { description = `File: ${finding.file}${finding.line ? `:${finding.line}` : ''}`; } this.notificationManager.showSecurityAlert('high', `⚠️ HIGH [${finding.tool.toUpperCase()}]: ${finding.title}`, description, finding.recommendation); }); } else if (highFindings.length > 3) { this.notificationManager.showSecurityAlert('high', `⚠️ ${highFindings.length} HIGH severity security issues found`, `Tools: ${result.tool}`, 'Run "woaru review" for detailed report'); } // Show secrets found by Gitleaks if (result.tool === 'gitleaks' && result.findings.length > 0) { result.findings.slice(0, 3).forEach(finding => { this.notificationManager.showSecurityAlert('critical', `🔍 SECRET DETECTED: ${finding.title}`, `File: ${finding.file}${finding.line ? `:${finding.line}` : ''}`, 'Remove or encrypt the secret immediately'); }); if (result.findings.length > 3) { this.notificationManager.showSecurityAlert('critical', `🔍 ${result.findings.length - 3} more secrets detected`, 'Multiple secrets found in codebase', 'Run "woaru review" for complete list'); } } }); // Store findings for next comparison this.lastSecurityFindings = allFindings; this.lastSecurityCheck = new Date(); if (!hasCriticalIssues && secretsFound === 0) { this.notificationManager.showSuccess('✅ Security scan complete - no critical issues found'); } else { // Show summary alert const summaryParts = []; if (criticalCount > 0) summaryParts.push(`${criticalCount} critical`); if (highCount > 0) summaryParts.push(`${highCount} high`); if (secretsFound > 0) summaryParts.push(`${secretsFound} secrets`); this.notificationManager.showSecurityAlert('critical', `🚨 SECURITY ALERT: ${summaryParts.join(', ')} issues found!`, 'Security issues detected in your project', 'Run "woaru review" for detailed analysis and fixes'); } } catch (error) { // Don't crash the supervisor if security check fails this.notificationManager.showWarning(`Security scan failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Runs periodic security check (every 15 minutes) */ async runPeriodicSecurityCheck() { const now = new Date(); const timeSinceLastCheck = now.getTime() - this.lastSecurityCheck.getTime(); // Skip if we just ran a security check (less than 10 minutes ago) if (timeSinceLastCheck < 10 * 60 * 1000) { return; } this.notificationManager.showProgress('🔒 Running periodic security scan...'); try { const allFiles = await this.getAllProjectFiles(); const securityResults = await this.qualityRunner.runSecurityChecksForReview(allFiles); // Only show new critical issues (not previously reported) const newCriticalFindings = this.getNewCriticalFindings(securityResults); if (newCriticalFindings.length > 0) { this.notificationManager.showSecurityAlert('critical', `🚨 NEW SECURITY ISSUES DETECTED`, `${newCriticalFindings.length} new critical/high security issues found`, 'Run "woaru review" for detailed analysis'); // Show top 2 new issues newCriticalFindings.slice(0, 2).forEach(finding => { let description = ''; if (finding.package) { description = `Package: ${finding.package}`; } else if (finding.file) { description = `File: ${finding.file}`; } this.notificationManager.showSecurityAlert(finding.severity, `NEW [${finding.tool.toUpperCase()}]: ${finding.title}`, description, finding.recommendation); }); } else { // Silent success for periodic checks console.log('✅ Periodic security scan: No new issues'); } // Update stored findings const allFindings = []; securityResults.forEach(result => { allFindings.push(...result.findings); }); this.lastSecurityFindings = allFindings; this.lastSecurityCheck = now; } catch (error) { console.error('Periodic security check failed:', error); } } /** * Compare current findings with previous ones to identify new critical issues */ getNewCriticalFindings(currentResults) { const currentFindings = []; currentResults.forEach(result => { currentFindings.push(...result.findings.filter(f => f.severity === 'critical' || f.severity === 'high')); }); // Find findings that weren't in the last check const newFindings = currentFindings.filter(current => { return !this.lastSecurityFindings.some(previous => previous.title === current.title && previous.file === current.file && previous.package === current.package && previous.tool === current.tool); }); return newFindings; } /** * Helper to get all project files (for security analysis) */ async getAllProjectFiles() { // For now, return empty array as security tools scan the whole project anyway // This could be enhanced to return actual file list if needed for file-specific scans return []; } } exports.WOARUSupervisor = WOARUSupervisor; //# sourceMappingURL=WAUSupervisor.js.map