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
213 lines • 8.8 kB
JavaScript
import { BaseToolHandler } from './base-handler.js';
import { TestEvolutionEngine } from '../test-evolution-engine.js';
/**
* Handler for test evolution tools
*/
export class EvolutionHandler extends BaseToolHandler {
evolutionEngine;
tools = [
{
name: 'evolve_tests',
description: 'Automatically evolve tests based on uncommitted code changes. Updates selectors, deprecates obsolete tests, and maintains test health.',
inputSchema: {
type: 'object',
properties: {
autoCommit: {
type: 'boolean',
description: 'Automatically commit test updates (default: false)',
default: false
},
dryRun: {
type: 'boolean',
description: 'Preview changes without applying them (default: false)',
default: false
}
}
}
},
{
name: 'evolve_tests_for_file',
description: 'Evolve tests for specific file changes. Useful for targeted test updates.',
inputSchema: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: 'Path to the changed file'
},
oldContent: {
type: 'string',
description: 'Previous content of the file (optional, will use git if not provided)'
},
newContent: {
type: 'string',
description: 'New content of the file (optional, will use current file if not provided)'
}
},
required: ['filePath']
}
},
{
name: 'detect_test_evolution_opportunities',
description: 'Analyze codebase for test evolution opportunities without making changes.',
inputSchema: {
type: 'object',
properties: {
includeSuggestions: {
type: 'boolean',
description: 'Include detailed suggestions for each opportunity (default: true)',
default: true
}
}
}
},
{
name: 'preview_test_evolution',
description: 'Preview what test evolution would do for current uncommitted changes.',
inputSchema: {
type: 'object',
properties: {}
}
}
];
constructor(evolutionEngine) {
super();
this.evolutionEngine = evolutionEngine || new TestEvolutionEngine();
}
async handle(toolName, args, sessions) {
try {
// Call the specific method based on tool name
const methodName = toolName.replace(/-/g, '_');
const method = this[methodName];
if (typeof method === 'function') {
return method.call(this, args);
}
throw new Error(`Unknown tool: ${toolName}`);
}
catch (error) {
return this.createErrorResponse(error);
}
}
async evolve_tests(args) {
try {
const result = await this.evolutionEngine.evolveTestsFromGitChanges({
autoCommit: args.autoCommit || false,
dryRun: args.dryRun || false
});
return this.createTextResponse(JSON.stringify({
success: result.success,
summary: {
filesUpdated: result.filesUpdated.length,
testsEvolved: result.testsEvolved,
errors: result.errors.length
},
details: result,
message: this.generateEvolutionSummary(result)
}, null, 2));
}
catch (error) {
return this.createErrorResponse(error);
}
}
async evolve_tests_for_file(args) {
try {
// Create a mock git change for the specific file
const mockEngine = new TestEvolutionEngine();
// Use private method through reflection (not ideal but works for now)
const result = await mockEngine.analyzeAndEvolveTests({
file: args.filePath,
status: 'modified',
oldContent: args.oldContent,
newContent: args.newContent
}, {
success: false,
filesUpdated: [],
testsEvolved: 0,
errors: [],
suggestions: []
}, { dryRun: true });
return this.createTextResponse(JSON.stringify({
success: true,
file: args.filePath,
result
}, null, 2));
}
catch (error) {
return this.createErrorResponse(error);
}
}
async detect_test_evolution_opportunities(args) {
try {
// Run evolution in dry-run mode to detect opportunities
const result = await this.evolutionEngine.evolveTestsFromGitChanges({
dryRun: true
});
const opportunities = {
totalOpportunities: result.suggestions.length + result.filesUpdated.length,
categories: {
selectorUpdates: result.filesUpdated.filter(f => f.includes('selector')).length,
deprecations: result.suggestions.filter(s => s.includes('deleted')).length,
signatureChanges: result.suggestions.filter(s => s.includes('signature')).length,
propertyChanges: result.suggestions.filter(s => s.includes('props')).length
},
suggestions: args.includeSuggestions ? result.suggestions : [],
affectedFiles: result.filesUpdated
};
return this.createTextResponse(JSON.stringify({
success: true,
opportunities,
message: `Found ${opportunities.totalOpportunities} test evolution opportunities`
}, null, 2));
}
catch (error) {
return this.createErrorResponse(error);
}
}
async preview_test_evolution(args) {
try {
// Run evolution in dry-run mode
const result = await this.evolutionEngine.evolveTestsFromGitChanges({
dryRun: true
});
const preview = {
wouldUpdate: result.filesUpdated,
wouldEvolve: result.testsEvolved,
changes: this.categorizeChanges(result),
suggestions: result.suggestions,
safeToRun: result.errors.length === 0
};
return this.createTextResponse(JSON.stringify({
success: true,
preview,
message: this.generatePreviewMessage(preview)
}, null, 2));
}
catch (error) {
return this.createErrorResponse(error);
}
}
generateEvolutionSummary(result) {
if (!result.success) {
return `Test evolution failed: ${result.errors.join(', ')}`;
}
if (result.filesUpdated.length === 0) {
return 'No test evolution needed - all tests are up to date!';
}
return `Successfully evolved ${result.testsEvolved} tests across ${result.filesUpdated.length} files. ${result.suggestions.length} suggestions for manual review.`;
}
generatePreviewMessage(preview) {
if (preview.wouldUpdate.length === 0) {
return 'No changes would be made - tests are already synchronized with code.';
}
return `Preview: Would update ${preview.wouldUpdate.length} test files with ${preview.wouldEvolve} test changes. ${preview.safeToRun ? 'Safe to run.' : 'Some issues detected - review before running.'}`;
}
categorizeChanges(result) {
return {
selectorUpdates: result.filesUpdated.filter((f) => result.suggestions.some((s) => s.includes('selector') && s.includes(f))).length,
deprecations: result.suggestions.filter((s) => s.includes('deleted') || s.includes('deprecated')).length,
signatureUpdates: result.suggestions.filter((s) => s.includes('signature') || s.includes('params')).length,
newTests: result.suggestions.filter((s) => s.includes('add test') || s.includes('new test')).length
};
}
}
//# sourceMappingURL=evolution-handler.js.map