mirror-magi-meta-agent
Version:
AI-powered development planning and execution system with Supabase integration
531 lines (437 loc) ⢠18.7 kB
JavaScript
const fs = require('fs').promises;
const path = require('path');
const readline = require('readline');
/**
* Interactive Project Planner
* Allows collaborative planning with full user control before finalizing
*/
class InteractivePlanner {
constructor() {
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
this.draftPlan = null;
this.config = null;
this.projectState = null;
}
async start() {
console.log('šÆ Interactive Project Planner');
console.log('ā'.repeat(50));
console.log('Create and refine your development plan collaboratively\n');
try {
// Load configuration
await this.loadConfiguration();
// Check for existing plans
await this.checkExistingPlans();
// Start interactive planning
await this.startPlanningSession();
} catch (error) {
console.error('ā Error:', error.message);
} finally {
this.rl.close();
}
}
async loadConfiguration() {
try {
this.config = JSON.parse(await fs.readFile('config/project-config.json', 'utf8'));
this.projectState = JSON.parse(await fs.readFile('state/project-state.json', 'utf8'));
console.log('ā
Configuration loaded successfully\n');
} catch (error) {
throw new Error('Configuration files not found. Run "npm run setup" first.');
}
}
async checkExistingPlans() {
try {
const planPath = path.join('state', 'master-plan.json');
const existingPlan = JSON.parse(await fs.readFile(planPath, 'utf8'));
console.log('š Found existing plan:', existingPlan.goal);
console.log('Created:', new Date(existingPlan.created).toLocaleString());
const choice = await this.askQuestion('\nWhat would you like to do?\n1. Edit existing plan\n2. Continue existing plan\n3. Create new plan\n4. View plan details\n\nChoose (1-4): ');
switch (choice.trim()) {
case '1':
await this.editExistingPlan(existingPlan);
break;
case '2':
await this.continueExistingPlan(existingPlan);
break;
case '3':
await this.createNewPlan();
break;
case '4':
await this.viewPlanDetails(existingPlan);
await this.checkExistingPlans(); // Return to menu
break;
default:
console.log('Invalid choice. Creating new plan...\n');
await this.createNewPlan();
}
} catch (error) {
// No existing plan, start fresh
await this.createNewPlan();
}
}
async createNewPlan() {
console.log('\nš Creating New Development Plan');
console.log('ā'.repeat(40));
// Step 1: Get project goal
const goal = await this.askQuestion('\nš What is your main project goal?\n(Be specific about what you want to build): ');
if (!goal.trim()) {
console.log('ā Goal cannot be empty. Please try again.');
return await this.createNewPlan();
}
// Step 2: Gather context
const context = await this.gatherProjectContext();
// Step 3: Generate initial plan structure
console.log('\nš Generating initial plan structure...');
this.draftPlan = await this.generateInitialPlan(goal, context);
// Step 4: Review and refine
await this.reviewAndRefinePlan();
}
async gatherProjectContext() {
console.log('\nš Let\'s gather some context about your project:');
const context = {};
// Required features
const featuresInput = await this.askQuestion('\nš§ What key features do you need? (comma-separated):\nExamples: charts, authentication, real-time updates, file upload\n> ');
context.requiredFeatures = featuresInput.split(',').map(f => f.trim()).filter(f => f);
// Design/UI approach
const designSystem = await this.askQuestion('\nšØ What design system/approach are you using?\nExamples: Tailwind CSS, Material-UI, Custom CSS, Bootstrap\n> ');
context.designSystem = designSystem.trim();
// Data source
const dataSource = await this.askQuestion('\nš¾ What\'s your primary data source?\nExamples: REST API, GraphQL, Database, Firebase, Static files\n> ');
context.dataSource = dataSource.trim();
// User requirements
const userReqs = await this.askQuestion('\nš„ Any specific user requirements? (comma-separated):\nExamples: mobile-friendly, offline support, accessibility\n> ');
context.userRequirements = userReqs.split(',').map(r => r.trim()).filter(r => r);
// Complexity/priority
const priority = await this.askQuestion('\nā Project priority/urgency?\n1. Low (take time, do it right)\n2. Medium (balanced approach)\n3. High (MVP fast, iterate later)\nChoose (1-3): ');
context.priority = priority.trim();
return context;
}
async generateInitialPlan(goal, context) {
// Analyze goal type
const goalType = this.classifyProjectGoal(goal);
console.log(`š Detected project type: ${goalType}`);
// Generate phases based on goal and context
const phases = this.generatePhases(goal, goalType, context);
const plan = {
id: `plan_${Date.now()}`,
goal: goal,
context: context,
goalType: goalType,
phases: phases,
status: 'draft',
created: new Date().toISOString(),
estimatedHours: this.calculateEstimate(phases)
};
return plan;
}
async reviewAndRefinePlan() {
console.log('\nš DRAFT PLAN REVIEW');
console.log('ā'.repeat(50));
this.displayPlanSummary();
while (true) {
const action = await this.askQuestion('\nWhat would you like to do?\n1. Review/edit phases\n2. Add new phase\n3. Remove phase\n4. Reorder phases\n5. Finalize plan\n6. Start over\n\nChoose (1-6): ');
switch (action.trim()) {
case '1':
await this.reviewPhases();
break;
case '2':
await this.addNewPhase();
break;
case '3':
await this.removePhase();
break;
case '4':
await this.reorderPhases();
break;
case '5':
await this.finalizePlan();
return;
case '6':
await this.createNewPlan();
return;
default:
console.log('Invalid choice. Please try again.');
}
this.displayPlanSummary();
}
}
displayPlanSummary() {
console.log(`\nšÆ PROJECT: ${this.draftPlan.goal}`);
console.log(`ā±ļø ESTIMATED: ${this.draftPlan.estimatedHours} hours`);
console.log(`š PHASES: ${this.draftPlan.phases.length}`);
console.log('\nš PHASE OVERVIEW:');
this.draftPlan.phases.forEach((phase, index) => {
const taskCount = phase.tasks ? phase.tasks.length : 'TBD';
console.log(`${index + 1}. ${phase.name} (${taskCount} tasks)`);
console.log(` ${phase.description}`);
});
}
async reviewPhases() {
console.log('\nš REVIEWING PHASES');
console.log('ā'.repeat(30));
for (let i = 0; i < this.draftPlan.phases.length; i++) {
const phase = this.draftPlan.phases[i];
console.log(`\nš PHASE ${i + 1}: ${phase.name}`);
console.log(`Description: ${phase.description}`);
if (phase.tasks) {
console.log(`Tasks (${phase.tasks.length}):`);
phase.tasks.forEach((task, idx) => {
console.log(` ${idx + 1}. ${task.description} (${task.estimatedTime}min)`);
});
}
const action = await this.askQuestion('\nActions:\n1. Keep as-is\n2. Edit description\n3. Edit tasks\n4. Skip for now\n\nChoose (1-4): ');
switch (action.trim()) {
case '2':
const newDesc = await this.askQuestion('New description: ');
if (newDesc.trim()) this.draftPlan.phases[i].description = newDesc.trim();
break;
case '3':
await this.editPhaseTasks(i);
break;
case '1':
case '4':
default:
// Continue to next phase
break;
}
}
}
async editPhaseTasks(phaseIndex) {
const phase = this.draftPlan.phases[phaseIndex];
console.log(`\nš§ EDITING TASKS FOR: ${phase.name}`);
if (!phase.tasks) {
phase.tasks = [];
}
while (true) {
console.log(`\nCurrent tasks (${phase.tasks.length}):`);
phase.tasks.forEach((task, idx) => {
console.log(`${idx + 1}. ${task.description} (${task.estimatedTime}min)`);
});
const action = await this.askQuestion('\n1. Add task\n2. Edit task\n3. Remove task\n4. Done\n\nChoose (1-4): ');
switch (action.trim()) {
case '1':
await this.addTaskToPhase(phase);
break;
case '2':
const editIdx = await this.askQuestion('Task number to edit: ');
const idx = parseInt(editIdx) - 1;
if (idx >= 0 && idx < phase.tasks.length) {
await this.editTask(phase.tasks[idx]);
}
break;
case '3':
const removeIdx = await this.askQuestion('Task number to remove: ');
const rIdx = parseInt(removeIdx) - 1;
if (rIdx >= 0 && rIdx < phase.tasks.length) {
phase.tasks.splice(rIdx, 1);
}
break;
case '4':
return;
}
}
}
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 finalizePlan() {
console.log('\nšÆ FINALIZING PLAN');
console.log('ā'.repeat(30));
this.displayPlanSummary();
// Validate plan structure before finalizing
const isValid = await this.validatePlanStructure();
if (!isValid) {
console.log('\nā ļø Plan has structure issues that need to be fixed before finalizing.');
const continueAnyway = await this.askQuestion('Continue anyway? (y/n): ');
if (!continueAnyway.toLowerCase().startsWith('y')) {
console.log('\nā©ļø Returning to plan editing...');
await this.reviewAndRefinePlan();
return;
}
}
const confirm = await this.askQuestion('\nā
Are you satisfied with this plan? (y/n): ');
if (confirm.toLowerCase().startsWith('y')) {
// Save the finalized plan
this.draftPlan.status = 'finalized';
this.draftPlan.finalizedAt = new Date().toISOString();
await this.savePlan(this.draftPlan);
console.log('\nš Plan finalized and saved!');
console.log('š Saved to: state/master-plan.json');
// Ask if they want to start execution
const startNow = await this.askQuestion('\nš Start executing the plan now? (y/n): ');
if (startNow.toLowerCase().startsWith('y')) {
await this.startPlanExecution();
} else {
console.log('\nš” To start execution later, run:');
console.log(' node plan-project.js continue');
console.log(' or: npm run plan:continue');
console.log('\nš” To view complete plan with all details:');
console.log(' node view-complete-plan.js');
console.log('\nš” To discuss and refine further:');
console.log(' node discuss-plan.js');
}
} else {
console.log('\nā©ļø Returning to plan editing...');
await this.reviewAndRefinePlan();
}
}
async validatePlanStructure() {
const CompletePlanViewer = require('./view-complete-plan');
const viewer = new CompletePlanViewer();
viewer.plan = this.draftPlan;
const checks = viewer.validatePlanStructure();
const allPassed = checks.every(check => check.passed);
console.log('\nā
PLAN STRUCTURE VALIDATION');
console.log('ā'.repeat(40));
checks.forEach(check => {
const icon = check.passed ? 'ā
' : 'ā';
console.log(`${icon} ${check.description}`);
if (!check.passed && check.suggestion) {
console.log(` š” ${check.suggestion}`);
}
});
console.log(`\n${allPassed ? 'š' : 'ā ļø'} Plan Structure: ${allPassed ? 'VALID' : 'NEEDS ATTENTION'}`);
return allPassed;
}
async startPlanExecution() {
console.log('\nš STARTING PLAN EXECUTION');
console.log('ā'.repeat(40));
const ProjectPlanner = require('./core/project-planner');
const planner = new ProjectPlanner(this.config, this.projectState);
planner.masterPlan = this.draftPlan;
const nextCommand = await planner.generateNextCommand();
if (nextCommand.status === 'complete') {
console.log('š All tasks completed!');
return;
}
const { task, command, progress } = nextCommand;
console.log(`š Progress: ${progress.completed}/${progress.total} tasks (${Math.round(progress.percentage)}%)`);
console.log(`\nšÆ NEXT TASK: ${task.description}`);
console.log(`š Type: ${task.type}`);
console.log(`ā±ļø Estimated: ${task.estimatedTime} minutes`);
console.log('\nš CLAUDE CODE MAX COMMAND:');
console.log('ā'.repeat(80));
console.log(command.command);
console.log('ā'.repeat(80));
console.log('\nš EXECUTION WORKFLOW:');
console.log('1. Copy the command above');
console.log('2. Paste it into Claude Code Max');
console.log('3. Let Claude implement the task');
console.log('4. When complete, run: node plan-project.js complete ' + task.id);
console.log('5. Then run: node plan-project.js continue');
console.log('\nā
VALIDATION STEPS:');
command.validationSteps.forEach((step, index) => {
console.log(`${index + 1}. ${step}`);
});
}
// Helper methods
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';
}
generatePhases(goal, goalType, context) {
const phases = [];
switch (goalType) {
case 'new_page':
phases.push(
{ id: 'foundation', name: 'Page Foundation', description: 'Create basic page structure and routing', tasks: [] },
{ id: 'data_layer', name: 'Data & State', description: 'Set up data fetching and state management', tasks: [] },
{ id: 'ui_components', name: 'UI Components', description: 'Build the main UI components and features', tasks: [] },
{ id: 'integration', name: 'Integration & Polish', description: 'Connect everything and add finishing touches', tasks: [] }
);
break;
case 'api_integration':
phases.push(
{ id: 'api_setup', name: 'API Setup', description: 'Configure API connections and authentication', tasks: [] },
{ id: 'data_models', name: 'Data Models', description: 'Create data types and validation', tasks: [] },
{ id: 'integration', name: 'Integration', description: 'Connect API to application', tasks: [] },
{ id: 'testing', name: 'Testing & Error Handling', description: 'Test integration and handle edge cases', tasks: [] }
);
break;
default:
phases.push(
{ id: 'planning', name: 'Technical Planning', description: 'Plan the technical approach', tasks: [] },
{ id: 'implementation', name: 'Core Implementation', description: 'Build the main functionality', tasks: [] },
{ id: 'testing', name: 'Testing & Validation', description: 'Test and validate the implementation', tasks: [] },
{ id: 'polish', name: 'Polish & Documentation', description: 'Final touches and documentation', tasks: [] }
);
}
return phases;
}
calculateEstimate(phases) {
// Simplified estimation without time - just count complexity
return phases.length; // Simple count for reference
}
async savePlan(plan) {
const planPath = path.join('state', 'master-plan.json');
await fs.writeFile(planPath, JSON.stringify(plan, null, 2));
}
askQuestion(question) {
return new Promise((resolve) => {
this.rl.question(question, resolve);
});
}
// Additional methods for editing, continuing plans, etc.
async editExistingPlan(existingPlan) {
this.draftPlan = { ...existingPlan, status: 'editing' };
console.log('\nāļø Editing existing plan...');
await this.reviewAndRefinePlan();
}
async continueExistingPlan(existingPlan) {
console.log('\nā¶ļø Continuing existing plan...');
const ProjectPlanner = require('./core/project-planner');
const planner = new ProjectPlanner(this.config, this.projectState);
planner.masterPlan = existingPlan;
const nextCommand = await planner.generateNextCommand();
if (nextCommand.status === 'complete') {
console.log('š All tasks completed!');
return;
}
// Show the next command
const { task, command } = nextCommand;
console.log('\nš NEXT CLAUDE CODE MAX COMMAND:');
console.log('ā'.repeat(80));
console.log(command.command);
console.log('ā'.repeat(80));
console.log(`\nš” After completing: node plan-project.js complete ${task.id}`);
}
async viewPlanDetails(plan) {
console.log('\nš PLAN DETAILS');
console.log('ā'.repeat(30));
console.log(`Goal: ${plan.goal}`);
console.log(`Status: ${plan.status}`);
console.log(`Created: ${new Date(plan.created).toLocaleString()}`);
console.log(`Estimated: ${plan.estimatedHours} hours`);
console.log('\nPhases:');
plan.phases.forEach((phase, i) => {
console.log(`${i + 1}. ${phase.name}`);
console.log(` ${phase.description}`);
if (phase.tasks) {
console.log(` Tasks: ${phase.tasks.length}`);
}
});
await this.askQuestion('\nPress Enter to continue...');
}
}
// Run the interactive planner
if (require.main === module) {
const planner = new InteractivePlanner();
planner.start().catch(console.error);
}
module.exports = InteractivePlanner;