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
634 lines (540 loc) • 20.6 kB
text/typescript
import { Page } from 'playwright';
export interface MetaFrameworkInfo {
framework: 'remix' | 'astro' | 'nuxt' | 'qwik' | 'solidjs' | 'sveltekit' | null;
version?: string;
buildTool?: 'vite' | 'webpack' | 'esbuild' | 'rollup';
features: string[];
}
export interface ViteHMREvent {
type: 'update' | 'full-reload' | 'error' | 'custom';
timestamp: Date;
files?: string[];
error?: string;
}
export interface RemixLoaderData {
route: string;
loader: string;
duration: number;
size: number;
cached: boolean;
}
export interface AstroIsland {
component: string;
props: any;
hydrationDirective?: 'load' | 'idle' | 'visible' | 'media' | 'only';
loading: 'eager' | 'lazy';
}
export interface QwikResumabilityInfo {
serializedState: number;
qrlCount: number;
symbolsLoaded: number;
resumeTime: number;
}
export class MetaFrameworkDebugEngine {
private page?: Page;
private viteHMREvents: ViteHMREvent[] = [];
private remixLoaderData: RemixLoaderData[] = [];
private astroIslands: AstroIsland[] = [];
private frameworkInfo?: MetaFrameworkInfo;
async attachToPage(page: Page): Promise<void> {
this.page = page;
// Detect framework first
this.frameworkInfo = await this.detectMetaFramework(page);
// Inject framework-specific monitoring
if (this.frameworkInfo) {
await this.injectFrameworkMonitoring(this.frameworkInfo.framework!);
}
// Setup Vite HMR monitoring if using Vite
if (this.frameworkInfo?.buildTool === 'vite') {
await this.setupViteHMRMonitoring();
}
}
async detectMetaFramework(page: Page): Promise<MetaFrameworkInfo> {
return await page.evaluate(() => {
const info: MetaFrameworkInfo = {
framework: null,
features: []
};
// Remix detection
if ((window as any).__remixContext || (window as any).__remixManifest) {
info.framework = 'remix';
info.features.push('loader', 'action', 'error-boundaries');
if ((window as any).__remixContext?.future) {
info.features.push(...Object.keys((window as any).__remixContext.future));
}
}
// Astro detection
else if (document.querySelector('astro-island') || (window as any).__ASTRO__) {
info.framework = 'astro';
info.features.push('islands', 'partial-hydration');
if ((window as any).__ASTRO_DEV_TOOLBAR__) {
info.features.push('dev-toolbar');
}
}
// Nuxt 3 detection
else if ((window as any).__NUXT__ || (window as any).$nuxt) {
info.framework = 'nuxt';
info.version = (window as any).__NUXT__?.version || '3.x';
info.features.push('auto-imports', 'file-routing', 'nitro-server');
if ((window as any).__NUXT__?.isHydrating === false) {
info.features.push('hydrated');
}
}
// Qwik detection
else if ((window as any).qwikloader || document.querySelector('[q\\:base]')) {
info.framework = 'qwik';
info.features.push('resumability', 'lazy-loading', 'fine-grained-reactivity');
}
// SolidJS detection
else if ((window as any)._$HY || document.querySelector('[data-hk]')) {
info.framework = 'solidjs';
info.features.push('fine-grained-reactivity', 'no-virtual-dom');
}
// SvelteKit detection
else if ((window as any).__sveltekit__ || document.querySelector('[data-sveltekit]')) {
info.framework = 'sveltekit';
info.features.push('file-routing', 'load-functions', 'form-actions');
}
// Detect build tool
if ((window as any).__vite_plugin_react_preamble_installed__ ||
document.querySelector('script[type="module"][src*="/@vite"]')) {
info.buildTool = 'vite';
} else if ((window as any).webpackChunkName) {
info.buildTool = 'webpack';
}
return info;
});
}
private async injectFrameworkMonitoring(framework: string): Promise<void> {
if (!this.page) return;
switch (framework) {
case 'remix':
await this.injectRemixMonitoring();
break;
case 'astro':
await this.injectAstroMonitoring();
break;
case 'nuxt':
await this.injectNuxtMonitoring();
break;
case 'qwik':
await this.injectQwikMonitoring();
break;
case 'solidjs':
await this.injectSolidJSMonitoring();
break;
case 'sveltekit':
await this.injectSvelteKitMonitoring();
break;
}
}
private async injectRemixMonitoring(): Promise<void> {
if (!this.page) return;
await this.page.addInitScript(() => {
(window as any).__REMIX_DEBUG__ = {
loaderData: [],
actionData: [],
transitions: [],
errors: [],
init: function() {
// Monitor loader execution
if ((window as any).__remixContext) {
const originalFetch = window.fetch;
window.fetch = async function(...args) {
const startTime = Date.now();
const url = typeof args[0] === 'string' ? args[0] : (args[0] as Request).url;
// Track data requests
if (url.includes('_data=')) {
const response = await originalFetch.apply(this, args);
const duration = Date.now() - startTime;
(window as any).__REMIX_DEBUG__.loaderData.push({
url,
duration,
status: response.status,
timestamp: new Date().toISOString()
});
return response;
}
return originalFetch.apply(this, args);
};
}
// Monitor navigation
if ((window as any).RemixBrowser) {
console.log('Remix navigation monitoring enabled');
}
}
};
(window as any).__REMIX_DEBUG__.init();
});
}
private async injectAstroMonitoring(): Promise<void> {
if (!this.page) return;
await this.page.addInitScript(() => {
(window as any).__ASTRO_DEBUG__ = {
islands: [],
hydrationEvents: [],
init: function() {
// Monitor Astro islands
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node instanceof Element && node.tagName === 'ASTRO-ISLAND') {
const island = {
uid: node.getAttribute('uid'),
component: node.getAttribute('component-url'),
props: node.getAttribute('props'),
hydrate: node.getAttribute('client:load') !== null ? 'load' :
node.getAttribute('client:idle') !== null ? 'idle' :
node.getAttribute('client:visible') !== null ? 'visible' :
node.getAttribute('client:media') !== null ? 'media' : 'none',
timestamp: new Date().toISOString()
};
this.islands.push(island);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Monitor hydration
const originalLog = console.log;
console.log = function(...args) {
const message = args.join(' ');
if (message.includes('[astro]') || message.includes('hydrat')) {
(window as any).__ASTRO_DEBUG__.hydrationEvents.push({
message,
timestamp: new Date().toISOString()
});
}
return originalLog.apply(console, args);
};
}
};
(window as any).__ASTRO_DEBUG__.init();
});
}
private async injectNuxtMonitoring(): Promise<void> {
if (!this.page) return;
await this.page.addInitScript(() => {
(window as any).__NUXT_DEBUG__ = {
payloads: [],
errors: [],
composables: new Set(),
init: function() {
const nuxt = (window as any).__NUXT__ || (window as any).$nuxt;
if (!nuxt) return;
// Monitor payloads
if (nuxt.payload) {
this.payloads.push({
data: Object.keys(nuxt.payload.data || {}),
state: Object.keys(nuxt.payload.state || {}),
timestamp: new Date().toISOString()
});
}
// Monitor composable usage
const composableNames = ['useState', 'useFetch', 'useAsyncData', 'useLazyFetch'];
composableNames.forEach(name => {
if ((window as any)[name]) {
this.composables.add(name);
}
});
// Monitor errors
if (nuxt.error) {
this.errors.push({
error: nuxt.error,
timestamp: new Date().toISOString()
});
}
}
};
// Initialize after Nuxt is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => (window as any).__NUXT_DEBUG__.init(), 100);
});
} else {
setTimeout(() => (window as any).__NUXT_DEBUG__.init(), 100);
}
});
}
private async injectQwikMonitoring(): Promise<void> {
if (!this.page) return;
await this.page.addInitScript(() => {
(window as any).__QWIK_DEBUG__ = {
resumabilityInfo: null,
qrlLoads: [],
init: function() {
// Monitor QRL loading
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name.includes('.js') && entry.name.includes('q-')) {
this.qrlLoads.push({
url: entry.name,
duration: entry.duration,
size: (entry as any).transferSize || 0,
timestamp: new Date().toISOString()
});
}
}
});
observer.observe({ entryTypes: ['resource'] });
// Check resumability state
const checkResumability = () => {
const container = document.querySelector('[q\\:base]');
if (container) {
const state = container.getAttribute('q:state');
this.resumabilityInfo = {
hasState: !!state,
stateSize: state ? state.length : 0,
qrlCount: document.querySelectorAll('[on\\:click]').length,
timestamp: new Date().toISOString()
};
}
};
setTimeout(checkResumability, 1000);
}
};
(window as any).__QWIK_DEBUG__.init();
});
}
private async injectSolidJSMonitoring(): Promise<void> {
if (!this.page) return;
await this.page.addInitScript(() => {
(window as any).__SOLID_DEBUG__ = {
reactivityUpdates: [],
componentCount: 0,
init: function() {
// Count hydrated components
this.componentCount = document.querySelectorAll('[data-hk]').length;
// Monitor reactivity if dev mode
if ((window as any)._$HY) {
console.log('SolidJS reactivity monitoring enabled');
}
}
};
(window as any).__SOLID_DEBUG__.init();
});
}
private async injectSvelteKitMonitoring(): Promise<void> {
if (!this.page) return;
await this.page.addInitScript(() => {
(window as any).__SVELTEKIT_DEBUG__ = {
navigations: [],
forms: [],
init: function() {
// Monitor client-side navigation
if ((window as any).__sveltekit__) {
console.log('SvelteKit navigation monitoring enabled');
}
}
};
(window as any).__SVELTEKIT_DEBUG__.init();
});
}
private async setupViteHMRMonitoring(): Promise<void> {
if (!this.page) return;
await this.page.addInitScript(() => {
(window as any).__VITE_HMR_DEBUG__ = {
events: [],
init: function() {
// Listen for Vite HMR events
if ((window as any).__vite_plugin_react_preamble_installed__) {
console.log('Vite HMR monitoring enabled');
// Monitor import.meta.hot
if ((import.meta as any).hot) {
(import.meta as any).hot.on('vite:beforeUpdate', (payload: any) => {
this.events.push({
type: 'update',
payload,
timestamp: new Date().toISOString()
});
});
(import.meta as any).hot.on('vite:error', (payload: any) => {
this.events.push({
type: 'error',
payload,
timestamp: new Date().toISOString()
});
});
}
}
}
};
// Vite may not be ready immediately
setTimeout(() => (window as any).__VITE_HMR_DEBUG__.init(), 100);
});
}
// Remix specific methods
async getRemixLoaderData(): Promise<RemixLoaderData[]> {
if (!this.page || this.frameworkInfo?.framework !== 'remix') return [];
const data = await this.page.evaluate(() => {
return (window as any).__REMIX_DEBUG__?.loaderData || [];
});
return data.map((d: any) => ({
...d,
timestamp: new Date(d.timestamp)
}));
}
async getRemixRouteModules(): Promise<any> {
if (!this.page || this.frameworkInfo?.framework !== 'remix') return null;
return await this.page.evaluate(() => {
const context = (window as any).__remixContext;
if (!context) return null;
return {
routes: Object.keys(context.routeModules || {}),
currentRoute: context.matches?.[context.matches.length - 1]?.route.id,
manifest: context.manifest ? {
version: context.manifest.version,
routes: Object.keys(context.manifest.routes || {})
} : null
};
});
}
// Astro specific methods
async getAstroIslands(): Promise<AstroIsland[]> {
if (!this.page || this.frameworkInfo?.framework !== 'astro') return [];
const islands = await this.page.evaluate(() => {
const islandElements = document.querySelectorAll('astro-island');
return Array.from(islandElements).map(island => ({
uid: island.getAttribute('uid'),
component: island.getAttribute('component-url') || '',
props: JSON.parse(island.getAttribute('props') || '{}'),
hydrationDirective: (
island.hasAttribute('client:load') ? 'load' :
island.hasAttribute('client:idle') ? 'idle' :
island.hasAttribute('client:visible') ? 'visible' :
island.hasAttribute('client:media') ? 'media' :
island.hasAttribute('client:only') ? 'only' : undefined
) as 'load' | 'idle' | 'visible' | 'media' | 'only' | undefined,
loading: island.hasAttribute('client:load') ? 'eager' : 'lazy' as 'eager' | 'lazy'
}));
});
return islands;
}
// Nuxt specific methods
async getNuxtPayload(): Promise<any> {
if (!this.page || this.frameworkInfo?.framework !== 'nuxt') return null;
return await this.page.evaluate(() => {
const nuxt = (window as any).__NUXT__ || (window as any).$nuxt;
if (!nuxt) return null;
return {
isHydrating: nuxt.isHydrating,
data: nuxt.payload?.data ? Object.keys(nuxt.payload.data) : [],
state: nuxt.payload?.state ? Object.keys(nuxt.payload.state) : [],
error: nuxt.error,
serverRendered: nuxt.serverRendered !== false
};
});
}
// Qwik specific methods
async getQwikResumability(): Promise<QwikResumabilityInfo | null> {
if (!this.page || this.frameworkInfo?.framework !== 'qwik') return null;
return await this.page.evaluate(() => {
const container = document.querySelector('[q\\:base]');
if (!container) return null;
const state = container.getAttribute('q:state');
const info = (window as any).__QWIK_DEBUG__?.resumabilityInfo;
return {
serializedState: state ? state.length : 0,
qrlCount: document.querySelectorAll('[on\\:click]').length,
symbolsLoaded: (window as any).__QWIK_DEBUG__?.qrlLoads?.length || 0,
resumeTime: 0 // Would need performance timing
};
});
}
// Vite HMR methods
async getViteHMREvents(): Promise<ViteHMREvent[]> {
if (!this.page || this.frameworkInfo?.buildTool !== 'vite') return [];
const events = await this.page.evaluate(() => {
return (window as any).__VITE_HMR_DEBUG__?.events || [];
});
return events.map((e: any) => ({
...e,
timestamp: new Date(e.timestamp)
}));
}
// Generic problem detection
async detectMetaFrameworkProblems(): Promise<Array<{
problem: string;
severity: 'low' | 'medium' | 'high';
description: string;
solution: string;
}>> {
const problems: Array<{
problem: string;
severity: 'low' | 'medium' | 'high';
description: string;
solution: string;
}> = [];
if (!this.frameworkInfo?.framework) {
return problems;
}
switch (this.frameworkInfo.framework) {
case 'remix':
const loaderData = await this.getRemixLoaderData();
const slowLoaders = loaderData.filter(l => l.duration > 1000);
if (slowLoaders.length > 0) {
problems.push({
problem: 'Slow Remix Loaders',
severity: 'medium' as const,
description: `${slowLoaders.length} loader functions take over 1 second`,
solution: 'Optimize database queries, add caching, or use defer() for non-critical data'
});
}
break;
case 'astro':
const islands = await this.getAstroIslands();
const eagerIslands = islands.filter(i => i.hydrationDirective === 'load');
if (eagerIslands.length > 5) {
problems.push({
problem: 'Too Many Eager Islands',
severity: 'medium' as const,
description: `${eagerIslands.length} components hydrate immediately on page load`,
solution: 'Use client:idle or client:visible for non-critical components'
});
}
break;
case 'nuxt':
const payload = await this.getNuxtPayload();
if (payload?.data?.length > 20) {
problems.push({
problem: 'Large Nuxt Payload',
severity: 'medium' as const,
description: `Payload contains ${payload.data.length} data keys`,
solution: 'Consider lazy-loading data or paginating large datasets'
});
}
break;
case 'qwik':
const resumability = await this.getQwikResumability();
if (resumability && resumability.serializedState > 100000) {
problems.push({
problem: 'Large Qwik Serialized State',
severity: 'high' as const,
description: `Serialized state is ${(resumability.serializedState / 1024).toFixed(2)}KB`,
solution: 'Reduce component state or move data to server-side stores'
});
}
break;
}
// Vite HMR issues
if (this.frameworkInfo.buildTool === 'vite') {
const hmrEvents = await this.getViteHMREvents();
const errors = hmrEvents.filter(e => e.type === 'error');
if (errors.length > 0) {
problems.push({
problem: 'Vite HMR Errors',
severity: 'high' as const,
description: `${errors.length} HMR errors detected during development`,
solution: 'Check console for specific errors and ensure proper HMR boundaries'
});
}
}
return problems;
}
getFrameworkInfo(): MetaFrameworkInfo | undefined {
return this.frameworkInfo;
}
}