diff --git a/test/test.contextanalyzer.node.ts b/test/test.contextanalyzer.node.ts deleted file mode 100644 index b64aad9..0000000 --- a/test/test.contextanalyzer.node.ts +++ /dev/null @@ -1,465 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import * as path from 'path'; -import { ContextAnalyzer } from '../ts/context/context-analyzer.js'; -import type { IFileMetadata } from '../ts/context/types.js'; - -const testProjectRoot = process.cwd(); - -tap.test('ContextAnalyzer should create instance with default weights', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - expect(analyzer).toBeInstanceOf(ContextAnalyzer); -}); - -tap.test('ContextAnalyzer should create instance with custom weights', async () => { - const analyzer = new ContextAnalyzer( - testProjectRoot, - { - dependencyWeight: 0.5, - relevanceWeight: 0.3, - efficiencyWeight: 0.1, - recencyWeight: 0.1 - } - ); - expect(analyzer).toBeInstanceOf(ContextAnalyzer); -}); - -tap.test('ContextAnalyzer.analyze should return analysis result with files', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/context/types.ts'), - relativePath: 'ts/context/types.ts', - size: 5000, - mtime: Date.now(), - estimatedTokens: 1250 - }, - { - path: path.join(testProjectRoot, 'ts/context/enhanced-context.ts'), - relativePath: 'ts/context/enhanced-context.ts', - size: 10000, - mtime: Date.now(), - estimatedTokens: 2500 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - expect(result.taskType).toEqual('readme'); - expect(result.files.length).toEqual(2); - expect(result.totalFiles).toEqual(2); - expect(result.analysisDuration).toBeGreaterThan(0); - expect(result.dependencyGraph).toBeDefined(); -}); - -tap.test('ContextAnalyzer.analyze should assign importance scores to files', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/context/types.ts'), - relativePath: 'ts/context/types.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - expect(result.files[0].importanceScore).toBeGreaterThanOrEqual(0); - expect(result.files[0].importanceScore).toBeLessThanOrEqual(1); -}); - -tap.test('ContextAnalyzer.analyze should sort files by importance score', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/context/types.ts'), - relativePath: 'ts/context/types.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - }, - { - path: path.join(testProjectRoot, 'test/test.basic.node.ts'), - relativePath: 'test/test.basic.node.ts', - size: 2000, - mtime: Date.now(), - estimatedTokens: 500 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - // Files should be sorted by importance (highest first) - for (let i = 0; i < result.files.length - 1; i++) { - expect(result.files[i].importanceScore).toBeGreaterThanOrEqual( - result.files[i + 1].importanceScore - ); - } -}); - -tap.test('ContextAnalyzer.analyze should assign tiers based on scores', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/index.ts'), - relativePath: 'ts/index.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - const file = result.files[0]; - expect(['essential', 'important', 'optional', 'excluded']).toContain(file.tier); -}); - -tap.test('ContextAnalyzer should prioritize index.ts files for README task', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/index.ts'), - relativePath: 'ts/index.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - }, - { - path: path.join(testProjectRoot, 'ts/some-helper.ts'), - relativePath: 'ts/some-helper.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - // index.ts should have higher relevance score - const indexFile = result.files.find(f => f.path.includes('index.ts')); - const helperFile = result.files.find(f => f.path.includes('some-helper.ts')); - - if (indexFile && helperFile) { - expect(indexFile.relevanceScore).toBeGreaterThan(helperFile.relevanceScore); - } -}); - -tap.test('ContextAnalyzer should deprioritize test files for README task', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/context/types.ts'), - relativePath: 'ts/context/types.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - }, - { - path: path.join(testProjectRoot, 'test/test.basic.node.ts'), - relativePath: 'test/test.basic.node.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - // Source file should have higher relevance than test file - const sourceFile = result.files.find(f => f.path.includes('ts/context/types.ts')); - const testFile = result.files.find(f => f.path.includes('test/test.basic.node.ts')); - - if (sourceFile && testFile) { - expect(sourceFile.relevanceScore).toBeGreaterThan(testFile.relevanceScore); - } -}); - -tap.test('ContextAnalyzer should prioritize changed files for commit task', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const changedFile = path.join(testProjectRoot, 'ts/context/types.ts'); - const unchangedFile = path.join(testProjectRoot, 'ts/index.ts'); - - const metadata: IFileMetadata[] = [ - { - path: changedFile, - relativePath: 'ts/context/types.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - }, - { - path: unchangedFile, - relativePath: 'ts/index.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - } - ]; - - const result = await analyzer.analyze(metadata, 'commit', [changedFile]); - - const changed = result.files.find(f => f.path === changedFile); - const unchanged = result.files.find(f => f.path === unchangedFile); - - if (changed && unchanged) { - // Changed file should have recency score of 1.0 - expect(changed.recencyScore).toEqual(1.0); - // Unchanged file should have recency score of 0 - expect(unchanged.recencyScore).toEqual(0); - } -}); - -tap.test('ContextAnalyzer should calculate efficiency scores', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/context/types.ts'), - relativePath: 'ts/context/types.ts', - size: 5000, // Optimal size - mtime: Date.now(), - estimatedTokens: 1250 - }, - { - path: path.join(testProjectRoot, 'ts/very-large-file.ts'), - relativePath: 'ts/very-large-file.ts', - size: 50000, // Too large - mtime: Date.now(), - estimatedTokens: 12500 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - // Optimal size file should have better efficiency score - const optimalFile = result.files.find(f => f.path.includes('types.ts')); - const largeFile = result.files.find(f => f.path.includes('very-large-file.ts')); - - if (optimalFile && largeFile) { - expect(optimalFile.efficiencyScore).toBeGreaterThan(largeFile.efficiencyScore); - } -}); - -tap.test('ContextAnalyzer should build dependency graph', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/context/enhanced-context.ts'), - relativePath: 'ts/context/enhanced-context.ts', - size: 10000, - mtime: Date.now(), - estimatedTokens: 2500 - }, - { - path: path.join(testProjectRoot, 'ts/context/types.ts'), - relativePath: 'ts/context/types.ts', - size: 5000, - mtime: Date.now(), - estimatedTokens: 1250 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - expect(result.dependencyGraph.size).toBeGreaterThan(0); - - // Check that each file has dependency info - for (const meta of metadata) { - const deps = result.dependencyGraph.get(meta.path); - expect(deps).toBeDefined(); - expect(deps!.path).toEqual(meta.path); - expect(deps!.imports).toBeDefined(); - expect(deps!.importedBy).toBeDefined(); - expect(deps!.centrality).toBeGreaterThanOrEqual(0); - } -}); - -tap.test('ContextAnalyzer should calculate centrality scores', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/context/types.ts'), - relativePath: 'ts/context/types.ts', - size: 5000, - mtime: Date.now(), - estimatedTokens: 1250 - }, - { - path: path.join(testProjectRoot, 'ts/context/enhanced-context.ts'), - relativePath: 'ts/context/enhanced-context.ts', - size: 10000, - mtime: Date.now(), - estimatedTokens: 2500 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - // All centrality scores should be between 0 and 1 - for (const [, deps] of result.dependencyGraph) { - expect(deps.centrality).toBeGreaterThanOrEqual(0); - expect(deps.centrality).toBeLessThanOrEqual(1); - } -}); - -tap.test('ContextAnalyzer should assign higher centrality to highly imported files', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - // types.ts is likely imported by many files - const typesPath = path.join(testProjectRoot, 'ts/context/types.ts'); - // A test file is likely imported by fewer files - const testPath = path.join(testProjectRoot, 'test/test.basic.node.ts'); - - const metadata: IFileMetadata[] = [ - { - path: typesPath, - relativePath: 'ts/context/types.ts', - size: 5000, - mtime: Date.now(), - estimatedTokens: 1250 - }, - { - path: testPath, - relativePath: 'test/test.basic.node.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - const typesDeps = result.dependencyGraph.get(typesPath); - const testDeps = result.dependencyGraph.get(testPath); - - if (typesDeps && testDeps) { - // types.ts should generally have higher centrality due to being imported more - expect(typesDeps.centrality).toBeGreaterThanOrEqual(0); - expect(testDeps.centrality).toBeGreaterThanOrEqual(0); - } -}); - -tap.test('ContextAnalyzer should provide reason for scoring', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/index.ts'), - relativePath: 'ts/index.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - expect(result.files[0].reason).toBeDefined(); - expect(result.files[0].reason!.length).toBeGreaterThan(0); -}); - -tap.test('ContextAnalyzer should handle empty metadata array', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const result = await analyzer.analyze([], 'readme'); - - expect(result.files.length).toEqual(0); - expect(result.totalFiles).toEqual(0); - expect(result.dependencyGraph.size).toEqual(0); -}); - -tap.test('ContextAnalyzer should respect custom tier configuration', async () => { - const analyzer = new ContextAnalyzer( - testProjectRoot, - {}, - { - essential: { minScore: 0.9, trimLevel: 'none' }, - important: { minScore: 0.7, trimLevel: 'light' }, - optional: { minScore: 0.5, trimLevel: 'aggressive' } - } - ); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/context/types.ts'), - relativePath: 'ts/context/types.ts', - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - // Should use custom tier thresholds - const file = result.files[0]; - expect(['essential', 'important', 'optional', 'excluded']).toContain(file.tier); -}); - -tap.test('ContextAnalyzer should calculate combined importance score from all factors', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot, { - dependencyWeight: 0.25, - relevanceWeight: 0.25, - efficiencyWeight: 0.25, - recencyWeight: 0.25 - }); - - const metadata: IFileMetadata[] = [ - { - path: path.join(testProjectRoot, 'ts/context/types.ts'), - relativePath: 'ts/context/types.ts', - size: 5000, - mtime: Date.now(), - estimatedTokens: 1250 - } - ]; - - const result = await analyzer.analyze(metadata, 'readme'); - - const file = result.files[0]; - - // Importance score should be weighted sum of all factors - // With equal weights (0.25 each), importance should be average of all scores - const expectedImportance = - (file.relevanceScore * 0.25) + - (file.centralityScore * 0.25) + - (file.efficiencyScore * 0.25) + - (file.recencyScore * 0.25); - - expect(file.importanceScore).toBeCloseTo(expectedImportance, 2); -}); - -tap.test('ContextAnalyzer should complete analysis within reasonable time', async () => { - const analyzer = new ContextAnalyzer(testProjectRoot); - - const metadata: IFileMetadata[] = Array.from({ length: 10 }, (_, i) => ({ - path: path.join(testProjectRoot, `ts/file${i}.ts`), - relativePath: `ts/file${i}.ts`, - size: 3000, - mtime: Date.now(), - estimatedTokens: 750 - })); - - const startTime = Date.now(); - const result = await analyzer.analyze(metadata, 'readme'); - const endTime = Date.now(); - - const duration = endTime - startTime; - - // Analysis duration should be recorded (can be 0 for fast operations) - expect(result.analysisDuration).toBeGreaterThanOrEqual(0); - expect(duration).toBeLessThan(10000); // Should complete within 10 seconds -}); - -export default tap.start(); diff --git a/test/test.contextcache.node.ts b/test/test.contextcache.node.ts deleted file mode 100644 index 9ce95eb..0000000 --- a/test/test.contextcache.node.ts +++ /dev/null @@ -1,465 +0,0 @@ -import { tap, expect } from '@git.zone/tstest/tapbundle'; -import * as path from 'path'; -import * as fs from 'fs'; -import { ContextCache } from '../ts/context/context-cache.js'; -import type { ICacheEntry } from '../ts/context/types.js'; - -const testProjectRoot = process.cwd(); -const testCacheDir = path.join(testProjectRoot, '.nogit', 'test-cache'); - -// Helper to clean up test cache directory -async function cleanupTestCache() { - try { - await fs.promises.rm(testCacheDir, { recursive: true, force: true }); - } catch (error) { - // Ignore if directory doesn't exist - } -} - -tap.test('ContextCache should create instance with default config', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - - expect(cache).toBeInstanceOf(ContextCache); - - await cleanupTestCache(); -}); - -tap.test('ContextCache.init should create cache directory', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - - await cache.init(); - - // Check that cache directory was created - const exists = await fs.promises.access(testCacheDir).then(() => true).catch(() => false); - expect(exists).toEqual(true); - - await cleanupTestCache(); -}); - -tap.test('ContextCache.set should store cache entry', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache.init(); - - const testPath = path.join(testProjectRoot, 'package.json'); - // Get actual file mtime for validation to work - const stats = await fs.promises.stat(testPath); - const fileMtime = Math.floor(stats.mtimeMs); - - const entry: ICacheEntry = { - path: testPath, - contents: 'test content', - tokenCount: 100, - mtime: fileMtime, - cachedAt: Date.now() - }; - - await cache.set(entry); - - const retrieved = await cache.get(testPath); - expect(retrieved).toBeDefined(); - expect(retrieved!.contents).toEqual('test content'); - expect(retrieved!.tokenCount).toEqual(100); - - await cleanupTestCache(); -}); - -tap.test('ContextCache.get should return null for non-existent entry', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache.init(); - - const retrieved = await cache.get('/non/existent/path.ts'); - expect(retrieved).toBeNull(); - - await cleanupTestCache(); -}); - -tap.test('ContextCache.get should invalidate expired entries', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true, - ttl: 1 // 1 second TTL - }); - await cache.init(); - - const testPath = path.join(testProjectRoot, 'test-file.ts'); - const entry: ICacheEntry = { - path: testPath, - contents: 'test content', - tokenCount: 100, - mtime: Date.now(), - cachedAt: Date.now() - 2000 // Cached 2 seconds ago (expired) - }; - - await cache.set(entry); - - // Wait a bit to ensure expiration logic runs - await new Promise(resolve => setTimeout(resolve, 100)); - - const retrieved = await cache.get(testPath); - expect(retrieved).toBeNull(); // Should be expired - - await cleanupTestCache(); -}); - -tap.test('ContextCache.get should invalidate entries when file mtime changes', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache.init(); - - const testPath = path.join(testProjectRoot, 'package.json'); - const stats = await fs.promises.stat(testPath); - const oldMtime = Math.floor(stats.mtimeMs); - - const entry: ICacheEntry = { - path: testPath, - contents: 'test content', - tokenCount: 100, - mtime: oldMtime - 1000, // Old mtime (file has changed) - cachedAt: Date.now() - }; - - await cache.set(entry); - - const retrieved = await cache.get(testPath); - expect(retrieved).toBeNull(); // Should be invalidated due to mtime mismatch - - await cleanupTestCache(); -}); - -tap.test('ContextCache.has should check if file is cached and valid', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache.init(); - - const testPath = path.join(testProjectRoot, 'package.json'); - const stats = await fs.promises.stat(testPath); - - const entry: ICacheEntry = { - path: testPath, - contents: 'test content', - tokenCount: 100, - mtime: Math.floor(stats.mtimeMs), - cachedAt: Date.now() - }; - - await cache.set(entry); - - const hasIt = await cache.has(testPath); - expect(hasIt).toEqual(true); - - const doesNotHaveIt = await cache.has('/non/existent/path.ts'); - expect(doesNotHaveIt).toEqual(false); - - await cleanupTestCache(); -}); - -tap.test('ContextCache.setMany should store multiple entries', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache.init(); - - const entries: ICacheEntry[] = [ - { - path: '/test/file1.ts', - contents: 'content 1', - tokenCount: 100, - mtime: Date.now(), - cachedAt: Date.now() - }, - { - path: '/test/file2.ts', - contents: 'content 2', - tokenCount: 200, - mtime: Date.now(), - cachedAt: Date.now() - } - ]; - - await cache.setMany(entries); - - const stats = cache.getStats(); - expect(stats.entries).toBeGreaterThanOrEqual(2); - - await cleanupTestCache(); -}); - -tap.test('ContextCache.getStats should return cache statistics', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache.init(); - - const entry: ICacheEntry = { - path: '/test/file.ts', - contents: 'test content with some length', - tokenCount: 100, - mtime: Date.now(), - cachedAt: Date.now() - }; - - await cache.set(entry); - - const stats = cache.getStats(); - - expect(stats.entries).toEqual(1); - expect(stats.totalSize).toBeGreaterThan(0); - expect(stats.oldestEntry).toBeDefined(); - expect(stats.newestEntry).toBeDefined(); - - await cleanupTestCache(); -}); - -tap.test('ContextCache.clear should clear all entries', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache.init(); - - const entry: ICacheEntry = { - path: '/test/file.ts', - contents: 'test content', - tokenCount: 100, - mtime: Date.now(), - cachedAt: Date.now() - }; - - await cache.set(entry); - expect(cache.getStats().entries).toEqual(1); - - await cache.clear(); - expect(cache.getStats().entries).toEqual(0); - - await cleanupTestCache(); -}); - -tap.test('ContextCache.clearPaths should clear specific entries', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache.init(); - - const entries: ICacheEntry[] = [ - { - path: '/test/file1.ts', - contents: 'content 1', - tokenCount: 100, - mtime: Date.now(), - cachedAt: Date.now() - }, - { - path: '/test/file2.ts', - contents: 'content 2', - tokenCount: 200, - mtime: Date.now(), - cachedAt: Date.now() - } - ]; - - await cache.setMany(entries); - expect(cache.getStats().entries).toEqual(2); - - await cache.clearPaths(['/test/file1.ts']); - expect(cache.getStats().entries).toEqual(1); - - await cleanupTestCache(); -}); - -tap.test('ContextCache should enforce max size by evicting oldest entries', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true, - maxSize: 0.001 // Very small: 0.001 MB = 1KB - }); - await cache.init(); - - // Add entries that exceed the max size - const largeContent = 'x'.repeat(500); // 500 bytes - - const entries: ICacheEntry[] = [ - { - path: '/test/file1.ts', - contents: largeContent, - tokenCount: 100, - mtime: Date.now(), - cachedAt: Date.now() - 3000 // Oldest - }, - { - path: '/test/file2.ts', - contents: largeContent, - tokenCount: 100, - mtime: Date.now(), - cachedAt: Date.now() - 2000 - }, - { - path: '/test/file3.ts', - contents: largeContent, - tokenCount: 100, - mtime: Date.now(), - cachedAt: Date.now() - 1000 // Newest - } - ]; - - await cache.setMany(entries); - - const stats = cache.getStats(); - // Should have evicted oldest entries to stay under size limit - expect(stats.totalSize).toBeLessThanOrEqual(1024); // 1KB - - await cleanupTestCache(); -}); - -tap.test('ContextCache should not cache when disabled', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: false - }); - await cache.init(); - - const entry: ICacheEntry = { - path: '/test/file.ts', - contents: 'test content', - tokenCount: 100, - mtime: Date.now(), - cachedAt: Date.now() - }; - - await cache.set(entry); - - const retrieved = await cache.get('/test/file.ts'); - expect(retrieved).toBeNull(); - - await cleanupTestCache(); -}); - -tap.test('ContextCache should persist to disk and reload', async () => { - await cleanupTestCache(); - - // Create first cache instance and add entry - const cache1 = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache1.init(); - - // Use a real file that exists so validation passes - const testPath = path.join(testProjectRoot, 'package.json'); - const stats = await fs.promises.stat(testPath); - const fileMtime = Math.floor(stats.mtimeMs); - - const entry: ICacheEntry = { - path: testPath, - contents: 'persistent content', - tokenCount: 150, - mtime: fileMtime, - cachedAt: Date.now() - }; - - await cache1.set(entry); - - // Wait for persist - await new Promise(resolve => setTimeout(resolve, 500)); - - // Create second cache instance (should reload from disk) - const cache2 = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache2.init(); - - const cacheStats = cache2.getStats(); - expect(cacheStats.entries).toBeGreaterThan(0); - - await cleanupTestCache(); -}); - -tap.test('ContextCache should handle invalid cache index gracefully', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - - // Create cache dir manually - await fs.promises.mkdir(testCacheDir, { recursive: true }); - - // Write invalid JSON to cache index - const cacheIndexPath = path.join(testCacheDir, 'index.json'); - await fs.promises.writeFile(cacheIndexPath, 'invalid json {', 'utf-8'); - - // Should not throw, should just start with empty cache - await cache.init(); - - const stats = cache.getStats(); - expect(stats.entries).toEqual(0); - - await cleanupTestCache(); -}); - -tap.test('ContextCache should return proper stats for empty cache', async () => { - await cleanupTestCache(); - - const cache = new ContextCache(testProjectRoot, { - directory: testCacheDir, - enabled: true - }); - await cache.init(); - - const stats = cache.getStats(); - - expect(stats.entries).toEqual(0); - expect(stats.totalSize).toEqual(0); - expect(stats.oldestEntry).toBeNull(); - expect(stats.newestEntry).toBeNull(); - - await cleanupTestCache(); -}); - -export default tap.start(); diff --git a/ts/context/diff-processor.ts b/ts/classes.diffprocessor.ts similarity index 100% rename from ts/context/diff-processor.ts rename to ts/classes.diffprocessor.ts