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

393 lines • 17.8 kB
export class ProblemDebugEngine { hydrationMismatches = []; bundles = new Map(); routeChanges = []; page; localDebugEngine; // Reference to parent engine for accessing network/state data async attachToPage(page, localDebugEngine) { this.page = page; this.localDebugEngine = localDebugEngine; // Inject problem detection scripts await this.injectHydrationDetection(); await this.setupBundleMonitoring(); await this.setupRouteMonitoring(); } async injectHydrationDetection() { if (!this.page) return; await this.page.addInitScript(() => { // Create hydration mismatch detector window.__HYDRATION_DEBUG__ = { mismatches: [], originalError: console.error, detectMismatch: function (args) { const errorStr = args.join(' '); // Common hydration error patterns const patterns = [ /Text content does not match/i, /Hydration failed/i, /did not match/i, /Warning:.*?during hydration/i, /Prop `.*?` did not match/i, /Expected server HTML/i ]; if (patterns.some(p => p.test(errorStr))) { // Try to extract details const stack = new Error().stack; this.mismatches.push({ message: errorStr, timestamp: new Date().toISOString(), stack: stack, url: window.location.href }); } } }; // Override console.error to catch hydration warnings console.error = function (...args) { window.__HYDRATION_DEBUG__.detectMismatch(args); return window.__HYDRATION_DEBUG__.originalError.apply(console, args); }; // Also catch React specific hydration errors if (window.React) { const originalCreateElement = window.React.createElement; window.React.createElement = function (...args) { try { return originalCreateElement.apply(this, args); } catch (error) { if (error.message?.includes('hydrat')) { window.__HYDRATION_DEBUG__.mismatches.push({ message: error.message, timestamp: new Date().toISOString(), component: args[0]?.name || 'Unknown' }); } throw error; } }; } }); } async setupBundleMonitoring() { if (!this.page) return; // Monitor resource timing for bundles this.page.on('response', async (response) => { const url = response.url(); const contentType = response.headers()['content-type'] || ''; // Track JavaScript bundles if (contentType.includes('javascript') || url.endsWith('.js')) { const request = response.request(); const timing = response.timing?.(); const size = parseInt(response.headers()['content-length'] || '0'); const gzipSize = response.headers()['content-encoding'] === 'gzip' ? size : undefined; const bundleInfo = { url, size, gzipSize, loadTime: timing ? timing.responseEnd - timing.requestStart : 0, cacheStatus: this.getCacheStatus(response), isLazyLoaded: this.isLazyLoaded(request) }; this.bundles.set(url, bundleInfo); } }); // Inject coverage tracking await this.page.coverage.startJSCoverage(); } getCacheStatus(response) { const cacheControl = response.headers()['cache-control']; const etag = response.headers()['etag']; const status = response.status(); if (status === 304) return 'hit'; if (cacheControl?.includes('no-cache')) return 'none'; if (etag) return 'miss'; // Has etag but not 304 return 'none'; } isLazyLoaded(request) { // Check if loaded after initial page load const timing = request.timing(); if (!timing) return false; // If request started more than 1s after navigation, likely lazy loaded return timing.requestStart > 1000; } async setupRouteMonitoring() { if (!this.page) return; await this.page.addInitScript(() => { window.__ROUTE_DEBUG__ = { changes: [], lastUrl: window.location.href, startTime: Date.now(), recordChange: function (from, to, type) { this.changes.push({ from, to, type, timestamp: new Date().toISOString(), duration: Date.now() - this.startTime }); this.lastUrl = to; this.startTime = Date.now(); } }; // Monitor pushState/replaceState const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function (...args) { const from = window.location.href; originalPushState.apply(history, args); const to = window.location.href; window.__ROUTE_DEBUG__.recordChange(from, to, 'push'); }; history.replaceState = function (...args) { const from = window.location.href; originalReplaceState.apply(history, args); const to = window.location.href; window.__ROUTE_DEBUG__.recordChange(from, to, 'replace'); }; // Monitor popstate (back/forward) window.addEventListener('popstate', () => { const from = window.__ROUTE_DEBUG__.lastUrl; const to = window.location.href; window.__ROUTE_DEBUG__.recordChange(from, to, 'pop'); }); }); } async getHydrationIssues() { if (!this.page) return []; const issues = await this.page.evaluate(() => { return window.__HYDRATION_DEBUG__?.mismatches || []; }); return issues.map((issue) => ({ element: this.extractElementFromError(issue.message), serverHTML: this.extractServerHTML(issue.message), clientHTML: this.extractClientHTML(issue.message), timestamp: new Date(issue.timestamp), stackTrace: issue.stack })); } extractElementFromError(message) { // Try to extract element info from common error messages const patterns = [ /Warning:.*?<(\w+).*?>/, /component:?\s*(\w+)/i, /element\s+(\w+)/i ]; for (const pattern of patterns) { const match = message.match(pattern); if (match) return match[1]; } return 'Unknown element'; } extractServerHTML(message) { const match = message.match(/server:?\s*(.+?)(?:client:|$)/i); return match ? match[1].trim() : 'N/A'; } extractClientHTML(message) { const match = message.match(/client:?\s*(.+?)$/i); return match ? match[1].trim() : 'N/A'; } async getBundleAnalysis() { const bundles = Array.from(this.bundles.values()); // Get JS coverage if available let coverage; if (this.page) { try { const jsCoverage = await this.page.coverage.stopJSCoverage(); const totalBytes = jsCoverage.reduce((sum, entry) => sum + entry.source.length, 0); const usedBytes = jsCoverage.reduce((sum, entry) => { return sum + (entry.functions || []).reduce((s, func) => { return s + func.ranges.reduce((rs, range) => rs + (range.end - range.start), 0); }, 0); }, 0); coverage = { used: usedBytes, total: totalBytes }; // Update bundle coverage jsCoverage.forEach(entry => { const bundle = this.bundles.get(entry.url); if (bundle) { const usedInBundle = (entry.functions || []).reduce((s, func) => { return s + func.ranges.reduce((rs, range) => rs + (range.end - range.start), 0); }, 0); bundle.coverage = (usedInBundle / entry.source.length) * 100; } }); // Restart coverage await this.page.coverage.startJSCoverage(); } catch (error) { console.error('Coverage analysis failed:', error); } } const totalSize = bundles.reduce((sum, b) => sum + b.size, 0); const lazyLoadedSize = bundles.filter(b => b.isLazyLoaded).reduce((sum, b) => sum + b.size, 0); const cacheHits = bundles.filter(b => b.cacheStatus === 'hit').length; const cacheHitRate = bundles.length > 0 ? (cacheHits / bundles.length) * 100 : 0; return { bundles: bundles.sort((a, b) => b.size - a.size), // Largest first totalSize, lazyLoadedSize, cacheHitRate, coverage }; } async getRouteChanges() { if (!this.page) return this.routeChanges; const changes = await this.page.evaluate(() => { return window.__ROUTE_DEBUG__?.changes || []; }); // Merge with any we've tracked const allChanges = [...this.routeChanges]; changes.forEach((change) => { if (!allChanges.some(c => c.timestamp.toISOString() === change.timestamp)) { allChanges.push({ from: change.from, to: change.to, duration: change.duration, timestamp: new Date(change.timestamp), type: change.type }); } }); return allChanges.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); } async detectCommonProblems() { const problems = []; // Check hydration issues const hydrationIssues = await this.getHydrationIssues(); if (hydrationIssues.length > 0) { problems.push({ problem: 'Hydration Mismatches', severity: 'high', description: `Found ${hydrationIssues.length} hydration errors. Server and client HTML don't match.`, solution: 'Ensure consistent rendering between server and client. Check for browser-only APIs, random values, or date/time differences.' }); } // Check bundle sizes const bundleAnalysis = await this.getBundleAnalysis(); const largeBundles = bundleAnalysis.bundles.filter(b => b.size > 500 * 1024); // 500KB if (largeBundles.length > 0) { problems.push({ problem: 'Large JavaScript Bundles', severity: 'medium', description: `${largeBundles.length} bundles exceed 500KB. Largest: ${(largeBundles[0].size / 1024 / 1024).toFixed(2)}MB`, solution: 'Enable code splitting, lazy load routes, and tree-shake unused code.' }); } // Check code coverage if (bundleAnalysis.coverage) { const percentage = bundleAnalysis.coverage.total > 0 ? (bundleAnalysis.coverage.used / bundleAnalysis.coverage.total) * 100 : 0; if (percentage < 50) { problems.push({ problem: 'Low Code Usage', severity: 'medium', description: `Only ${percentage.toFixed(1)}% of loaded JavaScript is being used.`, solution: 'Remove unused dependencies, enable tree-shaking, and lazy load features.' }); } } // Check route performance const routeChanges = await this.getRouteChanges(); const slowRoutes = routeChanges.filter(r => r.duration > 1000); // 1s if (slowRoutes.length > 0) { problems.push({ problem: 'Slow Route Transitions', severity: 'medium', description: `${slowRoutes.length} route changes took over 1 second.`, solution: 'Optimize data fetching, add loading states, and consider prefetching.' }); } // Check cache efficiency if (bundleAnalysis.cacheHitRate < 50 && bundleAnalysis.bundles.length > 5) { problems.push({ problem: 'Poor Cache Utilization', severity: 'low', description: `Only ${bundleAnalysis.cacheHitRate.toFixed(1)}% cache hit rate for JavaScript bundles.`, solution: 'Configure proper cache headers and use content hashing for static assets.' }); } // NEW: Check for API/UI data mismatches if (this.localDebugEngine) { try { const dataMismatches = await this.localDebugEngine.detectApiDataMismatches(); if (dataMismatches.length > 0) { problems.push({ problem: 'API/UI Data Mismatch', severity: 'high', description: `Detected ${dataMismatches.length} potential mismatches between API responses and UI display.`, solution: 'Check data filtering, state management, and ensure UI correctly reflects API data.' }); } // Check for React state mismatches const reactMismatches = await this.localDebugEngine.detectReactStateMismatches(); if (reactMismatches.length > 0) { problems.push({ problem: 'React State/API Mismatch', severity: 'high', description: `Component state doesn't match API data in ${reactMismatches.length} locations.`, solution: 'Verify state updates after API calls, check for race conditions, and ensure proper data flow.' }); } // Check for N+1 query problems const n1Queries = this.localDebugEngine.detectN1Queries(); if (n1Queries.length > 0) { problems.push({ problem: 'N+1 Database Queries', severity: 'high', description: `Detected ${n1Queries.length} N+1 query patterns causing performance issues.`, solution: n1Queries[0]?.suggestion || 'Use eager loading, includes, or batch queries to reduce database calls.' }); } // Check for slow API requests const slowRequests = this.localDebugEngine.getSlowRequests(2000); // 2s threshold if (slowRequests.length > 0) { problems.push({ problem: 'Slow API Requests', severity: 'medium', description: `${slowRequests.length} API requests took over 2 seconds to complete.`, solution: 'Optimize database queries, add caching, consider pagination, or use background jobs.' }); } // Check for failed network requests const failedRequests = this.localDebugEngine.getFailedRequests(); if (failedRequests.length > 0) { problems.push({ problem: 'Failed Network Requests', severity: 'high', description: `${failedRequests.length} network requests failed with errors.`, solution: 'Check API endpoints, authentication, CORS settings, and error handling.' }); } // Check console errors const consoleLogs = this.localDebugEngine.getConsoleLogs(); const errors = consoleLogs.filter((log) => log.type === 'error'); if (errors.length > 0) { problems.push({ problem: 'JavaScript Console Errors', severity: 'high', description: `Found ${errors.length} JavaScript errors in the console.`, solution: 'Fix JavaScript errors to ensure proper functionality. Check browser console for details.' }); } } catch (e) { // If any detection fails, continue without those checks } } return problems; } } //# sourceMappingURL=problem-debug-engine.js.map