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
JavaScript
/**
* 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