UNPKG

claude-flow-multilang

Version:

Revolutionary multilingual AI orchestration framework with cultural awareness and DDD architecture

529 lines (467 loc) 19.5 kB
// sparc.js - SPARC development mode commands import { printSuccess, printError, printWarning } from '../utils.js'; import { promises as fs } from 'fs'; import { spawn } from 'child_process'; import { promisify } from 'util'; import { createSparcPrompt } from './sparc-modes/index.js'; import { cwd, exit, existsSync } from '../node-compat.js'; import process from 'process'; export async function sparcCommand(subArgs, flags) { const sparcCmd = subArgs[0]; // Show help if requested or no args if ( flags.help || flags.h || sparcCmd === '--help' || sparcCmd === '-h' || (!sparcCmd && Object.keys(flags).length === 0) ) { showSparcHelp(); return; } // Merge flags back into subArgs for backward compatibility const mergedArgs = [...subArgs]; for (const [key, value] of Object.entries(flags)) { if (key === 'non-interactive' || key === 'n') { mergedArgs.push('--non-interactive'); } else if (key === 'dry-run' || key === 'd') { mergedArgs.push('--dry-run'); } else if (key === 'verbose' || key === 'v') { mergedArgs.push('--verbose'); } else if (key === 'no-permissions') { mergedArgs.push('--no-permissions'); } else if (key === 'enable-permissions') { mergedArgs.push('--enable-permissions'); } else if (key === 'namespace') { mergedArgs.push('--namespace', value); } else if (key === 'config') { mergedArgs.push('--config', value); } else if (key === 'interactive' || key === 'i') { mergedArgs.push('--interactive'); } } // Check if first arg is a known subcommand const knownSubcommands = ['modes', 'info', 'run', 'tdd']; if (!knownSubcommands.includes(sparcCmd)) { // If not a known subcommand, treat it as a task description for sparc orchestrator // Insert 'run' and 'sparc' to make it: ['run', 'sparc', ...rest of args] mergedArgs.unshift('run', 'sparc'); } // Now process the command const actualCmd = mergedArgs[0]; switch (actualCmd) { case 'modes': await listSparcModes(mergedArgs); break; case 'info': await showModeInfo(mergedArgs); break; case 'run': await runSparcMode(mergedArgs, flags); break; case 'tdd': await runTddWorkflow(mergedArgs); break; default: showSparcHelp(); } } async function listSparcModes(subArgs) { try { // Get the actual working directory where the command was run from const workingDir = process.env.PWD || cwd(); const configPath = `${workingDir}/.roomodes`; let configContent; try { configContent = await fs.readFile(configPath, 'utf8'); } catch (error) { printError('SPARC configuration file (.roomodes) not found'); console.log(`Please ensure .roomodes file exists in: ${workingDir}`); console.log(); console.log('To enable SPARC development modes, run:'); console.log(' npx claude-flow@latest init --sparc'); console.log(); console.log('This will create:'); console.log(' • .roomodes file with 17+ SPARC development modes'); console.log(' • .roo/ directory with templates and workflows'); console.log(' • SPARC-enhanced CLAUDE.md configuration'); return; } const config = JSON.parse(configContent); const verbose = subArgs.includes('--verbose') || subArgs.includes('-v'); printSuccess('Available SPARC Modes:'); console.log(); for (const mode of config.customModes) { console.log(`• ${mode.name} (${mode.slug})`); if (verbose) { console.log(` ${mode.roleDefinition}`); console.log(` Tools: ${mode.groups.join(', ')}`); console.log(); } } if (!verbose) { console.log(); console.log('Use --verbose for detailed descriptions'); } } catch (err) { printError(`Failed to list SPARC modes: ${err.message}`); } } async function showModeInfo(subArgs) { const modeSlug = subArgs[1]; if (!modeSlug) { printError('Usage: sparc info <mode-slug>'); return; } try { // Get the actual working directory where the command was run from const workingDir = process.env.PWD || cwd(); const configPath = `${workingDir}/.roomodes`; let configContent; try { configContent = await fs.readFile(configPath, 'utf8'); } catch (error) { printError('SPARC configuration file (.roomodes) not found'); console.log(`Please ensure .roomodes file exists in: ${workingDir}`); console.log(); console.log('To enable SPARC development modes, run:'); console.log(' npx claude-flow@latest init --sparc'); return; } const config = JSON.parse(configContent); const mode = config.customModes.find((m) => m.slug === modeSlug); if (!mode) { printError(`Mode not found: ${modeSlug}`); console.log('Available modes:'); for (const m of config.customModes) { console.log(` ${m.slug} - ${m.name}`); } return; } printSuccess(`SPARC Mode: ${mode.name}`); console.log(); console.log('Role Definition:'); console.log(mode.roleDefinition); console.log(); console.log('Custom Instructions:'); console.log(mode.customInstructions); console.log(); console.log('Tool Groups:'); console.log(mode.groups.join(', ')); console.log(); console.log('Source:'); console.log(mode.source); } catch (err) { printError(`Failed to show mode info: ${err.message}`); } } async function runSparcMode(subArgs, flags) { const runModeSlug = subArgs[1]; const taskDescription = subArgs .slice(2) .filter((arg) => !arg.startsWith('--')) .join(' '); if (!runModeSlug || !taskDescription) { printError('Usage: sparc run <mode-slug> <task-description>'); return; } try { // Get the actual working directory where the command was run from const workingDir = process.env.PWD || cwd(); const configPath = `${workingDir}/.roomodes`; let configContent; try { configContent = await fs.readFile(configPath, 'utf8'); } catch (error) { printError('SPARC configuration file (.roomodes) not found'); console.log(`Please ensure .roomodes file exists in: ${workingDir}`); console.log(); console.log('To enable SPARC development modes, run:'); console.log(' npx claude-flow@latest init --sparc'); return; } const config = JSON.parse(configContent); const mode = config.customModes.find((m) => m.slug === runModeSlug); if (!mode) { printError(`Mode not found: ${runModeSlug}`); return; } // Build enhanced SPARC prompt const memoryNamespace = subArgs.includes('--namespace') ? subArgs[subArgs.indexOf('--namespace') + 1] : mode.slug; const enhancedTask = createSparcPrompt(mode, taskDescription, memoryNamespace); // Build tools based on mode groups const tools = buildToolsFromGroups(mode.groups); const toolsList = Array.from(tools).join(','); const instanceId = `sparc-${runModeSlug}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; if (subArgs.includes('--dry-run') || subArgs.includes('-d')) { printWarning('DRY RUN - SPARC Mode Configuration:'); console.log(`Mode: ${mode.name} (${mode.slug})`); console.log(`Instance ID: ${instanceId}`); const enablePermissions = subArgs.includes('--enable-permissions'); if (!enablePermissions) { console.log(`Tools: ALL (via --dangerously-skip-permissions)`); console.log(`Permissions: Will be auto-skipped`); } else { console.log(`Tools: ${toolsList}`); console.log(`Permissions: Will prompt for actions`); } console.log(`Task: ${taskDescription}`); console.log(); console.log('Enhanced prompt preview:'); console.log(enhancedTask.substring(0, 300) + '...'); return; } printSuccess(`Starting SPARC mode: ${mode.name}`); console.log(`📝 Instance ID: ${instanceId}`); console.log(`🎯 Mode: ${mode.slug}`); const isNonInteractive = subArgs.includes('--non-interactive') || subArgs.includes('-n'); const enablePermissions = subArgs.includes('--enable-permissions'); if (!enablePermissions) { console.log(`🔧 Tools: ALL (including MCP and WebSearch via --dangerously-skip-permissions)`); console.log(`⚡ Permissions: Auto-skipped (--dangerously-skip-permissions)`); } else { console.log(`🔧 Tools: ${toolsList}`); console.log(`✅ Permissions: Enabled (will prompt for actions)`); } console.log(`📋 Task: ${taskDescription}`); if (isNonInteractive) { console.log(`🚀 Running in non-interactive mode with stream-json output`); console.log(); // Show debug info immediately for non-interactive mode console.log('🔍 Debug: Preparing claude command...'); console.log(`Enhanced prompt length: ${enhancedTask.length} characters`); console.log(`First 200 chars of prompt: ${enhancedTask.substring(0, 200)}...`); } console.log(); // Execute Claude with SPARC configuration await executeClaude(enhancedTask, toolsList, instanceId, memoryNamespace, subArgs); } catch (err) { printError(`Failed to run SPARC mode: ${err.message}`); } } async function runTddWorkflow(subArgs) { const tddTaskDescription = subArgs.slice(1).join(' '); if (!tddTaskDescription) { printError('Usage: sparc tdd <task-description>'); return; } printSuccess('Starting SPARC TDD Workflow'); console.log('Following Test-Driven Development with SPARC methodology'); console.log(); const phases = [ { name: 'Red', description: 'Write failing tests', mode: 'tdd' }, { name: 'Green', description: 'Minimal implementation', mode: 'code' }, { name: 'Refactor', description: 'Optimize and clean', mode: 'tdd' }, ]; console.log('TDD Phases:'); for (const phase of phases) { console.log(` ${phase.name}: ${phase.description} (${phase.mode} mode)`); } console.log(); if (subArgs.includes('--interactive') || subArgs.includes('-i')) { printSuccess('Starting interactive TDD workflow'); console.log('This would walk through each phase interactively'); console.log('Run each phase with: sparc run <mode> "Phase: <task>"'); } else { printSuccess('Starting full TDD workflow'); console.log('This would execute all phases automatically'); console.log('Use --interactive for step-by-step control'); } } // Remove the createSparcPrompt function from here as it's now imported from sparc-modes/index.js function buildToolsFromGroups(groups) { const toolMappings = { read: ['View', 'LS', 'GlobTool', 'GrepTool'], edit: ['Edit', 'Replace', 'MultiEdit', 'Write'], browser: ['WebFetch'], mcp: ['mcp_tools'], command: ['Bash', 'Terminal'], }; const tools = new Set(['View', 'Edit', 'Bash']); // Always include basic tools for (const group of groups) { if (Array.isArray(group)) { const groupName = group[0]; if (toolMappings[groupName]) { toolMappings[groupName].forEach((tool) => tools.add(tool)); } } else if (toolMappings[group]) { toolMappings[group].forEach((tool) => tools.add(tool)); } } return tools; } async function executeClaude(enhancedTask, toolsList, instanceId, memoryNamespace, subArgs) { // Check for non-interactive mode const isNonInteractive = subArgs.includes('--non-interactive') || subArgs.includes('-n'); const enablePermissions = subArgs.includes('--enable-permissions'); // Build arguments array correctly const claudeArgs = []; claudeArgs.push(enhancedTask); // Add --dangerously-skip-permissions by default unless --enable-permissions is set if (!enablePermissions) { claudeArgs.push('--dangerously-skip-permissions'); } if (isNonInteractive) { // Non-interactive mode: add additional flags claudeArgs.push('-p'); // Use short form for print claudeArgs.push('--output-format', 'stream-json'); claudeArgs.push('--verbose'); } else { // Interactive mode - check for verbose flag if (subArgs.includes('--verbose') || subArgs.includes('-v')) { claudeArgs.push('--verbose'); } } // When using --dangerously-skip-permissions, we don't need to specify individual tools // as it enables ALL tools including mcp and websearch // Only add --allowedTools if permissions are enabled if (enablePermissions) { claudeArgs.push('--allowedTools', toolsList); } if (subArgs.includes('--config')) { const configIndex = subArgs.indexOf('--config'); claudeArgs.push('--mcp-config', subArgs[configIndex + 1]); } // Show debug info for non-interactive mode or when verbose if (isNonInteractive || subArgs.includes('--verbose') || subArgs.includes('-v')) { console.log('\n🔍 Debug: Executing claude with:'); console.log('Command: claude'); console.log( 'Permissions:', enablePermissions ? '✅ Enabled (will prompt)' : '⚡ Skipped (--dangerously-skip-permissions)', ); console.log( 'Tools:', enablePermissions ? `Specified: ${toolsList}` : 'ALL tools enabled (MCP, WebSearch, etc.)', ); console.log('Mode:', isNonInteractive ? '🤖 Non-interactive' : '💬 Interactive'); console.log('Args array length:', claudeArgs.length); console.log('First arg (prompt) length:', claudeArgs[0].length, 'characters'); if (isNonInteractive) { console.log('First 200 chars of prompt:', claudeArgs[0].substring(0, 200) + '...'); console.log('\nAll arguments:'); claudeArgs.forEach((arg, i) => { if (i === 0) { console.log(` [0] <SPARC prompt with ${arg.length} characters>`); } else { console.log(` [${i}] ${arg}`); } }); console.log('\nFull command structure:'); console.log('claude "<SPARC prompt>" ' + claudeArgs.slice(1).join(' ')); } console.log(); } try { // Log the actual command being executed console.log('\n🚀 Executing command:'); console.log(`Command: claude`); console.log(`Working Directory: ${cwd()}`); console.log(`Number of args: ${claudeArgs.length}`); // Check if claude command exists try { const checkResult = await new Promise((resolve) => { const child = spawn('which', ['claude'], { stdio: ['pipe', 'pipe', 'pipe'] }); let stdout = ''; child.stdout?.on('data', (data) => { stdout += data; }); child.on('close', (code) => { resolve({ success: code === 0, stdout: Buffer.from(stdout) }); }); }); if (!checkResult.success) { console.error('❌ Error: claude command not found in PATH'); console.error('Please ensure claude CLI is installed and in your PATH'); return; } const claudePath = new TextDecoder().decode(checkResult.stdout).trim(); console.log(`Claude path: ${claudePath}`); } catch (e) { console.warn('⚠️ Could not verify claude command location'); } // Use spawn for claude command const env = { ...process.env, CLAUDE_INSTANCE_ID: instanceId }; console.log('\n📡 Spawning claude process...\n'); const child = spawn('claude', claudeArgs, { cwd: cwd(), env: env, stdio: 'inherit' }); const status = await new Promise((resolve) => { child.on('close', (code) => { resolve({ code, success: code === 0 }); }); }); if (status.success) { printSuccess(`SPARC instance ${instanceId} completed successfully`); } else { printError(`SPARC instance ${instanceId} exited with code ${status.code}`); } } catch (err) { printError(`Failed to execute Claude: ${err.message}`); console.error('Stack trace:', err.stack); } } function showSparcHelp() { console.log('SPARC commands:'); console.log(' <task> Run SPARC orchestrator (default mode)'); console.log(' modes List available SPARC development modes'); console.log(' info <mode> Show detailed information about a mode'); console.log(' run <mode> <task> Execute a task in specified SPARC mode'); console.log(' tdd <task> Run Test-Driven Development workflow'); console.log(); console.log('Examples:'); console.log(' claude-flow sparc "orchestrate app development" # Uses sparc orchestrator'); console.log(' claude-flow sparc modes --verbose'); console.log(' claude-flow sparc info architect'); console.log(' claude-flow sparc run code "implement user authentication"'); console.log(' claude-flow sparc run code "add login feature" --non-interactive'); console.log(' claude-flow sparc run tdd "create test suite" --namespace tests'); console.log(' claude-flow sparc tdd "payment processing system" --interactive'); console.log(); console.log('Parallel Execution with BatchTool:'); console.log(' # Run multiple SPARC modes concurrently'); console.log(' batchtool run --parallel \\'); console.log(' "npx claude-flow sparc run code \'user service\' --non-interactive" \\'); console.log(' "npx claude-flow sparc run code \'auth service\' --non-interactive" \\'); console.log(' "npx claude-flow sparc run tdd \'test suite\' --non-interactive"'); console.log(); console.log(' # Boomerang orchestration pattern'); console.log(' batchtool orchestrate --boomerang \\'); console.log( ' --research "npx claude-flow sparc run ask \'requirements\' --non-interactive" \\', ); console.log(' --design "npx claude-flow sparc run architect \'system\' --non-interactive" \\'); console.log(' --implement "npx claude-flow sparc run code \'features\' --non-interactive" \\'); console.log(' --test "npx claude-flow sparc run tdd \'validation\' --non-interactive"'); console.log(); console.log('Flags:'); console.log(' --dry-run, -d Show configuration without executing'); console.log(' --verbose, -v Show detailed output'); console.log(' --interactive, -i Run TDD workflow interactively'); console.log(' --non-interactive, -n Run in non-interactive mode with stream-json output'); console.log(' --enable-permissions Enable permission prompts (default: skip permissions)'); console.log(' --namespace <ns> Use custom memory namespace (default: mode slug)'); console.log(' --config <path> Use custom MCP configuration file'); console.log(); console.log('Permission Behavior:'); console.log(' By default, SPARC runs with --dangerously-skip-permissions for efficiency'); console.log(' Use --enable-permissions to restore permission prompts if needed'); console.log(); console.log('Non-Interactive Mode:'); console.log(' When using --non-interactive, claude will be executed with:'); console.log(' - --dangerously-skip-permissions (unless --enable-permissions is set)'); console.log(' - -p (print mode for streaming output)'); console.log(' - --output-format stream-json (structured output format)'); console.log(' - --verbose (detailed execution logs)'); console.log(); console.log('Boomerang Pattern:'); console.log(' A cyclical orchestration where outputs from one phase feed into the next:'); console.log(' Research → Design → Implement → Test → Optimize → Loop back'); console.log(' Perfect for iterative development with continuous refinement'); }