@claude-vector/claude-tools
Version:
Claude integration tools for AI-powered development assistance
480 lines (404 loc) • 14.5 kB
JavaScript
/**
* 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;