UNPKG

yoda-mcp

Version:

Intelligent Planning MCP with Optional Dependencies and Graceful Fallbacks - wise planning through the Force of lean excellence

382 lines (313 loc) 14.5 kB
/** * TDD Tests for Lean Planner Improvements * * Tests for the 11-point improvement areas: * 1. Language Precision * 2. Skill-based Time Estimation * 3. Output Density Optimization * 4. Enhanced Context Utilization */ import { LeanPlannerCore, LeanPlanRequest, FocusedPlan, Task } from '../src/core/lean-planner'; describe('Lean Planner Improvements TDD', () => { let planner: LeanPlannerCore; beforeEach(async () => { planner = new LeanPlannerCore(); await planner.initialize(); }); describe('1. Language Precision Improvements', () => { test('task descriptions should be direct and actionable without fluff', async () => { const request: LeanPlanRequest = { goal: 'Create a simple todo app' }; const plan = await planner.generatePlan(request); plan.tasks.forEach(task => { // ✅ Test: No fluff words const fluffWords = ['basically', 'simply', 'just', 'maybe', 'possibly', 'probably']; const hasFluff = fluffWords.some(word => task.description.toLowerCase().includes(word) ); expect(hasFluff).toBe(false); // ✅ Test: Starts with action verb const actionVerbs = ['create', 'build', 'implement', 'design', 'develop', 'configure', 'setup', 'write', 'add', 'test']; const startsWithAction = actionVerbs.some(verb => task.description.toLowerCase().startsWith(verb) ); expect(startsWithAction).toBe(true); // ✅ Test: Maximum 120 characters for clarity expect(task.description.length).toBeLessThanOrEqual(120); // ✅ Test: Contains specific deliverable expect(task.deliverable).toBeDefined(); expect(task.deliverable.length).toBeGreaterThan(10); }); }); test('task titles should be concise and specific', async () => { const request: LeanPlanRequest = { goal: 'Add user authentication to web app' }; const plan = await planner.generatePlan(request); plan.tasks.forEach(task => { // ✅ Test: Title is concise (2-5 words) const wordCount = task.title.split(' ').length; expect(wordCount).toBeGreaterThanOrEqual(2); expect(wordCount).toBeLessThanOrEqual(5); // ✅ Test: No generic titles const genericTitles = ['implement feature', 'add functionality', 'create component']; const isGeneric = genericTitles.some(generic => task.title.toLowerCase().includes(generic) ); expect(isGeneric).toBe(false); }); }); test('requirements should be outcome-focused not process-focused', async () => { const request: LeanPlanRequest = { goal: 'Build e-commerce website' }; const plan = await planner.generatePlan(request); plan.requirements.forEach(req => { // ✅ Test: Describes what user gets, not what system does const processPhrases = ['the system should', 'the app will', 'implementation must']; const isProcessFocused = processPhrases.some(phrase => req.description.toLowerCase().includes(phrase) ); expect(isProcessFocused).toBe(false); // ✅ Test: Uses user-centric language const userCentricPhrases = ['users can', 'customers can', 'visitors can', 'enables', 'allows']; const isUserCentric = userCentricPhrases.some(phrase => req.description.toLowerCase().includes(phrase) ); expect(isUserCentric).toBe(true); }); }); }); describe('2. Skill-based Time Estimation', () => { test('should adjust estimates based on task complexity patterns', async () => { const request: LeanPlanRequest = { goal: 'Build REST API with authentication', context: 'Node.js, Express, JWT' }; const plan = await planner.generatePlan(request); // ✅ Test: Auth tasks get security complexity multiplier const authTasks = plan.tasks.filter(task => task.description.toLowerCase().includes('auth') || task.description.toLowerCase().includes('security') || task.skills.includes('security') ); authTasks.forEach(task => { expect(task.estimatedHours).toBeGreaterThan(8); // Base + security multiplier }); // ✅ Test: Database tasks get data complexity multiplier const dbTasks = plan.tasks.filter(task => task.description.toLowerCase().includes('database') || task.description.toLowerCase().includes('schema') || task.skills.includes('database') ); dbTasks.forEach(task => { expect(task.estimatedHours).toBeGreaterThan(6); // Base + data multiplier }); // ✅ Test: UI tasks have appropriate estimates const uiTasks = plan.tasks.filter(task => task.description.toLowerCase().includes('interface') || task.description.toLowerCase().includes('frontend') || task.skills.includes('frontend') ); uiTasks.forEach(task => { expect(task.estimatedHours).toBeGreaterThan(4); expect(task.estimatedHours).toBeLessThan(20); // UI shouldn't be over-complex }); }); test('should provide skill-based estimates for different technologies', async () => { const reactRequest: LeanPlanRequest = { goal: 'Create dashboard', context: 'React, TypeScript' }; const plan = await planner.generatePlan(reactRequest); // ✅ Test: Skills array reflects technology choices const frontendTasks = plan.tasks.filter(task => task.skills.includes('frontend') || task.skills.includes('react') ); expect(frontendTasks.length).toBeGreaterThan(0); frontendTasks.forEach(task => { // React + TypeScript should have specific estimates expect(task.skills).toContain('frontend'); expect(task.estimatedHours).toBeGreaterThanOrEqual(4); }); }); test('should flag unrealistic estimates for review', async () => { const complexRequest: LeanPlanRequest = { goal: 'Build social media platform', constraints: { timeline: '1 week' } }; const plan = await planner.generatePlan(complexRequest); // ✅ Test: Feasibility gate should flag unrealistic timeline expect(plan.qualityGates.feasibility.passed).toBe(false); expect(plan.qualityGates.feasibility.improvements).toContainEqual( expect.stringMatching(/timeline|scope|buffer/i) ); // ✅ Test: Should suggest estimate adjustments const totalHours = plan.tasks.reduce((sum, task) => sum + task.estimatedHours, 0); const workingHours = 7 * 6; // 1 week * 6 productive hours expect(totalHours).toBeGreaterThan(workingHours * 1.5); // Clearly over timeline }); }); describe('3. Output Density Optimization', () => { test('should eliminate redundant information in formatted output', async () => { const request: LeanPlanRequest = { goal: 'Create blog website' }; const plan = await planner.generatePlan(request); // ✅ Test: No repeated information across sections const allText = [ ...plan.requirements.map(r => r.description), ...plan.tasks.map(t => t.description), ...plan.risks.map(r => r.description) ].join(' ').toLowerCase(); // Check for excessive repetition const goalWords = request.goal.toLowerCase().split(' '); goalWords.forEach(word => { if (word.length > 4) { const occurrences = (allText.match(new RegExp(word, 'g')) || []).length; expect(occurrences).toBeLessThan(5); // Not over-repeated } }); }); test('should prioritize high-value information in summaries', async () => { const request: LeanPlanRequest = { goal: 'Build customer management system' }; const plan = await planner.generatePlan(request); // ✅ Test: Must-have requirements come first const mustHaveIndex = plan.requirements.findIndex(r => r.priority === 'must-have'); const shouldHaveIndex = plan.requirements.findIndex(r => r.priority === 'should-have'); if (mustHaveIndex !== -1 && shouldHaveIndex !== -1) { expect(mustHaveIndex).toBeLessThan(shouldHaveIndex); } // ✅ Test: Critical path tasks are clearly marked const criticalTasks = plan.timeline.criticalPath; expect(criticalTasks.length).toBeGreaterThan(0); criticalTasks.forEach(taskId => { const task = plan.tasks.find(t => t.id === taskId); expect(task).toBeDefined(); }); }); test('should provide scannable output structure', async () => { const request: LeanPlanRequest = { goal: 'Create mobile app' }; const plan = await planner.generatePlan(request); // ✅ Test: Consistent information hierarchy expect(plan.requirements.length).toBeLessThanOrEqual(8); // Scannable list expect(plan.tasks.length).toBeLessThanOrEqual(12); // Manageable scope expect(plan.risks.length).toBeLessThanOrEqual(5); // Key risks only // ✅ Test: Each section has clear purpose expect(plan.requirements.every(r => r.acceptanceCriteria.length > 0)).toBe(true); expect(plan.tasks.every(t => t.deliverable.length > 10)).toBe(true); expect(plan.risks.every(r => r.mitigation.length > 10)).toBe(true); }); }); describe('4. Enhanced Context Utilization', () => { test('should leverage user context for targeted requirements', async () => { const request: LeanPlanRequest = { goal: 'Build inventory system', context: 'Small retail business, 200 products, paper-based currently' }; const plan = await planner.generatePlan(request); // ✅ Test: Requirements reflect small business context const businessRequirements = plan.requirements.filter(req => req.description.toLowerCase().includes('small') || req.description.toLowerCase().includes('simple') || req.description.toLowerCase().includes('basic') ); expect(businessRequirements.length).toBeGreaterThan(0); // ✅ Test: Should not include enterprise features const enterpriseRequirements = plan.requirements.filter(req => req.description.toLowerCase().includes('enterprise') || req.description.toLowerCase().includes('complex') || req.description.toLowerCase().includes('advanced') ); expect(enterpriseRequirements.length).toBe(0); // ✅ Test: Tasks should address paper-to-digital transition const migrationTasks = plan.tasks.filter(task => task.description.toLowerCase().includes('import') || task.description.toLowerCase().includes('migrate') || task.description.toLowerCase().includes('transition') ); expect(migrationTasks.length).toBeGreaterThan(0); }); test('should adapt complexity based on user experience level', async () => { const beginnerRequest: LeanPlanRequest = { goal: 'Create website', context: 'First time building a website, learning HTML/CSS' }; const plan = await planner.generatePlan(beginnerRequest); // ✅ Test: Beginner-friendly task breakdown const learningTasks = plan.tasks.filter(task => task.description.toLowerCase().includes('learn') || task.description.toLowerCase().includes('tutorial') || task.skills.includes('learning') ); expect(learningTasks.length).toBeGreaterThan(0); // ✅ Test: Realistic timeline for learning curve expect(plan.metadata.complexity).toBe('simple'); const totalHours = plan.tasks.reduce((sum, task) => sum + task.estimatedHours, 0); expect(totalHours).toBeGreaterThan(20); // Learning takes time }); test('should use technology context for specific guidance', async () => { const request: LeanPlanRequest = { goal: 'Build API', context: 'Using Python Flask, PostgreSQL database', constraints: { technology: ['Python', 'Flask', 'PostgreSQL'] } }; const plan = await planner.generatePlan(request); // ✅ Test: Technology-specific tasks const pythonTasks = plan.tasks.filter(task => task.skills.includes('python') || task.skills.includes('flask') ); expect(pythonTasks.length).toBeGreaterThan(0); const dbTasks = plan.tasks.filter(task => task.skills.includes('database') || task.skills.includes('postgresql') ); expect(dbTasks.length).toBeGreaterThan(0); // ✅ Test: Flask-specific deliverables const flaskDeliverables = plan.tasks.filter(task => task.deliverable.toLowerCase().includes('flask') || task.deliverable.toLowerCase().includes('route') || task.deliverable.toLowerCase().includes('endpoint') ); expect(flaskDeliverables.length).toBeGreaterThan(0); }); }); describe('Integration Tests - All Improvements Together', () => { test('improved planner should deliver sharp, focused, excellent plans', async () => { const request: LeanPlanRequest = { goal: 'Create task management app', context: 'Team of 3 developers, React expertise, 4-week timeline', constraints: { timeline: '4 weeks', team: '3 developers', technology: ['React', 'Node.js', 'MongoDB'] } }; const plan = await planner.generatePlan(request); // ✅ Test: Sharp language plan.tasks.forEach(task => { expect(task.title.split(' ').length).toBeLessThanOrEqual(5); expect(task.description.length).toBeLessThanOrEqual(120); }); // ✅ Test: Focused scope expect(plan.requirements.length).toBeLessThanOrEqual(8); expect(plan.tasks.length).toBeLessThanOrEqual(12); // ✅ Test: Excellent quality expect(plan.qualityGates.completeness.passed).toBe(true); expect(plan.qualityGates.clarity.passed).toBe(true); // ✅ Test: Context utilization const reactTasks = plan.tasks.filter(t => t.skills.includes('react')); expect(reactTasks.length).toBeGreaterThan(0); // ✅ Test: Realistic estimates for team const totalHours = plan.tasks.reduce((sum, task) => sum + task.estimatedHours, 0); const teamCapacity = 4 * 7 * 3 * 6; // 4 weeks * 7 days * 3 devs * 6 hours expect(totalHours).toBeLessThan(teamCapacity * 0.8); // 80% utilization }); }); });