UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

742 lines 32.1 kB
/** * V3 CLI Migrate Command * Migration tools for V2 to V3 transition */ import { output } from '../output.js'; import * as fs from 'fs'; import * as path from 'path'; // Migration targets const MIGRATION_TARGETS = [ { value: 'config', label: 'Configuration', hint: 'Migrate configuration files' }, { value: 'memory', label: 'Memory Data', hint: 'Migrate memory/database content' }, { value: 'agents', label: 'Agent Configs', hint: 'Migrate agent configurations' }, { value: 'hooks', label: 'Hooks', hint: 'Migrate hook definitions' }, { value: 'workflows', label: 'Workflows', hint: 'Migrate workflow definitions' }, { value: 'embeddings', label: 'Embeddings', hint: 'Migrate to ONNX with hyperbolic support' }, { value: 'all', label: 'All', hint: 'Full migration' } ]; // Status command const statusCommand = { name: 'status', description: 'Check migration status', action: async (ctx) => { const cwd = ctx.cwd || process.cwd(); const components = []; // Check v2 config: claude-flow.config.json with version "2" or missing version const v2ConfigPath = path.join(cwd, 'claude-flow.config.json'); const v3ConfigDir = path.join(cwd, '.claude-flow'); let hasV2Config = false; let hasV3Config = false; try { if (fs.existsSync(v2ConfigPath)) { const raw = fs.readFileSync(v2ConfigPath, 'utf-8'); const parsed = JSON.parse(raw); if (parsed.version === '2' || parsed.version === 2 || !parsed.version) { hasV2Config = true; } } } catch { /* ignore parse errors */ } try { hasV3Config = fs.existsSync(v3ConfigDir) && fs.statSync(v3ConfigDir).isDirectory(); } catch { /* ignore */ } if (hasV2Config && hasV3Config) { components.push({ component: 'Config', status: 'v2 + v3', migrationNeeded: 'no' }); } else if (hasV2Config) { components.push({ component: 'Config', status: 'v2', migrationNeeded: 'yes' }); } else if (hasV3Config) { components.push({ component: 'Config', status: 'v3', migrationNeeded: 'no' }); } else { components.push({ component: 'Config', status: 'missing', migrationNeeded: 'no' }); } // Check v2 memory: ./data/memory/*.json or memory.db const v2MemoryDir = path.join(cwd, 'data', 'memory'); let hasV2MemoryJson = false; let hasV2MemoryDb = false; try { if (fs.existsSync(v2MemoryDir)) { const files = fs.readdirSync(v2MemoryDir); hasV2MemoryJson = files.some(f => f.endsWith('.json')); hasV2MemoryDb = files.includes('memory.db'); } } catch { /* ignore */ } if (hasV2MemoryJson || hasV2MemoryDb) { components.push({ component: 'Memory', status: 'v2', migrationNeeded: 'yes' }); } else { components.push({ component: 'Memory', status: 'missing', migrationNeeded: 'no' }); } // Check v2 sessions: ./data/sessions/ const v2SessionsDir = path.join(cwd, 'data', 'sessions'); let hasV2Sessions = false; try { if (fs.existsSync(v2SessionsDir)) { const files = fs.readdirSync(v2SessionsDir); hasV2Sessions = files.length > 0; } } catch { /* ignore */ } if (hasV2Sessions) { components.push({ component: 'Sessions', status: 'v2', migrationNeeded: 'yes' }); } else { components.push({ component: 'Sessions', status: 'missing', migrationNeeded: 'no' }); } // Check migration state const migrationStatePath = path.join(cwd, '.claude-flow', 'migration-state.json'); let migrationState = null; try { if (fs.existsSync(migrationStatePath)) { const raw = fs.readFileSync(migrationStatePath, 'utf-8'); const parsed = JSON.parse(raw); migrationState = parsed.status || 'unknown'; } } catch { /* ignore */ } if (migrationState) { components.push({ component: 'Migration State', status: migrationState, migrationNeeded: 'no' }); } // Display results if (ctx.flags.format === 'json') { output.printJson({ components, migrationState }); return { success: true, data: { components, migrationState } }; } output.writeln(); output.writeln(output.bold('Migration Status')); output.writeln(); output.printTable({ columns: [ { key: 'component', header: 'Component', width: 20 }, { key: 'status', header: 'Status', width: 15 }, { key: 'migrationNeeded', header: 'Migration Needed', width: 20 } ], data: components.map(c => ({ component: c.component, status: formatMigrationStatus(c.status), migrationNeeded: c.migrationNeeded === 'yes' ? output.warning('yes') : output.dim('no') })), border: false }); const needsMigration = components.some(c => c.migrationNeeded === 'yes'); output.writeln(); if (needsMigration) { output.printInfo('V2 artifacts detected. Run "claude-flow migrate run" to migrate.'); } else { output.printSuccess('No migration needed.'); } return { success: true, data: { components, needsMigration } }; } }; // Run migration const runCommand = { name: 'run', description: 'Run migration', options: [ { name: 'target', short: 't', description: 'Migration target', type: 'string', choices: MIGRATION_TARGETS.map(t => t.value) }, { name: 'dry-run', description: 'Show what would be migrated without making changes', type: 'boolean', default: false }, { name: 'backup', description: 'Create backup before migration', type: 'boolean', default: true }, { name: 'force', short: 'f', description: 'Force migration (overwrite existing)', type: 'boolean', default: false } ], action: async (ctx) => { const cwd = ctx.cwd || process.cwd(); const dryRun = ctx.flags['dry-run'] === true; const skipBackup = ctx.flags.backup === false; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const v3Dir = path.join(cwd, '.claude-flow'); const backupDir = path.join(v3Dir, 'backup', `v2-${timestamp}`); const migrationStatePath = path.join(v3Dir, 'migration-state.json'); const migrated = []; const skipped = []; output.writeln(); output.writeln(output.bold('V2 to V3 Migration')); if (dryRun) { output.printWarning('Dry run mode — no changes will be made.'); } output.writeln(); // Ensure .claude-flow directory exists if (!dryRun) { fs.mkdirSync(v3Dir, { recursive: true }); } // --- Backup --- if (!skipBackup && !dryRun) { output.writeln(output.dim('Creating backup...')); fs.mkdirSync(backupDir, { recursive: true }); } // --- Config migration --- const v2ConfigPath = path.join(cwd, 'claude-flow.config.json'); try { if (fs.existsSync(v2ConfigPath)) { const raw = fs.readFileSync(v2ConfigPath, 'utf-8'); const parsed = JSON.parse(raw); if (parsed.version === '2' || parsed.version === 2 || !parsed.version) { if (dryRun) { output.printInfo(`Would migrate config: ${v2ConfigPath}`); } else { // Backup if (!skipBackup) { fs.copyFileSync(v2ConfigPath, path.join(backupDir, 'claude-flow.config.json')); } // Transform to v3 format const v3Config = { ...parsed, version: '3' }; // Rename swarm.mode -> swarm.topology if present if (v3Config.swarm && typeof v3Config.swarm === 'object') { const swarm = v3Config.swarm; if ('mode' in swarm && !('topology' in swarm)) { swarm.topology = swarm.mode; delete swarm.mode; } } // Rename memory.type -> memory.backend if present if (v3Config.memory && typeof v3Config.memory === 'object') { const mem = v3Config.memory; if ('type' in mem && !('backend' in mem)) { mem.backend = mem.type; delete mem.type; } } const v3ConfigPath = path.join(v3Dir, 'config.json'); fs.writeFileSync(v3ConfigPath, JSON.stringify(v3Config, null, 2)); output.printSuccess(`Config migrated to ${v3ConfigPath}`); } migrated.push('config'); } else { output.printInfo('Config already at v3 — skipping.'); skipped.push('config'); } } else { output.writeln(output.dim('No v2 config found — skipping config migration.')); skipped.push('config'); } } catch (err) { output.printError('Config migration failed', String(err)); skipped.push('config'); } // --- Memory migration --- const v2MemoryDir = path.join(cwd, 'data', 'memory'); try { if (fs.existsSync(v2MemoryDir)) { const files = fs.readdirSync(v2MemoryDir); const jsonFiles = files.filter(f => f.endsWith('.json')); const hasDb = files.includes('memory.db'); if (jsonFiles.length > 0 || hasDb) { if (dryRun) { output.printInfo(`Would migrate memory: ${jsonFiles.length} JSON files, ${hasDb ? '1 DB' : 'no DB'}`); } else { // Backup memory files if (!skipBackup) { const memBackup = path.join(backupDir, 'data', 'memory'); fs.mkdirSync(memBackup, { recursive: true }); for (const f of files) { const src = path.join(v2MemoryDir, f); if (fs.statSync(src).isFile()) { fs.copyFileSync(src, path.join(memBackup, f)); } } } output.printSuccess(`Memory files backed up (${jsonFiles.length} JSON, ${hasDb ? '1 DB' : '0 DB'}).`); output.printInfo('Run "claude-flow memory init --force" to import v2 memory into v3 AgentDB.'); } migrated.push('memory'); } else { output.writeln(output.dim('No v2 memory files found — skipping.')); skipped.push('memory'); } } else { output.writeln(output.dim('No v2 memory directory found — skipping.')); skipped.push('memory'); } } catch (err) { output.printError('Memory migration failed', String(err)); skipped.push('memory'); } // --- Session migration --- const v2SessionsDir = path.join(cwd, 'data', 'sessions'); try { if (fs.existsSync(v2SessionsDir)) { const files = fs.readdirSync(v2SessionsDir); if (files.length > 0) { if (dryRun) { output.printInfo(`Would migrate sessions: ${files.length} files from ${v2SessionsDir}`); } else { const v3SessionsDir = path.join(v3Dir, 'sessions'); fs.mkdirSync(v3SessionsDir, { recursive: true }); // Backup if (!skipBackup) { const sessBackup = path.join(backupDir, 'data', 'sessions'); fs.mkdirSync(sessBackup, { recursive: true }); for (const f of files) { const src = path.join(v2SessionsDir, f); if (fs.statSync(src).isFile()) { fs.copyFileSync(src, path.join(sessBackup, f)); } } } // Copy to v3 location for (const f of files) { const src = path.join(v2SessionsDir, f); if (fs.statSync(src).isFile()) { fs.copyFileSync(src, path.join(v3SessionsDir, f)); } } output.printSuccess(`Sessions migrated: ${files.length} files to ${v3SessionsDir}`); } migrated.push('sessions'); } else { output.writeln(output.dim('No v2 session files found — skipping.')); skipped.push('sessions'); } } else { output.writeln(output.dim('No v2 sessions directory found — skipping.')); skipped.push('sessions'); } } catch (err) { output.printError('Session migration failed', String(err)); skipped.push('sessions'); } // --- Save migration state --- if (!dryRun && migrated.length > 0) { const state = { status: 'completed', timestamp, backupPath: skipBackup ? null : backupDir, migrated, skipped }; fs.writeFileSync(migrationStatePath, JSON.stringify(state, null, 2)); output.writeln(); output.printSuccess(`Migration state saved to ${migrationStatePath}`); } // Summary output.writeln(); if (dryRun) { output.printInfo(`Dry run complete. ${migrated.length} component(s) would be migrated.`); } else if (migrated.length > 0) { output.printSuccess(`Migration complete. ${migrated.length} component(s) migrated: ${migrated.join(', ')}`); output.printInfo('Run "claude-flow migrate verify" to validate the migration.'); } else { output.printInfo('Nothing to migrate.'); } return { success: true, data: { migrated, skipped, dryRun } }; } }; // Verify migration const verifyCommand = { name: 'verify', description: 'Verify migration integrity', options: [ { name: 'fix', description: 'Automatically fix issues', type: 'boolean', default: false } ], action: async (ctx) => { const cwd = ctx.cwd || process.cwd(); const v3Dir = path.join(cwd, '.claude-flow'); const migrationStatePath = path.join(v3Dir, 'migration-state.json'); const checks = []; let allPassed = true; output.writeln(); output.writeln(output.bold('Migration Verification')); output.writeln(); // Check 1: Migration state file exists let migrationState = null; try { if (fs.existsSync(migrationStatePath)) { const raw = fs.readFileSync(migrationStatePath, 'utf-8'); migrationState = JSON.parse(raw); checks.push({ check: 'Migration state file', result: 'passed' }); } else { checks.push({ check: 'Migration state file', result: 'failed' }); allPassed = false; } } catch { checks.push({ check: 'Migration state file', result: 'failed' }); allPassed = false; } // Check 2: V3 config exists and is valid JSON const v3ConfigPath = path.join(v3Dir, 'config.json'); try { if (fs.existsSync(v3ConfigPath)) { const raw = fs.readFileSync(v3ConfigPath, 'utf-8'); JSON.parse(raw); // validate JSON checks.push({ check: 'V3 config (valid JSON)', result: 'passed' }); } else { // Config might not have been migrated if there was no v2 config const wasMigrated = migrationState && Array.isArray(migrationState.migrated) && migrationState.migrated.includes('config'); if (wasMigrated) { checks.push({ check: 'V3 config (valid JSON)', result: 'failed' }); allPassed = false; } else { checks.push({ check: 'V3 config (valid JSON)', result: 'skipped' }); } } } catch { checks.push({ check: 'V3 config (valid JSON)', result: 'failed' }); allPassed = false; } // Check 3: Backup exists if (migrationState && migrationState.backupPath) { const backupPath = migrationState.backupPath; try { if (fs.existsSync(backupPath) && fs.statSync(backupPath).isDirectory()) { checks.push({ check: 'Backup directory', result: 'passed' }); } else { checks.push({ check: 'Backup directory', result: 'failed' }); allPassed = false; } } catch { checks.push({ check: 'Backup directory', result: 'failed' }); allPassed = false; } } else if (migrationState && migrationState.backupPath === null) { checks.push({ check: 'Backup directory', result: 'skipped (backup was disabled)' }); } else { checks.push({ check: 'Backup directory', result: 'failed' }); allPassed = false; } // Check 4: V3 sessions directory if sessions were migrated if (migrationState && Array.isArray(migrationState.migrated) && migrationState.migrated.includes('sessions')) { const v3Sessions = path.join(v3Dir, 'sessions'); try { if (fs.existsSync(v3Sessions) && fs.readdirSync(v3Sessions).length > 0) { checks.push({ check: 'V3 sessions directory', result: 'passed' }); } else { checks.push({ check: 'V3 sessions directory', result: 'failed' }); allPassed = false; } } catch { checks.push({ check: 'V3 sessions directory', result: 'failed' }); allPassed = false; } } // Display if (ctx.flags.format === 'json') { output.printJson({ checks, allPassed }); return { success: allPassed, data: { checks, allPassed } }; } output.printTable({ columns: [ { key: 'check', header: 'Check', width: 30 }, { key: 'result', header: 'Result', width: 35 } ], data: checks.map(c => ({ check: c.check, result: formatMigrationStatus(c.result) })), border: false }); output.writeln(); if (allPassed) { output.printSuccess('All verification checks passed.'); } else { output.printError('Some verification checks failed.'); output.printInfo('Run "claude-flow migrate run" to re-run the migration, or "migrate rollback" to restore from backup.'); } return { success: allPassed, data: { checks, allPassed }, exitCode: allPassed ? 0 : 1 }; } }; // Rollback migration const rollbackCommand = { name: 'rollback', description: 'Rollback to previous version', options: [ { name: 'backup-id', description: 'Backup ID to restore', type: 'string' }, { name: 'force', short: 'f', description: 'Skip confirmation', type: 'boolean', default: false } ], action: async (ctx) => { const cwd = ctx.cwd || process.cwd(); const v3Dir = path.join(cwd, '.claude-flow'); const migrationStatePath = path.join(v3Dir, 'migration-state.json'); output.writeln(); output.writeln(output.bold('Migration Rollback')); output.writeln(); // Read migration state let migrationState; try { if (!fs.existsSync(migrationStatePath)) { output.printError('No migration state found.', 'Run "migrate run" first before attempting rollback.'); return { success: false, exitCode: 1 }; } const raw = fs.readFileSync(migrationStatePath, 'utf-8'); migrationState = JSON.parse(raw); } catch (err) { output.printError('Failed to read migration state', String(err)); return { success: false, exitCode: 1 }; } const backupPath = migrationState.backupPath; if (!backupPath) { output.printError('No backup path in migration state.', 'Migration was run with --no-backup. Cannot rollback.'); return { success: false, exitCode: 1 }; } if (!fs.existsSync(backupPath)) { output.printError('Backup directory not found.', `Expected: ${backupPath}`); return { success: false, exitCode: 1 }; } const restored = []; try { // Restore config const backupConfig = path.join(backupPath, 'claude-flow.config.json'); if (fs.existsSync(backupConfig)) { const destConfig = path.join(cwd, 'claude-flow.config.json'); fs.copyFileSync(backupConfig, destConfig); // Remove v3 config const v3Config = path.join(v3Dir, 'config.json'); if (fs.existsSync(v3Config)) { fs.unlinkSync(v3Config); } output.printSuccess('Restored: config'); restored.push('config'); } // Restore memory const backupMemory = path.join(backupPath, 'data', 'memory'); if (fs.existsSync(backupMemory)) { const destMemory = path.join(cwd, 'data', 'memory'); fs.mkdirSync(destMemory, { recursive: true }); const files = fs.readdirSync(backupMemory); for (const f of files) { fs.copyFileSync(path.join(backupMemory, f), path.join(destMemory, f)); } output.printSuccess(`Restored: memory (${files.length} files)`); restored.push('memory'); } // Restore sessions const backupSessions = path.join(backupPath, 'data', 'sessions'); if (fs.existsSync(backupSessions)) { const destSessions = path.join(cwd, 'data', 'sessions'); fs.mkdirSync(destSessions, { recursive: true }); const files = fs.readdirSync(backupSessions); for (const f of files) { fs.copyFileSync(path.join(backupSessions, f), path.join(destSessions, f)); } // Remove v3 sessions const v3Sessions = path.join(v3Dir, 'sessions'); if (fs.existsSync(v3Sessions)) { const v3Files = fs.readdirSync(v3Sessions); for (const f of v3Files) { fs.unlinkSync(path.join(v3Sessions, f)); } fs.rmdirSync(v3Sessions); } output.printSuccess(`Restored: sessions (${files.length} files)`); restored.push('sessions'); } // Delete migration state fs.unlinkSync(migrationStatePath); output.writeln(); output.printSuccess(`Rollback complete. Restored: ${restored.join(', ') || 'nothing to restore'}`); return { success: true, data: { restored } }; } catch (err) { output.printError('Rollback failed', String(err)); return { success: false, exitCode: 1 }; } } }; // Breaking changes info const breakingCommand = { name: 'breaking', description: 'Show V3 breaking changes', action: async (ctx) => { const changes = [ { category: 'Configuration', changes: [ { change: 'Config file renamed', from: 'claude-flow.json', to: 'claude-flow.config.json' }, { change: 'Swarm config restructured', from: 'swarm.mode', to: 'swarm.topology' }, { change: 'Provider config format', from: 'provider: "anthropic"', to: 'providers: [...]' } ] }, { category: 'Memory', changes: [ { change: 'Backend option changed', from: 'memory: { type }', to: 'memory: { backend }' }, { change: 'HNSW enabled by default', from: 'Manual opt-in', to: 'Auto-enabled' }, { change: 'Storage path changed', from: '.claude-flow/memory', to: 'data/memory' } ] }, { category: 'CLI', changes: [ { change: 'Agent command renamed', from: 'spawn <type>', to: 'agent spawn -t <type>' }, { change: 'Memory command added', from: 'N/A', to: 'memory <subcommand>' }, { change: 'Hook command enhanced', from: 'hook <type>', to: 'hooks <subcommand>' } ] }, { category: 'API', changes: [ { change: 'Removed Deno support', from: 'Deno + Node.js', to: 'Node.js 20+ only' }, { change: 'Event system changed', from: 'EventEmitter', to: 'Event sourcing' }, { change: 'Coordination unified', from: 'Multiple coordinators', to: 'SwarmCoordinator' } ] }, { category: 'Embeddings', changes: [ { change: 'Provider changed', from: 'OpenAI API / TF.js', to: 'ONNX Runtime (local)' }, { change: 'Geometry support', from: 'Euclidean only', to: 'Hyperbolic (Poincaré ball)' }, { change: 'Cache system', from: 'Memory-only', to: 'sql.js persistent cache' }, { change: 'Neural substrate', from: 'None', to: 'RuVector integration' } ] } ]; if (ctx.flags.format === 'json') { output.printJson(changes); return { success: true, data: changes }; } output.writeln(); output.writeln(output.bold('V3 Breaking Changes')); output.writeln(); for (const category of changes) { output.writeln(output.highlight(category.category)); output.printTable({ columns: [ { key: 'change', header: 'Change', width: 25 }, { key: 'from', header: 'V2', width: 25 }, { key: 'to', header: 'V3', width: 25 } ], data: category.changes, border: false }); output.writeln(); } output.printInfo('Run "claude-flow migrate run" to automatically handle these changes'); return { success: true, data: changes }; } }; // Main migrate command export const migrateCommand = { name: 'migrate', description: 'V2 to V3 migration tools', subcommands: [statusCommand, runCommand, verifyCommand, rollbackCommand, breakingCommand], options: [], examples: [ { command: 'claude-flow migrate status', description: 'Check migration status' }, { command: 'claude-flow migrate run --dry-run', description: 'Preview migration' }, { command: 'claude-flow migrate run -t all', description: 'Run full migration' } ], action: async (ctx) => { output.writeln(); output.writeln(output.bold('V2 to V3 Migration Tools')); output.writeln(); output.writeln('Usage: claude-flow migrate <subcommand> [options]'); output.writeln(); output.writeln('Subcommands:'); output.printList([ `${output.highlight('status')} - Check migration status`, `${output.highlight('run')} - Run migration`, `${output.highlight('verify')} - Verify migration integrity`, `${output.highlight('rollback')} - Rollback to previous version`, `${output.highlight('breaking')} - Show breaking changes` ]); return { success: true }; } }; // Helper functions function formatMigrationStatus(status) { if (status === 'migrated' || status === 'passed' || status === 'completed') { return output.success(status); } if (status === 'pending' || status === 'partial') { return output.warning(status); } if (status === 'failed') { return output.error(status); } if (status === 'not-required' || status.startsWith('skipped') || status === 'v3' || status === 'missing') { return output.dim(status); } if (status === 'v2') { return output.warning(status); } if (status === 'v2 + v3') { return output.success(status); } return status; } function getMigrationSteps(target) { const allSteps = [ { name: 'Configuration Files', description: 'Migrate config schema to V3 format', source: './claude-flow.json', dest: './claude-flow.config.json' }, { name: 'Memory Backend', description: 'Upgrade to hybrid backend with AgentDB', source: './.claude-flow/memory', dest: './data/memory' }, { name: 'Agent Definitions', description: 'Convert agent configs to V3 format', source: './.claude-flow/agents', dest: './v3/agents' }, { name: 'Hook Registry', description: 'Migrate hooks to V3 hook system', source: './src/hooks', dest: './v3/hooks' }, { name: 'Workflow Definitions', description: 'Convert workflows to event-sourced format', source: './.claude-flow/workflows', dest: './data/workflows' }, { name: 'Embeddings System', description: 'Migrate to ONNX with hyperbolic (Poincaré ball)', source: 'OpenAI/TF.js embeddings', dest: '.claude-flow/embeddings.json' } ]; if (target === 'all') return allSteps; return allSteps.filter(s => s.name.toLowerCase().includes(target.toLowerCase())); } export default migrateCommand; //# sourceMappingURL=migrate.js.map