@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
546 lines (532 loc) • 22.8 kB
JavaScript
import { Command } from "../cliffy-compat.js";
import colors from "chalk";
const { blue, yellow, green, magenta, cyan, red } = colors;
// Utility functions for consistent output
const success = (message) => console.log(green("✅"), message);
const error = (message) => console.log(red("❌"), message);
const warning = (message) => console.log(yellow("⚠️"), message);
const info = (message) => console.log(cyan("ℹ️"), message);
let sparcConfig = null;
async function loadSparcConfig() {
if (sparcConfig) {
return sparcConfig;
}
try {
const configPath = ".roomodes";
const { readFile } = await import("fs/promises");
const content = await readFile(configPath, "utf-8");
const roomodes = JSON.parse(content);
// Convert .roomodes format to SparcConfig format
const customModes = Object.entries(roomodes).map(([slug, config]) => ({
slug,
name: config.description || slug,
roleDefinition: config.prompt || `SPARC: ${slug}`,
customInstructions: config.prompt || `You are a ${slug} specialist.`,
groups: config.tools || [],
source: ".roomodes"
}));
sparcConfig = { customModes };
return sparcConfig;
}
catch (error) {
throw new Error(`Failed to load SPARC configuration: ${error.message}`);
}
}
export async function sparcAction(ctx) {
// Auto-initialize if needed (except for help)
if (ctx.args[0] !== "help" && ctx.args.length > 0) {
await checkAndAutoInitialize();
}
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;
}
}
/**
* Check if project is initialized and auto-initialize if needed
*/
async function checkAndAutoInitialize() {
try {
const fs = await import("fs/promises");
// Check if .roomodes and .claude directory exist
const roomodesExists = await fs.access(".roomodes").then(() => true).catch(() => false);
const claudeExists = await fs.access(".claude").then(() => true).catch(() => false);
if (!roomodesExists || !claudeExists) {
console.log(yellow("🔍 Claude-Flow project not initialized in current directory"));
console.log(cyan("🚀 Auto-initializing project..."));
console.log();
// Import and run init
const { initCommand: runInit } = await import("../init/index.js");
await runInit({ sparc: false, force: false });
console.log();
console.log(green("✅ Project auto-initialized successfully!"));
console.log(colors.gray("You can now use all claude-flow commands."));
console.log();
return true;
}
return false;
}
catch (error) {
// If auto-init fails, just warn and continue
console.log(yellow("⚠️ Auto-initialization failed, continuing anyway..."));
return false;
}
}
async function listSparcModes(ctx) {
try {
const config = await loadSparcConfig();
const verbose = ctx.flags.verbose;
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.message}`);
}
}
async function showModeInfo(ctx) {
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.message}`);
}
}
async function runSparcMode(ctx) {
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.message}`);
}
}
async function runTddFlow(ctx) {
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(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.message}`);
}
}
async function runSparcWorkflow(ctx) {
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((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.message}`);
}
}
function buildSparcPrompt(mode, taskDescription, flags) {
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) {
const toolMappings = {
read: ["View", "LS", "GlobTool", "GrepTool"],
edit: ["Edit", "Replace", "MultiEdit", "Write"],
browser: ["WebFetch"],
mcp: ["mcp_tools"],
command: ["Bash", "Terminal"],
};
const tools = new Set();
// 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, tools, instanceId, flags) {
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((resolve) => {
child.on("close", (code) => {
resolve({ success: code === 0, code });
});
});
if (status.success) {
success(`SPARC instance ${instanceId} completed successfully`);
}
else {
error(`SPARC instance ${instanceId} exited with code ${status.code}`);
}
}
catch (err) {
error(`Failed to execute Claude: ${err.message}`);
}
}
async function showSparcHelp() {
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");
}
export const sparcCommand = new Command()
.name("sparc")
.description("SPARC (Specification, Pseudocode, Architecture, Refinement, Completion) development methodology")
.option("--namespace <ns>", "Memory namespace for this session")
.option("--no-permissions", "Skip permission prompts")
.option("--config <file>", "MCP configuration file")
.option("--verbose", "Enable verbose output")
.option("--dry-run", "Preview what would be executed")
.option("--sequential", "Wait between workflow steps (default: true)")
.allowUnknownOption()
.action(async (options) => {
// Parse arguments directly from process.argv
const argv = process.argv;
const sparcIndex = argv.findIndex(arg => arg === "sparc");
if (sparcIndex === -1) {
await sparcAction({ args: [], flags: options });
return;
}
// Get all arguments after "sparc", filtering out known options
const rawArgs = argv.slice(sparcIndex + 1);
const filteredArgs = [];
for (let i = 0; i < rawArgs.length; i++) {
const arg = rawArgs[i];
// Skip known options and their values
if (arg.startsWith("--")) {
const optionName = arg.substring(2);
// Skip the option and its value if it takes a value
if (["namespace", "config"].includes(optionName) && i + 1 < rawArgs.length && !rawArgs[i + 1].startsWith("--")) {
i++; // Skip the next argument (the option value)
}
continue;
}
filteredArgs.push(arg);
}
// For debugging: log the parsed arguments
if (options.verbose) {
console.log("Debug - Parsed args:", filteredArgs);
console.log("Debug - Options:", options);
}
const ctx = {
args: filteredArgs,
flags: options,
};
await sparcAction(ctx);
});
//# sourceMappingURL=sparc.js.map