UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

401 lines 14.5 kB
/** * File Change Monitor * Monitors file system changes during development with automatic screenshot capture * Core utility for Code Quality Integration */ import * as fs from 'fs'; import * as path from 'path'; import { nanoid } from 'nanoid'; export class FileChangeMonitor { isMonitoring = false; monitorId; config; changes = []; screenshots = []; startTime; watchers = []; /** * Start monitoring file changes */ async startMonitoring(config) { if (this.isMonitoring) { throw new Error('File monitoring is already active'); } this.config = config; this.monitorId = nanoid(); this.startTime = Date.now(); this.isMonitoring = true; this.changes = []; this.screenshots = []; // Get list of files to watch const filesToWatch = await this.getFilesToWatch(config.projectPath, config.filePatterns); // Setup file watchers for (const filePath of filesToWatch) { try { const watcher = fs.watch(filePath, (eventType, filename) => { this.handleFileChange(filePath, eventType); }); this.watchers.push(watcher); } catch (error) { // File might not exist or be accessible - continue with others console.warn(`Could not watch file: ${filePath}`); } } // Setup directory watchers for new files await this.setupDirectoryWatchers(config.projectPath, config.filePatterns); console.log(`🔍 File change monitoring started: ${this.monitorId}`); console.log(`📁 Watching ${filesToWatch.length} files in ${config.projectPath}`); return { monitorId: this.monitorId, watchedFiles: filesToWatch.length }; } /** * Stop monitoring and generate report */ async stopMonitoring(generateReport = true) { if (!this.isMonitoring || !this.config || !this.startTime) { return null; } // Close all watchers this.watchers.forEach(watcher => { try { watcher.close(); } catch (error) { // Ignore close errors } }); this.watchers = []; const duration = this.formatDuration(Date.now() - this.startTime); // Group changes by file const fileMap = new Map(); this.changes.forEach(change => { const existing = fileMap.get(change.path); if (existing) { existing.changeCount++; existing.lastChanged = Math.max(existing.lastChanged, change.timestamp); } else { fileMap.set(change.path, { path: change.path, changeCount: 1, lastChanged: change.timestamp }); } }); const report = { duration, changedFiles: Array.from(fileMap.values()), totalChanges: this.changes.length, screenshotsCaptured: this.screenshots.length, uiStabilityScore: this.calculateUIStabilityScore() }; if (generateReport) { report.detailedReport = await this.generateDetailedReport(); } // Reset state this.isMonitoring = false; this.config = undefined; this.monitorId = undefined; this.startTime = undefined; console.log(`🏁 File change monitoring stopped. Report generated.`); return report; } /** * Get change impact analysis for a specific change */ async getChangeImpactAnalysis(options) { const change = options.changeId ? this.changes.find(c => c.id === options.changeId) : this.changes[this.changes.length - 1]; // Latest change if (!change) { return { change: null, uiImpact: 'none', functionalityImpact: 'none', performanceImpact: 'neutral', recommendations: ['No recent changes detected'] }; } const analysis = { change, uiImpact: this.assessUIImpact(change), functionalityImpact: this.assessFunctionalityImpact(change), performanceImpact: this.assessPerformanceImpact(change), recommendations: this.generateRecommendations(change) }; // Add visual diff analysis if requested if (options.includeVisualDiff) { analysis.visualDiff = await this.performVisualDiffAnalysis(change); } // Add performance analysis if requested if (options.includePerformanceImpact) { analysis.performanceAnalysis = await this.performPerformanceAnalysis(change); } return analysis; } /** * Handle individual file changes */ async handleFileChange(filePath, eventType) { if (!this.config || !this.isMonitoring) return; try { const stats = fs.statSync(filePath); const change = { id: nanoid(), timestamp: Date.now(), path: filePath, type: eventType === 'rename' ? 'created' : 'modified', size: stats.size, linesChanged: await this.countLinesChanged(filePath) }; this.changes.push(change); console.log(`📝 File changed: ${path.relative(this.config.projectPath, filePath)}`); // Capture screenshot if enabled if (this.config.screenshotOnChange && this.config.session) { await this.captureScreenshotForChange(change); } } catch (error) { // File might have been deleted or is temporarily inaccessible const change = { id: nanoid(), timestamp: Date.now(), path: filePath, type: 'deleted', size: 0 }; this.changes.push(change); } } /** * Get files to watch based on patterns */ async getFilesToWatch(projectPath, patterns) { const files = []; const scanDirectory = (dir) => { try { const entries = fs.readdirSync(dir); for (const entry of entries) { const fullPath = path.join(dir, entry); const stats = fs.statSync(fullPath); if (stats.isDirectory()) { // Skip node_modules and other common directories if (!['node_modules', '.git', 'dist', 'build', '.next'].includes(entry)) { scanDirectory(fullPath); } } else if (stats.isFile()) { // Check if file matches any pattern const matchesPattern = patterns.some(pattern => { const regex = new RegExp(pattern.replace('*', '.*')); return regex.test(entry); }); if (matchesPattern) { files.push(fullPath); } } } } catch (error) { // Directory might not be accessible - continue } }; scanDirectory(projectPath); return files; } /** * Setup directory watchers for new files */ async setupDirectoryWatchers(projectPath, patterns) { const watchDirectory = (dir) => { try { const watcher = fs.watch(dir, { recursive: true }, (eventType, filename) => { if (filename && patterns.some(pattern => { const regex = new RegExp(pattern.replace('*', '.*')); return regex.test(filename); })) { const fullPath = path.join(dir, filename); this.handleFileChange(fullPath, eventType); } }); this.watchers.push(watcher); } catch (error) { // Directory might not support recursive watching console.warn(`Could not setup recursive watcher for: ${dir}`); } }; watchDirectory(projectPath); } /** * Count lines changed in a file (simplified implementation) */ async countLinesChanged(filePath) { try { const content = fs.readFileSync(filePath, 'utf8'); return content.split('\n').length; } catch (error) { return 0; } } /** * Capture screenshot when file changes */ async captureScreenshotForChange(change) { if (!this.config?.session?.page) return; try { const screenshotPath = `screenshot_${change.id}_${Date.now()}.png`; await this.config.session.page.screenshot({ path: screenshotPath, fullPage: true }); this.screenshots.push(screenshotPath); console.log(`📸 Screenshot captured: ${screenshotPath}`); } catch (error) { console.warn(`Failed to capture screenshot: ${error}`); } } /** * Assess UI impact of a change */ assessUIImpact(change) { const filePath = change.path.toLowerCase(); if (filePath.includes('.css') || filePath.includes('.scss') || filePath.includes('.less')) { return 'high'; } if (filePath.includes('component') || filePath.includes('.tsx') || filePath.includes('.jsx')) { return 'medium'; } if (filePath.includes('.ts') || filePath.includes('.js')) { return 'low'; } return 'none'; } /** * Assess functionality impact of a change */ assessFunctionalityImpact(change) { const filePath = change.path.toLowerCase(); if (filePath.includes('api') || filePath.includes('service') || filePath.includes('util')) { return 'high'; } if (filePath.includes('component') || filePath.includes('hook')) { return 'medium'; } return 'low'; } /** * Assess performance impact of a change */ assessPerformanceImpact(change) { // Simplified logic - could be enhanced with actual performance analysis if (change.size > 10000) { // Large files might impact performance return 'negative'; } if (change.path.includes('optimization') || change.path.includes('cache')) { return 'positive'; } return 'neutral'; } /** * Generate recommendations based on change analysis */ generateRecommendations(change) { const recommendations = []; if (change.linesChanged && change.linesChanged > 300) { recommendations.push('Consider breaking down large files for better maintainability'); } if (change.path.includes('component') && !change.path.includes('test')) { recommendations.push('Consider adding or updating tests for modified components'); } if (change.path.includes('.css') || change.path.includes('.scss')) { recommendations.push('Verify visual consistency across different screen sizes'); } return recommendations; } /** * Perform visual diff analysis */ async performVisualDiffAnalysis(change) { // Simplified implementation - would integrate with actual visual comparison tools return { screenshotsCompared: 2, similarity: Math.random() * 20 + 80, // 80-100% similarity changesDetected: Math.floor(Math.random() * 5) }; } /** * Perform performance analysis */ async performPerformanceAnalysis(change) { // Simplified implementation - would integrate with actual performance monitoring return { loadTimeChange: Math.floor(Math.random() * 200 - 100), // -100 to +100ms bundleSizeChange: Math.floor(Math.random() * 20 - 10), // -10 to +10KB memoryImpact: Math.floor(Math.random() * 10 - 5) // -5 to +5MB }; } /** * Calculate UI stability score */ calculateUIStabilityScore() { if (this.changes.length === 0) return 100; const uiChanges = this.changes.filter(change => this.assessUIImpact(change) !== 'none').length; const score = Math.max(0, 100 - (uiChanges * 10)); return Math.min(100, score); } /** * Generate detailed report */ async generateDetailedReport() { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const reportPath = `file_change_report_${timestamp}.json`; const report = { monitorId: this.monitorId, startTime: this.startTime, endTime: Date.now(), config: this.config, changes: this.changes, screenshots: this.screenshots, summary: { totalChanges: this.changes.length, screenshotsCaptured: this.screenshots.length, uiStabilityScore: this.calculateUIStabilityScore() } }; try { fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); return reportPath; } catch (error) { console.warn(`Failed to write detailed report: ${error}`); return 'Report generation failed'; } } /** * Format duration string */ formatDuration(ms) { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) { return `${hours}h ${minutes % 60}m ${seconds % 60}s`; } else if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } else { return `${seconds}s`; } } } //# sourceMappingURL=file-change-monitor.js.map