devflow-ai
Version:
Enterprise-grade AI agent orchestration with swarm management UI dashboard
267 lines (229 loc) • 7.32 kB
text/typescript
/**
* 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;