/** * DiffCache — in-memory TTL cache for git diff results. * * Keyed by `phaseId:headHash` (or `phaseId:headHash:filePath` for per-file diffs). * TTL defaults to 5 minutes, configurable via REVIEW_DIFF_CACHE_TTL_MS env var. * Prefix-based invalidation clears all entries for a phase when a new commit lands. */ import type { FileStatEntry } from '../git/types.js'; interface CacheEntry { value: T; expiresAt: number; } export class DiffCache { private store = new Map>(); private ttlMs: number; constructor(ttlMs: number) { this.ttlMs = ttlMs; } get(key: string): T | undefined { const entry = this.store.get(key); if (!entry) return undefined; if (Date.now() > entry.expiresAt) { this.store.delete(key); return undefined; } return entry.value; } set(key: string, value: T): void { this.store.set(key, { value, expiresAt: Date.now() + this.ttlMs }); } invalidateByPrefix(prefix: string): void { for (const key of this.store.keys()) { if (key.startsWith(prefix)) this.store.delete(key); } } } // --------------------------------------------------------------------------- // Response shapes (mirror the return types of getPhaseReviewDiff / getFileDiff) // --------------------------------------------------------------------------- export interface PhaseMetaResponse { phaseName: string; sourceBranch: string; targetBranch: string; files: FileStatEntry[]; totalAdditions: number; totalDeletions: number; } export interface FileDiffResponse { binary: boolean; rawDiff: string; } // --------------------------------------------------------------------------- // Singleton instances — TTL is read once at module load time // --------------------------------------------------------------------------- const TTL = parseInt(process.env.REVIEW_DIFF_CACHE_TTL_MS ?? '300000', 10); export const phaseMetaCache = new DiffCache(TTL); export const fileDiffCache = new DiffCache(TTL);