UNPKG

mirror-magi-meta-agent

Version:

AI-powered development planning and execution system with Supabase integration

342 lines (285 loc) • 10.2 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'); // Git helper functions const git = { async status() { try { const { stdout } = await execAsync('git status --porcelain'); return stdout.trim().split('\n').filter(line => line.length > 0); } catch (error) { throw new Error('Not a git repository'); } }, async hasChanges() { const changes = await this.status(); return changes.length > 0; }, async stash(message) { await execAsync(`git stash push -m "${message}"`); }, async add(files = '.') { await execAsync(`git add ${files}`); }, async commit(message) { await execAsync(`git commit -m "${message}"`); }, async getCurrentBranch() { const { stdout } = await execAsync('git branch --show-current'); return stdout.trim(); }, async diff(staged = false) { const { stdout } = await execAsync(`git diff ${staged ? '--staged' : ''}`); return stdout; } }; // 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, commitPrefix: 'feat', includeTaskId: true, interactive: 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)); } // Generate commit message based on task function generateCommitMessage(task, customMessage = null) { if (customMessage) return customMessage; const typeMap = { 'supabase_migration': 'feat(db)', 'supabase_rls': 'feat(security)', 'component_creation': 'feat(ui)', 'api_integration': 'feat(api)', 'configuration': 'chore(config)', 'testing': 'test', 'bug_fix': 'fix', 'documentation': 'docs', 'styling': 'style', 'refactoring': 'refactor', 'feature_development': 'feat' }; const prefix = typeMap[task.type] || 'feat'; const description = task.description; const taskRef = ` (${task.id})`; return `${prefix}: ${description}${taskRef}`; } // Interactive commit prompt async function interactiveCommit(task, changes) { const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout }); const question = (prompt) => new Promise(resolve => readline.question(prompt, resolve)); console.log('\nšŸ“ Changed files:'); changes.forEach(change => console.log(` ${change}`)); console.log('\nšŸ’¬ Suggested commit message:'); const suggested = generateCommitMessage(task); console.log(` ${suggested}`); const choice = await question('\nOptions:\n1. Use suggested message\n2. Custom message\n3. Skip commit\n> '); let message = null; if (choice === '1') { message = suggested; } else if (choice === '2') { message = await question('Enter commit message: '); } readline.close(); return message; } // Handle the complete command with optional commit async function handleComplete(taskId, options = {}) { if (!taskId) { console.log('Usage: node plan-project.js complete <task_id> [--commit] [--message "..."]'); 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 for git changes if commit is requested const shouldCommit = options.commit || (preferences.autoCommit && !options.noCommit); if (shouldCommit) { const hasChanges = await git.hasChanges(); if (hasChanges) { const changes = await git.status(); // Check for unrelated changes const unrelatedChanges = []; const taskChanges = []; // In a real implementation, we'd analyze which files are related to the task // For now, we'll assume all changes are task-related taskChanges.push(...changes); if (preferences.interactive && !options.message) { // Interactive mode const commitMessage = await interactiveCommit(task, taskChanges); 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 { // Automatic mode const commitMessage = options.message || generateCommitMessage(task); console.log('\nšŸ”„ Auto-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('ā„¹ļø 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 phase = planner.getCurrentPhase(); const phaseProgress = planner.calculatePhaseProgress(phase.id); if (phaseProgress.percentage === 100) { console.log(`\nšŸŽ‰ Phase "${phase.name}" completed!`); } // Save preferences if they were updated if (options.savePrefs) { await saveUserPreferences(preferences); } } 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('Current preferences:'); console.log(JSON.stringify(preferences, null, 2)); return; } switch (setting) { case 'autoCommit': preferences.autoCommit = value === 'true'; break; case 'commitPrefix': preferences.commitPrefix = value; break; case 'includeTaskId': preferences.includeTaskId = value === 'true'; break; case 'interactive': preferences.interactive = value === 'true'; break; default: console.log(`Unknown setting: ${setting}`); console.log('Available settings: autoCommit, commitPrefix, includeTaskId, interactive'); return; } await saveUserPreferences(preferences); console.log(`āœ… Updated ${setting} to ${value}`); } // Main function remains largely the same 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'); } } } // Parse command line arguments const args = process.argv.slice(2); const command = args[0]; // Parse options const options = {}; 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(args[1], options); } else if (command === 'config') { handleConfig(args[1], args[2]); } else if (command === 'next' || command === 'continue') { main(); } else if (command === 'progress' || command === 'status') { process.argv[2] = '3'; main(); } else { main(); } // Export the continue and create functions (rest of the file remains the same) async function createNewPlan(planner) { // ... same as original } async function continueExistingPlan(planner) { // ... same as original } async function showPlanProgress(planner) { // ... same as original }