UNPKG

erosolar-cli

Version:

Unified AI agent framework for the command line - Multi-provider support with schema-driven tools, code intelligence, and transparent reasoning

282 lines 11.3 kB
/** * Comprehensive Tool Validation System * * Provides proactive validation for AI tool usage with: * - Pre-flight validation before tool execution * - Real-time AI flow guidance * - Pattern-based anti-pattern detection * - Recovery suggestions for common failures */ /** * Comprehensive tool validator */ export class ToolValidator { patterns = [ { pattern: 'edit_without_read', description: 'Attempting Edit tool without first reading the file', guidance: 'ALWAYS use Read tool before Edit tool to get exact text including whitespace and indentation', examples: [ 'Edit tool called without preceding Read tool', 'old_string contains guessed content instead of exact file content' ] }, { pattern: 'broad_search_pattern', description: 'Using overly broad search patterns that may cause context overflow', guidance: 'Use specific search patterns like "**/*.ts" or "src/**/*.js" instead of "*" or "."', examples: [ 'Glob(".") or Glob("*") without head_limit', 'Search patterns that could return thousands of files' ] }, { pattern: 'redundant_git_operations', description: 'Running multiple git status calls or inefficient git command chains', guidance: 'Use single combined commands: "git add -A && git commit -m msg && git push"', examples: [ 'git status → git add → git status → git commit → git push', 'Multiple git status calls in sequence' ] }, { pattern: 'incomplete_npm_publish', description: 'Incomplete npm publish workflow', guidance: 'Use npm_publish tool for complete workflow: version bump → build → publish → push', examples: [ 'Running npm publish without version bump', 'Manual publish without build step' ] }, { pattern: 'sequential_file_reads', description: 'Reading files sequentially when parallel execution is possible', guidance: 'Use parallel tool calls for independent file reads to improve performance', examples: [ 'Multiple Read calls in separate tool calls', 'Sequential file operations that could be parallelized' ] }, { pattern: 'validation_after_each_edit', description: 'Running validation after each individual edit', guidance: 'Complete ALL edits first, run ONE validation at the end only if needed', examples: [ 'Running type-check after each file edit', 'Running build/test after every small change' ] } ]; /** * Validate tool usage before execution */ validateToolUsage(tool, context) { const warnings = []; const suggestions = []; const criticalErrors = []; // Tool-specific validation this.validateToolSpecific(tool, context, warnings, suggestions, criticalErrors); // AI flow pattern validation this.validateAIFlowPatterns(tool, context, warnings, suggestions); // Performance optimization validation this.validatePerformance(tool, context, warnings, suggestions); return { valid: criticalErrors.length === 0, warnings, suggestions, criticalErrors }; } /** * Validate tool-specific patterns */ validateToolSpecific(tool, context, warnings, suggestions, criticalErrors) { switch (tool.name) { case 'Edit': this.validateEditTool(tool, context, warnings, criticalErrors); break; case 'Glob': case 'Grep': this.validateSearchTools(tool, context, warnings, suggestions); break; case 'execute_bash': this.validateBashCommands(tool, context, warnings, suggestions); break; } } /** * Validate Edit tool usage */ validateEditTool(tool, context, warnings, criticalErrors) { const params = tool.parameters; // Check for placeholder patterns in old_string if (params.old_string && this.containsPlaceholderPattern(params.old_string)) { warnings.push({ code: 'EDIT_PLACEHOLDER_DETECTED', message: 'Edit tool old_string contains placeholder patterns', severity: 'high', suggestion: 'Use Read tool first to get exact file content instead of guessing' }); } // Check for long single lines (likely guessed content) if (params.old_string && this.isLongSingleLine(params.old_string)) { warnings.push({ code: 'EDIT_LONG_SINGLE_LINE', message: 'Edit tool old_string is a long single line (likely guessed content)', severity: 'medium', suggestion: 'Read the file first to get exact multi-line content' }); } // Check for indentation mismatches if (params.old_string && params.new_string && this.hasIndentationMismatch(params.old_string, params.new_string)) { warnings.push({ code: 'EDIT_INDENTATION_MISMATCH', message: 'Edit tool old_string and new_string have indentation mismatches', severity: 'high', suggestion: 'Ensure exact whitespace matching between old and new strings' }); } } /** * Validate search tools (Glob, Grep) */ validateSearchTools(tool, context, warnings, suggestions) { const params = tool.parameters; // Check for overly broad patterns if (params.pattern && this.isOverlyBroadPattern(params.pattern)) { warnings.push({ code: 'SEARCH_OVERLY_BROAD', message: `Search pattern "${params.pattern}" is overly broad`, severity: 'medium', suggestion: 'Use specific patterns like "**/*.ts" or add head_limit parameter' }); } // Suggest head_limit for broad patterns if (params.pattern && this.isBroadPattern(params.pattern) && !params.head_limit) { suggestions.push({ code: 'SEARCH_SUGGEST_HEAD_LIMIT', message: 'Broad search pattern detected without head_limit', action: 'Add head_limit parameter to prevent context overflow' }); } } /** * Validate bash commands */ validateBashCommands(tool, context, warnings, suggestions) { const params = tool.parameters; if (!params.command) return; // Check for redundant git operations if (this.containsRedundantGitOperations(params.command)) { warnings.push({ code: 'GIT_REDUNDANT_OPERATIONS', message: 'Redundant git operations detected', severity: 'low', suggestion: 'Use combined git commands: "git add -A && git commit -m msg && git push"' }); } // Check for incomplete npm publish if (this.isIncompleteNpmPublish(params.command)) { suggestions.push({ code: 'NPM_INCOMPLETE_PUBLISH', message: 'Incomplete npm publish workflow detected', action: 'Use npm_publish tool for complete workflow automation' }); } } /** * Validate AI flow patterns */ validateAIFlowPatterns(tool, context, warnings, suggestions) { // Check for sequential file reads if (tool.name === 'read_file' && context.workspaceContext?.recentOperations) { const recentReads = context.workspaceContext.recentOperations .filter(op => op === 'read_file') .length; if (recentReads > 2) { suggestions.push({ code: 'PARALLEL_READ_SUGGESTION', message: 'Multiple sequential file reads detected', action: 'Use parallel tool calls (e.g., read_files) for independent file reads to improve performance' }); } } } /** * Validate performance optimizations */ validatePerformance(tool, context, warnings, suggestions) { // Add performance optimization suggestions if (tool.name === 'execute_bash' && context.workspaceContext?.recentOperations) { const recentValidations = context.workspaceContext.recentOperations .filter(op => op.includes('validate') || op.includes('test') || op.includes('build')) .length; if (recentValidations > 1) { warnings.push({ code: 'EXCESSIVE_VALIDATION', message: 'Multiple validation operations detected', severity: 'low', suggestion: 'Complete ALL edits first, run ONE validation at the end only if needed' }); } } } // Helper methods containsPlaceholderPattern(text) { const patterns = [ /\.\.\./, /\[.*\]/, /\/\/.*\.\.\./, /TODO/, /FIXME/ ]; return patterns.some(pattern => pattern.test(text)); } isLongSingleLine(text) { const lines = text.split('\n'); return lines.length === 1 && text.length > 100; } hasIndentationMismatch(oldString, newString) { const oldIndent = this.detectIndentation(oldString); const newIndent = this.detectIndentation(newString); return oldIndent !== newIndent; } detectIndentation(text) { const firstLine = text.split('\n')[0] || ''; const match = firstLine.match(/^(\s+)/); return match?.[1] ?? ''; } isOverlyBroadPattern(pattern) { const broadPatterns = ['.', '*', '**']; return broadPatterns.includes(pattern); } isBroadPattern(pattern) { return pattern.includes('*') && !pattern.includes('.'); } containsRedundantGitOperations(command) { const gitStatusPattern = /git status/g; const matches = command.match(gitStatusPattern); return matches ? matches.length > 1 : false; } isIncompleteNpmPublish(command) { return command.includes('npm publish') && !command.includes('npm version') && !command.includes('npm run build'); } /** * Get AI flow guidance for a specific pattern */ getAIFlowGuidance(patternId) { return this.patterns.find(p => p.pattern === patternId); } /** * Get all AI flow patterns */ getAllAIFlowPatterns() { return [...this.patterns]; } } // Global tool validator instance export const globalToolValidator = new ToolValidator(); //# sourceMappingURL=toolValidation.js.map