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