UNPKG

mirror-magi-meta-agent

Version:

AI-powered development planning and execution system with Supabase integration

472 lines (386 loc) 14.3 kB
#!/usr/bin/env node const fs = require('fs').promises; const path = require('path'); const readline = require('readline'); /** * Plan Editor - Edit and modify existing development plans */ class PlanEditor { constructor() { this.rl = readline.createInterface({ input: process.stdin, output: process.stdout }); this.plan = null; this.originalPlan = null; } async start() { console.log('✏️ Plan Editor'); console.log('═'.repeat(30)); console.log('Edit and modify your development plans\n'); try { await this.loadPlan(); await this.editPlan(); } catch (error) { console.error('❌ Error:', error.message); } finally { this.rl.close(); } } async loadPlan() { try { const planPath = path.join('state', 'master-plan.json'); const planData = await fs.readFile(planPath, 'utf8'); this.plan = JSON.parse(planData); this.originalPlan = JSON.parse(planData); // Keep backup console.log('📋 Loaded plan:', this.plan.goal); console.log('Status:', this.plan.status); console.log('Created:', new Date(this.plan.created).toLocaleString()); } catch (error) { throw new Error('No plan found. Create a plan first with "node interactive-planner.js"'); } } async editPlan() { while (true) { this.displayPlanOverview(); const choice = await this.askQuestion('\nWhat would you like to edit?\n1. Goal/description\n2. Phases\n3. Tasks\n4. Context/settings\n5. Save changes\n6. Discard changes\n\nChoose (1-6): '); switch (choice.trim()) { case '1': await this.editGoal(); break; case '2': await this.editPhases(); break; case '3': await this.editTasks(); break; case '4': await this.editContext(); break; case '5': await this.saveChanges(); return; case '6': await this.discardChanges(); return; default: console.log('Invalid choice. Please try again.'); } } } displayPlanOverview() { console.log('\n📋 CURRENT PLAN OVERVIEW'); console.log('─'.repeat(40)); console.log(`🎯 Goal: ${this.plan.goal}`); console.log(`📊 Phases: ${this.plan.phases.length}`); console.log(`📈 Status: ${this.plan.status}`); const totalTasks = this.plan.phases.reduce((sum, phase) => sum + (phase.tasks ? phase.tasks.length : 0), 0 ); console.log(`📝 Total tasks: ${totalTasks}`); } async editGoal() { console.log('\n✏️ EDIT GOAL'); console.log('─'.repeat(20)); console.log('Current goal:', this.plan.goal); const newGoal = await this.askQuestion('\nEnter new goal (or press Enter to keep current): '); if (newGoal.trim()) { this.plan.goal = newGoal.trim(); this.plan.lastModified = new Date().toISOString(); console.log('✅ Goal updated'); } } async editPhases() { console.log('\n📋 EDIT PHASES'); console.log('─'.repeat(20)); while (true) { console.log('\nCurrent phases:'); this.plan.phases.forEach((phase, index) => { const taskCount = phase.tasks ? phase.tasks.length : 0; console.log(`${index + 1}. ${phase.name} (${taskCount} tasks)`); console.log(` ${phase.description}`); }); const action = await this.askQuestion('\n1. Edit phase\n2. Add phase\n3. Remove phase\n4. Reorder phases\n5. Back to main menu\n\nChoose (1-5): '); switch (action.trim()) { case '1': await this.editSinglePhase(); break; case '2': await this.addPhase(); break; case '3': await this.removePhase(); break; case '4': await this.reorderPhases(); break; case '5': return; default: console.log('Invalid choice.'); } } } async editSinglePhase() { const phaseNum = await this.askQuestion('Which phase number to edit? '); const index = parseInt(phaseNum) - 1; if (index < 0 || index >= this.plan.phases.length) { console.log('Invalid phase number.'); return; } const phase = this.plan.phases[index]; console.log(`\nEditing: ${phase.name}`); const newName = await this.askQuestion(`New name (current: ${phase.name}): `); if (newName.trim()) { phase.name = newName.trim(); } const newDesc = await this.askQuestion(`New description (current: ${phase.description}): `); if (newDesc.trim()) { phase.description = newDesc.trim(); } console.log('✅ Phase updated'); } async addPhase() { const name = await this.askQuestion('Phase name: '); const description = await this.askQuestion('Phase description: '); const position = await this.askQuestion(`Insert at position (1-${this.plan.phases.length + 1}, default: end): `); const newPhase = { id: `phase_${Date.now()}`, name: name.trim(), description: description.trim(), tasks: [] }; const pos = parseInt(position) - 1; if (pos >= 0 && pos <= this.plan.phases.length) { this.plan.phases.splice(pos, 0, newPhase); } else { this.plan.phases.push(newPhase); } console.log('✅ Phase added'); } async removePhase() { const phaseNum = await this.askQuestion('Which phase number to remove? '); const index = parseInt(phaseNum) - 1; if (index < 0 || index >= this.plan.phases.length) { console.log('Invalid phase number.'); return; } const phase = this.plan.phases[index]; const confirm = await this.askQuestion(`Really remove "${phase.name}"? (y/n): `); if (confirm.toLowerCase().startsWith('y')) { this.plan.phases.splice(index, 1); console.log('✅ Phase removed'); } } async editTasks() { console.log('\n📝 EDIT TASKS'); console.log('─'.repeat(20)); // Show phases with task counts console.log('\nPhases:'); this.plan.phases.forEach((phase, index) => { const taskCount = phase.tasks ? phase.tasks.length : 0; console.log(`${index + 1}. ${phase.name} (${taskCount} tasks)`); }); const phaseNum = await this.askQuestion('\nWhich phase to edit tasks for? '); const phaseIndex = parseInt(phaseNum) - 1; if (phaseIndex < 0 || phaseIndex >= this.plan.phases.length) { console.log('Invalid phase number.'); return; } await this.editPhaseTasks(this.plan.phases[phaseIndex]); } async editPhaseTasks(phase) { console.log(`\n🔧 EDITING TASKS FOR: ${phase.name}`); if (!phase.tasks) { phase.tasks = []; } while (true) { console.log(`\nCurrent tasks (${phase.tasks.length}):`); if (phase.tasks.length === 0) { console.log(' No tasks yet'); } else { phase.tasks.forEach((task, idx) => { console.log(`${idx + 1}. ${task.description} (${task.type || 'general'})`); }); } const action = await this.askQuestion('\n1. Add task\n2. Edit task\n3. Remove task\n4. Reorder tasks\n5. Back\n\nChoose (1-5): '); switch (action.trim()) { case '1': await this.addTaskToPhase(phase); break; case '2': await this.editTask(phase); break; case '3': await this.removeTask(phase); break; case '4': await this.reorderTasks(phase); break; case '5': return; default: console.log('Invalid choice.'); } } } async addTaskToPhase(phase) { const description = await this.askQuestion('Task description: '); const type = await this.askQuestion('Task type (component_creation, api_integration, configuration, etc.): '); const task = { id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`, description: description.trim(), type: type.trim() || 'feature_development', dependencies: [], specifics: {} }; phase.tasks.push(task); console.log('✅ Task added successfully'); } async editTask(phase) { if (phase.tasks.length === 0) { console.log('No tasks to edit.'); return; } const taskNum = await this.askQuestion('Which task number to edit? '); const index = parseInt(taskNum) - 1; if (index < 0 || index >= phase.tasks.length) { console.log('Invalid task number.'); return; } const task = phase.tasks[index]; console.log(`\nEditing: ${task.description}`); const newDesc = await this.askQuestion(`New description (current: ${task.description}): `); if (newDesc.trim()) { task.description = newDesc.trim(); } const newType = await this.askQuestion(`New type (current: ${task.type}): `); if (newType.trim()) { task.type = newType.trim(); } console.log('✅ Task updated'); } async removeTask(phase) { if (phase.tasks.length === 0) { console.log('No tasks to remove.'); return; } const taskNum = await this.askQuestion('Which task number to remove? '); const index = parseInt(taskNum) - 1; if (index < 0 || index >= phase.tasks.length) { console.log('Invalid task number.'); return; } const task = phase.tasks[index]; const confirm = await this.askQuestion(`Really remove "${task.description}"? (y/n): `); if (confirm.toLowerCase().startsWith('y')) { phase.tasks.splice(index, 1); console.log('✅ Task removed'); } } async editContext() { console.log('\n⚙️ EDIT CONTEXT & SETTINGS'); console.log('─'.repeat(30)); if (this.plan.context) { console.log('Current context:'); Object.entries(this.plan.context).forEach(([key, value]) => { console.log(` ${key}: ${JSON.stringify(value)}`); }); } const editChoice = await this.askQuestion('\n1. Edit required features\n2. Edit design system\n3. Edit data source\n4. Edit user requirements\n5. Edit priority\n6. Back\n\nChoose (1-6): '); if (!this.plan.context) { this.plan.context = {}; } switch (editChoice.trim()) { case '1': const features = await this.askQuestion('Required features (comma-separated): '); this.plan.context.requiredFeatures = features.split(',').map(f => f.trim()).filter(f => f); break; case '2': const design = await this.askQuestion('Design system: '); if (design.trim()) this.plan.context.designSystem = design.trim(); break; case '3': const dataSource = await this.askQuestion('Data source: '); if (dataSource.trim()) this.plan.context.dataSource = dataSource.trim(); break; case '4': const userReqs = await this.askQuestion('User requirements (comma-separated): '); this.plan.context.userRequirements = userReqs.split(',').map(r => r.trim()).filter(r => r); break; case '5': const priority = await this.askQuestion('Priority (1=Low, 2=Medium, 3=High): '); if (priority.trim()) this.plan.context.priority = priority.trim(); break; case '6': return; } console.log('✅ Context updated'); } async saveChanges() { console.log('\n💾 SAVING CHANGES'); console.log('─'.repeat(20)); // Update metadata this.plan.lastModified = new Date().toISOString(); this.plan.status = 'modified'; // Recalculate estimate this.plan.estimatedHours = this.calculateEstimate(); // Show changes summary this.showChangesSummary(); const confirm = await this.askQuestion('\n✅ Save these changes? (y/n): '); if (confirm.toLowerCase().startsWith('y')) { const planPath = path.join('state', 'master-plan.json'); await fs.writeFile(planPath, JSON.stringify(this.plan, null, 2)); console.log('\n🎉 Plan saved successfully!'); console.log('📁 Location: state/master-plan.json'); console.log('\n💡 Next steps:'); console.log('• Continue plan: node plan-project.js continue'); console.log('• View progress: node plan-project.js progress'); } else { console.log('Changes not saved.'); } } async discardChanges() { const confirm = await this.askQuestion('\n⚠️ Discard all changes? (y/n): '); if (confirm.toLowerCase().startsWith('y')) { this.plan = this.originalPlan; console.log('✅ Changes discarded'); } } showChangesSummary() { console.log('\n📋 CHANGES SUMMARY:'); if (this.plan.goal !== this.originalPlan.goal) { console.log(`🎯 Goal changed: ${this.originalPlan.goal} → ${this.plan.goal}`); } if (this.plan.phases.length !== this.originalPlan.phases.length) { console.log(`📊 Phase count changed: ${this.originalPlan.phases.length} → ${this.plan.phases.length}`); } const originalTasks = this.originalPlan.phases.reduce((sum, p) => sum + (p.tasks?.length || 0), 0); const currentTasks = this.plan.phases.reduce((sum, p) => sum + (p.tasks?.length || 0), 0); if (currentTasks !== originalTasks) { console.log(`📝 Task count changed: ${originalTasks} → ${currentTasks}`); } console.log(`📊 Current structure: ${this.plan.phases.length} phases, ${currentTasks} tasks`); } calculateEstimate() { // Simple task count for reference (no time estimation) let totalTasks = 0; this.plan.phases.forEach(phase => { if (phase.tasks) { totalTasks += phase.tasks.length; } }); return totalTasks; // Return task count instead of time } askQuestion(question) { return new Promise((resolve) => { this.rl.question(question, resolve); }); } } // Run the plan editor if (require.main === module) { const editor = new PlanEditor(); editor.start().catch(console.error); } module.exports = PlanEditor;