UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

150 lines (149 loc) 5.05 kB
import logger from '../../logger.js'; export class CacheManager { cache = new Map(); accessOrder = new Map(); stats; config; accessCounter = 0; constructor(config = {}) { this.config = { maxEntries: config.maxEntries || 1000, defaultTtl: config.defaultTtl || 5 * 60 * 1000, maxMemoryUsage: config.maxMemoryUsage || 50 * 1024 * 1024, enableStats: config.enableStats !== false }; this.stats = { totalEntries: 0, hitRate: 0, memoryUsage: 0, evictions: 0, avgQueryTime: 0 }; logger.debug({ config: this.config }, 'Cache manager initialized'); } generateKey(query, options) { const keyData = { query, strategy: options.searchStrategy || 'fuzzy', fileTypes: options.fileTypes?.sort(), maxResults: options.maxResults, caseSensitive: options.caseSensitive, minScore: options.minScore, excludeDirs: options.excludeDirs?.sort() }; return JSON.stringify(keyData); } get(query, options) { if (!options.cacheResults) return null; const key = this.generateKey(query, options); const entry = this.cache.get(key); if (!entry) { logger.debug({ query }, 'Cache miss'); return null; } const now = Date.now(); if (now - entry.timestamp.getTime() > entry.ttl) { logger.debug({ query }, 'Cache entry expired'); this.cache.delete(key); this.accessOrder.delete(key); this.updateStats(); return null; } entry.hitCount++; this.accessOrder.set(key, ++this.accessCounter); this.updateStats(); logger.debug({ query, hitCount: entry.hitCount }, 'Cache hit'); return entry.results; } set(query, options, results) { if (!options.cacheResults) return; const key = this.generateKey(query, options); const ttl = this.config.defaultTtl; const entry = { query, options: { ...options }, results: [...results], timestamp: new Date(), ttl, hitCount: 0 }; this.evictIfNecessary(); this.cache.set(key, entry); this.accessOrder.set(key, ++this.accessCounter); this.updateStats(); logger.debug({ query, resultsCount: results.length }, 'Results cached'); } clear(projectPath) { if (projectPath) { const keysToDelete = []; for (const [key, entry] of this.cache.entries()) { if (entry.results.some(result => result.filePath.startsWith(projectPath))) { keysToDelete.push(key); } } keysToDelete.forEach(key => { this.cache.delete(key); this.accessOrder.delete(key); }); logger.info({ projectPath, clearedEntries: keysToDelete.length }, 'Cache cleared for project'); } else { const totalEntries = this.cache.size; this.cache.clear(); this.accessOrder.clear(); this.accessCounter = 0; logger.info({ clearedEntries: totalEntries }, 'Cache cleared completely'); } this.updateStats(); } getStats() { return { ...this.stats }; } evictIfNecessary() { if (this.cache.size >= this.config.maxEntries) { this.evictLRU(); } const memoryUsage = this.calculateMemoryUsage(); if (memoryUsage > this.config.maxMemoryUsage) { this.evictLRU(); } } evictLRU() { let oldestKey = null; let oldestAccess = Infinity; for (const [key, accessTime] of this.accessOrder.entries()) { if (accessTime < oldestAccess) { oldestAccess = accessTime; oldestKey = key; } } if (oldestKey) { this.cache.delete(oldestKey); this.accessOrder.delete(oldestKey); this.stats.evictions++; logger.debug({ evictedKey: oldestKey }, 'Evicted LRU cache entry'); } } calculateMemoryUsage() { let totalSize = 0; for (const entry of this.cache.values()) { totalSize += JSON.stringify(entry).length * 2; } return totalSize; } updateStats() { if (!this.config.enableStats) return; this.stats.totalEntries = this.cache.size; this.stats.memoryUsage = this.calculateMemoryUsage(); let totalHits = 0; let totalAccesses = 0; for (const entry of this.cache.values()) { totalHits += entry.hitCount; totalAccesses += entry.hitCount + 1; } this.stats.hitRate = totalAccesses > 0 ? totalHits / totalAccesses : 0; } }