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
text/typescript
/**
* 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
});
});
});