UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

527 lines (454 loc) 13.7 kB
#!/usr/bin/env node /** * AIOS Agent Migration Script (V2 → V3) * Story 2.4: Migrates agents to V3 format with Auto-Claude capabilities * * Usage: * node migrate-agent.js <agent-id> [--dry-run] [--backup] [--force] * node migrate-agent.js --list * * @module migrate-agent */ const fs = require('fs').promises; const path = require('path'); const yaml = require('js-yaml'); // Paths const AGENTS_DIR = '.aios-core/development/agents'; const BACKUP_DIR = '.aios/migration-backup'; const SCHEMAS_DIR = '.aios-core/schemas'; // Agent capability matrix based on role const AGENT_CAPABILITIES = { pm: { specPipeline: { canGather: true, canWrite: true, }, }, architect: { specPipeline: { canAssess: true, }, execution: { canCreatePlan: true, canCreateContext: true, }, }, analyst: { specPipeline: { canResearch: true, }, memory: { canExtractPatterns: true, }, }, dev: { execution: { canExecute: true, canVerify: true, selfCritique: { enabled: true, checklistRef: 'story-dod-checklist.md', }, }, recovery: { canTrack: true, canRollback: true, maxAttempts: 3, stuckDetection: true, }, memory: { canCaptureInsights: true, }, }, qa: { specPipeline: { canCritique: true, }, execution: { canVerify: true, }, qa: { canReview: true, canFixRequest: true, reviewPhases: 10, maxIterations: 5, }, }, devops: { worktree: { canCreate: true, canMerge: true, canCleanup: true, }, }, po: { specPipeline: { canGather: true, canWrite: true, }, }, sm: { // Scrum Master doesn't have specific Auto-Claude capabilities }, 'aios-master': { specPipeline: { canGather: true, canAssess: true, canResearch: true, canWrite: true, canCritique: true, }, execution: { canCreatePlan: true, canCreateContext: true, }, }, 'data-engineer': { execution: { canExecute: true, canVerify: true, }, memory: { canExtractPatterns: true, }, }, 'ux-design-expert': { specPipeline: { canResearch: true, }, execution: { canCreateContext: true, }, }, 'squad-creator': { execution: { canCreatePlan: true, }, }, }; /** * Extract YAML content from markdown file */ function extractYamlFromMarkdown(content) { const yamlBlockMatch = content.match(/```yaml\n([\s\S]*?)\n```/); if (yamlBlockMatch) { try { return { yaml: yaml.load(yamlBlockMatch[1]), raw: yamlBlockMatch[1] }; } catch (e) { return { error: e.message }; } } return { yaml: null, raw: null }; } /** * Check if agent already has V3 autoClaude section */ function isAlreadyV3(parsed) { return parsed?.autoClaude?.version === '3.0'; } /** * Generate autoClaude section for an agent */ function generateAutoClaudeSection(agentId) { const capabilities = AGENT_CAPABILITIES[agentId] || {}; const autoClaude = { version: '3.0', migratedAt: new Date().toISOString(), }; // Add capabilities based on agent role if (capabilities.specPipeline) { autoClaude.specPipeline = { canGather: capabilities.specPipeline.canGather || false, canAssess: capabilities.specPipeline.canAssess || false, canResearch: capabilities.specPipeline.canResearch || false, canWrite: capabilities.specPipeline.canWrite || false, canCritique: capabilities.specPipeline.canCritique || false, }; } if (capabilities.execution) { autoClaude.execution = { canCreatePlan: capabilities.execution.canCreatePlan || false, canCreateContext: capabilities.execution.canCreateContext || false, canExecute: capabilities.execution.canExecute || false, canVerify: capabilities.execution.canVerify || false, }; if (capabilities.execution.selfCritique) { autoClaude.execution.selfCritique = capabilities.execution.selfCritique; } } if (capabilities.recovery) { autoClaude.recovery = capabilities.recovery; } if (capabilities.qa) { autoClaude.qa = capabilities.qa; } if (capabilities.memory) { autoClaude.memory = { canCaptureInsights: capabilities.memory.canCaptureInsights || false, canExtractPatterns: capabilities.memory.canExtractPatterns || false, canDocumentGotchas: capabilities.memory.canDocumentGotchas || false, }; } if (capabilities.worktree) { autoClaude.worktree = capabilities.worktree; } return autoClaude; } /** * Convert autoClaude object to YAML string with proper indentation */ function autoClaudeToYaml(autoClaude) { return yaml.dump({ autoClaude }, { indent: 2, lineWidth: -1 }).trim(); } /** * Insert autoClaude section into markdown content */ function insertAutoClaudeSection(content, autoClaudeYaml) { // Find the closing ``` of the YAML block const yamlBlockMatch = content.match(/(```yaml\n[\s\S]*?)(```)/); if (!yamlBlockMatch) { return { error: 'Could not find YAML block in file' }; } const beforeClosing = yamlBlockMatch[1]; const closing = yamlBlockMatch[2]; // Insert autoClaude section before the closing ``` const newContent = content.replace( yamlBlockMatch[0], `${beforeClosing}\n${autoClaudeYaml}\n${closing}` ); return { content: newContent }; } /** * Generate diff output */ function generateDiff(agentId, autoClaude) { const lines = []; lines.push(''); lines.push(`═══════════════════════════════════════════════════════════`); lines.push(` Migration Preview: ${agentId}.md (V2 → V3)`); lines.push(`═══════════════════════════════════════════════════════════`); lines.push(''); lines.push('+ Added autoClaude section:'); lines.push(''); const yamlStr = autoClaudeToYaml(autoClaude); yamlStr.split('\n').forEach((line) => { lines.push(` + ${line}`); }); lines.push(''); lines.push(`═══════════════════════════════════════════════════════════`); return lines.join('\n'); } /** * Migrate a single agent */ async function migrateAgent(rootPath, agentId, options = {}) { const { dryRun = false, backup = false, force = false } = options; const agentPath = path.join(rootPath, AGENTS_DIR, `${agentId}.md`); // Check if file exists try { await fs.access(agentPath); } catch { return { success: false, error: `Agent file not found: ${agentPath}` }; } // Read file const content = await fs.readFile(agentPath, 'utf-8'); // Parse YAML const { yaml: parsed, error: parseError } = extractYamlFromMarkdown(content); if (parseError) { return { success: false, error: `YAML parse error: ${parseError}` }; } if (!parsed) { return { success: false, error: 'No YAML block found in file' }; } // Check if already V3 if (isAlreadyV3(parsed) && !force) { return { success: false, error: `Agent ${agentId} is already V3. Use --force to re-migrate.`, version: 'V3', }; } // Generate autoClaude section const autoClaude = generateAutoClaudeSection(agentId); const autoClaudeYaml = autoClaudeToYaml(autoClaude); // Dry run - just show diff if (dryRun) { return { success: true, dryRun: true, diff: generateDiff(agentId, autoClaude), autoClaude, }; } // Create backup if requested if (backup) { const backupPath = path.join(rootPath, BACKUP_DIR); await fs.mkdir(backupPath, { recursive: true }); const backupFile = path.join(backupPath, `${agentId}.md.bak`); await fs.writeFile(backupFile, content); } // Insert autoClaude section const { content: newContent, error: insertError } = insertAutoClaudeSection( content, autoClaudeYaml ); if (insertError) { return { success: false, error: insertError }; } // Write migrated file await fs.writeFile(agentPath, newContent); // Validate migration const { validateFile } = require(path.join(rootPath, SCHEMAS_DIR, 'validate-v3-schema.js')); const validation = await validateFile(agentPath, { type: 'agent', strict: true }); return { success: true, agentId, migrated: true, backup: backup ? path.join(BACKUP_DIR, `${agentId}.md.bak`) : null, validation: { valid: validation.valid, version: validation.version, errors: validation.errors, warnings: validation.warnings, }, }; } /** * List all agents and their migration status */ async function listAgents(rootPath) { const agentsPath = path.join(rootPath, AGENTS_DIR); const files = await fs.readdir(agentsPath); const agents = []; for (const file of files) { if (!file.endsWith('.md')) continue; const agentId = path.basename(file, '.md'); const content = await fs.readFile(path.join(agentsPath, file), 'utf-8'); const { yaml: parsed } = extractYamlFromMarkdown(content); const hasCapabilities = AGENT_CAPABILITIES[agentId] !== undefined; const isV3 = isAlreadyV3(parsed); agents.push({ id: agentId, name: parsed?.agent?.name || 'Unknown', version: isV3 ? 'V3' : 'V2', hasCapabilities, file, }); } return agents; } /** * Format list output */ function formatListOutput(agents) { const lines = []; lines.push(''); lines.push('═══════════════════════════════════════════════════════════'); lines.push(' AIOS Agents - Migration Status'); lines.push('═══════════════════════════════════════════════════════════'); lines.push(''); const v2Count = agents.filter((a) => a.version === 'V2').length; const v3Count = agents.filter((a) => a.version === 'V3').length; lines.push(`Total: ${agents.length} agents | V2: ${v2Count} | V3: ${v3Count}`); lines.push(''); agents.forEach((agent) => { const vBadge = agent.version === 'V3' ? '🆕' : '📦'; const capBadge = agent.hasCapabilities ? '✓' : '○'; lines.push(` ${vBadge} ${agent.id.padEnd(20)} ${agent.version} ${capBadge} ${agent.name}`); }); lines.push(''); lines.push('Legend: 🆕 V3 | 📦 V2 | ✓ Has capability mapping | ○ No mapping'); lines.push(''); lines.push('═══════════════════════════════════════════════════════════'); return lines.join('\n'); } /** * CLI handler */ async function main() { const args = process.argv.slice(2); if (args.length === 0 || args.includes('--help')) { console.log(` AIOS Agent Migration Script (V2 → V3) Usage: node migrate-agent.js <agent-id> [options] node migrate-agent.js --list Options: --dry-run Show migration diff without applying --backup Create backup before migration --force Re-migrate even if already V3 --list List all agents and migration status --help Show this help message Examples: node migrate-agent.js dev --dry-run node migrate-agent.js dev --backup node migrate-agent.js qa --dry-run node migrate-agent.js --list `); return; } // Find project root let rootPath = process.cwd(); while (rootPath !== '/') { try { await fs.access(path.join(rootPath, '.aios-core')); break; } catch { rootPath = path.dirname(rootPath); } } if (rootPath === '/') { console.error('Error: Could not find .aios-core directory. Run from project root.'); process.exit(1); } // List mode if (args.includes('--list')) { const agents = await listAgents(rootPath); console.log(formatListOutput(agents)); return; } // Migration mode const agentId = args.find((a) => !a.startsWith('--')); if (!agentId) { console.error('Error: Agent ID required'); process.exit(1); } const dryRun = args.includes('--dry-run'); const backup = args.includes('--backup'); const force = args.includes('--force'); const result = await migrateAgent(rootPath, agentId, { dryRun, backup, force }); if (!result.success) { console.error(`❌ Migration failed: ${result.error}`); process.exit(1); } if (result.dryRun) { console.log(result.diff); console.log('\nTo apply migration, run without --dry-run flag.'); } else { console.log(`\n✅ Agent ${agentId} migrated to V3 successfully!`); if (result.backup) { console.log(` Backup: ${result.backup}`); } console.log(` Validation: ${result.validation.valid ? 'PASSED' : 'FAILED'}`); if (result.validation.errors.length > 0) { console.log(` Errors: ${result.validation.errors.join(', ')}`); } if (result.validation.warnings.length > 0) { console.log(` Warnings: ${result.validation.warnings.join(', ')}`); } } } // Export for programmatic use module.exports = { migrateAgent, listAgents, generateAutoClaudeSection, AGENT_CAPABILITIES, }; // Run CLI if called directly if (require.main === module) { main().catch((error) => { console.error('Error:', error.message); process.exit(1); }); }