UNPKG

@invisiblecities/sidequest-cqo

Version:

Configuration-agnostic TypeScript and ESLint orchestrator with real-time watch mode, SQLite persistence, and intelligent terminal detection

768 lines • 31.4 kB
/** * Unified Code Quality Orchestrator * * Combines the analysis capabilities of CodeQualityOrchestrator with the * service management and persistence capabilities of OrchestratorService. * This replaces both legacy systems with a single, unified orchestrator. */ import { EventEmitter } from "node:events"; import { ConfigManager } from "./config-manager.js"; import { getPollingService } from "./polling-service.js"; import { getAnalysisService } from "./analysis-service.js"; import { getViolationTracker } from "./violation-tracker.js"; import { TypeScriptAuditEngine } from "../engines/typescript-engine.js"; import { ESLintAuditEngine } from "../engines/eslint-engine.js"; import { UnusedExportsEngine } from "../engines/unused-exports-engine.js"; import { ZodDetectionEngine } from "../engines/zod-detection-engine.js"; import { CodeArchaeologyEngine } from "../engines/code-archaeology-engine.js"; import { createCrossoverDetector } from "../utils/crossover-detector.js"; /** * Unified Code Quality Orchestrator Implementation * * This class merges the capabilities of both legacy orchestrators: * - Direct analysis execution with multi-engine coordination * - Service management with SQLite persistence * - Watch mode with real-time monitoring * - Configuration management and health monitoring */ export class UnifiedOrchestrator extends EventEmitter { // Service Management (from enhanced orchestrator) configManager; storageService = undefined; pollingService = undefined; analysisService = undefined; violationTracker = undefined; // Engine Management (from legacy orchestrator) engines = new Map(); unifiedConfig; // State Management initialized = false; watchModeActive = false; watchModeInterval = undefined; silent = false; // Event System (from legacy orchestrator) eventListeners = new Map(); constructor(config, configManager) { super(); this.unifiedConfig = config; this.configManager = configManager || new ConfigManager(); this.initializeEngines(); } // ======================================================================== // Lifecycle Management (from enhanced orchestrator) // ======================================================================== async initialize() { if (this.initialized) { console.log("[UnifiedOrchestrator] Already initialized"); return; } console.log("[UnifiedOrchestrator] Initializing services..."); try { // Initialize all services through config manager const { storageService } = await this.configManager.initializeServices(); this.storageService = storageService; // Initialize other services with storage service dependency this.pollingService = getPollingService(this.storageService); this.analysisService = getAnalysisService(this.storageService); this.violationTracker = getViolationTracker(this.storageService); // Set up event forwarding from polling service this.pollingService.on("ruleStarted", (ruleId, engine) => { this.emit("ruleStarted", ruleId, engine); }); this.pollingService.on("ruleCompleted", (result) => { this.emit("ruleCompleted", result); }); this.pollingService.on("ruleFailed", (ruleId, engine, error) => { this.emit("ruleFailed", ruleId, engine, error); }); this.pollingService.on("cycleCompleted", (results) => { this.emit("cycleCompleted", results); }); this.initialized = true; console.log("[UnifiedOrchestrator] Initialization completed successfully"); this.emit("initialized"); } catch (error) { console.error("[UnifiedOrchestrator] Initialization failed:", error); this.emit("initializationFailed", error); throw error; } } async shutdown() { if (!this.initialized) { console.log("[UnifiedOrchestrator] Not initialized, nothing to shutdown"); return; } console.log("[UnifiedOrchestrator] Shutting down..."); try { // Stop watch mode if active if (this.watchModeActive) { await this.stopWatchMode(); } // Stop polling service if running if (this.pollingService?.isRunning()) { await this.pollingService.stop(); } // Shutdown config manager (closes database connections) await this.configManager.shutdown(); this.initialized = false; console.log("[UnifiedOrchestrator] Shutdown completed"); this.emit("shutdown"); } catch (error) { console.error("[UnifiedOrchestrator] Shutdown error:", error); this.emit("shutdownError", error); throw error; } } // ======================================================================== // Core Analysis Capabilities (from legacy orchestrator) // ======================================================================== /** * Execute all enabled engines and return unified results * This is the core analysis method from the legacy orchestrator */ async analyze(targetPath) { const analysisPath = targetPath || this.unifiedConfig.targetPath; const startTime = Date.now(); const engineResults = []; this.emitEvent("analysis-started", { engines: [...this.engines.keys()] }); // Sort engines by priority const sortedEngines = [...this.engines.entries()].sort(([, a], [, b]) => a.getConfig().priority - b.getConfig().priority); // Execute engines in parallel (or series based on dependencies) const enginePromises = sortedEngines.map(async ([name, engine]) => { try { console.log(`[UnifiedOrchestrator] Starting ${name} engine...`); const result = await engine.execute(analysisPath); console.log(`[UnifiedOrchestrator] ${name} engine completed: ${result.violations.length} violations found`); return result; } catch (error) { console.error(`[UnifiedOrchestrator] ${name} engine failed:`, error); // Return failed result instead of throwing return { engineName: name, violations: [], executionTime: 0, success: false, error: error instanceof Error ? error.message : String(error), }; } }); const results = await Promise.all(enginePromises); engineResults.push(...results); // Merge and deduplicate violations const allViolations = this.mergeViolations(results); const deduplicatedViolations = this.deduplicateViolations(allViolations); // Generate summary const summary = this.generateSummary(deduplicatedViolations); const totalExecutionTime = Date.now() - startTime; const orchestratorResult = { violations: deduplicatedViolations, engineResults, totalExecutionTime, summary, timestamp: new Date().toISOString(), }; // Crossover detection and warnings if (this.unifiedConfig.crossover?.enabled !== false) { const crossoverDetector = createCrossoverDetector(this.unifiedConfig.crossover); const crossoverWarnings = crossoverDetector.analyze(orchestratorResult); if (crossoverWarnings.length > 0) { // Display warnings if console output is enabled if (this.unifiedConfig.output?.console !== false) { crossoverDetector.displayWarnings(); } // Add to orchestrator result for programmatic access orchestratorResult.crossoverWarnings = crossoverWarnings; // Optionally fail on crossover issues if (this.unifiedConfig.crossover?.failOnCrossover && crossoverDetector.hasCriticalIssues()) { throw new Error("Critical crossover issues detected between ESLint and TypeScript engines"); } } } // Persist results if services are initialized if (this.initialized && this.storageService) { try { await this.persistAnalysisResults(orchestratorResult); } catch (error) { console.warn("[UnifiedOrchestrator] Failed to persist results:", error); } } this.emitEvent("analysis-completed", { violationCount: deduplicatedViolations.length, executionTime: totalExecutionTime, }); return orchestratorResult; } // ======================================================================== // Engine Management (from legacy orchestrator) // ======================================================================== /** * Initialize audit engines based on configuration */ initializeEngines() { // Initialize TypeScript engine if (this.unifiedConfig.engines.typescript?.enabled !== false) { const tsEngine = new TypeScriptAuditEngine(this.unifiedConfig.engines.typescript); this.engines.set("typescript", tsEngine); } // Initialize ESLint engine if (this.unifiedConfig.engines.eslint?.enabled !== false) { const eslintEngine = new ESLintAuditEngine(this.unifiedConfig.engines.eslint); this.engines.set("eslint", eslintEngine); } // Initialize Unused Exports engine if (this.unifiedConfig.engines.unusedExports?.enabled !== false) { const unusedExportsEngine = new UnusedExportsEngine(); this.engines.set("unused-exports", unusedExportsEngine); } // Initialize Zod Detection engine if (this.unifiedConfig.engines.zodDetection?.enabled !== false) { const zodEngine = new ZodDetectionEngine(this.unifiedConfig.engines.zodDetection); this.engines.set("zod-detection", zodEngine); } // Initialize Code Archaeology engine if (this.unifiedConfig.engines.archaeology?.enabled !== false) { const archaeologyEngine = new CodeArchaeologyEngine(this.unifiedConfig.engines.archaeology); this.engines.set("archaeology", archaeologyEngine); } } addEngine(name, engine) { this.engines.set(name, engine); } removeEngine(name) { this.engines.delete(name); } getEngine(name) { return this.engines.get(name); } getEngineMetadata() { const metadata = {}; for (const [name, engine] of this.engines) { metadata[name] = engine.getMetadata(); } return metadata; } // ======================================================================== // Watch Mode (unified from both systems) // ======================================================================== async startWatchMode(options = {}) { this.ensureInitialized(); if (this.watchModeActive) { console.log("[UnifiedOrchestrator] Watch mode already active"); return; } const watchOptions = { intervalMs: options.intervalMs || this.unifiedConfig.watch.intervalMs, debounceMs: options.debounceMs || this.unifiedConfig.watch.debounceMs, autoCleanup: options.autoCleanup === undefined ? this.unifiedConfig.watch.autoCleanup : options.autoCleanup, maxConcurrentChecks: options.maxConcurrentChecks || this.unifiedConfig.polling.maxConcurrentChecks, }; console.log("[UnifiedOrchestrator] Starting watch mode with options:", watchOptions); // Start polling service await this.pollingService.start(); // Initial analysis await this.analyze(); // Set up watch mode interval combining both legacy and enhanced approaches this.watchModeInterval = setInterval(async () => { try { // Execute analysis (legacy approach) await this.analyze(); // Execute service-based watch cycle (enhanced approach) await this.executeWatchCycle(watchOptions); } catch (error) { console.error("[UnifiedOrchestrator] Watch cycle error:", error); this.emit("watchError", error); } }, watchOptions.intervalMs); // Handle graceful shutdown (from legacy orchestrator) process.on("SIGINT", () => { this.stopWatchMode(); process.stdout.write("\u001B[?25h"); // Show cursor console.log("\n\nšŸ‘‹ Unified Code Quality Orchestrator watch stopped."); process.exit(0); }); this.watchModeActive = true; console.log("[UnifiedOrchestrator] Watch mode started"); this.emit("watchModeStarted", watchOptions); } async stopWatchMode() { if (!this.watchModeActive) { console.log("[UnifiedOrchestrator] Watch mode not active"); return; } console.log("[UnifiedOrchestrator] Stopping watch mode..."); // Stop watch interval if (this.watchModeInterval) { clearInterval(this.watchModeInterval); this.watchModeInterval = undefined; } // Stop polling service if (this.pollingService?.isRunning()) { await this.pollingService.stop(); } this.watchModeActive = false; console.log("[UnifiedOrchestrator] Watch mode stopped"); this.emit("watchModeStopped"); this.emitEvent("watch-stopped", {}); } isWatchModeActive() { return this.watchModeActive; } // ======================================================================== // Service Access (from enhanced orchestrator) // ======================================================================== getStorageService() { this.ensureInitialized(); return this.storageService; } getPollingService() { this.ensureInitialized(); return this.pollingService; } getAnalysisService() { this.ensureInitialized(); return this.analysisService; } getViolationTracker() { this.ensureInitialized(); return this.violationTracker; } setSilentMode(silent) { this.silent = silent; if (this.violationTracker) { this.violationTracker.setSilentMode(silent); } } // ======================================================================== // Manual Operations (from enhanced orchestrator) // ======================================================================== async runSingleCheck(rule, engine) { this.ensureInitialized(); console.log(`[UnifiedOrchestrator] Running single check: ${rule} (${engine})`); const result = await this.pollingService.executeRule(rule, engine); this.emit("singleCheckCompleted", result); return result; } async runAllChecks() { this.ensureInitialized(); console.log("[UnifiedOrchestrator] Running all scheduled checks..."); const results = await this.pollingService.executeNextRules(100); // Large number to get all console.log(`[UnifiedOrchestrator] Completed ${results.length} checks`); this.emit("allChecksCompleted", results); return results; } // ======================================================================== // Configuration Management (unified) // ======================================================================== async updateConfiguration(config) { console.log("[UnifiedOrchestrator] Updating configuration..."); // Update config manager this.configManager.updateConfig(config); // If already initialized, may need to reinitialize services if (this.initialized) { console.log("[UnifiedOrchestrator] Restarting services with new configuration..."); const wasWatchActive = this.watchModeActive; if (wasWatchActive) { await this.stopWatchMode(); } await this.shutdown(); await this.initialize(); if (wasWatchActive) { await this.startWatchMode(); } } this.emit("configurationUpdated", config); } async updateUnifiedConfig(config) { console.log("[UnifiedOrchestrator] Updating unified configuration..."); this.unifiedConfig = { ...this.unifiedConfig, ...config }; // Reinitialize engines if engine config changed if (config.engines) { this.engines.clear(); this.initializeEngines(); } // Update service configuration if needed if (config.database || config.polling || config.watch || config.performance) { const serviceConfig = {}; if (config.database) { serviceConfig.database = config.database; } if (config.polling) { serviceConfig.polling = config.polling; } if (config.watch) { serviceConfig.watch = config.watch; } if (config.performance) { serviceConfig.performance = config.performance; } await this.updateConfiguration(serviceConfig); } this.emit("unifiedConfigurationUpdated", config); } getConfiguration() { const config = this.configManager.getConfig(); return { database: { path: config.database.path, enableWAL: config.database.enableWAL || false, maxHistoryDays: config.database.maxHistoryDays || 30, }, polling: { defaultFrequencyMs: config.scheduling.defaultFrequencyMs || 30_000, maxConcurrentChecks: config.scheduling.maxConcurrentChecks || 3, adaptivePolling: config.scheduling.adaptivePolling || true, }, watch: { intervalMs: config.watch.intervalMs || 3000, debounceMs: config.watch.debounceMs || 500, autoCleanup: config.watch.autoCleanup || true, }, performance: { batchSize: config.performance.batchSize || 100, enableMetrics: config.monitoring.enablePerformanceMetrics || true, }, }; } getUnifiedConfig() { return { ...this.unifiedConfig }; } // ======================================================================== // Health and Monitoring (from enhanced orchestrator) // ======================================================================== async healthCheck() { const result = { overall: false, services: { storage: false, polling: false, analysis: false, tracker: false, }, errors: [], }; try { // Check config manager health const configHealth = await this.configManager.healthCheck(); result.services.storage = configHealth.storageService; if (!configHealth.overall) { result.errors.push("Config manager health check failed"); } // Check individual services if initialized if (this.initialized) { result.services.polling = this.pollingService?.isRunning() || false; result.services.analysis = this.analysisService !== undefined; result.services.tracker = this.violationTracker !== undefined; // Test storage service try { await this.storageService.getStorageStats(); result.services.storage = true; } catch (error) { result.services.storage = false; result.errors.push(`Storage service error: ${error}`); } } else { result.errors.push("Services not initialized"); } // Overall health result.overall = Object.values(result.services).every(Boolean) && result.errors.length === 0; } catch (error) { result.errors.push(`Health check error: ${error}`); } return result; } async getSystemStats() { const configStats = await this.configManager.getSystemStats(); return { uptime: configStats.performance.uptime, memoryUsage: configStats.performance.memoryUsage, database: configStats.database, storage: configStats.storage, activeChecks: this.pollingService ? this.pollingService.activeChecks?.size || 0 : 0, watchMode: this.watchModeActive, }; } // ======================================================================== // Event System (from legacy orchestrator) // ======================================================================== addWatchEventListener(_event, _callback) { if (!this.eventListeners.has(_event)) { this.eventListeners.set(_event, []); } this.eventListeners.get(_event).push(_callback); } emitEvent(type, payload) { const eventData = { type, timestamp: new Date().toISOString(), payload, }; const listeners = this.eventListeners.get(type) || []; listeners.forEach((callback) => { try { callback(eventData); } catch (error) { console.error(`[UnifiedOrchestrator] Event listener error for ${type}:`, error); } }); } // ======================================================================== // Private Helper Methods // ======================================================================== ensureInitialized() { if (!this.initialized) { throw new Error("UnifiedOrchestrator must be initialized before use"); } } async executeWatchCycle(options) { const startTime = performance.now(); // Execute next batch of rules const results = await this.pollingService.executeNextRules(options.maxConcurrentChecks); // Perform cleanup if enabled if (options.autoCleanup && Math.random() < 0.1) { // 10% chance each cycle try { const cleanupResult = await this.storageService.cleanupOldData(); if (!this.silent) { console.log("[UnifiedOrchestrator] Auto-cleanup completed:", cleanupResult); } } catch (error) { if (!this.silent) { console.error("[UnifiedOrchestrator] Auto-cleanup failed:", error); } } } const executionTime = performance.now() - startTime; // Record watch cycle metrics await this.storageService.recordPerformanceMetric("watch_cycle", executionTime, "ms", `rules: ${results.length}`); this.emit("watchCycle", { executionTime, rulesExecuted: results.length, results, }); } /** * Merge violations from multiple engines (from legacy orchestrator) */ mergeViolations(results) { const allViolations = []; for (const result of results) { if (result.success) { allViolations.push(...result.violations); } } // Sort violations for consistent output return allViolations.sort((a, b) => { // Sort by source, then severity, then file, then line if (a.source !== b.source) { return a.source === "typescript" ? -1 : 1; } const severityOrder = { error: 0, warn: 1, info: 2 }; if (severityOrder[a.severity] !== severityOrder[b.severity]) { return severityOrder[a.severity] - severityOrder[b.severity]; } if (a.file !== b.file) { return a.file.localeCompare(b.file); } return a.line - b.line; }); } /** * Deduplicate violations based on configuration (from legacy orchestrator) */ deduplicateViolations(violations) { if (!this.unifiedConfig.deduplication?.enabled) { return violations; } const strategy = this.unifiedConfig.deduplication.strategy || "exact"; const seen = new Set(); const deduplicated = []; for (const violation of violations) { let key; switch (strategy) { case "exact": { key = `${violation.file}:${violation.line}:${violation.code}:${violation.source}`; break; } case "location": { key = `${violation.file}:${violation.line}`; break; } case "similar": { key = `${violation.file}:${violation.category}:${violation.code.slice(0, 50)}`; break; } default: { key = `${violation.file}:${violation.line}:${violation.code}`; } } if (!seen.has(key)) { seen.add(key); deduplicated.push(violation); } } return deduplicated; } /** * Generate summary statistics (from legacy orchestrator) */ generateSummary(violations) { const summary = { total: violations.length, bySeverity: { error: 0, warn: 0, info: 0 }, bySource: { typescript: 0, eslint: 0, "unused-exports": 0, "zod-detection": 0, parser: 0, complexity: 0, security: 0, performance: 0, archaeology: 0, custom: 0, }, byCategory: {}, topFiles: [], }; const fileViolationCount = new Map(); for (const violation of violations) { // Count by severity summary.bySeverity[violation.severity]++; // Count by source summary.bySource[violation.source]++; // Count by category summary.byCategory[violation.category] = (summary.byCategory[violation.category] || 0) + 1; // Count by file fileViolationCount.set(violation.file, (fileViolationCount.get(violation.file) || 0) + 1); } // Get top violated files summary.topFiles = [...fileViolationCount.entries()] .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([file, count]) => ({ file, count })); return summary; } /** * Persist analysis results to storage service */ async persistAnalysisResults(result) { if (!this.storageService || !this.violationTracker) { return; } try { // Track violations through the violation tracker await this.violationTracker.processViolations(result.violations); // Record performance metrics await this.storageService.recordPerformanceMetric("analysis_execution", result.totalExecutionTime, "ms", `violations: ${result.violations.length}, engines: ${result.engineResults.length}`); } catch (error) { console.warn("[UnifiedOrchestrator] Failed to persist analysis results:", error); } } } // ============================================================================ // Service Factory and Utilities // ============================================================================ let unifiedOrchestratorInstance; /** * Get or create unified orchestrator service instance */ export function getUnifiedOrchestrator(config, configManager) { if (!unifiedOrchestratorInstance) { unifiedOrchestratorInstance = new UnifiedOrchestrator(config, configManager); } return unifiedOrchestratorInstance; } /** * Reset unified orchestrator service instance (useful for testing) */ export function resetUnifiedOrchestrator() { if (unifiedOrchestratorInstance) { if (unifiedOrchestratorInstance.isWatchModeActive()) { unifiedOrchestratorInstance.stopWatchMode().catch(console.error); } unifiedOrchestratorInstance.shutdown().catch(console.error); } unifiedOrchestratorInstance = undefined; } /** * Create a fully configured unified orchestrator for a specific environment */ export async function createUnifiedOrchestrator(config, environment = "development") { const configManager = ConfigManager.createEnvironmentConfig(environment); const orchestrator = new UnifiedOrchestrator(config, configManager); await orchestrator.initialize(); return orchestrator; } /** * Create default unified orchestrator configuration */ export function createDefaultUnifiedConfig(targetPath) { return { // Analysis Configuration targetPath, engines: { typescript: { enabled: true, priority: 1, options: {} }, eslint: { enabled: true, priority: 2, options: {} }, unusedExports: { enabled: true, priority: 3, options: {} }, zodDetection: { enabled: true, priority: 4, options: {} }, archaeology: { enabled: false, priority: 5, options: {} }, // Disabled by default }, deduplication: { enabled: true, strategy: "exact", }, crossover: { enabled: true, warnOnTypeAwareRules: true, warnOnDuplicateViolations: true, failOnCrossover: false, }, output: { console: true, }, // Service Configuration database: { path: "./data/violations.db", enableWAL: true, maxHistoryDays: 30, }, polling: { defaultFrequencyMs: 30_000, maxConcurrentChecks: 3, adaptivePolling: true, }, watch: { intervalMs: 3000, debounceMs: 500, autoCleanup: true, }, performance: { batchSize: 100, enableMetrics: true, }, }; } //# sourceMappingURL=unified-orchestrator.js.map