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.

414 lines (413 loc) 15.1 kB
import fs from 'fs-extra'; import path from 'path'; import { TaskManagerMemoryManager } from './memory-manager-integration.js'; import logger from '../../../logger.js'; export class MultiLevelCache { memoryCache = new Map(); accessOrder = new Map(); accessFrequency = new Map(); config; stats; accessCounter = 0; warmingInterval = null; syncInterval = null; memoryManager = null; constructor(config) { this.config = config; this.stats = { totalEntries: 0, memoryEntries: 0, diskEntries: 0, hitRate: 0, missRate: 0, totalHits: 0, totalMisses: 0, totalMemoryUsage: 0, totalDiskUsage: 0, averageAccessTime: 0, evictionCount: 0 }; this.memoryManager = TaskManagerMemoryManager.getInstance(); this.memoryManager?.registerCleanupCallback('multi-level-cache', () => this.performCleanup()); if (config.diskConfig.enabled) { fs.ensureDirSync(config.diskConfig.directory); } if (config.warming.enabled) { this.startCacheWarming(); } if (config.consistency.enabled) { this.startConsistencySync(); } logger.info({ config }, 'Multi-level cache initialized'); } async get(key) { const startTime = performance.now(); try { const memoryEntry = this.memoryCache.get(key); if (memoryEntry) { const now = Date.now(); if (now - memoryEntry.timestamp.getTime() <= memoryEntry.ttl) { memoryEntry.accessCount++; memoryEntry.lastAccessed = new Date(now); this.accessOrder.set(key, ++this.accessCounter); this.stats.totalHits++; this.stats.hitRate = this.stats.totalHits / (this.stats.totalHits + this.stats.totalMisses); return memoryEntry.value; } else { this.memoryCache.delete(key); this.accessOrder.delete(key); this.accessFrequency.delete(key); } } if (this.config.diskConfig.enabled && this.config.strategy !== 'memory') { const diskEntry = await this.getDiskEntry(key); if (diskEntry && !this.isExpired(diskEntry)) { this.memoryCache.set(key, diskEntry); this.updateAccessMetrics(diskEntry); this.stats.totalHits++; this.updateHitRate(); return diskEntry.value; } } this.stats.totalMisses++; this.stats.hitRate = this.stats.totalHits / (this.stats.totalHits + this.stats.totalMisses); return null; } finally { const accessTime = performance.now() - startTime; this.updateAverageAccessTime(accessTime); } } async set(key, value, ttl, tags = []) { const now = Date.now(); const entry = { key, value, timestamp: new Date(now), ttl: ttl || 300000, accessCount: 0, lastAccessed: new Date(now), size: this.estimateSize(value), tags }; const currentSize = this.memoryCache.size; if (currentSize >= this.config.memoryConfig.maxEntries * 0.9) { await this.evictIfNecessary(); } this.memoryCache.set(key, entry); this.accessOrder.set(key, ++this.accessCounter); this.accessFrequency.set(key, 0); if (this.config.diskConfig.enabled && this.config.strategy !== 'memory') { this.setDiskEntry(key, entry).catch(error => { logger.warn({ err: error, key }, 'Async disk cache write failed'); }); } this.stats.totalEntries = currentSize + 1; this.stats.memoryEntries = this.stats.totalEntries; this.stats.totalMemoryUsage += entry.size; } async delete(key) { let deleted = false; if (this.memoryCache.delete(key)) { this.accessOrder.delete(key); this.accessFrequency.delete(key); deleted = true; } if (this.config.diskConfig.enabled) { const diskDeleted = await this.deleteDiskEntry(key); deleted = deleted || diskDeleted; } if (deleted) { this.updateStats(); logger.debug({ key }, 'Cache entry deleted'); } return deleted; } async clearByTags(tags) { let cleared = 0; for (const [key, entry] of this.memoryCache) { if (entry.tags.some(tag => tags.includes(tag))) { await this.delete(key); cleared++; } } logger.info({ tags, cleared }, 'Cache entries cleared by tags'); return cleared; } isExpired(entry) { return Date.now() - entry.timestamp.getTime() > entry.ttl; } updateAccessMetrics(entry) { entry.accessCount++; entry.lastAccessed = new Date(); this.accessOrder.set(entry.key, ++this.accessCounter); this.accessFrequency.set(entry.key, (this.accessFrequency.get(entry.key) || 0) + 1); } async evictIfNecessary() { const memoryUsage = this.calculateMemoryUsage(); const entryCount = this.memoryCache.size; if (entryCount >= this.config.memoryConfig.maxEntries || memoryUsage >= this.config.memoryConfig.maxMemoryUsage) { const evictCount = Math.max(1, Math.floor(entryCount * 0.1)); await this.evictEntries(evictCount); } } async evictEntries(count) { const entries = Array.from(this.memoryCache.entries()); let toEvict = []; switch (this.config.memoryConfig.evictionPolicy) { case 'lru': toEvict = this.selectLRUEntries(entries, count); break; case 'lfu': toEvict = this.selectLFUEntries(entries, count); break; case 'ttl': toEvict = this.selectTTLEntries(entries, count); break; case 'size': toEvict = this.selectSizeEntries(entries, count); break; case 'hybrid': toEvict = this.selectHybridEntries(entries, count); break; } for (const key of toEvict) { await this.delete(key); this.stats.evictionCount++; } if (toEvict.length > 0) { logger.debug({ evicted: toEvict.length, policy: this.config.memoryConfig.evictionPolicy }, 'Cache entries evicted'); } } selectLRUEntries(entries, count) { return entries .sort((a, b) => (this.accessOrder.get(a[0]) || 0) - (this.accessOrder.get(b[0]) || 0)) .slice(0, count) .map(([key]) => key); } selectLFUEntries(entries, count) { return entries .sort((a, b) => (this.accessFrequency.get(a[0]) || 0) - (this.accessFrequency.get(b[0]) || 0)) .slice(0, count) .map(([key]) => key); } selectTTLEntries(entries, count) { return entries .sort((a, b) => a[1].timestamp.getTime() - b[1].timestamp.getTime()) .slice(0, count) .map(([key]) => key); } selectSizeEntries(entries, count) { return entries .sort((a, b) => b[1].size - a[1].size) .slice(0, count) .map(([key]) => key); } selectHybridEntries(entries, count) { return entries .map(([key, entry]) => ({ key, score: this.calculateEvictionScore(entry) })) .sort((a, b) => a.score - b.score) .slice(0, count) .map(item => item.key); } calculateEvictionScore(entry) { const now = Date.now(); const age = now - entry.timestamp.getTime(); const timeSinceAccess = now - entry.lastAccessed.getTime(); const frequency = this.accessFrequency.get(entry.key) || 0; return (frequency * 0.4) + ((entry.ttl - age) / entry.ttl * 0.3) + ((entry.ttl - timeSinceAccess) / entry.ttl * 0.2) + (1 / (entry.size / 1024) * 0.1); } async getDiskEntry(key) { try { const filePath = path.join(this.config.diskConfig.directory, `${key}.json`); if (await fs.pathExists(filePath)) { const data = await fs.readJson(filePath); return data; } } catch (error) { logger.debug({ err: error, key }, 'Failed to read disk cache entry'); } return null; } async setDiskEntry(key, entry) { try { const filePath = path.join(this.config.diskConfig.directory, `${key}.json`); await fs.writeJson(filePath, entry); } catch (error) { logger.warn({ err: error, key }, 'Failed to write disk cache entry'); } } async deleteDiskEntry(key) { try { const filePath = path.join(this.config.diskConfig.directory, `${key}.json`); if (await fs.pathExists(filePath)) { await fs.remove(filePath); return true; } } catch (error) { logger.warn({ err: error, key }, 'Failed to delete disk cache entry'); } return false; } estimateSize(value) { try { return Buffer.byteLength(JSON.stringify(value)); } catch { return 1024; } } calculateMemoryUsage() { return Array.from(this.memoryCache.values()) .reduce((sum, entry) => sum + entry.size, 0); } updateStats() { this.stats.totalEntries = this.memoryCache.size; this.stats.memoryEntries = this.memoryCache.size; this.stats.totalMemoryUsage = this.calculateMemoryUsage(); } updateHitRate() { const total = this.stats.totalHits + this.stats.totalMisses; this.stats.hitRate = total > 0 ? this.stats.totalHits / total : 0; this.stats.missRate = 1 - this.stats.hitRate; } updateAverageAccessTime(accessTime) { const total = this.stats.totalHits + this.stats.totalMisses; this.stats.averageAccessTime = total > 1 ? (this.stats.averageAccessTime * (total - 1) + accessTime) / total : accessTime; } startCacheWarming() { if (this.config.warming.strategies.includes('scheduled')) { this.warmingInterval = setInterval(() => { this.performCacheWarming(); }, this.config.warming.scheduledInterval); } } async performCacheWarming() { logger.debug('Cache warming performed'); } startConsistencySync() { this.syncInterval = setInterval(() => { this.performConsistencySync(); }, this.config.consistency.syncInterval); } async performConsistencySync() { logger.debug('Consistency sync performed'); } async performCleanup() { const startTime = Date.now(); const initialMemory = this.calculateMemoryUsage(); try { const expiredKeys = Array.from(this.memoryCache.entries()) .filter(([, entry]) => this.isExpired(entry)) .map(([key]) => key); for (const key of expiredKeys) { await this.delete(key); } const currentUsage = this.calculateMemoryUsage(); const threshold = this.config.memoryConfig.maxMemoryUsage * 0.7; if (currentUsage > threshold) { const evictCount = Math.floor(this.memoryCache.size * 0.2); await this.evictEntries(evictCount); } const finalMemory = this.calculateMemoryUsage(); const memoryFreed = Math.max(0, initialMemory - finalMemory); logger.info({ expiredRemoved: expiredKeys.length, memoryFreed: `${Math.round(memoryFreed / 1024 / 1024)}MB` }, 'Cache cleanup completed'); return { success: true, memoryFreed, itemsRemoved: expiredKeys.length, duration: Date.now() - startTime }; } catch (error) { logger.error({ err: error }, 'Cache cleanup failed'); return { success: false, memoryFreed: 0, itemsRemoved: 0, duration: Date.now() - startTime, error: error instanceof Error ? error.message : String(error) }; } } getStatistics() { this.updateStats(); return { ...this.stats }; } async clear() { this.memoryCache.clear(); this.accessOrder.clear(); this.accessFrequency.clear(); if (this.config.diskConfig.enabled) { try { await fs.emptyDir(this.config.diskConfig.directory); } catch (error) { logger.warn({ err: error }, 'Failed to clear disk cache'); } } this.updateStats(); logger.info('Cache cleared'); } async shutdown() { if (this.warmingInterval) { clearInterval(this.warmingInterval); } if (this.syncInterval) { clearInterval(this.syncInterval); } this.memoryManager?.unregisterCleanupCallback('multi-level-cache'); await this.clear(); logger.info('Multi-level cache shutdown'); } } export class CacheFactory { static createCache(name, config) { const cacheConfig = { strategy: config.strategy, memoryConfig: { maxEntries: 1000, maxMemoryUsage: config.maxCacheSize, evictionPolicy: 'hybrid' }, diskConfig: { enabled: config.strategy === 'disk' || config.strategy === 'hybrid', directory: path.join(process.cwd(), 'data', 'cache', name), maxDiskUsage: config.maxCacheSize * 2, compression: false }, warming: { enabled: config.enableWarmup, strategies: ['scheduled'], preloadPatterns: [], predictiveThreshold: 0.8, scheduledInterval: 300000 }, consistency: { enabled: config.strategy === 'hybrid', syncInterval: 60000, conflictResolution: 'memory' } }; return new MultiLevelCache(cacheConfig); } }