UNPKG

devflow-ai

Version:

Enterprise-grade AI agent orchestration with swarm management UI dashboard

267 lines (229 loc) 7.32 kB
/** * Claude Flow SPARC Executor * Executes tasks using the full claude-flow SPARC system in non-interactive mode */ import type { TaskDefinition, AgentState, TaskResult } from './types.js'; import { Logger } from '../core/logger.js'; import * as path from 'node:path'; import { spawn } from 'node:child_process'; import { getClaudeFlowBin } from '../utils/paths.js'; export interface ClaudeFlowExecutorConfig { logger?: Logger; claudeFlowPath?: string; enableSparc?: boolean; verbose?: boolean; timeoutMinutes?: number; } export class ClaudeFlowExecutor { private logger: Logger; private claudeFlowPath: string; private enableSparc: boolean; private verbose: boolean; private timeoutMinutes: number; constructor(config: ClaudeFlowExecutorConfig = {}) { this.logger = config.logger || new Logger( { level: 'info', format: 'text', destination: 'console' }, { component: 'ClaudeFlowExecutor' }, ); this.claudeFlowPath = config.claudeFlowPath || getClaudeFlowBin(); this.enableSparc = config.enableSparc ?? true; this.verbose = config.verbose ?? false; this.timeoutMinutes = config.timeoutMinutes ?? 59; } async executeTask( task: TaskDefinition, agent: AgentState, targetDir?: string, ): Promise<TaskResult> { this.logger.info('Executing task with Claude Flow SPARC', { taskId: task.id.id, taskName: task.name, agentType: agent.type, targetDir, }); const startTime = Date.now(); try { // Determine the SPARC mode based on task type and agent type const sparcMode = this.determineSparcMode(task, agent); // Build the command const command = this.buildSparcCommand(task, sparcMode, targetDir); this.logger.info('Executing SPARC command', { mode: sparcMode, command: command.join(' '), }); // Execute the command const result = await this.executeCommand(command); const endTime = Date.now(); const executionTime = endTime - startTime; return { output: result.output, artifacts: result.artifacts || {}, metadata: { executionTime, sparcMode, command: command.join(' '), exitCode: result.exitCode, quality: 0.95, completeness: 0.9, }, error: result.error, }; } catch (error) { this.logger.error('Failed to execute Claude Flow SPARC command', { error: error instanceof Error ? error.message : String(error), taskId: task.id.id, }); return { output: '', artifacts: {}, metadata: { executionTime: Date.now() - startTime, quality: 0, completeness: 0, }, error: error instanceof Error ? error.message : String(error), }; } } private determineSparcMode(task: TaskDefinition, agent: AgentState): string { // Map task types and agent types to SPARC modes const modeMap = { // Task type mappings coding: 'code', testing: 'tdd', analysis: 'spec-pseudocode', documentation: 'docs-writer', research: 'spec-pseudocode', review: 'refinement-optimization-mode', deployment: 'devops', optimization: 'refinement-optimization-mode', integration: 'integration', // Agent type overrides coder: 'code', tester: 'tdd', analyst: 'spec-pseudocode', documenter: 'docs-writer', reviewer: 'refinement-optimization-mode', researcher: 'spec-pseudocode', coordinator: 'architect', }; // Check for specific keywords in task description const description = task.description.toLowerCase(); if (description.includes('architecture') || description.includes('design')) { return 'architect'; } if (description.includes('security')) { return 'security-review'; } if (description.includes('debug')) { return 'debug'; } if (description.includes('test')) { return 'tdd'; } if (description.includes('document')) { return 'docs-writer'; } if (description.includes('integrate')) { return 'integration'; } // Use agent type first, then task type return modeMap[agent.type] || modeMap[task.type] || 'code'; } private buildSparcCommand(task: TaskDefinition, mode: string, targetDir?: string): string[] { const command = [ this.claudeFlowPath, 'sparc', 'run', mode, `"${this.formatTaskDescription(task)}"`, ]; // Add options if (targetDir) { command.push('--target-dir', targetDir); } if (this.verbose) { command.push('--verbose'); } // Add non-interactive flag command.push('--non-interactive'); // Add auto-confirm flag command.push('--yes'); return command; } private formatTaskDescription(task: TaskDefinition): string { // Format the task description for SPARC command let description = task.description; // If the task has specific instructions, include them if (task.instructions && task.instructions !== task.description) { description = `${task.description}. ${task.instructions}`; } // Add context if available if (task.context?.targetDir) { description += ` in ${task.context.targetDir}`; } return description.replace(/"/g, '\\"'); } private async executeCommand(command: string[]): Promise<any> { return new Promise((resolve, reject) => { const [cmd, ...args] = command; const proc = spawn(cmd, args, { shell: true, env: { ...process.env, CLAUDE_FLOW_NON_INTERACTIVE: 'true', CLAUDE_FLOW_AUTO_CONFIRM: 'true', }, }); let stdout = ''; let stderr = ''; const artifacts: Record<string, any> = {}; proc.stdout.on('data', (data) => { const chunk = data.toString(); stdout += chunk; // Parse artifacts from output const artifactMatch = chunk.match(/Created file: (.+)/g); if (artifactMatch) { artifactMatch.forEach((match) => { const filePath = match.replace('Created file: ', '').trim(); artifacts[filePath] = true; }); } }); proc.stderr.on('data', (data) => { stderr += data.toString(); }); proc.on('close', (code) => { clearTimeout(timeoutId); // Clear timeout when process completes if (code === 0) { resolve({ output: stdout, artifacts, exitCode: code, error: null, }); } else { resolve({ output: stdout, artifacts, exitCode: code, error: stderr || `Command exited with code ${code}`, }); } }); proc.on('error', (err) => { reject(err); }); // Handle timeout - configurable for SPARC operations const timeoutMs = this.timeoutMinutes * 60 * 1000; const timeoutId = setTimeout(() => { proc.kill('SIGTERM'); reject(new Error('Command execution timeout')); }, timeoutMs); }); } } // Export for use in swarm coordinator export default ClaudeFlowExecutor;