UNPKG

mirror-magi-meta-agent

Version:

AI-powered development planning and execution system with Supabase integration

664 lines (574 loc) 18.8 kB
const fs = require('fs').promises; const path = require('path'); class ProjectPlanner { constructor(config, projectState) { this.config = config; this.projectState = projectState; this.masterPlan = null; this.currentPhase = 'planning'; this.completedTasks = []; } /** * Create a master development plan from a high-level goal */ async createMasterPlan(projectGoal, context = {}) { const plan = { id: `plan_${Date.now()}`, goal: projectGoal, created: new Date().toISOString(), context: { ...context, existingComponents: await this.analyzeExistingComponents(), techStack: this.config.tech_stack, domains: this.config.project.domains }, phases: await this.breakdownIntoPhases(projectGoal, context), dependencies: this.mapDependencies(), estimatedTimeline: null, status: 'active' }; plan.estimatedTimeline = this.calculateTimeline(plan.phases); this.masterPlan = plan; await this.savePlan(plan); return plan; } /** * Break down project goal into logical development phases */ async breakdownIntoPhases(goal, context) { const phases = []; // Analyze the goal to determine what type of development work this is const goalType = this.classifyProjectGoal(goal); switch (goalType) { case 'new_feature': phases.push(...await this.createFeatureDevelopmentPhases(goal, context)); break; case 'new_page': phases.push(...await this.createPageDevelopmentPhases(goal, context)); break; case 'api_integration': phases.push(...await this.createAPIIntegrationPhases(goal, context)); break; case 'refactoring': phases.push(...await this.createRefactoringPhases(goal, context)); break; default: phases.push(...await this.createGenericDevelopmentPhases(goal, context)); } return phases; } /** * Create specific phases for new page development (like dashboard) */ async createPageDevelopmentPhases(goal, context) { const pageName = this.extractPageName(goal); const features = this.extractFeatures(goal, context); return [ { id: 'foundation', name: 'Page Foundation', description: `Create the basic ${pageName} page structure and routing`, tasks: [ { id: 'create_page_component', description: `Create ${pageName} main component`, type: 'component_creation', specifics: { componentName: `${pageName}Page`, filePath: `src/pages/${pageName}Page.tsx`, props: this.determinePageProps(features), routing: this.determineRouting(pageName), layout: this.determineLayout(features) }, dependencies: [], estimatedTime: 30 }, { id: 'setup_routing', description: `Add ${pageName} to application routing`, type: 'configuration', specifics: { routePath: `/${pageName.toLowerCase()}`, routeFile: this.config.tech_stack.frontend.framework.includes('Next') ? `src/app/${pageName.toLowerCase()}/page.tsx` : `src/App.tsx`, authRequired: this.determineAuthRequirement(features) }, dependencies: ['create_page_component'], estimatedTime: 15 } ] }, { id: 'data_layer', name: 'Data & State Management', description: `Set up data fetching and state management for ${pageName}`, tasks: await this.createDataLayerTasks(pageName, features), dependencies: ['foundation'] }, { id: 'feature_components', name: 'Feature Components', description: `Build the main functional components for ${pageName}`, tasks: await this.createFeatureComponentTasks(pageName, features), dependencies: ['data_layer'] }, { id: 'integration', name: 'Integration & Polish', description: `Connect components, add error handling, and polish UX`, tasks: await this.createIntegrationTasks(pageName, features), dependencies: ['feature_components'] } ]; } /** * Generate the next specific, actionable command */ async generateNextCommand() { if (!this.masterPlan) { throw new Error('No master plan exists. Create a plan first with createMasterPlan()'); } const nextTask = this.findNextTask(); if (!nextTask) { return { status: 'complete', message: 'All planned tasks have been completed!', command: null }; } // Generate a specific command for this task const command = await this.generateSpecificCommand(nextTask); return { status: 'active', task: nextTask, command: command, progress: this.calculateProgress() }; } /** * Generate a specific, actionable command (no placeholders!) */ async generateSpecificCommand(task) { const { TemplateEngine } = require('./template-engine'); const templateEngine = new TemplateEngine(this.config, this.projectState); // Create a detailed task specification with actual values const taskSpec = { description: task.description, type: task.type, complexity: this.calculateTaskComplexity(task), domains: this.determineTaskDomains(task), specifics: task.specifics, // This contains the actual file names, props, etc. context: { existingWork: this.getRelevantExistingWork(task), projectGoal: this.masterPlan.goal, currentPhase: this.getCurrentPhase(), dependencies: task.dependencies.map(dep => this.getCompletedTask(dep)) } }; // Generate command with specific details filled in const commandData = await templateEngine.generateSpecificCommand(taskSpec); return commandData; } /** * Mark a task as completed and update project state */ async markTaskCompleted(taskId, results = {}) { // Load current state to ensure we have the latest await this.loadPlan(); const task = this.findTask(taskId); if (!task) { throw new Error(`Task ${taskId} not found`); } // Update project state with what was built await this.updateProjectState(task, results); // Mark as completed in the plan if (!this.masterPlan.completedTasks) { this.masterPlan.completedTasks = []; } this.masterPlan.completedTasks.push({ ...task, completedAt: new Date().toISOString(), results: results }); // Save updated plan await this.savePlan(this.masterPlan); return this.calculateProgress(); } /** * Find a task by ID across all phases */ findTask(taskId) { if (!this.masterPlan || !this.masterPlan.phases) return null; for (const phase of this.masterPlan.phases) { if (phase.tasks) { const task = phase.tasks.find(t => t.id === taskId); if (task) { return { ...task, phaseId: phase.id }; } } } return null; } /** * Find the next uncompleted task */ findNextTask() { if (!this.masterPlan || !this.masterPlan.phases) return null; const completedTaskIds = (this.masterPlan.completedTasks || []).map(t => t.id); for (const phase of this.masterPlan.phases) { if (phase.tasks) { for (const task of phase.tasks) { // Skip if already completed if (completedTaskIds.includes(task.id)) continue; // Check if all dependencies are completed const dependenciesMet = !task.dependencies || task.dependencies.every(dep => completedTaskIds.includes(dep)); if (dependenciesMet) { return { ...task, phaseId: phase.id }; } } } } return null; } /** * Calculate overall progress */ calculateProgress() { if (!this.masterPlan || !this.masterPlan.phases) { return { completed: 0, total: 0, percentage: 0 }; } let totalTasks = 0; let completedTasks = 0; const completedTaskIds = (this.masterPlan.completedTasks || []).map(t => t.id); for (const phase of this.masterPlan.phases) { if (phase.tasks) { totalTasks += phase.tasks.length; completedTasks += phase.tasks.filter(t => completedTaskIds.includes(t.id)).length; } } const percentage = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0; return { completed: completedTasks, total: totalTasks, percentage: percentage }; } /** * Calculate progress for a specific phase */ calculatePhaseProgress(phaseId) { if (!this.masterPlan || !this.masterPlan.phases) { return { completed: 0, total: 0, percentage: 0 }; } const phase = this.masterPlan.phases.find(p => p.id === phaseId); if (!phase || !phase.tasks) { return { completed: 0, total: 0, percentage: 0 }; } const completedTaskIds = (this.masterPlan.completedTasks || []).map(t => t.id); const completedInPhase = phase.tasks.filter(t => completedTaskIds.includes(t.id)).length; const percentage = phase.tasks.length > 0 ? (completedInPhase / phase.tasks.length) * 100 : 0; return { completed: completedInPhase, total: phase.tasks.length, percentage: percentage }; } /** * Get the current phase based on progress */ getCurrentPhase() { if (!this.masterPlan || !this.masterPlan.phases) return null; // Find the first phase that's not 100% complete for (const phase of this.masterPlan.phases) { const progress = this.calculatePhaseProgress(phase.id); if (progress.percentage < 100) { return phase; } } // All phases complete, return the last one return this.masterPlan.phases[this.masterPlan.phases.length - 1]; } /** * Update project state with completed task results */ async updateProjectState(task, results = {}) { if (!this.projectState.components) { this.projectState.components = {}; } // Track what was created/modified switch (task.type) { case 'component_creation': if (task.specifics && task.specifics.componentName) { this.projectState.components[task.specifics.componentName] = { path: task.specifics.filePath, type: 'component', createdAt: new Date().toISOString(), ...results }; } break; case 'api_integration': if (!this.projectState.apis) { this.projectState.apis = {}; } if (task.specifics && task.specifics.endpoint) { this.projectState.apis[task.specifics.endpoint] = { method: task.specifics.method || 'GET', createdAt: new Date().toISOString(), ...results }; } break; case 'configuration': if (!this.projectState.configurations) { this.projectState.configurations = {}; } this.projectState.configurations[task.id] = { ...task.specifics, appliedAt: new Date().toISOString(), ...results }; break; } // Save updated state const statePath = path.join(__dirname, '..', 'state', 'project-state.json'); await fs.writeFile(statePath, JSON.stringify(this.projectState, null, 2)); } /** * Get completed task by ID */ getCompletedTask(taskId) { if (!this.masterPlan || !this.masterPlan.completedTasks) return null; return this.masterPlan.completedTasks.find(t => t.id === taskId); } /** * Extract what type of project this is from the goal description */ classifyProjectGoal(goal) { const goalLower = goal.toLowerCase(); if (goalLower.includes('dashboard') || goalLower.includes('page')) { return 'new_page'; } if (goalLower.includes('api') || goalLower.includes('integration')) { return 'api_integration'; } if (goalLower.includes('refactor') || goalLower.includes('improve')) { return 'refactoring'; } return 'new_feature'; } /** * Extract the actual page name from the goal */ extractPageName(goal) { // Look for patterns like "dashboard page", "investment dashboard", etc. const matches = goal.match(/(\w+)\s*(?:page|dashboard|screen)/i); if (matches) { return matches[1].charAt(0).toUpperCase() + matches[1].slice(1); } // Fallback: extract first meaningful word const words = goal.split(' ').filter(word => word.length > 3 && !['create', 'build', 'make', 'new', 'add'].includes(word.toLowerCase()) ); return words[0] ? words[0].charAt(0).toUpperCase() + words[0].slice(1) : 'New'; } /** * Extract specific features mentioned in the goal */ extractFeatures(goal, context) { const features = []; const goalLower = goal.toLowerCase(); // Common dashboard features if (goalLower.includes('chart') || goalLower.includes('graph')) { features.push('charts'); } if (goalLower.includes('table') || goalLower.includes('list')) { features.push('data_table'); } if (goalLower.includes('filter') || goalLower.includes('search')) { features.push('filtering'); } if (goalLower.includes('user') || goalLower.includes('profile')) { features.push('user_data'); } if (goalLower.includes('real-time') || goalLower.includes('live')) { features.push('real_time'); } // Add features from context if (context.requiredFeatures) { features.push(...context.requiredFeatures); } return [...new Set(features)]; // Remove duplicates } /** * Calculate timeline based on task estimates */ calculateTimeline(phases) { let totalMinutes = 0; for (const phase of phases) { if (phase.tasks) { for (const task of phase.tasks) { totalMinutes += task.estimatedTime || 30; // Default 30 min per task } } } return Math.ceil(totalMinutes / 60); // Convert to hours } /** * Map dependencies (placeholder for now) */ mapDependencies() { return {}; } /** * Calculate task complexity */ calculateTaskComplexity(task) { // Simple heuristic based on task type and dependencies let complexity = 'medium'; if (task.dependencies && task.dependencies.length > 2) { complexity = 'high'; } else if (!task.dependencies || task.dependencies.length === 0) { complexity = 'low'; } if (task.type === 'api_integration' || task.type === 'data_modeling') { complexity = 'high'; } return complexity; } /** * Determine task domains */ determineTaskDomains(task) { const domains = []; if (task.type.includes('component') || task.type.includes('styling')) { domains.push('frontend'); } if (task.type.includes('api') || task.type.includes('supabase')) { domains.push('backend'); } if (task.type.includes('test')) { domains.push('testing'); } return domains.length > 0 ? domains : ['general']; } /** * Get relevant existing work for context */ getRelevantExistingWork(task) { // This would analyze project state to find related components return { components: [], apis: [], configurations: [] }; } /** * Placeholder methods for different phase types */ async createFeatureDevelopmentPhases(goal, context) { return this.createGenericDevelopmentPhases(goal, context); } async createAPIIntegrationPhases(goal, context) { return this.createGenericDevelopmentPhases(goal, context); } async createRefactoringPhases(goal, context) { return this.createGenericDevelopmentPhases(goal, context); } async createGenericDevelopmentPhases(goal, context) { return [ { id: 'planning', name: 'Planning & Setup', description: 'Initial setup and planning', tasks: [] }, { id: 'implementation', name: 'Implementation', description: 'Core implementation', tasks: [] }, { id: 'testing', name: 'Testing & Polish', description: 'Testing and final polish', tasks: [] } ]; } /** * Helper methods for page development */ determinePageProps(features) { const props = {}; if (features.includes('user_data')) { props.userId = 'string'; } return props; } determineRouting(pageName) { return { path: `/${pageName.toLowerCase()}`, exact: true }; } determineLayout(features) { return features.includes('charts') ? 'dashboard' : 'default'; } determineAuthRequirement(features) { return features.includes('user_data') || features.includes('admin'); } async createDataLayerTasks(pageName, features) { return []; // Placeholder } async createFeatureComponentTasks(pageName, features) { return []; // Placeholder } async createIntegrationTasks(pageName, features) { return []; // Placeholder } /** * Analyze existing components in the project */ async analyzeExistingComponents() { // This would scan the actual project files // For now, return mock data based on typical project structure const components = { pages: [], components: [], hooks: [], services: [], types: [] }; // In a real implementation, this would scan: // - src/pages/ or src/app/ for existing pages // - src/components/ for reusable components // - src/hooks/ for custom hooks // - src/services/ or src/api/ for API services // - src/types/ for TypeScript definitions return components; } async savePlan(plan) { const planPath = path.join(__dirname, '..', 'state', 'master-plan.json'); await fs.writeFile(planPath, JSON.stringify(plan, null, 2)); } async loadPlan() { try { const planPath = path.join(__dirname, '..', 'state', 'master-plan.json'); const planData = await fs.readFile(planPath, 'utf8'); this.masterPlan = JSON.parse(planData); // Load completed tasks if they exist if (!this.masterPlan.completedTasks) { this.masterPlan.completedTasks = []; } return this.masterPlan; } catch (error) { return null; } } } module.exports = ProjectPlanner;