claude-flow
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
532 lines (446 loc) • 18 kB
text/typescript
import { getErrorMessage } from '../../utils/error-handler.js';
import { success, error, warning, info } from "../cli-core.js";
import type { CommandContext } from "../cli-core.js";
import chalk from "chalk";
const { blue, yellow, green, magenta, cyan } = chalk;
interface SparcMode {
slug: string;
name: string;
roleDefinition: string;
customInstructions: string;
groups: string[];
source: string;
}
interface SparcConfig {
customModes: SparcMode[];
}
let sparcConfig: SparcConfig | null = null;
async function loadSparcConfig(): Promise<SparcConfig> {
if (sparcConfig) {
return sparcConfig;
}
try {
const configPath = ".roomodes";
const { readFile } = await import("fs/promises");
const content = await readFile(configPath, "utf-8");
sparcConfig = JSON.parse(content);
return sparcConfig!;
} catch (error) {
throw new Error(`Failed to load SPARC configuration: ${(error instanceof Error ? error.message : String(error))}`);
}
}
export async function sparcAction(ctx: CommandContext): Promise<void> {
const subcommand = ctx.args[0];
switch (subcommand) {
case "modes":
await listSparcModes(ctx);
break;
case "info":
await showModeInfo(ctx);
break;
case "run":
await runSparcMode(ctx);
break;
case "tdd":
await runTddFlow(ctx);
break;
case "workflow":
await runSparcWorkflow(ctx);
break;
default:
await showSparcHelp();
break;
}
}
async function listSparcModes(ctx: CommandContext): Promise<void> {
try {
const config = await loadSparcConfig();
const verbose = ctx.flags.verbose as boolean;
success("Available SPARC Modes:");
console.log();
for (const mode of config.customModes) {
console.log(`${cyan("•")} ${green(mode.name)} ${blue(`(${mode.slug})`)}`);
if (verbose) {
console.log(` ${mode.roleDefinition}`);
console.log(` Tools: ${mode.groups.join(", ")}`);
console.log();
}
}
if (!verbose) {
console.log();
info("Use --verbose for detailed descriptions");
}
} catch (err) {
error(`Failed to list SPARC modes: ${(err as Error).message}`);
}
}
async function showModeInfo(ctx: CommandContext): Promise<void> {
const modeSlug = ctx.args[1];
if (!modeSlug) {
error("Usage: sparc info <mode-slug>");
return;
}
try {
const config = await loadSparcConfig();
const mode = config.customModes.find(m => m.slug === modeSlug);
if (!mode) {
error(`Mode not found: ${modeSlug}`);
console.log("Available modes:");
for (const m of config.customModes) {
console.log(` ${m.slug} - ${m.name}`);
}
return;
}
success(`SPARC Mode: ${mode.name}`);
console.log();
console.log(blue("Role Definition:"));
console.log(mode.roleDefinition);
console.log();
console.log(blue("Custom Instructions:"));
console.log(mode.customInstructions);
console.log();
console.log(blue("Tool Groups:"));
console.log(mode.groups.join(", "));
console.log();
console.log(blue("Source:"));
console.log(mode.source);
} catch (err) {
error(`Failed to show mode info: ${(err as Error).message}`);
}
}
async function runSparcMode(ctx: CommandContext): Promise<void> {
const modeSlug = ctx.args[1];
const taskDescription = ctx.args.slice(2).join(" ");
if (!modeSlug || !taskDescription) {
error("Usage: sparc run <mode-slug> <task-description>");
return;
}
try {
const config = await loadSparcConfig();
const mode = config.customModes.find(m => m.slug === modeSlug);
if (!mode) {
error(`Mode not found: ${modeSlug}`);
return;
}
// Build the enhanced task prompt using SPARC methodology
const enhancedTask = buildSparcPrompt(mode, taskDescription, ctx.flags);
const instanceId = `sparc-${modeSlug}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
// Build tools based on mode groups
const tools = buildToolsFromGroups(mode.groups);
if (ctx.flags.dryRun || ctx.flags["dry-run"]) {
warning("DRY RUN - SPARC Mode Configuration:");
console.log(`Mode: ${mode.name} (${mode.slug})`);
console.log(`Instance ID: ${instanceId}`);
console.log(`Tools: ${tools}`);
console.log(`Task: ${taskDescription}`);
console.log();
console.log("Enhanced prompt preview:");
console.log(enhancedTask.substring(0, 300) + "...");
return;
}
success(`Starting SPARC mode: ${mode.name}`);
console.log(`📝 Instance ID: ${instanceId}`);
console.log(`🎯 Mode: ${mode.slug}`);
console.log(`🔧 Tools: ${tools}`);
console.log(`📋 Task: ${taskDescription}`);
console.log();
// Execute Claude with SPARC configuration
await executeClaudeWithSparc(enhancedTask, tools, instanceId, ctx.flags);
} catch (err) {
error(`Failed to run SPARC mode: ${(err as Error).message}`);
}
}
async function runTddFlow(ctx: CommandContext): Promise<void> {
const taskDescription = ctx.args.slice(1).join(" ");
if (!taskDescription) {
error("Usage: sparc tdd <task-description>");
return;
}
try {
const config = await loadSparcConfig();
// Build TDD workflow using SPARC methodology
const workflow = [
{ mode: "spec-pseudocode", phase: "Specification", description: `Create detailed spec and pseudocode for: ${taskDescription}` },
{ mode: "tdd", phase: "Red", description: `Write failing tests for: ${taskDescription}` },
{ mode: "code", phase: "Green", description: `Implement minimal code to pass tests for: ${taskDescription}` },
{ mode: "refinement-optimization-mode", phase: "Refactor", description: `Refactor and optimize implementation for: ${taskDescription}` },
{ mode: "integration", phase: "Integration", description: `Integrate and verify complete solution for: ${taskDescription}` }
];
if (ctx.flags.dryRun || ctx.flags["dry-run"]) {
warning("DRY RUN - TDD Workflow:");
for (const step of workflow) {
console.log(`${cyan(step.phase)}: ${step.mode} - ${step.description}`);
}
return;
}
success("Starting SPARC TDD Workflow");
console.log("Following Test-Driven Development with SPARC methodology");
console.log();
for (let i = 0; i < workflow.length; i++) {
const step = workflow[i];
const mode = config.customModes.find(m => m.slug === step.mode);
if (!mode) {
warning(`Mode not found: ${step.mode}, skipping step`);
continue;
}
info(`Phase ${i + 1}/5: ${step.phase} (${mode.name})`);
console.log(`📋 ${step.description}`);
console.log();
const enhancedTask = buildSparcPrompt(mode, step.description, {
...ctx.flags,
tddPhase: step.phase,
workflowStep: i + 1,
totalSteps: workflow.length
});
const tools = buildToolsFromGroups(mode.groups);
const instanceId = `sparc-tdd-${step.phase.toLowerCase()}-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
await executeClaudeWithSparc(enhancedTask, tools, instanceId, ctx.flags);
// Store phase completion in memory for next step
if (ctx.flags.sequential !== false) {
console.log("Phase completed. Press Enter to continue to next phase, or Ctrl+C to stop...");
await new Promise<void>(async (resolve) => {
const readline = await import("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("", () => {
rl.close();
resolve();
});
});
}
}
success("SPARC TDD Workflow completed!");
} catch (err) {
error(`Failed to run TDD flow: ${(err as Error).message}`);
}
}
async function runSparcWorkflow(ctx: CommandContext): Promise<void> {
const workflowFile = ctx.args[1];
if (!workflowFile) {
error("Usage: sparc workflow <workflow-file.json>");
return;
}
try {
const { readFile } = await import("fs/promises");
const workflowContent = await readFile(workflowFile, "utf-8");
const workflow = JSON.parse(workflowContent);
if (!workflow.steps || !Array.isArray(workflow.steps)) {
error("Invalid workflow file: missing 'steps' array");
return;
}
const config = await loadSparcConfig();
success(`Loading SPARC workflow: ${workflow.name || "Unnamed"}`);
console.log(`📋 Steps: ${workflow.steps.length}`);
console.log(`📝 Description: ${workflow.description || "No description"}`);
console.log();
if (ctx.flags.dryRun || ctx.flags["dry-run"]) {
warning("DRY RUN - Workflow Steps:");
for (let i = 0; i < workflow.steps.length; i++) {
const step = workflow.steps[i];
console.log(`${i + 1}. ${cyan(step.mode)} - ${step.description || step.task}`);
}
return;
}
for (let i = 0; i < workflow.steps.length; i++) {
const step = workflow.steps[i];
const mode = config.customModes.find(m => m.slug === step.mode);
if (!mode) {
warning(`Mode not found: ${step.mode}, skipping step ${i + 1}`);
continue;
}
info(`Step ${i + 1}/${workflow.steps.length}: ${mode.name}`);
console.log(`📋 ${step.description || step.task}`);
console.log();
const enhancedTask = buildSparcPrompt(mode, step.description || step.task, {
...ctx.flags,
workflowStep: i + 1,
totalSteps: workflow.steps.length,
workflowName: workflow.name
});
const tools = buildToolsFromGroups(mode.groups);
const instanceId = `sparc-workflow-${i + 1}-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
await executeClaudeWithSparc(enhancedTask, tools, instanceId, ctx.flags);
if (workflow.sequential !== false && i < workflow.steps.length - 1) {
console.log("Step completed. Press Enter to continue, or Ctrl+C to stop...");
await new Promise<void>((resolve) => {
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("", () => {
rl.close();
resolve();
});
});
}
}
success("SPARC workflow completed!");
} catch (err) {
error(`Failed to run workflow: ${(err as Error).message}`);
}
}
function buildSparcPrompt(mode: SparcMode, taskDescription: string, flags: any): string {
const memoryNamespace = flags.namespace || mode.slug || "default";
return `# SPARC Development Mode: ${mode.name}
## Your Role
${mode.roleDefinition}
## Your Task
${taskDescription}
## Mode-Specific Instructions
${mode.customInstructions}
## SPARC Development Environment
You are working within the SPARC (Specification, Pseudocode, Architecture, Refinement, Completion) methodology using claude-flow orchestration features.
### Available Development Tools
- **Memory Persistence**: Use \`npx claude-flow memory store <key> "<value>"\` to save progress and findings
- **Memory Retrieval**: Use \`npx claude-flow memory query <search>\` to access previous work
- **Namespace**: Your work is stored in the "${memoryNamespace}" namespace
### SPARC Methodology Integration
${flags.tddPhase ? `
**Current TDD Phase**: ${flags.tddPhase}
- Follow the Red-Green-Refactor cycle
- Store test results and refactoring notes in memory
` : ''}
${flags.workflowStep ? `
**Workflow Progress**: Step ${flags.workflowStep} of ${flags.totalSteps}
- Review previous steps: \`npx claude-flow memory query previous_steps\`
- Store this step's output: \`npx claude-flow memory store step_${flags.workflowStep}_output "<results>"\`
` : ''}
### Best Practices
1. **Modular Development**: Keep all files under 500 lines
2. **Environment Safety**: Never hardcode secrets or environment values
3. **Memory Usage**: Store key findings and decisions in memory for future reference
4. **Tool Integration**: Use \`new_task\` for subtasks and \`attempt_completion\` when finished
### Memory Commands Examples
\`\`\`bash
# Store your progress
npx claude-flow memory store ${memoryNamespace}_progress "Current status and findings"
# Check for previous work
npx claude-flow memory query ${memoryNamespace}
# Store phase-specific results
npx claude-flow memory store ${memoryNamespace}_${flags.tddPhase || 'results'} "Phase output and decisions"
\`\`\`
### Integration with Other SPARC Modes
When working with other SPARC modes, use memory to:
- Share findings with spec-pseudocode mode
- Pass requirements to architect mode
- Coordinate with code and tdd modes
- Communicate results to integration mode
Now proceed with your task following the SPARC methodology and your specific role instructions.`;
}
function buildToolsFromGroups(groups: string[]): string {
const toolMappings: Record<string, string[]> = {
read: ["View", "LS", "GlobTool", "GrepTool"],
edit: ["Edit", "Replace", "MultiEdit", "Write"],
browser: ["WebFetch"],
mcp: ["mcp_tools"],
command: ["Bash", "Terminal"]
};
const tools = new Set<string>();
// Always include basic tools
tools.add("View");
tools.add("Edit");
tools.add("Bash");
for (const group of groups) {
if (Array.isArray(group)) {
// Handle nested group definitions
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 Array.from(tools).join(",");
}
async function executeClaudeWithSparc(
enhancedTask: string,
tools: string,
instanceId: string,
flags: any
): Promise<void> {
const claudeArgs = [enhancedTask];
claudeArgs.push("--allowedTools", tools);
if (flags.noPermissions || flags["no-permissions"]) {
claudeArgs.push("--dangerously-skip-permissions");
}
if (flags.config) {
claudeArgs.push("--mcp-config", flags.config);
}
if (flags.verbose) {
claudeArgs.push("--verbose");
}
try {
const { spawn } = await import("child_process");
const child = spawn("claude", claudeArgs, {
env: {
...process.env,
CLAUDE_INSTANCE_ID: instanceId,
CLAUDE_SPARC_MODE: "true",
CLAUDE_FLOW_MEMORY_ENABLED: "true",
CLAUDE_FLOW_MEMORY_NAMESPACE: flags.namespace || "sparc",
},
stdio: "inherit",
});
const status = await new Promise<{ success: boolean; code: number | null }>((resolve) => {
child.on("close", (code) => {
resolve({ success: code === 0, code });
});
});
if ((status as any).success) {
success(`SPARC instance ${instanceId} completed successfully`);
} else {
error(`SPARC instance ${instanceId} exited with code ${(status as any).code}`);
}
} catch (err) {
error(`Failed to execute Claude: ${(err as Error).message}`);
}
}
async function showSparcHelp(): Promise<void> {
console.log(`${cyan("SPARC")} - ${green("Specification, Pseudocode, Architecture, Refinement, Completion")}`);
console.log();
console.log("SPARC development methodology with TDD and multi-agent coordination.");
console.log();
console.log(blue("Commands:"));
console.log(" modes List all available SPARC modes");
console.log(" info <mode> Show detailed information about a mode");
console.log(" run <mode> <task> Execute a task using a specific SPARC mode");
console.log(" tdd <task> Run full TDD workflow using SPARC methodology");
console.log(" workflow <file> Execute a custom SPARC workflow from JSON file");
console.log();
console.log(blue("Common Modes:"));
console.log(" spec-pseudocode Create specifications and pseudocode");
console.log(" architect Design system architecture");
console.log(" code Implement code solutions");
console.log(" tdd Test-driven development");
console.log(" debug Debug and troubleshoot issues");
console.log(" security-review Security analysis and review");
console.log(" docs-writer Documentation creation");
console.log(" integration System integration and testing");
console.log();
console.log(blue("Options:"));
console.log(" --namespace <ns> Memory namespace for this session");
console.log(" --no-permissions Skip permission prompts");
console.log(" --config <file> MCP configuration file");
console.log(" --verbose Enable verbose output");
console.log(" --dry-run Preview what would be executed");
console.log(" --sequential Wait between workflow steps (default: true)");
console.log();
console.log(blue("Examples:"));
console.log(` ${yellow("claude-flow sparc modes")} # List all modes`);
console.log(` ${yellow("claude-flow sparc run code")} "implement user auth" # Run specific mode`);
console.log(` ${yellow("claude-flow sparc tdd")} "payment processing system" # Full TDD workflow`);
console.log(` ${yellow("claude-flow sparc workflow")} project-workflow.json # Custom workflow`);
console.log();
console.log(blue("TDD Workflow:"));
console.log(" 1. Specification - Define requirements and create pseudocode");
console.log(" 2. Red Phase - Write failing tests");
console.log(" 3. Green Phase - Implement minimal code to pass tests");
console.log(" 4. Refactor Phase - Optimize and clean up code");
console.log(" 5. Integration - Verify complete solution");
console.log();
console.log("For more information: https://github.com/ruvnet/claude-code-flow/docs/sparc.md");
}