UNPKG

@claude-vector/claude-tools

Version:

Claude integration tools for AI-powered development assistance

480 lines (404 loc) 14.5 kB
/** * Query Optimizer for Claude Vector Search * Optimizes search queries based on task context and learning * * Responsibilities: * - Optimize queries for specific task types * - Expand queries with synonyms and related terms * - Learn from search feedback * - Apply task-specific search strategies */ import { promises as fs } from 'fs'; import path from 'path'; export class QueryOptimizer { constructor(config = {}) { // Configuration this.learningEnabled = config.learningEnabled !== false; this.historyFile = config.historyFile || path.join(process.cwd(), '.claude-query-history.json'); // Query expansion dictionaries this.synonyms = { // Programming concepts 'error': ['exception', 'bug', 'issue', 'problem', 'fault', 'failure'], 'fix': ['repair', 'resolve', 'patch', 'correct', 'remedy'], 'implement': ['create', 'build', 'develop', 'code', 'add'], 'function': ['method', 'procedure', 'routine', 'fn', 'func'], 'class': ['object', 'type', 'model', 'entity'], 'variable': ['var', 'const', 'let', 'property', 'field'], 'authenticate': ['auth', 'login', 'signin', 'verify', 'validate'], 'database': ['db', 'storage', 'persistence', 'datastore'], 'api': ['endpoint', 'route', 'service', 'interface'], 'test': ['spec', 'unit test', 'integration test', 'check', 'verify'], // Common abbreviations 'auth': ['authentication', 'authorization'], 'db': ['database'], 'api': ['application programming interface'], 'ui': ['user interface', 'frontend'], 'ux': ['user experience'], // Task-specific terms 'refactor': ['restructure', 'reorganize', 'improve', 'clean up'], 'optimize': ['improve', 'enhance', 'speed up', 'performance'], 'debug': ['troubleshoot', 'diagnose', 'trace', 'investigate'] }; // Task-specific query patterns this.taskPatterns = { 'bug-fix': { prefixes: ['error', 'exception', 'bug', 'issue with'], suffixes: ['not working', 'fails', 'broken', 'throws error'], boostTerms: ['fix', 'solution', 'resolved', 'patch'] }, 'feature': { prefixes: ['implement', 'create', 'add', 'new'], suffixes: ['example', 'implementation', 'how to', 'pattern'], boostTerms: ['example', 'sample', 'template', 'boilerplate'] }, 'refactor': { prefixes: ['improve', 'refactor', 'clean up', 'optimize'], suffixes: ['pattern', 'best practice', 'structure', 'design'], boostTerms: ['pattern', 'principle', 'clean code', 'solid'] }, 'debug': { prefixes: ['debug', 'trace', 'why', 'investigate'], suffixes: ['stack trace', 'log', 'error message', 'breakpoint'], boostTerms: ['console', 'debugger', 'log', 'trace'] } }; // Learning data this.queryHistory = []; this.feedbackData = new Map(); // query -> feedback scores this.successfulPatterns = new Map(); // pattern -> success count // Load history on initialization this.loadHistory(); } /** * Optimize a query based on context */ async optimizeQuery(query, context = {}) { const { taskType, taskProfile, previousSearches = [] } = context; // Start with original query let optimizedQuery = query.toLowerCase().trim(); // Apply task-specific patterns if (taskType && this.taskPatterns[taskType]) { optimizedQuery = this.applyTaskPatterns(optimizedQuery, taskType); } // Expand with synonyms optimizedQuery = this.expandWithSynonyms(optimizedQuery); // Learn from previous searches in session if (previousSearches.length > 0) { optimizedQuery = this.refineFromHistory(optimizedQuery, previousSearches); } // Apply learned optimizations if (this.learningEnabled) { optimizedQuery = this.applyLearnedOptimizations(optimizedQuery, taskType); } // Record the optimization this.recordQueryOptimization(query, optimizedQuery, context); return optimizedQuery; } /** * Expand query with related terms */ expandQuery(query) { const terms = query.toLowerCase().split(/\s+/); const expanded = new Set(terms); // Add synonyms for each term for (const term of terms) { if (this.synonyms[term]) { this.synonyms[term].forEach(synonym => expanded.add(synonym)); } // Also check if term is a synonym value for (const [key, values] of Object.entries(this.synonyms)) { if (values.includes(term)) { expanded.add(key); values.forEach(v => expanded.add(v)); } } } // Create expanded query return { original: query, expanded: Array.from(expanded).join(' '), terms: Array.from(expanded), addedTerms: Array.from(expanded).filter(t => !terms.includes(t)) }; } /** * Record feedback for a query */ async recordFeedback(query, feedback) { const { useful, resultIds = [], taskType } = feedback; // Store feedback if (!this.feedbackData.has(query)) { this.feedbackData.set(query, []); } this.feedbackData.get(query).push({ timestamp: Date.now(), useful, resultIds, taskType }); // Learn patterns from successful queries if (useful) { this.learnSuccessfulPattern(query, taskType); } // Save history await this.saveHistory(); } /** * Get query suggestions based on context */ getQuerySuggestions(partialQuery, context = {}) { const { taskType } = context; const suggestions = []; // Task-specific suggestions if (taskType && this.taskPatterns[taskType]) { const patterns = this.taskPatterns[taskType]; // Add prefix suggestions patterns.prefixes.forEach(prefix => { suggestions.push(`${prefix} ${partialQuery}`); }); // Add suffix suggestions patterns.suffixes.forEach(suffix => { suggestions.push(`${partialQuery} ${suffix}`); }); } // Add suggestions from successful patterns const relevantPatterns = Array.from(this.successfulPatterns.entries()) .filter(([pattern]) => pattern.includes(partialQuery.toLowerCase())) .sort((a, b) => b[1] - a[1]) // Sort by success count .slice(0, 5) .map(([pattern]) => pattern); suggestions.push(...relevantPatterns); // Add synonym-based suggestions const expanded = this.expandQuery(partialQuery); if (expanded.addedTerms.length > 0) { suggestions.push(expanded.expanded); } // Remove duplicates and return return [...new Set(suggestions)].slice(0, 10); } // Private methods applyTaskPatterns(query, taskType) { const patterns = this.taskPatterns[taskType]; if (!patterns) return query; // Check if query already has task-specific terms const hasPrefix = patterns.prefixes.some(prefix => query.toLowerCase().includes(prefix) ); const hasSuffix = patterns.suffixes.some(suffix => query.toLowerCase().includes(suffix) ); // Don't add boost terms automatically - they make queries too broad let enhanced = query; // Only add prefix for very short queries (1-2 words) and if it makes sense if (!hasPrefix && query.split(' ').length <= 2 && taskType === 'bug-fix') { // For bug-fix, adding "error" prefix can be helpful enhanced = `${patterns.prefixes[0]} ${enhanced}`; } return enhanced.trim(); } expandWithSynonyms(query) { const words = query.split(/\s+/); const expandedWords = []; // Common abbreviations that should be expanded const shouldExpand = { 'auth': 'authentication', 'db': 'database', 'api': 'interface', 'ui': 'interface', 'fn': 'function', 'func': 'function' }; for (const word of words) { expandedWords.push(word); // Only expand known abbreviations if (shouldExpand[word.toLowerCase()]) { expandedWords.push(shouldExpand[word.toLowerCase()]); } } // Remove duplicates while preserving order const seen = new Set(); const result = []; for (const word of expandedWords) { if (!seen.has(word)) { seen.add(word); result.push(word); } } return result.join(' '); } refineFromHistory(query, previousSearches) { // Analyze previous searches for patterns const commonTerms = new Map(); previousSearches.forEach(search => { const terms = search.optimizedQuery?.split(/\s+/) || []; terms.forEach(term => { commonTerms.set(term, (commonTerms.get(term) || 0) + 1); }); }); // Find frequently used terms not in current query const queryTerms = new Set(query.split(/\s+/)); const missingFrequentTerms = Array.from(commonTerms.entries()) .filter(([term, count]) => count >= 2 && !queryTerms.has(term)) .sort((a, b) => b[1] - a[1]) .slice(0, 2) .map(([term]) => term); // Add missing frequent terms if (missingFrequentTerms.length > 0) { return `${query} ${missingFrequentTerms.join(' ')}`; } return query; } applyLearnedOptimizations(query, taskType) { // Find similar successful queries const queryTerms = new Set(query.toLowerCase().split(/\s+/)); let bestMatch = null; let bestScore = 0; for (const [pattern, count] of this.successfulPatterns.entries()) { const patternTerms = pattern.split(/\s+/); const overlap = patternTerms.filter(term => queryTerms.has(term)).length; const score = (overlap / patternTerms.length) * Math.log(count + 1); if (score > bestScore) { bestScore = score; bestMatch = pattern; } } // If we found a good match, blend it with the query if (bestMatch && bestScore > 0.5) { const bestTerms = new Set(bestMatch.split(/\s+/)); const additionalTerms = Array.from(bestTerms) .filter(term => !queryTerms.has(term)) .slice(0, 3); if (additionalTerms.length > 0) { return `${query} ${additionalTerms.join(' ')}`; } } return query; } recordQueryOptimization(original, optimized, context) { this.queryHistory.push({ timestamp: Date.now(), original, optimized, taskType: context.taskType, expansions: optimized.split(' ').filter(term => !original.toLowerCase().includes(term) ) }); // Limit history size if (this.queryHistory.length > 1000) { this.queryHistory = this.queryHistory.slice(-500); } } learnSuccessfulPattern(query, taskType) { const pattern = query.toLowerCase(); const count = this.successfulPatterns.get(pattern) || 0; this.successfulPatterns.set(pattern, count + 1); // Also learn term combinations const terms = pattern.split(/\s+/); if (terms.length >= 2) { for (let i = 0; i < terms.length - 1; i++) { const bigram = `${terms[i]} ${terms[i + 1]}`; const bigramCount = this.successfulPatterns.get(bigram) || 0; this.successfulPatterns.set(bigram, bigramCount + 0.5); } } } async loadHistory() { try { const data = await fs.readFile(this.historyFile, 'utf-8'); const history = JSON.parse(data); this.queryHistory = history.queryHistory || []; this.successfulPatterns = new Map(history.successfulPatterns || []); // Rebuild feedback data if (history.feedbackData) { this.feedbackData = new Map(history.feedbackData); } } catch (error) { // File doesn't exist or is invalid, start fresh this.queryHistory = []; this.successfulPatterns = new Map(); this.feedbackData = new Map(); } } async saveHistory() { if (!this.learningEnabled) return; const history = { queryHistory: this.queryHistory.slice(-500), // Keep last 500 successfulPatterns: Array.from(this.successfulPatterns.entries()) .sort((a, b) => b[1] - a[1]) // Sort by count .slice(0, 200), // Keep top 200 feedbackData: Array.from(this.feedbackData.entries()).slice(-100), // Keep last 100 lastUpdated: new Date().toISOString() }; try { await fs.writeFile(this.historyFile, JSON.stringify(history, null, 2)); } catch (error) { console.error('Failed to save query history:', error); } } /** * Get optimization statistics */ getStats() { return { totalQueries: this.queryHistory.length, successfulPatterns: this.successfulPatterns.size, feedbackEntries: this.feedbackData.size, synonymEntries: Object.keys(this.synonyms).length, taskPatterns: Object.keys(this.taskPatterns).length }; } /** * Get learning history for status display */ async getHistory() { // Load latest history from file to ensure freshness await this.loadHistory(); return { queries: Object.fromEntries(this.feedbackData), expansions: this.getExpansionStats(), successfulPatterns: Array.from(this.successfulPatterns.entries()), queryHistory: this.queryHistory, lastUpdated: new Date().toISOString() }; } /** * Get expansion statistics */ getExpansionStats() { const expansions = {}; // Track usage of common abbreviations const abbrevs = { 'auth': 'authentication', 'db': 'database', 'api': 'interface', 'ui': 'interface', 'fn': 'function', 'func': 'function' }; // Count usage from query history this.queryHistory.forEach(entry => { if (entry.expansions) { entry.expansions.forEach(expansion => { const abbrev = Object.keys(abbrevs).find(key => abbrevs[key] === expansion); if (abbrev) { if (!expansions[abbrev]) { expansions[abbrev] = { expanded: expansion, uses: 0 }; } expansions[abbrev].uses++; } }); } }); return expansions; } /** * Clear learning data */ clearHistory() { this.queryHistory = []; this.successfulPatterns.clear(); this.feedbackData.clear(); } } export default QueryOptimizer;