/** * @claude-flow/codex - Migrations * * Migration utilities for converting Claude Code configurations to Codex format * Supports full CLAUDE.md parsing with section extraction, skill conversion, * and proper AGENTS.md/config.toml generation. */ /** * Feature mappings between Claude Code and Codex */ export const FEATURE_MAPPINGS = [ { claudeCode: 'CLAUDE.md', codex: 'AGENTS.md', status: 'mapped', notes: 'Main instruction file - content is portable', }, { claudeCode: 'CLAUDE.local.md', codex: '.codex/AGENTS.override.md', status: 'mapped', notes: 'Local overrides - gitignored in both', }, { claudeCode: 'settings.json', codex: 'config.toml', status: 'mapped', notes: 'Format conversion required (JSON to TOML)', }, { claudeCode: '/skill-name', codex: '$skill-name', status: 'mapped', notes: 'Skill invocation syntax - search and replace', }, { claudeCode: 'TodoWrite', codex: 'Task tracking', status: 'mapped', notes: 'Similar functionality with different API', }, { claudeCode: 'Task tool agents', codex: 'Sub-agent collaboration', status: 'partial', notes: 'Codex sub-agents via CODEX_HANDOFF_TARGET env var', }, { claudeCode: 'MCP servers', codex: '[mcp_servers]', status: 'mapped', notes: 'Configuration format differs but same functionality', }, { claudeCode: 'hooks system', codex: 'Automations', status: 'partial', notes: 'Codex automations are scheduled, not event-driven', }, { claudeCode: 'EnterPlanMode', codex: 'No direct equivalent', status: 'unsupported', notes: 'Codex uses different planning paradigm', }, { claudeCode: 'Permission modes', codex: 'approval_policy + sandbox_mode', status: 'mapped', notes: 'Codex provides more granular control', }, ]; /** * Hook keywords recognized in CLAUDE.md */ const HOOK_KEYWORDS = [ 'pre-task', 'post-task', 'pre-edit', 'post-edit', 'pre-command', 'post-command', 'session-start', 'session-end', 'session-restore', 'route', 'explain', 'pretrain', 'notify', ]; /** * Patterns that need migration warnings */ const WARNING_PATTERNS = [ { pattern: 'EnterPlanMode', message: 'EnterPlanMode has no direct Codex equivalent - review planning workflow' }, { pattern: 'claude -p', message: 'claude -p headless mode - use Codex sub-agent patterns instead' }, { pattern: 'TodoWrite', message: 'TodoWrite - Codex uses different task tracking approach' }, { pattern: /--dangerously-skip-permissions/g, message: 'Dangerous permission skip detected - use Codex approval_policy instead' }, { pattern: /mcp__claude-flow__/g, message: 'MCP tool calls need migration to Codex MCP configuration' }, { pattern: /mcp__ruv-swarm__/g, message: 'Swarm MCP calls - ensure ruv-swarm MCP server is configured in config.toml' }, ]; /** * Parse a CLAUDE.md file completely */ export async function parseClaudeMd(content) { const lines = content.split('\n'); const result = { title: '', sections: [], skills: [], hooks: [], customInstructions: [], codeBlocks: [], mcpServers: [], settings: {}, warnings: [], }; // Extract title (first H1) const titleMatch = content.match(/^#\s+(.+)$/m); if (titleMatch && titleMatch[1]) { result.title = titleMatch[1].trim(); result.settings.projectName = result.title; } // Parse sections result.sections = parseSections(content, lines); // Extract skills (both /skill-name and $skill-name syntax) result.skills = extractSkills(content, lines); // Extract hooks result.hooks = extractHooks(content); // Extract code blocks result.codeBlocks = extractCodeBlocks(content, lines); // Extract MCP server configurations from code blocks result.mcpServers = extractMcpServers(result.codeBlocks); // Extract settings from content result.settings = { ...result.settings, ...extractSettings(content, result.sections), }; // Extract custom instructions (behavioral rules) result.customInstructions = extractBehavioralRules(content); // Check for patterns that need warnings for (const { pattern, message } of WARNING_PATTERNS) { if (typeof pattern === 'string') { if (content.includes(pattern)) { result.warnings.push(message); } } else { if (pattern.test(content)) { result.warnings.push(message); } } } return result; } /** * Parse sections from markdown content */ function parseSections(content, lines) { const sections = []; const sectionRegex = /^(#{1,6})\s+(.+)$/; let currentSection = null; let contentLines = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const match = sectionRegex.exec(line); if (match && match[1] && match[2]) { // Save previous section if (currentSection) { currentSection.content = contentLines.join('\n').trim(); currentSection.endLine = i; sections.push(currentSection); } // Start new section currentSection = { level: match[1].length, title: match[2].trim(), content: '', startLine: i + 1, endLine: i + 1, }; contentLines = []; } else if (currentSection) { contentLines.push(line); } } // Save last section if (currentSection) { currentSection.content = contentLines.join('\n').trim(); currentSection.endLine = lines.length; sections.push(currentSection); } return sections; } /** * Extract skill references from content */ function extractSkills(content, lines) { const skills = []; const seenSkills = new Set(); // Slash syntax: /skill-name const slashRegex = /\/([a-z][a-z0-9-]*)/g; let match; while ((match = slashRegex.exec(content)) !== null) { const name = match[1]; // Skip common false positives if (['src', 'dist', 'docs', 'tests', 'config', 'scripts', 'examples', 'node_modules', 'workspaces'].includes(name)) { continue; } if (!seenSkills.has(`slash:${name}`)) { seenSkills.add(`slash:${name}`); const lineNum = findLineNumber(content, match.index); skills.push({ name, syntax: 'slash', context: getContextAroundMatch(lines, lineNum), line: lineNum, }); } } // Dollar syntax: $skill-name const dollarRegex = /\$([a-z][a-z0-9-]+)/g; while ((match = dollarRegex.exec(content)) !== null) { const name = match[1]; if (!seenSkills.has(`dollar:${name}`)) { seenSkills.add(`dollar:${name}`); const lineNum = findLineNumber(content, match.index); skills.push({ name, syntax: 'dollar', context: getContextAroundMatch(lines, lineNum), line: lineNum, }); } } return skills; } /** * Extract hooks referenced in content */ function extractHooks(content) { const hooks = []; const lowerContent = content.toLowerCase(); for (const hook of HOOK_KEYWORDS) { if (lowerContent.includes(hook)) { hooks.push(hook); } } return hooks; } /** * Extract code blocks from markdown */ function extractCodeBlocks(content, lines) { const blocks = []; const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g; let match; while ((match = codeBlockRegex.exec(content)) !== null) { blocks.push({ language: match[1] || 'text', content: match[2].trim(), line: findLineNumber(content, match.index), }); } return blocks; } /** * Extract MCP server configurations from code blocks */ function extractMcpServers(codeBlocks) { const servers = []; for (const block of codeBlocks) { // Look for MCP server configurations in bash/shell blocks if (['bash', 'shell', 'sh', 'zsh'].includes(block.language)) { // Pattern: claude mcp add [args...] const mcpAddRegex = /claude\s+mcp\s+add\s+(\S+)\s+(.+)/g; let match; while ((match = mcpAddRegex.exec(block.content)) !== null) { const name = match[1]; const parts = match[2].trim().split(/\s+/); servers.push({ name, command: parts[0] || 'npx', args: parts.slice(1), enabled: true, }); } } // Look for JSON MCP configurations if (['json', 'jsonc'].includes(block.language)) { try { const parsed = JSON.parse(block.content); if (parsed.mcpServers) { for (const [name, config] of Object.entries(parsed.mcpServers)) { const mcpConfig = config; servers.push({ name, command: mcpConfig.command || 'npx', args: mcpConfig.args || [], enabled: true, }); } } } catch { // Not valid JSON, skip } } // Look for JavaScript/TypeScript MCP tool calls if (['javascript', 'typescript', 'js', 'ts'].includes(block.language)) { // Pattern: mcp____ const mcpCallRegex = /mcp__([a-z-]+)__/g; const seenServers = new Set(); let match; while ((match = mcpCallRegex.exec(block.content)) !== null) { const serverName = match[1].replace(/-/g, '_'); if (!seenServers.has(serverName)) { seenServers.add(serverName); // Don't add as full config, just note it exists } } } } // Deduplicate const seen = new Set(); return servers.filter((s) => { if (seen.has(s.name)) return false; seen.add(s.name); return true; }); } /** * Extract settings from content and sections */ function extractSettings(content, sections) { const settings = {}; // Look for tech stack const techMatch = content.match(/(?:Tech\s*Stack|Technology|Stack)[:\s]+([^\n]+)/i); if (techMatch && techMatch[1]) { settings.techStack = techMatch[1].replace(/\*\*/g, '').trim(); } // Look for build command const buildMatch = content.match(/(?:Build|Compile)[:\s]*\n?```(?:bash|sh)?\n([^\n]+)/i); if (buildMatch && buildMatch[1]) { settings.buildCommand = buildMatch[1].trim(); } else { // Check for npm run build pattern if (content.includes('npm run build')) { settings.buildCommand = 'npm run build'; } } // Look for test command const testMatch = content.match(/(?:Test)[:\s]*\n?```(?:bash|sh)?\n([^\n]+)/i); if (testMatch && testMatch[1]) { settings.testCommand = testMatch[1].trim(); } else { if (content.includes('npm test')) { settings.testCommand = 'npm test'; } } // Look for dev command if (content.includes('npm run dev')) { settings.devCommand = 'npm run dev'; } // Look for approval/permission settings if (content.includes('auto-approve') || content.includes('autoApprove')) { settings.approvalPolicy = 'never'; } else if (content.includes('read-only')) { settings.sandboxMode = 'read-only'; } // Look for model specification const modelMatch = content.match(/model[:\s]+["']?([^"'\n]+)["']?/i); if (modelMatch && modelMatch[1]) { settings.model = modelMatch[1].trim(); } return settings; } /** * Extract behavioral rules from content */ function extractBehavioralRules(content) { const rules = []; // Look for Behavioral Rules section const behavioralMatch = content.match(/##\s*Behavioral\s*Rules[^\n]*\n([\s\S]*?)(?=\n##|\n#\s|$)/i); if (behavioralMatch && behavioralMatch[1]) { const ruleLines = behavioralMatch[1].split('\n'); for (const line of ruleLines) { const trimmed = line.trim(); if (trimmed.startsWith('- ')) { rules.push(trimmed.substring(2)); } else if (trimmed.startsWith('* ')) { rules.push(trimmed.substring(2)); } } } // Also look for Security Rules const securityMatch = content.match(/##\s*Security\s*Rules?[^\n]*\n([\s\S]*?)(?=\n##|\n#\s|$)/i); if (securityMatch && securityMatch[1]) { const securityLines = securityMatch[1].split('\n'); for (const line of securityLines) { const trimmed = line.trim(); if (trimmed.startsWith('- ')) { rules.push(trimmed.substring(2)); } else if (trimmed.startsWith('* ')) { rules.push(trimmed.substring(2)); } } } return rules; } /** * Find line number for a character index */ function findLineNumber(content, index) { return content.substring(0, index).split('\n').length; } /** * Get context around a match */ function getContextAroundMatch(lines, lineNum) { const start = Math.max(0, lineNum - 2); const end = Math.min(lines.length, lineNum + 1); return lines.slice(start, end).join('\n'); } /** * Analyze a CLAUDE.md file for migration (simplified interface) */ export async function analyzeClaudeMd(content) { const parsed = await parseClaudeMd(content); return { sections: parsed.sections.map((s) => s.title), skills: [...new Set(parsed.skills.map((s) => s.name))], hooks: parsed.hooks, customInstructions: parsed.customInstructions, warnings: parsed.warnings, }; } /** * Convert skill invocation syntax from slash to dollar */ export function convertSkillSyntax(content) { // Convert /skill-name to $skill-name, but avoid path-like patterns return content.replace(/(? { // Skip common directory names const skipPatterns = ['src', 'dist', 'docs', 'tests', 'config', 'scripts', 'examples', 'node_modules', 'workspaces', 'data', 'logs', 'tmp', 'var', 'etc', 'usr', 'bin', 'lib']; if (skipPatterns.includes(skillName)) { return match; } return `$${skillName}`; }); } /** * Generate AGENTS.md content from parsed CLAUDE.md */ export function generateAgentsMdFromParsed(parsed) { const lines = []; // Title lines.push(`# ${parsed.title || 'Project Agent Guide'}`); lines.push(''); lines.push('> Migrated from CLAUDE.md by @claude-flow/codex'); lines.push(''); // Project Overview lines.push('## Project Overview'); lines.push(''); if (parsed.settings.techStack) { lines.push(`**Tech Stack**: ${parsed.settings.techStack}`); } lines.push(''); // Quick Start lines.push('## Setup'); lines.push(''); if (parsed.settings.buildCommand) { lines.push('### Build'); lines.push('```bash'); lines.push(parsed.settings.buildCommand); lines.push('```'); lines.push(''); } if (parsed.settings.testCommand) { lines.push('### Test'); lines.push('```bash'); lines.push(parsed.settings.testCommand); lines.push('```'); lines.push(''); } if (parsed.settings.devCommand) { lines.push('### Development'); lines.push('```bash'); lines.push(parsed.settings.devCommand); lines.push('```'); lines.push(''); } // Code Standards lines.push('## Code Standards'); lines.push(''); lines.push('- Files under 500 lines'); lines.push('- No hardcoded secrets'); lines.push('- Input validation at boundaries'); lines.push('- Typed interfaces for public APIs'); lines.push(''); // Skills if (parsed.skills.length > 0) { lines.push('## Skills'); lines.push(''); lines.push('| Skill | Original Syntax |'); lines.push('|-------|-----------------|'); for (const skill of parsed.skills) { const codexSyntax = `$${skill.name}`; const originalSyntax = skill.syntax === 'slash' ? `/${skill.name}` : `$${skill.name}`; lines.push(`| \`${codexSyntax}\` | \`${originalSyntax}\` |`); } lines.push(''); } // Security lines.push('## Security'); lines.push(''); if (parsed.customInstructions.length > 0) { for (const rule of parsed.customInstructions.slice(0, 10)) { lines.push(`- ${rule}`); } } else { lines.push('- NEVER commit secrets or credentials'); lines.push('- Validate all user inputs'); lines.push('- Prevent directory traversal attacks'); } lines.push(''); // Hooks (as reference) if (parsed.hooks.length > 0) { lines.push('## Automation Hooks'); lines.push(''); lines.push('The following hooks were detected in the original configuration:'); lines.push(''); for (const hook of parsed.hooks) { lines.push(`- \`${hook}\``); } lines.push(''); lines.push('> Note: Codex uses scheduled Automations instead of event-driven hooks.'); lines.push(''); } // MCP Servers if (parsed.mcpServers.length > 0) { lines.push('## MCP Servers'); lines.push(''); lines.push('Configure in config.toml:'); lines.push(''); for (const server of parsed.mcpServers) { lines.push(`- **${server.name}**: \`${server.command} ${(server.args || []).join(' ')}\``); } lines.push(''); } // Migration notes if (parsed.warnings.length > 0) { lines.push('## Migration Notes'); lines.push(''); for (const warning of parsed.warnings) { lines.push(`- ${warning}`); } lines.push(''); } return lines.join('\n'); } /** * Convert settings.json to config.toml format */ export function convertSettingsToToml(settings) { const lines = []; lines.push('# Migrated from settings.json'); lines.push('# Generated by @claude-flow/codex'); lines.push(''); // Model if (settings.model) { lines.push(`model = "${settings.model}"`); } else { lines.push('model = "gpt-5.3-codex"'); } lines.push(''); // Permissions mapping if (settings.permissions) { const perms = settings.permissions; if (perms.autoApprove === true) { lines.push('approval_policy = "never"'); lines.push('sandbox_mode = "danger-full-access"'); } else if (perms.autoApprove === 'read-only') { lines.push('approval_policy = "on-request"'); lines.push('sandbox_mode = "read-only"'); } else { lines.push('approval_policy = "on-request"'); lines.push('sandbox_mode = "workspace-write"'); } } else { lines.push('approval_policy = "on-request"'); lines.push('sandbox_mode = "workspace-write"'); } lines.push(''); // Web search if (settings.webSearch !== undefined) { const mode = settings.webSearch === true ? 'live' : settings.webSearch === false ? 'disabled' : 'cached'; lines.push(`web_search = "${mode}"`); } else { lines.push('web_search = "cached"'); } lines.push(''); // Features lines.push('[features]'); lines.push('child_agents_md = true'); lines.push('shell_snapshot = true'); lines.push('request_rule = true'); lines.push(''); // MCP servers if (settings.mcpServers && typeof settings.mcpServers === 'object') { for (const [name, config] of Object.entries(settings.mcpServers)) { const mcpConfig = config; lines.push(`[mcp_servers.${name}]`); if (mcpConfig.command) { lines.push(`command = "${mcpConfig.command}"`); } if (mcpConfig.args && mcpConfig.args.length > 0) { const argsStr = mcpConfig.args.map((a) => `"${a}"`).join(', '); lines.push(`args = [${argsStr}]`); } lines.push('enabled = true'); if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) { lines.push(''); lines.push(`[mcp_servers.${name}.env]`); for (const [key, value] of Object.entries(mcpConfig.env)) { lines.push(`${key} = "${value}"`); } } lines.push(''); } } else { // Add default claude-flow server lines.push('[mcp_servers.claude-flow]'); lines.push('command = "npx"'); lines.push('args = ["-y", "@claude-flow/cli@latest"]'); lines.push('enabled = true'); lines.push(''); } // History lines.push('[history]'); lines.push('persistence = "save-all"'); lines.push(''); // Profiles lines.push('# Development profile'); lines.push('[profiles.dev]'); lines.push('approval_policy = "never"'); lines.push('sandbox_mode = "danger-full-access"'); lines.push(''); lines.push('# Safe profile'); lines.push('[profiles.safe]'); lines.push('approval_policy = "untrusted"'); lines.push('sandbox_mode = "read-only"'); lines.push(''); return lines.join('\n'); } /** * Generate config.toml from parsed CLAUDE.md */ export function generateConfigTomlFromParsed(parsed) { const lines = []; lines.push('# Migrated from CLAUDE.md'); lines.push('# Generated by @claude-flow/codex'); lines.push(''); // Model lines.push(`model = "${parsed.settings.model || 'gpt-5.3-codex'}"`); lines.push(''); // Approval policy const approvalPolicy = parsed.settings.approvalPolicy || 'on-request'; lines.push(`approval_policy = "${approvalPolicy}"`); lines.push(''); // Sandbox mode const sandboxMode = parsed.settings.sandboxMode || 'workspace-write'; lines.push(`sandbox_mode = "${sandboxMode}"`); lines.push(''); // Web search lines.push('web_search = "cached"'); lines.push(''); // Features lines.push('[features]'); lines.push('child_agents_md = true'); lines.push('shell_snapshot = true'); lines.push('request_rule = true'); lines.push(''); // MCP servers if (parsed.mcpServers.length > 0) { for (const server of parsed.mcpServers) { lines.push(`[mcp_servers.${server.name.replace(/-/g, '_')}]`); lines.push(`command = "${server.command}"`); if (server.args && server.args.length > 0) { const argsStr = server.args.map((a) => `"${a}"`).join(', '); lines.push(`args = [${argsStr}]`); } lines.push(`enabled = ${server.enabled ?? true}`); lines.push(''); } } else { // Default claude-flow server lines.push('[mcp_servers.claude_flow]'); lines.push('command = "npx"'); lines.push('args = ["-y", "@claude-flow/cli@latest"]'); lines.push('enabled = true'); lines.push(''); } // Skills if (parsed.skills.length > 0) { lines.push('# Skills detected in original configuration'); for (const skill of parsed.skills) { lines.push(`# - ${skill.name} (${skill.syntax} syntax)`); } lines.push(''); } // History lines.push('[history]'); lines.push('persistence = "save-all"'); lines.push(''); // Profiles lines.push('[profiles.dev]'); lines.push('approval_policy = "never"'); lines.push('sandbox_mode = "danger-full-access"'); lines.push(''); lines.push('[profiles.safe]'); lines.push('approval_policy = "untrusted"'); lines.push('sandbox_mode = "read-only"'); lines.push(''); return lines.join('\n'); } /** * Migrate from Claude Code (CLAUDE.md) to Codex (AGENTS.md) */ export async function migrateFromClaudeCode(options) { const { sourcePath, targetPath, preserveComments = true, generateSkills = true } = options; try { // In actual implementation, this would read the file // For now, we provide the structure for the migration const result = { success: true, agentsMdPath: `${targetPath}/AGENTS.md`, skillsCreated: generateSkills ? ['swarm-orchestration', 'memory-management', 'security-audit'] : [], configTomlPath: `${targetPath}/.agents/config.toml`, mappings: FEATURE_MAPPINGS, warnings: [ 'Review skill invocation syntax (changed from / to $)', 'Check hook configurations for Automation compatibility', 'Verify MCP server configurations in config.toml', ], }; return result; } catch (error) { return { success: false, warnings: [`Migration failed: ${error instanceof Error ? error.message : 'Unknown error'}`], }; } } /** * Perform full migration with content */ export async function performFullMigration(claudeMdContent, settingsJson) { // Parse CLAUDE.md const parsed = await parseClaudeMd(claudeMdContent); // Generate AGENTS.md let agentsMd = generateAgentsMdFromParsed(parsed); // Convert skill syntax in the generated content agentsMd = convertSkillSyntax(agentsMd); // Generate config.toml let configToml; if (settingsJson) { configToml = convertSettingsToToml(settingsJson); } else { configToml = generateConfigTomlFromParsed(parsed); } // Collect skills to create const skillsToCreate = [...new Set(parsed.skills.map((s) => s.name))]; return { agentsMd, configToml, warnings: parsed.warnings, skillsToCreate, }; } /** * Generate migration report */ export function generateMigrationReport(result) { const lines = []; lines.push('# Migration Report'); lines.push(''); lines.push(`**Status**: ${result.success ? 'Success' : 'Failed'}`); lines.push(`**Generated**: ${new Date().toISOString()}`); lines.push(''); if (result.agentsMdPath) { lines.push('## Generated Files'); lines.push(''); lines.push(`- AGENTS.md: \`${result.agentsMdPath}\``); if (result.configTomlPath) { lines.push(`- config.toml: \`${result.configTomlPath}\``); } lines.push(''); } if (result.skillsCreated && result.skillsCreated.length > 0) { lines.push('## Skills Created'); lines.push(''); for (const skill of result.skillsCreated) { lines.push(`- \`$${skill}\``); } lines.push(''); } if (result.mappings) { lines.push('## Feature Mappings'); lines.push(''); lines.push('| Claude Code | Codex | Status | Notes |'); lines.push('|-------------|-------|--------|-------|'); for (const mapping of result.mappings) { const notes = mapping.notes || ''; lines.push(`| \`${mapping.claudeCode}\` | \`${mapping.codex}\` | ${mapping.status} | ${notes} |`); } lines.push(''); } if (result.warnings && result.warnings.length > 0) { lines.push('## Warnings'); lines.push(''); for (const warning of result.warnings) { lines.push(`- ${warning}`); } lines.push(''); } lines.push('## Next Steps'); lines.push(''); lines.push('1. Review generated AGENTS.md for accuracy'); lines.push('2. Update skill references from `/skill-name` to `$skill-name`'); lines.push('3. Configure MCP servers in config.toml'); lines.push('4. Create skill definitions in `.agents/skills/`'); lines.push('5. Test with `codex` CLI'); lines.push(''); return lines.join('\n'); } //# sourceMappingURL=index.js.map