BOLT / lib /file-search-executor.ts
legends810's picture
Upload folder using huggingface_hub
d83e271 verified
/**
* Agentic file search executor
* Executes search plans to find exact code locations before editing
*/
export interface SearchResult {
filePath: string;
lineNumber: number;
lineContent: string;
matchedTerm?: string;
matchedPattern?: string;
contextBefore: string[];
contextAfter: string[];
confidence: 'high' | 'medium' | 'low';
}
export interface SearchPlan {
editType: string;
reasoning: string;
searchTerms: string[];
regexPatterns?: string[];
fileTypesToSearch?: string[];
expectedMatches?: number;
fallbackSearch?: {
terms: string[];
patterns?: string[];
};
}
export interface SearchExecutionResult {
success: boolean;
results: SearchResult[];
filesSearched: number;
executionTime: number;
usedFallback: boolean;
error?: string;
}
/**
* Execute a search plan against the codebase
*/
export function executeSearchPlan(
searchPlan: SearchPlan,
files: Record<string, string>
): SearchExecutionResult {
const startTime = Date.now();
const results: SearchResult[] = [];
let filesSearched = 0;
let usedFallback = false;
const {
searchTerms = [],
regexPatterns = [],
fileTypesToSearch = ['.jsx', '.tsx', '.js', '.ts'],
fallbackSearch
} = searchPlan;
// Helper function to perform search
const performSearch = (terms: string[], patterns?: string[]): SearchResult[] => {
const searchResults: SearchResult[] = [];
for (const [filePath, content] of Object.entries(files)) {
// Skip files that don't match the desired extensions
const shouldSearch = fileTypesToSearch.some(ext => filePath.endsWith(ext));
if (!shouldSearch) continue;
filesSearched++;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
let matched = false;
let matchedTerm: string | undefined;
let matchedPattern: string | undefined;
// Check simple search terms (case-insensitive)
for (const term of terms) {
if (line.toLowerCase().includes(term.toLowerCase())) {
matched = true;
matchedTerm = term;
break;
}
}
// Check regex patterns if no term match
if (!matched && patterns) {
for (const pattern of patterns) {
try {
const regex = new RegExp(pattern, 'i');
if (regex.test(line)) {
matched = true;
matchedPattern = pattern;
break;
}
} catch (e) {
console.warn(`[file-search] Invalid regex pattern: ${pattern}`);
}
}
}
if (matched) {
// Get context lines (3 before, 3 after)
const contextBefore = lines.slice(Math.max(0, i - 3), i);
const contextAfter = lines.slice(i + 1, Math.min(lines.length, i + 4));
// Determine confidence based on match type and context
let confidence: 'high' | 'medium' | 'low' = 'medium';
// High confidence if it's an exact match or in a component definition
if (matchedTerm && line.includes(matchedTerm)) {
confidence = 'high';
} else if (line.includes('function') || line.includes('export') || line.includes('return')) {
confidence = 'high';
} else if (matchedPattern) {
confidence = 'medium';
}
searchResults.push({
filePath,
lineNumber: i + 1,
lineContent: line.trim(),
matchedTerm,
matchedPattern,
contextBefore,
contextAfter,
confidence
});
}
}
}
return searchResults;
};
// Execute primary search
results.push(...performSearch(searchTerms, regexPatterns));
// If no results and we have a fallback, try it
if (results.length === 0 && fallbackSearch) {
console.log('[file-search] No results from primary search, trying fallback...');
usedFallback = true;
results.push(...performSearch(
fallbackSearch.terms,
fallbackSearch.patterns
));
}
const executionTime = Date.now() - startTime;
// Sort results by confidence
results.sort((a, b) => {
const confidenceOrder = { high: 3, medium: 2, low: 1 };
return confidenceOrder[b.confidence] - confidenceOrder[a.confidence];
});
return {
success: results.length > 0,
results,
filesSearched,
executionTime,
usedFallback,
error: results.length === 0 ? 'No matches found for search terms' : undefined
};
}
/**
* Format search results for AI consumption
*/
export function formatSearchResultsForAI(results: SearchResult[]): string {
if (results.length === 0) {
return 'No search results found.';
}
const sections: string[] = [];
sections.push('πŸ” SEARCH RESULTS - EXACT LOCATIONS FOUND:\n');
// Group by file for better readability
const resultsByFile = new Map<string, SearchResult[]>();
for (const result of results) {
if (!resultsByFile.has(result.filePath)) {
resultsByFile.set(result.filePath, []);
}
resultsByFile.get(result.filePath)!.push(result);
}
for (const [filePath, fileResults] of resultsByFile) {
sections.push(`\nπŸ“„ FILE: ${filePath}`);
for (const result of fileResults) {
sections.push(`\n πŸ“ Line ${result.lineNumber} (${result.confidence} confidence)`);
if (result.matchedTerm) {
sections.push(` Matched: "${result.matchedTerm}"`);
} else if (result.matchedPattern) {
sections.push(` Pattern: ${result.matchedPattern}`);
}
sections.push(` Code: ${result.lineContent}`);
if (result.contextBefore.length > 0 || result.contextAfter.length > 0) {
sections.push(` Context:`);
for (const line of result.contextBefore) {
sections.push(` ${line}`);
}
sections.push(` β†’ ${result.lineContent}`);
for (const line of result.contextAfter) {
sections.push(` ${line}`);
}
}
}
}
sections.push('\n\n🎯 RECOMMENDED ACTION:');
// Recommend the highest confidence result
const bestResult = results[0];
sections.push(`Edit ${bestResult.filePath} at line ${bestResult.lineNumber}`);
return sections.join('\n');
}
/**
* Select the best file to edit based on search results
*/
export function selectTargetFile(
results: SearchResult[],
editType: string
): { filePath: string; lineNumber: number; reason: string } | null {
if (results.length === 0) return null;
// For style updates, prefer components over CSS files
if (editType === 'UPDATE_STYLE') {
const componentResult = results.find(r =>
r.filePath.endsWith('.jsx') || r.filePath.endsWith('.tsx')
);
if (componentResult) {
return {
filePath: componentResult.filePath,
lineNumber: componentResult.lineNumber,
reason: 'Found component with style to update'
};
}
}
// For remove operations, find the component that renders the element
if (editType === 'REMOVE_ELEMENT') {
const renderResult = results.find(r =>
r.lineContent.includes('return') ||
r.lineContent.includes('<')
);
if (renderResult) {
return {
filePath: renderResult.filePath,
lineNumber: renderResult.lineNumber,
reason: 'Found element to remove in render output'
};
}
}
// Default: use highest confidence result
const best = results[0];
return {
filePath: best.filePath,
lineNumber: best.lineNumber,
reason: `Highest confidence match (${best.confidence})`
};
}