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