feat(IterativeContextBuilder): Add iterative AI-driven context builder and integrate into task factory; add tests and iterative configuration
This commit is contained in:
		
							
								
								
									
										147
									
								
								test/test.iterativecontextbuilder.node.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								test/test.iterativecontextbuilder.node.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
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();
 | 
			
		||||
		Reference in New Issue
	
	Block a user