/** * FILE-LEVEL MEMORY CACHE * * Caches semantic summaries per file to reduce token costs across multiple PRs. * Prevents re-analyzing the same file repeatedly. */ interface FileSummary { path: string summary: string functions: string[] imports: string[] exports: string[] lastAnalyzed: number hash: string } /** * Generate a simple hash of file content for cache validation */ function hashContent(content: string): string { let hash = 0 for (let i = 0; i < content.length; i++) { hash = ((hash << 5) - hash + content.charCodeAt(i)) | 0 } return Math.abs(hash).toString(36) } /** * Extract semantic summary from file content */ function extractSummary( content: string, language: string = 'typescript' ): string { const lines = content.split('\n') const summary: string[] = [] // Extract function definitions const functionRegex = /(?:function|const|let|var)\s+(\w+)\s*(?:=\s*(?:async\s*)?\(|\()/ const functions: string[] = [] // Extract imports const importRegex = /import\s+.*from\s+['"]([^'"]+)['"]/ const imports: string[] = [] // Extract exports const exportRegex = /export\s+(?:const|function|class|interface)\s+(\w+)/ const exports: string[] = [] for (const line of lines) { const funcMatch = line.match(functionRegex) if (funcMatch) functions.push(funcMatch[1]) const importMatch = line.match(importRegex) if (importMatch) imports.push(importMatch[1]) const exportMatch = line.match(exportRegex) if (exportMatch) exports.push(exportMatch[1]) } // Build summary if (functions.length > 0) { summary.push(`Functions: ${functions.slice(0, 5).join(', ')}`) } if (imports.length > 0) { summary.push(`Imports: ${imports.slice(0, 5).join(', ')}`) } if (exports.length > 0) { summary.push(`Exports: ${exports.join(', ')}`) } // Detect patterns if (content.includes('useState') || content.includes('useEffect')) { summary.push('React hooks detected') } if (content.includes('async') || content.includes('await')) { summary.push('Async operations') } if (content.includes('fetch') || content.includes('axios')) { summary.push('HTTP requests') } return summary.join(' | ') } /** * File cache implementation */ class FileCache { private cache: Map = new Map() private readonly TTL_MS = 7 * 24 * 60 * 60 * 1000 // 7 days private readonly MAX_CACHE_SIZE = 1000 /** * Get cached summary for a file */ get(path: string, content: string): FileSummary | null { const cached = this.cache.get(path) if (!cached) return null // Check if expired if (Date.now() - cached.lastAnalyzed > this.TTL_MS) { this.cache.delete(path) return null } // Check if content has changed const currentHash = hashContent(content) if (cached.hash !== currentHash) { this.cache.delete(path) return null } return cached } /** * Set cached summary for a file */ set( path: string, content: string, language: string = 'typescript' ): FileSummary { const summary = extractSummary(content, language) const lines = content.split('\n') const functions: string[] = [] const imports: string[] = [] const exports: string[] = [] for (const line of lines) { const funcMatch = line.match( /(?:function|const|let|var)\s+(\w+)\s*(?:=\s*(?:async\s*)?\(|\()/ ) if (funcMatch) functions.push(funcMatch[1]) const importMatch = line.match(/import\s+.*from\s+['"]([^'"]+)['"]/) if (importMatch) imports.push(importMatch[1]) const exportMatch = line.match( /export\s+(?:const|function|class|interface)\s+(\w+)/ ) if (exportMatch) exports.push(exportMatch[1]) } const fileSummary: FileSummary = { path, summary, functions, imports, exports, lastAnalyzed: Date.now(), hash: hashContent(content) } this.cache.set(path, fileSummary) // Evict old entries if cache is too large if (this.cache.size > this.MAX_CACHE_SIZE) { this.evictOld() } return fileSummary } /** * Evict old entries */ private evictOld(): void { const entries = Array.from(this.cache.entries()) entries.sort((a, b) => a[1].lastAnalyzed - b[1].lastAnalyzed) // Remove oldest 10% of entries const toRemove = Math.floor(entries.length * 0.1) for (let i = 0; i < toRemove; i++) { this.cache.delete(entries[i][0]) } } /** * Clear cache */ clear(): void { this.cache.clear() } /** * Get cache statistics */ getStats(): {size: number; hitRate?: number} { return {size: this.cache.size} } /** * Check if file is in cache */ has(path: string): boolean { return this.cache.has(path) } } // Global file cache instance const fileCache = new FileCache() /** * Get cached summary for a file */ export function getFileSummary( path: string, content: string, language: string = 'typescript' ): FileSummary | null { const cached = fileCache.get(path, content) if (cached) { return cached } // Cache miss - analyze and cache return fileCache.set(path, content, language) } /** * Get summary string for a file (for use in prompts) */ export function getFileSummaryString( path: string, content: string, language: string = 'typescript' ): string { const summary = getFileSummary(path, content, language) if (!summary) { return extractSummary(content, language) } return summary.summary } /** * Clear file cache */ export function clearFileCache(): void { fileCache.clear() } /** * Get file cache statistics */ export function getFileCacheStats(): {size: number} { return fileCache.getStats() } /** * Check if file is cached */ export function isFileCached(path: string): boolean { return fileCache.has(path) }