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