update
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { DiffProcessor } from '../ts/context/diff-processor.js';
|
||||
import { DiffProcessor } from '../ts/classes.diffprocessor.js';
|
||||
|
||||
// Sample diff strings for testing
|
||||
const createSmallDiff = (filepath: string, addedLines = 5, removedLines = 3): string => {
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as path from 'path';
|
||||
import { IterativeContextBuilder } from '../ts/context/iterative-context-builder.js';
|
||||
import type { IIterativeConfig, TaskType } from '../ts/context/types.js';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
|
||||
// Test project directory
|
||||
const testProjectRoot = path.join(process.cwd());
|
||||
|
||||
// Helper to check if OPENAI_TOKEN is available
|
||||
async function hasOpenAIToken(): Promise<boolean> {
|
||||
try {
|
||||
const qenvInstance = new qenv.Qenv();
|
||||
const token = await qenvInstance.getEnvVarOnDemand('OPENAI_TOKEN');
|
||||
return !!token;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
tap.test('IterativeContextBuilder should create instance with default config', async () => {
|
||||
const builder = new IterativeContextBuilder(testProjectRoot);
|
||||
expect(builder).toBeInstanceOf(IterativeContextBuilder);
|
||||
});
|
||||
|
||||
tap.test('IterativeContextBuilder should create instance with custom config', async () => {
|
||||
const customConfig: Partial<IIterativeConfig> = {
|
||||
maxIterations: 3,
|
||||
firstPassFileLimit: 5,
|
||||
subsequentPassFileLimit: 3,
|
||||
temperature: 0.5,
|
||||
model: 'gpt-4',
|
||||
};
|
||||
const builder = new IterativeContextBuilder(testProjectRoot, customConfig);
|
||||
expect(builder).toBeInstanceOf(IterativeContextBuilder);
|
||||
});
|
||||
|
||||
tap.test('IterativeContextBuilder should initialize successfully', async () => {
|
||||
if (!(await hasOpenAIToken())) {
|
||||
console.log('⚠️ Skipping initialization test - OPENAI_TOKEN not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const builder = new IterativeContextBuilder(testProjectRoot);
|
||||
await builder.initialize();
|
||||
// If we get here without error, initialization succeeded
|
||||
expect(true).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('IterativeContextBuilder should build context iteratively for readme task', async () => {
|
||||
if (!(await hasOpenAIToken())) {
|
||||
console.log('⚠️ Skipping iterative build test - OPENAI_TOKEN not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const builder = new IterativeContextBuilder(testProjectRoot, {
|
||||
maxIterations: 2, // Limit iterations for testing
|
||||
firstPassFileLimit: 3,
|
||||
subsequentPassFileLimit: 2,
|
||||
});
|
||||
|
||||
await builder.initialize();
|
||||
|
||||
const result = await builder.buildContextIteratively('readme');
|
||||
|
||||
// Verify result structure
|
||||
expect(result).toBeTypeOf('object');
|
||||
expect(result.context).toBeTypeOf('string');
|
||||
expect(result.context.length).toBeGreaterThan(0);
|
||||
expect(result.tokenCount).toBeTypeOf('number');
|
||||
expect(result.tokenCount).toBeGreaterThan(0);
|
||||
expect(result.includedFiles).toBeInstanceOf(Array);
|
||||
expect(result.includedFiles.length).toBeGreaterThan(0);
|
||||
expect(result.iterationCount).toBeTypeOf('number');
|
||||
expect(result.iterationCount).toBeGreaterThan(0);
|
||||
expect(result.iterationCount).toBeLessThanOrEqual(2);
|
||||
expect(result.iterations).toBeInstanceOf(Array);
|
||||
expect(result.iterations.length).toEqual(result.iterationCount);
|
||||
expect(result.apiCallCount).toBeTypeOf('number');
|
||||
expect(result.apiCallCount).toBeGreaterThan(0);
|
||||
expect(result.totalDuration).toBeTypeOf('number');
|
||||
expect(result.totalDuration).toBeGreaterThan(0);
|
||||
|
||||
// Verify iteration structure
|
||||
for (const iteration of result.iterations) {
|
||||
expect(iteration.iteration).toBeTypeOf('number');
|
||||
expect(iteration.filesLoaded).toBeInstanceOf(Array);
|
||||
expect(iteration.tokensUsed).toBeTypeOf('number');
|
||||
expect(iteration.totalTokensUsed).toBeTypeOf('number');
|
||||
expect(iteration.decision).toBeTypeOf('object');
|
||||
expect(iteration.duration).toBeTypeOf('number');
|
||||
}
|
||||
|
||||
console.log(`✅ Iterative context build completed:`);
|
||||
console.log(` Iterations: ${result.iterationCount}`);
|
||||
console.log(` Files: ${result.includedFiles.length}`);
|
||||
console.log(` Tokens: ${result.tokenCount}`);
|
||||
console.log(` API calls: ${result.apiCallCount}`);
|
||||
console.log(` Duration: ${(result.totalDuration / 1000).toFixed(2)}s`);
|
||||
});
|
||||
|
||||
tap.test('IterativeContextBuilder should respect token budget', async () => {
|
||||
if (!(await hasOpenAIToken())) {
|
||||
console.log('⚠️ Skipping token budget test - OPENAI_TOKEN not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const builder = new IterativeContextBuilder(testProjectRoot, {
|
||||
maxIterations: 5,
|
||||
});
|
||||
|
||||
await builder.initialize();
|
||||
|
||||
const result = await builder.buildContextIteratively('description');
|
||||
|
||||
// Token count should not exceed budget significantly (allow 5% margin for safety)
|
||||
const configManager = (await import('../ts/context/config-manager.js')).ConfigManager.getInstance();
|
||||
const maxTokens = configManager.getMaxTokens();
|
||||
expect(result.tokenCount).toBeLessThanOrEqual(maxTokens * 1.05);
|
||||
|
||||
console.log(`✅ Token budget respected: ${result.tokenCount}/${maxTokens}`);
|
||||
});
|
||||
|
||||
tap.test('IterativeContextBuilder should work with different task types', async () => {
|
||||
if (!(await hasOpenAIToken())) {
|
||||
console.log('⚠️ Skipping task types test - OPENAI_TOKEN not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const taskTypes: TaskType[] = ['readme', 'description', 'commit'];
|
||||
|
||||
for (const taskType of taskTypes) {
|
||||
const builder = new IterativeContextBuilder(testProjectRoot, {
|
||||
maxIterations: 2,
|
||||
firstPassFileLimit: 2,
|
||||
});
|
||||
|
||||
await builder.initialize();
|
||||
const result = await builder.buildContextIteratively(taskType);
|
||||
|
||||
expect(result.includedFiles.length).toBeGreaterThan(0);
|
||||
|
||||
console.log(`✅ ${taskType}: ${result.includedFiles.length} files, ${result.tokenCount} tokens`);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -1,243 +0,0 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as path from 'path';
|
||||
import { LazyFileLoader } from '../ts/context/lazy-file-loader.js';
|
||||
import type { IFileMetadata } from '../ts/context/types.js';
|
||||
|
||||
const testProjectRoot = process.cwd();
|
||||
|
||||
tap.test('LazyFileLoader should create instance with project root', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
expect(loader).toBeInstanceOf(LazyFileLoader);
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.getMetadata should return file metadata without loading contents', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
const packageJsonPath = path.join(testProjectRoot, 'package.json');
|
||||
|
||||
const metadata = await loader.getMetadata(packageJsonPath);
|
||||
|
||||
expect(metadata.path).toEqual(packageJsonPath);
|
||||
expect(metadata.relativePath).toEqual('package.json');
|
||||
expect(metadata.size).toBeGreaterThan(0);
|
||||
expect(metadata.mtime).toBeGreaterThan(0);
|
||||
expect(metadata.estimatedTokens).toBeGreaterThan(0);
|
||||
// Rough estimate: size / 4 (with reasonable tolerance)
|
||||
expect(metadata.estimatedTokens).toBeGreaterThan(metadata.size / 5);
|
||||
expect(metadata.estimatedTokens).toBeLessThan(metadata.size / 3);
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.getMetadata should cache metadata for same file', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
const packageJsonPath = path.join(testProjectRoot, 'package.json');
|
||||
|
||||
const metadata1 = await loader.getMetadata(packageJsonPath);
|
||||
const metadata2 = await loader.getMetadata(packageJsonPath);
|
||||
|
||||
// Should return identical metadata from cache
|
||||
expect(metadata1.mtime).toEqual(metadata2.mtime);
|
||||
expect(metadata1.size).toEqual(metadata2.size);
|
||||
expect(metadata1.estimatedTokens).toEqual(metadata2.estimatedTokens);
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.scanFiles should scan TypeScript files', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
|
||||
const metadata = await loader.scanFiles(['ts/context/types.ts']);
|
||||
|
||||
expect(metadata.length).toBeGreaterThan(0);
|
||||
const typesFile = metadata.find(m => m.relativePath.includes('types.ts'));
|
||||
expect(typesFile).toBeDefined();
|
||||
expect(typesFile!.size).toBeGreaterThan(0);
|
||||
expect(typesFile!.estimatedTokens).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.scanFiles should handle multiple globs', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
|
||||
const metadata = await loader.scanFiles([
|
||||
'package.json',
|
||||
'readme.md'
|
||||
]);
|
||||
|
||||
expect(metadata.length).toBeGreaterThanOrEqual(2);
|
||||
const hasPackageJson = metadata.some(m => m.relativePath === 'package.json');
|
||||
const hasReadme = metadata.some(m => m.relativePath.toLowerCase() === 'readme.md');
|
||||
expect(hasPackageJson).toEqual(true);
|
||||
expect(hasReadme).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.loadFile should load file with actual token count', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
const packageJsonPath = path.join(testProjectRoot, 'package.json');
|
||||
|
||||
const tokenizer = (content: string) => Math.ceil(content.length / 4);
|
||||
const fileInfo = await loader.loadFile(packageJsonPath, tokenizer);
|
||||
|
||||
expect(fileInfo.path).toEqual(packageJsonPath);
|
||||
expect(fileInfo.contents).toBeDefined();
|
||||
expect(fileInfo.contents.length).toBeGreaterThan(0);
|
||||
expect(fileInfo.tokenCount).toBeGreaterThan(0);
|
||||
expect(fileInfo.relativePath).toEqual('package.json');
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.loadFiles should load multiple files in parallel', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
|
||||
const metadata: IFileMetadata[] = [
|
||||
{
|
||||
path: path.join(testProjectRoot, 'package.json'),
|
||||
relativePath: 'package.json',
|
||||
size: 100,
|
||||
mtime: Date.now(),
|
||||
estimatedTokens: 25
|
||||
},
|
||||
{
|
||||
path: path.join(testProjectRoot, 'readme.md'),
|
||||
relativePath: 'readme.md',
|
||||
size: 200,
|
||||
mtime: Date.now(),
|
||||
estimatedTokens: 50
|
||||
}
|
||||
];
|
||||
|
||||
const tokenizer = (content: string) => Math.ceil(content.length / 4);
|
||||
const startTime = Date.now();
|
||||
const files = await loader.loadFiles(metadata, tokenizer);
|
||||
const endTime = Date.now();
|
||||
|
||||
expect(files.length).toEqual(2);
|
||||
expect(files[0].contents).toBeDefined();
|
||||
expect(files[1].contents).toBeDefined();
|
||||
|
||||
// Should be fast (parallel loading)
|
||||
expect(endTime - startTime).toBeLessThan(5000); // 5 seconds max
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.updateImportanceScores should update cached metadata', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
const packageJsonPath = path.join(testProjectRoot, 'package.json');
|
||||
|
||||
// Get initial metadata
|
||||
await loader.getMetadata(packageJsonPath);
|
||||
|
||||
// Update importance scores
|
||||
const scores = new Map<string, number>();
|
||||
scores.set(packageJsonPath, 0.95);
|
||||
loader.updateImportanceScores(scores);
|
||||
|
||||
// Check cached metadata has updated score
|
||||
const cached = loader.getCachedMetadata();
|
||||
const packageJsonMeta = cached.find(m => m.path === packageJsonPath);
|
||||
|
||||
expect(packageJsonMeta).toBeDefined();
|
||||
expect(packageJsonMeta!.importanceScore).toEqual(0.95);
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.getTotalEstimatedTokens should sum all cached metadata tokens', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
|
||||
// Scan some files
|
||||
await loader.scanFiles(['package.json', 'readme.md']);
|
||||
|
||||
const totalTokens = loader.getTotalEstimatedTokens();
|
||||
|
||||
expect(totalTokens).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.clearCache should clear metadata cache', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
|
||||
// Scan files to populate cache
|
||||
await loader.scanFiles(['package.json']);
|
||||
expect(loader.getCachedMetadata().length).toBeGreaterThan(0);
|
||||
|
||||
// Clear cache
|
||||
loader.clearCache();
|
||||
|
||||
expect(loader.getCachedMetadata().length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.getCachedMetadata should return all cached entries', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
|
||||
// Scan files
|
||||
await loader.scanFiles(['package.json', 'readme.md']);
|
||||
|
||||
const cached = loader.getCachedMetadata();
|
||||
|
||||
expect(cached.length).toBeGreaterThanOrEqual(2);
|
||||
expect(cached.every(m => m.path && m.size && m.estimatedTokens)).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader should handle non-existent files gracefully', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
const nonExistentPath = path.join(testProjectRoot, 'this-file-does-not-exist.ts');
|
||||
|
||||
try {
|
||||
await loader.getMetadata(nonExistentPath);
|
||||
expect(false).toEqual(true); // Should not reach here
|
||||
} catch (error) {
|
||||
expect(error).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader.loadFiles should filter out failed file loads', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
|
||||
const metadata: IFileMetadata[] = [
|
||||
{
|
||||
path: path.join(testProjectRoot, 'package.json'),
|
||||
relativePath: 'package.json',
|
||||
size: 100,
|
||||
mtime: Date.now(),
|
||||
estimatedTokens: 25
|
||||
},
|
||||
{
|
||||
path: path.join(testProjectRoot, 'non-existent-file.txt'),
|
||||
relativePath: 'non-existent-file.txt',
|
||||
size: 100,
|
||||
mtime: Date.now(),
|
||||
estimatedTokens: 25
|
||||
}
|
||||
];
|
||||
|
||||
const tokenizer = (content: string) => Math.ceil(content.length / 4);
|
||||
const files = await loader.loadFiles(metadata, tokenizer);
|
||||
|
||||
// Should only include the successfully loaded file
|
||||
expect(files.length).toEqual(1);
|
||||
expect(files[0].relativePath).toEqual('package.json');
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader should handle glob patterns for TypeScript source files', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
|
||||
const metadata = await loader.scanFiles(['ts/context/*.ts']);
|
||||
|
||||
expect(metadata.length).toBeGreaterThan(0);
|
||||
|
||||
// Should find multiple context files
|
||||
const hasEnhancedContext = metadata.some(m => m.relativePath.includes('enhanced-context.ts'));
|
||||
const hasTypes = metadata.some(m => m.relativePath.includes('types.ts'));
|
||||
|
||||
expect(hasEnhancedContext).toEqual(true);
|
||||
expect(hasTypes).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('LazyFileLoader should estimate tokens reasonably accurately', async () => {
|
||||
const loader = new LazyFileLoader(testProjectRoot);
|
||||
const packageJsonPath = path.join(testProjectRoot, 'package.json');
|
||||
|
||||
const metadata = await loader.getMetadata(packageJsonPath);
|
||||
const tokenizer = (content: string) => Math.ceil(content.length / 4);
|
||||
const fileInfo = await loader.loadFile(packageJsonPath, tokenizer);
|
||||
|
||||
// Estimated tokens should be close to actual (within reasonable range)
|
||||
const difference = Math.abs(metadata.estimatedTokens - fileInfo.tokenCount);
|
||||
const percentDiff = (difference / fileInfo.tokenCount) * 100;
|
||||
|
||||
// Should be within 20% accuracy (since it's just an estimate)
|
||||
expect(percentDiff).toBeLessThan(20);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user