UNPKG

@iservu-inc/adf-cli

Version:

CLI tool for AgentDevFramework - AI-assisted development framework with multi-provider AI support

343 lines (266 loc) 11.2 kB
const QuestionMapper = require('../lib/analysis/question-mapper'); const KnowledgeGraph = require('../lib/analysis/knowledge-graph'); const fs = require('fs-extra'); const path = require('path'); const os = require('os'); describe('QuestionMapper', () => { let mapper; let kg; let tempDir; beforeEach(async () => { mapper = new QuestionMapper(); tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'qm-test-')); kg = new KnowledgeGraph(tempDir); }); afterEach(async () => { await fs.remove(tempDir); }); describe('mapQuestion', () => { it('should map tech stack question correctly', () => { const question = { id: 'tech-stack', text: 'What tech stack will you use?' }; const mapping = mapper.mapQuestion(question); expect(mapping.types).toContain('tech_stack'); expect(mapping.priority).toBeLessThanOrEqual(2); // High priority expect(mapping.confidence).toBeGreaterThan(70); }); it('should map goal question correctly', () => { const question = { id: 'project-goal', text: 'What are you building?' }; const mapping = mapper.mapQuestion(question); expect(mapping.types).toContain('project_goal'); expect(mapping.priority).toBe(1); // Highest priority expect(mapping.confidence).toBeGreaterThan(70); }); it('should map user question correctly', () => { const question = { id: 'target-users', text: 'Who are your target users?' }; const mapping = mapper.mapQuestion(question); expect(mapping.types).toContain('target_users'); expect(mapping.confidence).toBeGreaterThan(70); }); it('should map architecture question correctly', () => { const question = { id: 'architecture', text: 'How will you structure your application?' }; const mapping = mapper.mapQuestion(question); expect(mapping.types).toContain('architecture'); expect(mapping.confidence).toBeGreaterThan(70); }); it('should map platform question correctly', () => { const question = { id: 'platform', text: 'What platform will this run on?' }; const mapping = mapper.mapQuestion(question); expect(mapping.types).toContain('platform'); expect(mapping.confidence).toBeGreaterThan(70); }); it('should return default mapping for unrecognized questions', () => { const question = { id: 'random', text: 'Something totally unrelated?' }; const mapping = mapper.mapQuestion(question); expect(mapping.types.length).toBeGreaterThan(0); // Has some type expect(mapping.confidence).toBeGreaterThan(0); // Has some confidence }); it('should map multiple information types for comprehensive questions', () => { const question = { id: 'goal-and-tech', text: 'What are you building and what technology will you use?' }; const mapping = mapper.mapQuestion(question); expect(mapping.types).toContain('project_goal'); expect(mapping.types).toContain('tech_stack'); expect(mapping.types.length).toBeGreaterThan(1); }); }); describe('canSkipQuestion', () => { it('should allow skipping when all information types are satisfied', () => { const question = { id: 'tech-stack', text: 'What tech stack will you use?' }; // Add tech stack knowledge kg.add([ { type: 'tech_stack', content: 'React and Node.js', confidence: 90, source: 'prev-q' } ]); const result = mapper.canSkipQuestion(question, kg, 70); expect(result.canSkip).toBe(true); expect(result.reason).toContain('tech stack'); expect(result.satisfiedTypes).toHaveLength(1); expect(result.missingTypes).toHaveLength(0); }); it('should not skip when confidence is below threshold', () => { const question = { id: 'tech-stack', text: 'What tech stack?' }; kg.add([ { type: 'tech_stack', content: 'React', confidence: 60, source: 'prev-q' } ]); const result = mapper.canSkipQuestion(question, kg, 70); expect(result.canSkip).toBe(false); }); it('should not skip when information is missing', () => { const question = { id: 'tech-stack', text: 'What tech stack will you use?' }; // No knowledge added const result = mapper.canSkipQuestion(question, kg, 70); expect(result.canSkip).toBe(false); expect(result.satisfiedTypes).toHaveLength(0); expect(result.missingTypes.length).toBeGreaterThan(0); }); it('should handle partial satisfaction', () => { const question = { id: 'goal-and-tech', text: 'What are you building and what technology?' }; // Only have goal, missing tech kg.add([ { type: 'project_goal', content: 'Building a CRM', confidence: 90, source: 'prev-q' } ]); const result = mapper.canSkipQuestion(question, kg, 70); expect(result.canSkip).toBe(false); expect(result.satisfiedTypes.length).toBeGreaterThan(0); expect(result.missingTypes.length).toBeGreaterThan(0); expect(result.reason).toContain('Partial'); }); it('should respect custom minConfidence threshold', () => { const question = { id: 'tech-stack', text: 'What tech stack?' }; kg.add([ { type: 'tech_stack', content: 'React', confidence: 75, source: 'prev-q' } ]); // Should skip with 70 threshold expect(mapper.canSkipQuestion(question, kg, 70).canSkip).toBe(true); // Should not skip with 80 threshold expect(mapper.canSkipQuestion(question, kg, 80).canSkip).toBe(false); }); }); describe('reorderQuestions', () => { it('should prioritize questions with missing information', () => { const questions = [ { id: 'tech-stack', text: 'What tech stack?' }, { id: 'timeline', text: 'What is the timeline?' }, { id: 'platform', text: 'What platform?' } ]; // Already have tech stack kg.add([ { type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' } ]); const reordered = mapper.reorderQuestions(questions, kg); // Questions without satisfied info should come first expect(reordered[0].question.id).not.toBe('tech-stack'); expect(reordered[2].question.id).toBe('tech-stack'); // Should be last expect(reordered[2].relevanceScore).toBe(0); // Can skip }); it('should assign high scores to unsatisfied questions', () => { const questions = [ { id: 'tech-stack', text: 'What tech stack?' } ]; const reordered = mapper.reorderQuestions(questions, kg); expect(reordered[0].relevanceScore).toBe(100); }); it('should assign zero score to fully satisfied questions', () => { const questions = [ { id: 'tech-stack', text: 'What tech stack?' } ]; kg.add([ { type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' }, { type: 'architecture', content: 'SPA', confidence: 85, source: 'prev' } ]); const reordered = mapper.reorderQuestions(questions, kg); expect(reordered[0].relevanceScore).toBe(0); expect(reordered[0].skipInfo.canSkip).toBe(true); }); it('should boost high-priority questions', () => { const questions = [ { id: 'timeline', text: 'Timeline?' }, // Priority 4 { id: 'goal', text: 'What building?' } // Priority 1 ]; // Add some knowledge so we can see priority effects kg.add([ { type: 'timeline', content: '2 weeks', confidence: 60, source: 'prev' } ]); const reordered = mapper.reorderQuestions(questions, kg); // Goal question should have higher score due to priority boost const goalItem = reordered.find(r => r.question.id === 'goal'); const timelineItem = reordered.find(r => r.question.id === 'timeline'); expect(goalItem.relevanceScore).toBeGreaterThanOrEqual(timelineItem.relevanceScore); }); it('should handle partially satisfied questions', () => { const questions = [ { id: 'goal', text: 'What building?' }, { id: 'tech', text: 'What tech?' } ]; // Partial knowledge - only have goal kg.add([ { type: 'project_goal', content: 'CRM', confidence: 90, source: 'prev' } ]); const reordered = mapper.reorderQuestions(questions, kg); // Tech should have full score (100), goal should be skippable (0) const techItem = reordered.find(r => r.question.id === 'tech'); const goalItem = reordered.find(r => r.question.id === 'goal'); expect(techItem.relevanceScore).toBeGreaterThan(goalItem.relevanceScore); }); }); describe('getStats', () => { it('should calculate statistics correctly', () => { const questions = [ { id: 'tech-stack', text: 'What tech stack?' }, { id: 'platform', text: 'What platform?' }, { id: 'timeline', text: 'Timeline?' }, { id: 'users', text: 'Who are users?' } ]; // Add some knowledge kg.add([ { type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' }, { type: 'platform', content: 'Web', confidence: 85, source: 'prev' } ]); const stats = mapper.getStats(questions, kg); expect(stats.total).toBe(4); expect(stats.canSkip).toBeGreaterThanOrEqual(2); // tech_stack and platform expect(stats.needed).toBeGreaterThan(0); expect(stats.estimatedTimeSaved).toBeGreaterThan(0); }); it('should handle empty knowledge graph', () => { const questions = [ { id: 'tech-stack', text: 'What tech stack?' }, { id: 'platform', text: 'What platform?' } ]; const stats = mapper.getStats(questions, kg); expect(stats.total).toBe(2); expect(stats.canSkip).toBe(0); expect(stats.needed).toBe(2); expect(stats.estimatedTimeSaved).toBe(0); }); it('should estimate time saved correctly', () => { const questions = [ { id: 'tech-stack', text: 'What tech stack?' }, { id: 'platform', text: 'What platform?' }, { id: 'timeline', text: 'What is the timeline?' } ]; // Can skip 2 questions kg.add([ { type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' }, { type: 'platform', content: 'Web', confidence: 90, source: 'prev' } ]); const stats = mapper.getStats(questions, kg); // If 2 can be skipped, time saved should be around 3 minutes (1.5 * 2) expect(stats.estimatedTimeSaved).toBeGreaterThanOrEqual(1.5); }); }); });