PRININIT / src /file-cache.ts
Rachit-Tw's picture
Upload 50 files
7d9309d verified
Raw
History Blame Contribute Delete
6.19 kB
/**
* 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<string, FileSummary> = 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)
}