UNPKG

agent-team-composer

Version:

Transform README files into GitHub project plans with AI-powered agent teams

279 lines 12.9 kB
import Anthropic from '@anthropic-ai/sdk'; import { PHASE_GENERATOR_PROMPT } from '../prompts/phase-generator.js'; import { EPIC_GENERATOR_PROMPT } from '../prompts/epic-generator.js'; import { ISSUE_FORMATTER_PROMPT } from '../prompts/issue-formatter.js'; import { PromptSanitizer } from './prompt-sanitizer.js'; import { z } from 'zod'; import { RobustLLMHandler } from './json-extractor.js'; import { ProgressCache } from './progress-cache.js'; export class LLMOrchestrator { anthropic; model = 'claude-3-sonnet-20240229'; constructor(apiKey = process.env.ANTHROPIC_API_KEY) { this.anthropic = new Anthropic({ apiKey }); } async generateProjectPlan(projectInfo, readmeContent, forceFresh) { // Initialize progress cache const cache = new ProgressCache(projectInfo.title); // Clear cache if fresh generation is requested if (forceFresh) { console.log('🔄 Fresh generation requested, clearing cache...'); await cache.clear(); } const cachedProgress = await cache.load(); let phases; let phasesWithTasks; // Check if we can resume from cache if (cachedProgress && cachedProgress.projectInfo?.title === projectInfo.title && cachedProgress.readmeContent === readmeContent) { console.log('📂 Found cached progress, resuming...'); if (cachedProgress.finalPhases) { console.log('✅ Using fully processed data from cache'); return { summary: { title: projectInfo.title, description: projectInfo.description, domain: projectInfo.domain, repository: '', // These will be filled by UI branch: '' }, phases: cachedProgress.finalPhases }; } phases = cachedProgress.phases || []; phasesWithTasks = cachedProgress.phasesWithTasks || []; } else { // Clear old cache if project info doesn't match await cache.clear(); phases = []; phasesWithTasks = []; } // Step 1: Generate phases based on README (if not cached) if (phases.length === 0) { console.log('🤖 Generating project phases...'); phases = await this.generatePhases(projectInfo, readmeContent); await cache.save({ projectInfo, readmeContent, phases }); } else { console.log('✅ Using cached phases'); } // Step 2: For each phase, generate epics/tasks with role assignments (if not cached) if (phasesWithTasks.length === 0) { console.log('👥 Generating epics and assigning roles...'); phasesWithTasks = await this.generateEpicsForPhases(phases, projectInfo); await cache.save({ phasesWithTasks }); } else { console.log('✅ Using cached phases with tasks'); } // Step 3: Format issues for GitHub console.log('📝 Formatting issues for GitHub...'); const formattedPhases = await this.formatIssuesForGitHub(phasesWithTasks, projectInfo); await cache.save({ finalPhases: formattedPhases }); return { summary: { title: projectInfo.title, description: projectInfo.description, domain: projectInfo.domain, repository: 'unknown/unknown', // Will be set by CLI branch: 'main' }, phases: formattedPhases }; } async generatePhases(projectInfo, readmeContent) { // Sanitize inputs const sanitizedInfo = PromptSanitizer.sanitizeProjectInfo(projectInfo); const sanitizedReadme = PromptSanitizer.sanitizeReadme(readmeContent); // Check for suspicious patterns if (PromptSanitizer.containsSuspiciousPatterns(readmeContent)) { throw new Error('Input contains potentially malicious patterns'); } const prompt = PromptSanitizer.buildSafePrompt(PHASE_GENERATOR_PROMPT, { domain: sanitizedInfo.domain, readmeContent: sanitizedReadme }); // Define schema for validation const PhaseGenerationSchema = z.object({ phases: z.array(z.object({ id: z.string(), title: z.string(), description: z.string(), suggestedRoles: z.array(z.string()) })) }); // Use bulletproof extraction const result = await RobustLLMHandler.getStructuredResponse(this.anthropic, prompt, PhaseGenerationSchema, 'phase-generation'); // Ensure all fields are non-optional return result.phases.map(phase => ({ id: phase.id, title: phase.title, description: phase.description, suggestedRoles: phase.suggestedRoles })); } async generateEpicsForPhases(phases, projectInfo) { const results = await Promise.all(phases.map(async (phase) => { const commonRoles = this.getCommonRolesForPhase(phase.title); const prompt = PromptSanitizer.buildSafePrompt(EPIC_GENERATOR_PROMPT, { phaseTitle: phase.title, phaseDescription: phase.description, productContext: `${projectInfo.title}: ${projectInfo.description}`, commonRoles: commonRoles.join('\n'), phaseId: phase.id }); // Define schema for epic generation const EpicGenerationSchema = z.object({ phaseId: z.string(), roles: z.array(z.string()), tasks: z.array(z.object({ title: z.string(), description: z.string(), suggestedRole: z.string() })) }); // Use bulletproof extraction const epicGeneration = await RobustLLMHandler.getStructuredResponse(this.anthropic, prompt, EpicGenerationSchema, `epic-generation-${phase.id}`); return { id: phase.id, title: phase.title, description: phase.description, roles: epicGeneration.roles, tasks: epicGeneration.tasks, issues: [], // Will be populated in next step approved: false }; })); return results; } async formatIssuesForGitHub(phasesWithTasks, projectInfo) { // Process phases sequentially to avoid rate limiting const formattedPhases = []; for (const phase of phasesWithTasks) { // Process issues in batches to control concurrency const BATCH_SIZE = 3; // Process 3 issues at a time const issues = []; console.log(`Processing ${phase.tasks.length} tasks for phase: ${phase.title}`); for (let i = 0; i < phase.tasks.length; i += BATCH_SIZE) { const batch = phase.tasks.slice(i, i + BATCH_SIZE); // Process batch in parallel const batchResults = await Promise.all(batch.map(async (task) => { const prompt = PromptSanitizer.buildSafePrompt(ISSUE_FORMATTER_PROMPT, { taskTitle: task.title, taskDescription: task.description, assignedRole: task.suggestedRole, phaseTitle: phase.title, productContext: projectInfo.title }); // Define schema for issue formatting const IssueFormatSchema = z.object({ title: z.string(), body: z.string() }); try { // Use bulletproof extraction with optional schema const formatted = await RobustLLMHandler.getStructuredResponse(this.anthropic, prompt, IssueFormatSchema, `issue-format-${phase.id}-${task.title.substring(0, 20)}`); return { title: formatted.title, description: formatted.body, role: task.suggestedRole }; } catch (error) { // Fallback to simple format if extraction fails console.warn('Failed to format issue, using fallback:', error); return { title: task.title, description: task.description, role: task.suggestedRole }; } })); issues.push(...batchResults); // Add a small delay between batches to respect rate limits if (i + BATCH_SIZE < phase.tasks.length) { console.log(`Processed batch ${Math.floor(i / BATCH_SIZE) + 1}, waiting before next batch...`); await new Promise(resolve => setTimeout(resolve, 1000)); // 1 second delay between batches } } formattedPhases.push({ id: phase.id, title: phase.title, description: phase.description, roles: phase.roles, issues, approved: true // Default to approved for now }); // Add delay between phases as well if (formattedPhases.length < phasesWithTasks.length) { console.log(`Completed phase ${phase.title}, waiting before next phase...`); await new Promise(resolve => setTimeout(resolve, 2000)); // 2 seconds between phases } } return formattedPhases; } getCommonRolesForPhase(phaseTitle) { const rolesByPhase = { 'Discovery': [ 'Market Research Analyst - Conducts market research and competitive analysis', 'User Research Specialist - Performs user interviews and creates personas', 'Product Owner - Defines vision and prioritizes features' ], 'Specification': [ 'Product Requirements Author - Creates comprehensive PRDs', 'UI/UX Designer - Designs wireframes and user interfaces', 'System Architect - Reviews technical feasibility' ], 'Architecture': [ 'System Architect - Designs scalable system architecture', 'Sprint Planner - Creates actionable sprint plans', 'Tech Lead - Reviews and approves technical decisions' ], 'Implementation': [ 'Frontend Developer - Implements UI components', 'Backend Developer - Builds APIs and services', 'Database Engineer - Designs data models', 'DevOps Engineer - Sets up CI/CD pipelines' ], 'Testing': [ 'Test Engineer - Creates test plans and automation', 'Security Auditor - Identifies vulnerabilities', 'Performance Tester - Validates system performance' ], 'Deployment': [ 'Release Manager - Coordinates deployments', 'DevOps Engineer - Manages infrastructure', 'Technical Writer - Creates documentation' ], 'Monitoring': [ 'Analytics Engineer - Implements tracking', 'Site Reliability Engineer - Ensures system reliability', 'Support Engineer - Handles user issues' ], 'Enhancement': [ 'Feature Developer - Implements new features', 'Refactoring Specialist - Improves code quality', 'Product Owner - Prioritizes enhancements' ] }; // Find matching phase for (const [key, roles] of Object.entries(rolesByPhase)) { if (phaseTitle.toLowerCase().includes(key.toLowerCase())) { return roles; } } // Default roles if no match return [ 'Product Owner - Defines requirements and priorities', 'Software Engineer - Implements features and fixes', 'QA Engineer - Ensures quality standards' ]; } } export async function generateProjectPlan(projectInfo, readmeContent, forceFresh) { const orchestrator = new LLMOrchestrator(); return orchestrator.generateProjectPlan(projectInfo, readmeContent, forceFresh); } //# sourceMappingURL=llm-orchestrator.js.map