UNPKG

mirror-magi-meta-agent

Version:

AI-powered development planning and execution system with Supabase integration

460 lines (382 loc) • 15.5 kB
#!/usr/bin/env node const fs = require('fs').promises; const path = require('path'); const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); const ProjectPlanner = require('../../core/project-planner'); const SupabasePreflightChecker = require('./supabase-preflight'); const git = require('./git-helpers'); const { buildInteractiveCommitMessage, generateCommitMessage, generateCommitBody } = require('./commit-utils'); const ClaudeResponseManager = require('./claude-response-manager'); // Initialize response manager const responseManager = new ClaudeResponseManager(); // Load user preferences async function loadUserPreferences() { try { const prefsPath = path.join(__dirname, '../../state/user-preferences.json'); const prefs = await fs.readFile(prefsPath, 'utf8'); return JSON.parse(prefs); } catch { return { autoCommit: false, interactive: true, includeTaskId: true, showGitInstructions: true }; } } // Save user preferences async function saveUserPreferences(prefs) { const prefsPath = path.join(__dirname, '../../state/user-preferences.json'); await fs.writeFile(prefsPath, JSON.stringify(prefs, null, 2)); } // Handle the complete command with git integration async function handleComplete(taskId, options = {}) { if (!taskId) { console.log('Usage: npm run plan:complete <task_id> [--commit] [--message "..."] [--no-commit]'); process.exit(1); } try { // Load configurations const config = JSON.parse(await fs.readFile(path.join(__dirname, '../../config/project-config.json'), 'utf8')); const projectState = JSON.parse(await fs.readFile(path.join(__dirname, '../../state/project-state.json'), 'utf8')); const preferences = await loadUserPreferences(); const planner = new ProjectPlanner(config, projectState); await planner.loadPlan(); // Find the task const task = planner.findTask(taskId); if (!task) { console.error(`āŒ Task ${taskId} not found`); process.exit(1); } // Check if we should commit const shouldCommit = options.commit || (preferences.autoCommit && !options.noCommit); if (shouldCommit) { // Check if we're in a git repository const isGitRepo = await git.isGitRepo(); if (!isGitRepo) { console.log('āš ļø Not a git repository. Skipping commit.'); } else { const hasChanges = await git.hasChanges(); if (hasChanges) { const changes = await git.status(); // Get Claude's response for this task if available const claudeResponse = await responseManager.getResponse(taskId); let commitMessage; if (preferences.interactive && !options.message) { // Interactive mode commitMessage = await buildInteractiveCommitMessage(task, claudeResponse, changes); } else { // Automatic mode if (options.message) { commitMessage = options.message; } else if (claudeResponse && claudeResponse.summary) { // Use Claude's summary commitMessage = generateCommitMessage(task, { claudeSummary: claudeResponse.summary, includeTaskId: preferences.includeTaskId }); const body = generateCommitBody(task, claudeResponse.summary, changes); if (body) { commitMessage = `${commitMessage}\n\n${body}`; } } else { // Fall back to task description commitMessage = generateCommitMessage(task, { includeTaskId: preferences.includeTaskId }); } } if (commitMessage) { console.log('\nšŸ”„ Committing changes...'); await git.add(); await git.commit(commitMessage); console.log('āœ… Changes committed!'); const { stdout } = await execAsync('git log -1 --oneline'); console.log(` ${stdout.trim()}`); } else { console.log('ā­ļø Skipping commit'); } } else { console.log('ā„¹ļø No changes to commit'); } } } // Mark task as completed const progress = await planner.markTaskCompleted(taskId); console.log(`\nāœ… Task ${taskId} marked as complete!`); console.log(`šŸ“Š Progress: ${progress.completed}/${progress.total} tasks (${Math.round(progress.percentage)}%)`); // Check if phase is complete const currentPhase = planner.getCurrentPhase(); if (currentPhase) { const phaseProgress = planner.calculatePhaseProgress(currentPhase.id); if (phaseProgress.percentage === 100) { console.log(`\nšŸŽ‰ Phase "${currentPhase.name}" completed!`); } } // Show next task preview const nextTask = planner.findNextTask(); if (nextTask) { console.log(`\nšŸŽÆ Next task: ${nextTask.description}`); console.log('Run: npm run plan:continue'); } else { console.log('\nšŸŽ‰ All tasks completed! Great work!'); } } catch (error) { console.error('āŒ Error:', error.message); process.exit(1); } } // Handle the config command async function handleConfig(setting, value) { const preferences = await loadUserPreferences(); if (!setting) { console.log('\nšŸ“‹ Current Git Integration Settings:\n'); console.log(JSON.stringify(preferences, null, 2)); console.log('\nAvailable settings:'); console.log(' autoCommit - Automatically commit on task completion (true/false)'); console.log(' interactive - Show commit prompt (true/false)'); console.log(' includeTaskId - Include task ID in commit messages (true/false)'); console.log(' showGitInstructions - Show git instructions after task (true/false)'); console.log('\nUsage: npm run plan:config <setting> <value>'); return; } switch (setting) { case 'autoCommit': preferences.autoCommit = value === 'true'; break; case 'interactive': preferences.interactive = value === 'true'; break; case 'includeTaskId': preferences.includeTaskId = value === 'true'; break; case 'showGitInstructions': preferences.showGitInstructions = value === 'true'; break; default: console.log(`āŒ Unknown setting: ${setting}`); return; } await saveUserPreferences(preferences); console.log(`āœ… Updated ${setting} to ${value}`); } // Save Claude's response after showing command async function saveClaudeInstructions(taskId) { console.log('\nšŸ“ After Claude implements this task, you can save the response:'); console.log(`npm run plan:save-response ${taskId} "Claude's response here"`); console.log('Or paste the response into a file and run:'); console.log(`npm run plan:save-response ${taskId} --file response.txt\n`); } async function main() { console.log('šŸŽÆ Mirror Magi Project Planner\n'); console.log('Create a specific, actionable development plan for your project goal.\n'); try { // Load configurations const config = JSON.parse(await fs.readFile(path.join(__dirname, '../../config/project-config.json'), 'utf8')); const projectState = JSON.parse(await fs.readFile(path.join(__dirname, '../../state/project-state.json'), 'utf8')); const planner = new ProjectPlanner(config, projectState); // Check if there's an existing plan const existingPlan = await planner.loadPlan(); if (existingPlan) { console.log('šŸ“‹ Found existing plan:', existingPlan.goal); console.log('Would you like to:'); console.log('1. Continue with existing plan'); console.log('2. Create a new plan'); console.log('3. Show plan progress\n'); // For demo purposes, we'll continue with existing plan const choice = process.argv[2] || '1'; if (choice === '3') { await showPlanProgress(planner); return; } else if (choice === '2') { await createNewPlan(planner); return; } // Continue with existing plan await continueExistingPlan(planner); return; } // Create new plan await createNewPlan(planner); } catch (error) { console.error('āŒ Error:', error.message); if (error.message.includes('project-config.json')) { console.log('šŸ’” Run "npm run setup" first to create configuration files'); } } } async function createNewPlan(planner) { const goal = process.argv[2] || 'Create an investment dashboard page with portfolio overview and transaction history'; console.log('šŸŽÆ PROJECT GOAL:', goal); console.log('\nšŸ”„ Creating master development plan...\n'); // For a real implementation, you'd want to gather more context const context = { requiredFeatures: ['charts', 'data_table', 'filtering'], designSystem: 'Tailwind CSS', dataSource: 'REST API', userRequirements: [ 'View portfolio performance over time', 'Filter transactions by date range', 'Export data to CSV' ] }; const plan = await planner.createMasterPlan(goal, context); console.log('āœ… Master Plan Created!'); console.log(`šŸ“Š ${plan.phases.length} phases planned`); console.log(`ā±ļø Estimated timeline: ${plan.estimatedTimeline} hours`); console.log('\nšŸ“‹ DEVELOPMENT PHASES:'); plan.phases.forEach((phase, index) => { console.log(`\n${index + 1}. ${phase.name}`); console.log(` ${phase.description}`); if (phase.tasks) { console.log(` šŸ“ ${phase.tasks.length} tasks`); } }); console.log('\nšŸš€ Ready to start development!'); console.log('Run: npm run plan:continue'); } async function continueExistingPlan(planner) { console.log('šŸš€ Generating next specific command...\n'); const nextCommand = await planner.generateNextCommand(); if (nextCommand.status === 'complete') { console.log('šŸŽ‰ All tasks completed!'); console.log(nextCommand.message); return; } const { task, command, progress } = nextCommand; // Check if this is a Supabase task const isSupabaseTask = ['supabase_migration', 'supabase_function', 'supabase_rls'].includes(task.type); if (isSupabaseTask) { console.log('šŸ—„ļø SUPABASE TASK DETECTED'); console.log('Running pre-flight checks...\n'); const checker = new SupabasePreflightChecker(); const passed = await checker.runChecks(); if (!passed) { console.log('\nāŒ Pre-flight checks failed!'); console.log('Fix the errors above before proceeding with Supabase tasks.'); return; } console.log('\nāœ… Pre-flight checks passed!'); console.log('─'.repeat(40)); } console.log(`\nšŸ“Š Progress: ${progress.completed}/${progress.total} tasks (${Math.round(progress.percentage)}%)`); console.log(`\nšŸŽÆ NEXT TASK: ${task.description}`); console.log(`šŸ“‹ Type: ${task.type}`); console.log(`šŸ“‹ Task ID: ${task.id}`); if (task.specifics) { console.log('\nšŸ“ SPECIFIC DETAILS:'); Object.entries(task.specifics).forEach(([key, value]) => { console.log(` ${key}: ${JSON.stringify(value)}`); }); } console.log('\nšŸš€ GENERATED COMMAND:'); console.log('=' .repeat(80)); console.log(command.command); console.log('=' .repeat(80)); console.log('\nāœ… VALIDATION STEPS:'); command.validationSteps.forEach((step, index) => { console.log(`${index + 1}. ${step}`); }); console.log('\nšŸŽÆ SUCCESS CRITERIA:'); command.successCriteria.forEach((criteria, index) => { console.log(`${index + 1}. ${criteria}`); }); // Git integration instructions const preferences = await loadUserPreferences(); if (preferences.showGitInstructions || preferences.autoCommit) { console.log('\nšŸ”„ GIT INTEGRATION:'); if (preferences.autoCommit) { console.log('āœ… Auto-commit is enabled. Changes will be committed automatically when you complete this task.'); } else { console.log('šŸ’” To commit changes when done:'); console.log(` npm run plan:complete ${task.id} --commit`); } console.log('\nšŸ“ To save Claude\'s response for better commit messages:'); console.log(` npm run plan:save-response ${task.id} "Claude's final message"`); } console.log('\nšŸ’” After completing this task, run:'); console.log(`npm run plan:complete ${task.id}`); } async function showPlanProgress(planner) { const plan = planner.masterPlan; console.log(`šŸ“Š PROJECT: ${plan.goal}`); console.log(`šŸ“… Created: ${new Date(plan.created).toLocaleDateString()}`); const progress = planner.calculateProgress(); console.log(`\nšŸŽÆ PROGRESS: ${progress.completed}/${progress.total} tasks (${Math.round(progress.percentage)}%)`); console.log('\nšŸ“‹ PHASES:'); plan.phases.forEach((phase, index) => { const phaseProgress = planner.calculatePhaseProgress(phase.id); const status = phaseProgress.percentage === 100 ? 'āœ…' : phaseProgress.percentage > 0 ? 'šŸ”„' : 'ā³'; console.log(`${status} ${index + 1}. ${phase.name} (${Math.round(phaseProgress.percentage)}%)`); }); const nextTask = planner.findNextTask(); if (nextTask) { console.log(`\nšŸŽÆ NEXT: ${nextTask.description}`); console.log('Run: npm run plan:continue'); } // Show git preferences const preferences = await loadUserPreferences(); console.log('\nāš™ļø Git Integration:', preferences.autoCommit ? 'Enabled' : 'Disabled'); } // Handle saving Claude's response async function handleSaveResponse(taskId, response, options = {}) { if (!taskId) { console.log('Usage: npm run plan:save-response <task_id> "response" | --file <file>'); process.exit(1); } try { let responseText = response; if (options.file) { responseText = await fs.readFile(options.file, 'utf8'); } if (!responseText) { console.error('āŒ No response provided'); process.exit(1); } await responseManager.saveResponse(taskId, responseText); const savedResponse = await responseManager.getResponse(taskId); console.log(`āœ… Saved Claude's response for task ${taskId}`); if (savedResponse.summary) { console.log(`šŸ“ Extracted summary: "${savedResponse.summary}"`); console.log('This will be used for commit messages!'); } } catch (error) { console.error('āŒ Error saving response:', error.message); process.exit(1); } } // Parse command line arguments const args = process.argv.slice(2); const command = args[0]; // Parse options const options = {}; let mainArg = args[1]; for (let i = 1; i < args.length; i++) { if (args[i].startsWith('--')) { const key = args[i].substring(2); if (args[i + 1] && !args[i + 1].startsWith('--')) { options[key] = args[i + 1]; i++; } else { options[key] = true; } } } // Route to appropriate handler if (command === 'complete') { handleComplete(mainArg, options); } else if (command === 'config') { handleConfig(args[1], args[2]); } else if (command === 'save-response') { handleSaveResponse(mainArg, args[2], options); } else if (command === 'next' || command === 'continue') { main(); } else if (command === 'progress' || command === 'status') { process.argv[2] = '3'; main(); } else { main(); }