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

446 lines 18 kB
/** * Refactoring Safety Net * Provides before/after comparison capabilities for safe refactoring * Supporting utility for Code Quality Integration */ import { nanoid } from 'nanoid'; import * as fs from 'fs'; export class RefactoringSafetyNet { baselines = new Map(); /** * Capture baseline before refactoring */ async captureBaseline(config) { const baselineId = nanoid(); const timestamp = Date.now(); const baseline = { baselineId, refactoringName: config.refactoringName, timestamp, screenshots: [] }; // Capture screenshots of critical pages if (config.criticalPages.length > 0) { for (const pageUrl of config.criticalPages) { const screenshot = await this.capturePageScreenshot(config.session, pageUrl, baselineId); if (screenshot) { baseline.screenshots.push(screenshot); } } } else { // Capture current page if no critical pages specified const currentUrl = config.session.page?.url() || 'current-page'; const screenshot = await this.capturePageScreenshot(config.session, currentUrl, baselineId); if (screenshot) { baseline.screenshots.push(screenshot); } } // Capture application state if requested if (config.includeState) { baseline.applicationState = await this.captureApplicationState(config.session); } // Capture performance metrics const perfMetrics = await this.capturePerformanceMetrics(config.session); if (perfMetrics) { baseline.performanceMetrics = perfMetrics; } // Store baseline this.baselines.set(baselineId, baseline); console.log(`📸 Refactoring baseline captured: ${baselineId}`); console.log(` - Screenshots: ${baseline.screenshots.length}`); console.log(` - State captured: ${!!baseline.applicationState}`); console.log(` - Performance metrics: ${!!baseline.performanceMetrics}`); return baseline; } /** * Validate after refactoring completion */ async validateAfterRefactoring(config) { const baseline = this.baselines.get(config.baselineId); if (!baseline) { return { baseline: null, overallScore: 0, visualComparison: [], issuesFound: [{ severity: 'critical', description: `Baseline with ID '${config.baselineId}' not found` }], recommendations: ['Ensure the baseline was captured before refactoring'] }; } const validation = { baseline, overallScore: 0, visualComparison: [], issuesFound: [], recommendations: [] }; // Perform visual comparison validation.visualComparison = await this.performVisualComparison(baseline, config.session, config.toleranceLevel); // Compare performance metrics if (baseline.performanceMetrics) { validation.performanceImpact = await this.comparePerformanceMetrics(baseline.performanceMetrics, config.session); } // Analyze state changes if (baseline.applicationState) { const stateIssues = await this.analyzeStateChanges(baseline.applicationState, config.session); validation.issuesFound.push(...stateIssues); } // Calculate overall score validation.overallScore = this.calculateOverallScore(validation, config.toleranceLevel); // Generate recommendations validation.recommendations = this.generateRecommendations(validation); console.log(`🔍 Refactoring validation complete: ${validation.overallScore}/100`); return validation; } /** * Capture screenshot of a specific page */ async capturePageScreenshot(session, pageUrl, baselineId) { if (!session?.page) { console.warn('No active page session for screenshot capture'); return null; } try { // Navigate to page if it's different from current const currentUrl = session.page.url(); if (pageUrl !== currentUrl && pageUrl !== 'current-page') { await session.page.goto(pageUrl); // Wait for page to load await session.page.waitForLoadState('networkidle'); } const timestamp = Date.now(); const pageName = this.getPageNameFromUrl(pageUrl); const screenshotPath = `baseline_${baselineId}_${pageName}_${timestamp}.png`; await session.page.screenshot({ path: screenshotPath, fullPage: true }); return { page: pageName, path: screenshotPath, url: pageUrl }; } catch (error) { console.warn(`Failed to capture screenshot for ${pageUrl}:`, error); return null; } } /** * Capture application state */ async captureApplicationState(session) { if (!session?.page) return null; try { // Capture various state elements const state = await session.page.evaluate(() => { return { // DOM state documentTitle: document.title, url: window.location.href, // Local storage localStorage: Object.keys(localStorage).reduce((acc, key) => { acc[key] = localStorage.getItem(key); return acc; }, {}), // Session storage sessionStorage: Object.keys(sessionStorage).reduce((acc, key) => { acc[key] = sessionStorage.getItem(key); return acc; }, {}), // Cookies cookies: document.cookie, // Basic DOM structure elementCount: document.querySelectorAll('*').length, // Viewport viewport: { width: window.innerWidth, height: window.innerHeight } }; }); return state; } catch (error) { console.warn('Failed to capture application state:', error); return null; } } /** * Capture performance metrics */ async capturePerformanceMetrics(session) { if (!session?.page) return null; try { const metrics = await session.page.evaluate(() => { const navigation = performance.getEntriesByType('navigation')[0]; const resources = performance.getEntriesByType('resource'); return { loadTime: navigation ? navigation.loadEventEnd - navigation.loadEventStart : 0, networkRequests: resources.length, // Memory usage approximation memoryUsage: performance.memory ? performance.memory.usedJSHeapSize : 0 }; }); return metrics; } catch (error) { console.warn('Failed to capture performance metrics:', error); return { loadTime: 0, memoryUsage: 0, networkRequests: 0 }; } } /** * Perform visual comparison between baseline and current state */ async performVisualComparison(baseline, session, toleranceLevel) { const comparisons = []; for (const baselineScreenshot of baseline.screenshots) { try { // Capture new screenshot of the same page const newScreenshot = await this.capturePageScreenshot(session, baselineScreenshot.url, `validation_${Date.now()}`); if (!newScreenshot) { comparisons.push({ page: baselineScreenshot.page, similarity: 0, differences: 100, critical: true }); continue; } // Perform image comparison (simplified simulation) const comparison = await this.compareImages(baselineScreenshot.path, newScreenshot.path, toleranceLevel); comparisons.push({ page: baselineScreenshot.page, similarity: comparison.similarity, differences: comparison.differences, critical: comparison.critical }); } catch (error) { console.warn(`Failed to compare ${baselineScreenshot.page}:`, error); comparisons.push({ page: baselineScreenshot.page, similarity: 0, differences: 100, critical: true }); } } return comparisons; } /** * Compare images (simplified implementation) */ async compareImages(baselinePath, currentPath, toleranceLevel) { // In a real implementation, this would use image comparison libraries // like pixelmatch, looks-same, or similar tools // Simulate comparison results based on tolerance level const toleranceThresholds = { strict: { minSimilarity: 98, maxDifferences: 5 }, moderate: { minSimilarity: 90, maxDifferences: 20 }, loose: { minSimilarity: 80, maxDifferences: 50 } }; const threshold = toleranceThresholds[toleranceLevel]; // Simulate comparison (in real implementation, would analyze actual images) const similarity = Math.random() * 20 + 80; // 80-100% similarity const differences = Math.floor((100 - similarity) * 2); // Convert to difference count const critical = similarity < threshold.minSimilarity || differences > threshold.maxDifferences; return { similarity: Math.round(similarity), differences, critical }; } /** * Compare performance metrics */ async comparePerformanceMetrics(baselineMetrics, session) { const currentMetrics = await this.capturePerformanceMetrics(session); if (!currentMetrics) { return { loadTimeChange: 0, memoryChange: 0, networkChange: 0 }; } return { loadTimeChange: currentMetrics.loadTime - baselineMetrics.loadTime, memoryChange: (currentMetrics.memoryUsage - baselineMetrics.memoryUsage) / (1024 * 1024), // Convert to MB networkChange: currentMetrics.networkRequests - baselineMetrics.networkRequests }; } /** * Analyze state changes */ async analyzeStateChanges(baselineState, session) { const issues = []; const currentState = await this.captureApplicationState(session); if (!currentState) { issues.push({ severity: 'warning', description: 'Unable to capture current application state for comparison' }); return issues; } // Compare URL changes if (baselineState.url !== currentState.url) { issues.push({ severity: 'info', description: `URL changed from ${baselineState.url} to ${currentState.url}` }); } // Compare element count (rough DOM structure check) const elementDiff = Math.abs(currentState.elementCount - baselineState.elementCount); const elementChangePercent = (elementDiff / baselineState.elementCount) * 100; if (elementChangePercent > 20) { issues.push({ severity: 'warning', description: `Significant DOM structure change: ${elementChangePercent.toFixed(1)}% difference in element count` }); } // Compare localStorage changes const baselineKeys = Object.keys(baselineState.localStorage || {}); const currentKeys = Object.keys(currentState.localStorage || {}); if (baselineKeys.length !== currentKeys.length) { issues.push({ severity: 'info', description: `LocalStorage keys changed: ${baselineKeys.length}${currentKeys.length}` }); } return issues; } /** * Calculate overall validation score */ calculateOverallScore(validation, toleranceLevel) { let score = 100; // Visual comparison impact if (validation.visualComparison.length > 0) { const avgSimilarity = validation.visualComparison.reduce((sum, comp) => sum + comp.similarity, 0) / validation.visualComparison.length; score = (score + avgSimilarity) / 2; // Average with visual similarity } // Performance impact if (validation.performanceImpact) { if (validation.performanceImpact.loadTimeChange > 200) { // More than 200ms slower score -= 15; } else if (validation.performanceImpact.loadTimeChange < -100) { // More than 100ms faster score += 5; } if (validation.performanceImpact.memoryChange > 10) { // More than 10MB increase score -= 10; } } // Issues impact const criticalIssues = validation.issuesFound.filter(issue => issue.severity === 'critical').length; const warningIssues = validation.issuesFound.filter(issue => issue.severity === 'warning').length; score -= criticalIssues * 20; score -= warningIssues * 5; // Apply tolerance level adjustment const toleranceBonus = { strict: 0, moderate: 5, loose: 10 }; score += toleranceBonus[toleranceLevel]; return Math.max(0, Math.min(100, Math.round(score))); } /** * Generate recommendations based on validation results */ generateRecommendations(validation) { const recommendations = []; // Visual comparison recommendations const criticalVisualIssues = validation.visualComparison.filter(comp => comp.critical); if (criticalVisualIssues.length > 0) { recommendations.push(`Review visual changes in ${criticalVisualIssues.length} page(s) - significant differences detected`); } // Performance recommendations if (validation.performanceImpact) { if (validation.performanceImpact.loadTimeChange > 200) { recommendations.push('Performance regression detected - load time increased significantly'); } if (validation.performanceImpact.memoryChange > 10) { recommendations.push('Memory usage increased - check for potential memory leaks'); } if (validation.performanceImpact.loadTimeChange < -100) { recommendations.push('Performance improvement detected - great work!'); } } // Issue-based recommendations const criticalIssues = validation.issuesFound.filter(issue => issue.severity === 'critical').length; if (criticalIssues > 0) { recommendations.push('Address critical issues before deploying refactored code'); } // Overall score recommendations if (validation.overallScore >= 90) { recommendations.push('Refactoring validation successful - changes look good to deploy'); } else if (validation.overallScore >= 75) { recommendations.push('Refactoring mostly successful - review minor issues before deployment'); } else { recommendations.push('Refactoring has significant issues - recommend thorough review before deployment'); } return recommendations; } /** * Get page name from URL */ getPageNameFromUrl(url) { if (url === 'current-page') return 'current'; try { const urlObj = new URL(url); const path = urlObj.pathname; return path === '/' ? 'home' : path.replace(/[^a-zA-Z0-9]/g, '_').replace(/^_+|_+$/g, ''); } catch { return url.replace(/[^a-zA-Z0-9]/g, '_').replace(/^_+|_+$/g, '') || 'unknown'; } } /** * Get baseline by ID */ getBaseline(baselineId) { return this.baselines.get(baselineId); } /** * List all baselines */ listBaselines() { return Array.from(this.baselines.values()); } /** * Cleanup old baselines */ cleanup(olderThanMs = 7 * 24 * 60 * 60 * 1000) { const cutoff = Date.now() - olderThanMs; for (const [id, baseline] of this.baselines.entries()) { if (baseline.timestamp < cutoff) { // Clean up screenshot files baseline.screenshots.forEach(screenshot => { try { if (fs.existsSync(screenshot.path)) { fs.unlinkSync(screenshot.path); } } catch (error) { console.warn(`Failed to delete screenshot ${screenshot.path}:`, error); } }); this.baselines.delete(id); } } } } //# sourceMappingURL=refactoring-safety-net.js.map