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