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

626 lines • 24.7 kB
/** * Python Backend Debug Engine * Handles Python code analysis, test debugging, Pydantic validation, and database schema issues */ import { execSync } from 'child_process'; import { readFileSync, existsSync, statSync } from 'fs'; import { join, extname } from 'path'; export class PythonBackendEngine { pythonExecutable; constructor() { // Try to find Python executable this.pythonExecutable = this.findPythonExecutable(); } findPythonExecutable() { const candidates = ['python3', 'python', 'py']; for (const candidate of candidates) { try { execSync(`${candidate} --version`, { stdio: 'pipe' }); return candidate; } catch { continue; } } return 'python3'; // Default fallback } async analyzeTests(testFilePath, testOutput, includeStackTrace = true) { try { // Check if path exists if (!existsSync(testFilePath)) { throw new Error(`Test file/directory not found: ${testFilePath}`); } const isDirectory = statSync(testFilePath).isDirectory(); const result = { summary: { total: 0, passed: 0, failed: 0, skipped: 0, errors: 0 }, failures: [], errorAnalysis: [], recommendations: [] }; // If we have test output, parse it if (testOutput) { this.parseTestOutput(testOutput, result); } else { // Run pytest to get test results await this.runPytestAnalysis(testFilePath, result); } // Analyze the test file(s) for structure and potential issues if (isDirectory) { await this.analyzeTestDirectory(testFilePath, result); } else { await this.analyzeTestFile(testFilePath, result); } // Generate recommendations this.generateTestRecommendations(result); return result; } catch (error) { throw new Error(`Failed to analyze Python tests: ${error instanceof Error ? error.message : String(error)}`); } } async validatePydanticModels(modelFilePath, validationError, testData) { try { if (!existsSync(modelFilePath)) { throw new Error(`Model file not found: ${modelFilePath}`); } const result = { models: [], errors: [], fieldMismatches: [], suggestions: [] }; // Parse the Python file to extract Pydantic models const fileContent = readFileSync(modelFilePath, 'utf8'); await this.parsePydanticModels(fileContent, result); // If validation error provided, analyze it if (validationError) { this.parseValidationError(validationError, result); } // If test data provided, validate against models if (testData) { await this.validateTestData(testData, result); } // Generate suggestions this.generatePydanticSuggestions(result); return result; } catch (error) { throw new Error(`Failed to validate Pydantic models: ${error instanceof Error ? error.message : String(error)}`); } } async debugDatabaseSchema(schemaFilePath, migrationError, databaseType) { try { if (!existsSync(schemaFilePath)) { throw new Error(`Schema file not found: ${schemaFilePath}`); } const result = { tables: [], migrationIssues: [], inconsistencies: [], recommendations: [] }; // Parse schema file based on type const fileContent = readFileSync(schemaFilePath, 'utf8'); const fileExt = extname(schemaFilePath); if (fileExt === '.py') { await this.parsePythonSchemaFile(fileContent, result); } else if (fileExt === '.sql') { await this.parseSqlSchemaFile(fileContent, result); } // Analyze migration error if provided if (migrationError) { this.analyzeMigrationError(migrationError, result); } // Check for schema inconsistencies this.checkSchemaConsistency(result); // Generate recommendations this.generateSchemaRecommendations(result, databaseType); return result; } catch (error) { throw new Error(`Failed to debug database schema: ${error instanceof Error ? error.message : String(error)}`); } } async analyzeBackendLogic(codeFilePath, errorMessage, logContext, analysisType = 'general') { try { if (!existsSync(codeFilePath)) { throw new Error(`Code file not found: ${codeFilePath}`); } const result = { structure: { functions: 0, classes: 0, lines: 0 }, logicIssues: [], suggestions: [] }; // Analyze code structure const fileContent = readFileSync(codeFilePath, 'utf8'); await this.analyzeCodeStructure(fileContent, result); // Analyze specific error if provided if (errorMessage) { result.errorAnalysis = this.analyzeRuntimeError(errorMessage, fileContent); } // Analyze based on specific type switch (analysisType) { case 'api_endpoints': await this.analyzeApiEndpoints(fileContent, result); break; case 'data_processing': await this.analyzeDataProcessing(fileContent, result); break; case 'business_logic': await this.analyzeBusinessLogic(fileContent, result); break; default: await this.performGeneralAnalysis(fileContent, result); } // Generate suggestions this.generateLogicSuggestions(result, analysisType); return result; } catch (error) { throw new Error(`Failed to analyze backend logic: ${error instanceof Error ? error.message : String(error)}`); } } async debugImports(projectPath, importError) { try { if (!existsSync(projectPath)) { throw new Error(`Project path not found: ${projectPath}`); } const result = { modules: [], errors: [], pathIssues: [], solutions: [] }; // Analyze project structure await this.analyzeProjectStructure(projectPath, result); // If import error provided, analyze it if (importError) { this.parseImportError(importError, result); } // Check Python path issues this.checkPythonPath(projectPath, result); // Generate solutions this.generateImportSolutions(result); return result; } catch (error) { throw new Error(`Failed to debug Python imports: ${error instanceof Error ? error.message : String(error)}`); } } async analyzeApiIntegration(apiEndpointPath, requestData, responseError, frameworkType = 'fastapi') { try { if (!existsSync(apiEndpointPath)) { throw new Error(`API endpoint file not found: ${apiEndpointPath}`); } const result = { endpoints: [], issues: [], recommendations: [] }; // Parse API endpoints based on framework const fileContent = readFileSync(apiEndpointPath, 'utf8'); await this.parseApiEndpoints(fileContent, frameworkType, result); // Analyze request/response if provided if (requestData) { await this.analyzeRequestData(requestData, result); } if (responseError) { this.analyzeResponseError(responseError, result); } // Validate API integration result.validation = await this.validateApiIntegration(result.endpoints, requestData); // Generate recommendations this.generateApiRecommendations(result, frameworkType); return result; } catch (error) { throw new Error(`Failed to analyze API integration: ${error instanceof Error ? error.message : String(error)}`); } } // Private helper methods for parsing and analysis parseTestOutput(output, result) { const lines = output.split('\n'); // Parse pytest output patterns for (const line of lines) { if (line.includes('passed') || line.includes('failed') || line.includes('error')) { // Extract test counts const passedMatch = line.match(/(\d+) passed/); const failedMatch = line.match(/(\d+) failed/); const errorMatch = line.match(/(\d+) error/); const skippedMatch = line.match(/(\d+) skipped/); if (passedMatch) result.summary.passed = parseInt(passedMatch[1]); if (failedMatch) result.summary.failed = parseInt(failedMatch[1]); if (errorMatch) result.summary.errors = parseInt(errorMatch[1]); if (skippedMatch) result.summary.skipped = parseInt(skippedMatch[1]); } // Parse failure details if (line.includes('FAILED')) { const failureMatch = line.match(/FAILED (.+?) - (.+)/); if (failureMatch) { result.failures.push({ test_name: failureMatch[1], error_message: failureMatch[2], file_path: '' }); } } } result.summary.total = result.summary.passed + result.summary.failed + result.summary.errors + result.summary.skipped; } async runPytestAnalysis(testPath, result) { try { // Run pytest with verbose output const command = `${this.pythonExecutable} -m pytest ${testPath} -v --tb=short`; const output = execSync(command, { encoding: 'utf8', timeout: 30000 }); this.parseTestOutput(output, result); } catch (error) { // pytest returns non-zero exit code for failed tests, so parse the output anyway if (error.stdout) { this.parseTestOutput(error.stdout, result); } } } async analyzeTestFile(filePath, result) { const content = readFileSync(filePath, 'utf8'); // Count test functions const testFunctions = content.match(/def test_\w+/g) || []; result.summary.total = testFunctions.length; // Look for common test issues if (content.includes('assert False')) { result.errorAnalysis.push({ type: 'Hard-coded failure', description: 'Found assert False statements that will always fail', severity: 'high', suggestions: ['Replace assert False with proper test conditions'] }); } if (!content.includes('import pytest') && content.includes('pytest.')) { result.errorAnalysis.push({ type: 'Missing import', description: 'Using pytest functions without importing pytest', severity: 'medium', suggestions: ['Add "import pytest" at the top of the file'] }); } } async analyzeTestDirectory(dirPath, result) { // This would recursively analyze all test files in the directory // For now, just add a general analysis result.errorAnalysis.push({ type: 'Directory analysis', description: `Analyzing test directory: ${dirPath}`, severity: 'low', suggestions: ['Ensure all test files follow naming convention test_*.py'] }); } generateTestRecommendations(result) { if (result.summary.failed > 0) { result.recommendations.push('Focus on fixing failed tests first'); } if (result.summary.total === 0) { result.recommendations.push('No tests found. Consider adding test functions with names starting with "test_"'); } if (result.errorAnalysis.length > 0) { result.recommendations.push('Address the identified test issues for better reliability'); } } async parsePydanticModels(content, result) { // Parse Pydantic model definitions const modelMatches = content.match(/class\s+(\w+)\s*\([^)]*BaseModel[^)]*\):/g) || []; for (const match of modelMatches) { const nameMatch = match.match(/class\s+(\w+)/); if (nameMatch) { result.models.push({ name: nameMatch[1], fields: [] // Would parse field definitions in a real implementation }); } } } parseValidationError(error, result) { // Parse Pydantic validation error messages if (error.includes('validation error')) { result.errors.push({ field: 'unknown', message: error, error_type: 'validation_error' }); } } async validateTestData(testData, result) { try { const data = JSON.parse(testData); // Would validate data against model schemas in a real implementation } catch { result.errors.push({ field: 'data', message: 'Invalid JSON format in test data', error_type: 'json_error' }); } } generatePydanticSuggestions(result) { if (result.errors.length > 0) { result.suggestions.push('Check field types and constraints in your Pydantic models'); } if (result.models.length === 0) { result.suggestions.push('No Pydantic models found. Ensure they inherit from BaseModel'); } } async parsePythonSchemaFile(content, result) { // Parse SQLAlchemy or Django model definitions const tableMatches = content.match(/class\s+(\w+)\s*\([^)]*Model[^)]*\):/g) || []; for (const match of tableMatches) { const nameMatch = match.match(/class\s+(\w+)/); if (nameMatch) { result.tables.push({ name: nameMatch[1], columns: [], // Would parse column definitions indexes: [], foreign_keys: [] }); } } } async parseSqlSchemaFile(content, result) { // Parse SQL CREATE TABLE statements const tableMatches = content.match(/CREATE\s+TABLE\s+(\w+)/gi) || []; for (const match of tableMatches) { const nameMatch = match.match(/CREATE\s+TABLE\s+(\w+)/i); if (nameMatch) { result.tables.push({ name: nameMatch[1], columns: [], // Would parse column definitions indexes: [], foreign_keys: [] }); } } } analyzeMigrationError(error, result) { if (error.includes('column') && error.includes('does not exist')) { result.migrationIssues.push({ type: 'missing_column', description: 'Referenced column does not exist in database' }); } } checkSchemaConsistency(result) { // Check for common schema issues for (const table of result.tables) { if (table.columns.length === 0) { result.inconsistencies.push({ table: table.name, issue: 'Table has no columns defined', severity: 'high' }); } } } generateSchemaRecommendations(result, databaseType) { if (result.migrationIssues.length > 0) { result.recommendations.push('Run database migrations to sync schema changes'); } if (result.inconsistencies.length > 0) { result.recommendations.push('Address schema inconsistencies before deployment'); } } async analyzeCodeStructure(content, result) { // Count functions and classes const functions = content.match(/def\s+\w+/g) || []; const classes = content.match(/class\s+\w+/g) || []; const lines = content.split('\n').length; result.structure = { functions: functions.length, classes: classes.length, lines: lines }; } analyzeRuntimeError(error, content) { return { type: 'Runtime Error', rootCause: 'Unknown - requires deeper analysis', location: 'Check stack trace for exact location', suggestions: ['Add proper error handling', 'Validate input parameters'] }; } async analyzeApiEndpoints(content, result) { // Look for FastAPI/Flask endpoint patterns const endpoints = content.match(/@\w+\.(get|post|put|delete)/g) || []; if (endpoints.length > 0) { result.logicIssues.push({ type: 'api_analysis', description: `Found ${endpoints.length} API endpoints`, severity: 'low' }); } } async analyzeDataProcessing(content, result) { // Look for data processing patterns if (content.includes('pandas') || content.includes('numpy')) { result.logicIssues.push({ type: 'data_processing', description: 'Data processing code detected', severity: 'low' }); } } async analyzeBusinessLogic(content, result) { // Analyze business logic patterns if (content.includes('if') && content.includes('else')) { result.logicIssues.push({ type: 'business_logic', description: 'Complex conditional logic detected', severity: 'medium' }); } } async performGeneralAnalysis(content, result) { // General code analysis if (content.length > 10000) { result.logicIssues.push({ type: 'code_size', description: 'Large file - consider breaking into smaller modules', severity: 'medium' }); } } generateLogicSuggestions(result, analysisType) { if (result.structure.lines > 1000) { result.suggestions.push('Consider breaking large files into smaller modules'); } if (result.logicIssues.length > 0) { result.suggestions.push('Address identified logic issues for better maintainability'); } } async analyzeProjectStructure(projectPath, result) { // Analyze Python project structure // This would recursively scan for Python modules result.modules.push({ name: 'project_root', path: projectPath, available: true }); } parseImportError(error, result) { if (error.includes('ModuleNotFoundError')) { const moduleMatch = error.match(/No module named '([^']+)'/); if (moduleMatch) { result.errors.push({ module: moduleMatch[1], message: error, type: 'ModuleNotFoundError' }); } } } checkPythonPath(projectPath, result) { // Check for common Python path issues if (!existsSync(join(projectPath, '__init__.py'))) { result.pathIssues.push({ type: 'missing_init', description: 'Missing __init__.py file in project root', path: projectPath }); } } generateImportSolutions(result) { if (result.errors.length > 0) { result.solutions.push('Install missing packages using pip install'); } if (result.pathIssues.length > 0) { result.solutions.push('Add __init__.py files to make directories into Python packages'); } } async parseApiEndpoints(content, framework, result) { // Parse API endpoints based on framework switch (framework) { case 'fastapi': this.parseFastApiEndpoints(content, result); break; case 'flask': this.parseFlaskEndpoints(content, result); break; case 'django': this.parseDjangoEndpoints(content, result); break; default: this.parseGenericEndpoints(content, result); } } parseFastApiEndpoints(content, result) { const endpoints = content.match(/@\w+\.(get|post|put|delete)\s*\(\s*["']([^"']+)["']/g) || []; for (const endpoint of endpoints) { const match = endpoint.match(/@\w+\.(\w+)\s*\(\s*["']([^"']+)["']/); if (match) { result.endpoints.push({ method: match[1].toUpperCase(), path: match[2], status: 'unknown' }); } } } parseFlaskEndpoints(content, result) { const endpoints = content.match(/@\w+\.route\s*\(\s*["']([^"']+)["'][^)]*methods\s*=\s*\[["']([^"']+)["']\]/g) || []; for (const endpoint of endpoints) { const match = endpoint.match(/@\w+\.route\s*\(\s*["']([^"']+)["'][^)]*methods\s*=\s*\[["']([^"']+)["']\]/); if (match) { result.endpoints.push({ method: match[2].toUpperCase(), path: match[1], status: 'unknown' }); } } } parseDjangoEndpoints(content, result) { // Django URL patterns const patterns = content.match(/path\s*\(\s*["']([^"']+)["']/g) || []; for (const pattern of patterns) { const match = pattern.match(/path\s*\(\s*["']([^"']+)["']/); if (match) { result.endpoints.push({ method: 'GET', // Default for Django path: match[1], status: 'unknown' }); } } } parseGenericEndpoints(content, result) { // Generic endpoint detection result.endpoints.push({ method: 'GET', path: '/api/generic', status: 'unknown' }); } async analyzeRequestData(requestData, result) { try { JSON.parse(requestData); // Valid JSON request data } catch { result.issues.push({ type: 'invalid_request', description: 'Request data is not valid JSON', severity: 'high' }); } } analyzeResponseError(error, result) { if (error.includes('422')) { result.issues.push({ type: 'validation_error', description: 'Request validation failed (422 Unprocessable Entity)', severity: 'medium' }); } } async validateApiIntegration(endpoints, requestData) { return { requestValid: requestData ? true : false, responseValid: true, schemaMatches: true }; } generateApiRecommendations(result, framework) { if (result.endpoints.length === 0) { result.recommendations.push(`No ${framework} endpoints found. Check your API route definitions`); } if (result.issues.length > 0) { result.recommendations.push('Address API integration issues for better reliability'); } } } //# sourceMappingURL=python-backend-engine.js.map