UNPKG

@pimzino/claude-code-spec-workflow

Version:

Automated workflows for Claude Code. Includes spec-driven development (Requirements → Design → Tasks → Implementation) with intelligent orchestration, optional steering documents and streamlined bug fix workflow (Report → Analyze → Fix → Verify). We have

442 lines (441 loc) 23.2 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const chalk_1 = __importDefault(require("chalk")); const inquirer_1 = __importDefault(require("inquirer")); const ora_1 = __importDefault(require("ora")); const setup_1 = require("./setup"); const utils_1 = require("./utils"); const task_generator_1 = require("./task-generator"); const get_content_1 = require("./get-content"); const using_agents_1 = require("./using-agents"); const get_tasks_1 = require("./get-tasks"); const fs_1 = require("fs"); const path = __importStar(require("path")); // Read version from package.json // Use require.resolve to find package.json in both dev and production let packageJson; try { const packageJsonPath = path.join(__dirname, '..', 'package.json'); packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8')); } catch { // Fallback for edge cases packageJson = { version: '1.3.0' }; } const program = new commander_1.Command(); // Debug logging for WSL issues if (process.env.DEBUG_CLI) { console.log('process.argv:', process.argv); console.log('process.execPath:', process.execPath); console.log('__filename:', __filename); } program .name('claude-spec-setup') .description('Set up Claude Code Spec Workflow with automated orchestration in your project') .version(packageJson.version) .addHelpText('after', ` Examples: npx @pimzino/claude-code-spec-workflow@latest # Run setup (default) npx @pimzino/claude-code-spec-workflow@latest setup # Run setup explicitly npx @pimzino/claude-code-spec-workflow@latest test # Test setup in temp directory npx @pimzino/claude-code-spec-workflow@latest get-content <file> # Read file content npx @pimzino/claude-code-spec-workflow@latest using-agents # Check if agents enabled npx @pimzino/claude-code-spec-workflow@latest get-tasks <spec> # Get tasks from spec For help with a specific command: npx @pimzino/claude-code-spec-workflow@latest <command> --help `); // Setup command program .command('setup') .description('Set up Claude Code Spec Workflow in your project') .option('-p, --project <path>', 'Project directory', process.cwd()) .option('-f, --force', 'Force overwrite existing files') .option('-y, --yes', 'Skip confirmation prompts') .action(async (options) => { console.log(chalk_1.default.cyan.bold('Claude Code Spec Workflow Setup')); console.log(chalk_1.default.gray('Automated spec-driven development with intelligent orchestration')); console.log(); const projectPath = options.project; const spinner = (0, ora_1.default)('Analyzing project...').start(); try { // Detect project type const projectTypes = await (0, utils_1.detectProjectType)(projectPath); spinner.succeed(`Project analyzed: ${projectPath}`); if (projectTypes.length > 0) { console.log(chalk_1.default.blue(`Detected project type(s): ${projectTypes.join(', ')}`)); } // Check Claude Code availability const claudeAvailable = await (0, utils_1.validateClaudeCode)(); if (claudeAvailable) { console.log(chalk_1.default.green('Claude Code is available')); } else { console.log(chalk_1.default.yellow('WARNING: Claude Code not found. Please install Claude Code first.')); console.log(chalk_1.default.gray(' Visit: https://docs.anthropic.com/claude-code')); } // Check for existing .claude directory let setup = new setup_1.SpecWorkflowSetup(projectPath); const claudeExists = await setup.claudeDirectoryExists(); if (claudeExists && !options.force) { if (!options.yes) { console.log(chalk_1.default.cyan('Existing installation detected')); console.log(chalk_1.default.green('Your spec documents (requirements.md, design.md, tasks.md) will not be modified')); console.log(); // Ask what to update const updateChoices = await inquirer_1.default.prompt([ { type: 'checkbox', name: 'updateItems', message: 'What would you like to update?', choices: [ { name: 'Commands (slash commands)', value: 'commands', checked: true }, { name: 'Templates', value: 'templates', checked: true }, { name: 'Agents', value: 'agents', checked: true }, { name: 'Task commands (regenerate for existing specs)', value: 'taskCommands', checked: true } ], validate: (answer) => { if (answer.length < 1) { return 'You must choose at least one item to update.'; } return true; } } ]); console.log(); console.log(chalk_1.default.yellow('WARNING: This will overwrite existing files')); console.log(chalk_1.default.gray('- Commands and templates will be replaced with latest versions')); console.log(chalk_1.default.gray('- Selected task commands will be regenerated')); console.log(chalk_1.default.green('Your spec documents (requirements.md, design.md, tasks.md) will not be modified')); console.log(); const { confirmUpdate } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'confirmUpdate', message: 'Continue with update?', default: true } ]); if (!confirmUpdate) { console.log(chalk_1.default.yellow('Update cancelled.')); process.exit(0); } // Store update choices for later use setup._updateChoices = updateChoices; } else { // Auto-select all items if --yes flag is used setup._updateChoices = { updateItems: ['commands', 'templates', 'agents', 'taskCommands'] }; } } // Confirm setup if (!options.yes) { console.log(); console.log(chalk_1.default.cyan('This will create:')); console.log(chalk_1.default.gray(' .claude/ directory structure')); console.log(chalk_1.default.gray(' 14 slash commands (9 spec workflow + 5 bug fix workflow)')); console.log(chalk_1.default.gray(' Auto-generated task commands')); console.log(chalk_1.default.gray(' Intelligent orchestrator for automated execution')); console.log(chalk_1.default.gray(' Document templates')); console.log(chalk_1.default.gray(' NPX-based task command generation')); console.log(chalk_1.default.gray(' Configuration files')); console.log(chalk_1.default.gray(' Complete workflow instructions embedded in each command')); console.log(); const { useAgents } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'useAgents', message: 'Enable Claude Code sub-agents for enhanced task execution?', default: true } ]); // Create setup instance with agent preference, preserving update choices const updateChoices = setup._updateChoices; setup = new setup_1.SpecWorkflowSetup(process.cwd(), useAgents); setup._updateChoices = updateChoices; } // Run setup or update if (claudeExists && setup._updateChoices) { // This is an update scenario const updateSpinner = (0, ora_1.default)('Updating installation...').start(); const { SpecWorkflowUpdater } = await Promise.resolve().then(() => __importStar(require('./update'))); const updater = new SpecWorkflowUpdater(projectPath); if (setup._updateChoices.updateItems.includes('commands')) { await updater.updateCommands(); } if (setup._updateChoices.updateItems.includes('templates')) { await updater.updateTemplates(); } if (setup._updateChoices.updateItems.includes('agents')) { await updater.updateAgents(); } if (setup._updateChoices.updateItems.includes('taskCommands')) { await updater.regenerateTaskCommands(); } updateSpinner.succeed('Update complete!'); } else { // This is a fresh setup const setupSpinner = (0, ora_1.default)('Setting up spec workflow...').start(); await setup.runSetup(); setupSpinner.succeed('Setup complete!'); } // Success message console.log(); if (claudeExists && setup._updateChoices) { console.log(chalk_1.default.green.bold('Spec Workflow updated successfully!')); } else { console.log(chalk_1.default.green.bold('Spec Workflow installed successfully!')); } console.log(); console.log(chalk_1.default.cyan('Available commands:')); console.log(chalk_1.default.white.bold('Spec Workflow (for new features):')); console.log(chalk_1.default.gray(' /spec-create <feature-name> - Create a new spec')); console.log(chalk_1.default.gray(' /spec-orchestrate <spec> - NEW! Automated execution')); console.log(chalk_1.default.gray(' /spec-execute <task-id> - Execute tasks manually')); console.log(chalk_1.default.gray(' /{spec-name}-task-{id} - Auto-generated task commands')); console.log(chalk_1.default.gray(' /spec-status - Show status')); console.log(chalk_1.default.gray(' /spec-completion-review - Final review when all tasks complete')); console.log(chalk_1.default.gray(' /spec-list - List all specs')); console.log(); // Show agents section if enabled if (setup && setup['createAgents']) { console.log(chalk_1.default.white.bold('Sub-Agents (automatic):')); console.log(chalk_1.default.gray(' spec-task-executor - Specialized task implementation agent')); console.log(chalk_1.default.gray(' spec-requirements-validator - Requirements quality validation agent')); console.log(chalk_1.default.gray(' spec-design-validator - Design quality validation agent')); console.log(chalk_1.default.gray(' spec-task-validator - Task atomicity validation agent')); console.log(chalk_1.default.gray(' spec-task-implementation-reviewer - Post-implementation review agent')); console.log(chalk_1.default.gray(' spec-integration-tester - Integration testing and validation agent')); console.log(chalk_1.default.gray(' spec-completion-reviewer - End-to-end feature completion agent')); console.log(chalk_1.default.gray(' bug-root-cause-analyzer - Enhanced bug analysis with git history')); console.log(chalk_1.default.gray(' steering-document-updater - Analyzes codebase and suggests doc updates')); console.log(chalk_1.default.gray(' spec-dependency-analyzer - Optimizes task execution order')); console.log(chalk_1.default.gray(' spec-test-generator - Generates tests from requirements')); console.log(chalk_1.default.gray(' spec-documentation-generator - Maintains project documentation')); console.log(chalk_1.default.gray(' spec-performance-analyzer - Analyzes performance implications')); console.log(chalk_1.default.gray(' spec-duplication-detector - Identifies code reuse opportunities')); console.log(chalk_1.default.gray(' spec-breaking-change-detector - Detects API compatibility issues')); console.log(); } console.log(chalk_1.default.white.bold('Bug Fix Workflow (for bug fixes):')); console.log(chalk_1.default.gray(' /bug-create <bug-name> - Start bug fix')); console.log(chalk_1.default.gray(' /bug-analyze - Analyze root cause')); console.log(chalk_1.default.gray(' /bug-fix - Implement fix')); console.log(chalk_1.default.gray(' /bug-verify - Verify fix')); console.log(chalk_1.default.gray(' /bug-status - Show bug status')); console.log(); // Show restart message if commands were updated if (claudeExists && setup._updateChoices && (setup._updateChoices.updateItems.includes('commands') || setup._updateChoices.updateItems.includes('taskCommands'))) { console.log(chalk_1.default.yellow.bold('RESTART REQUIRED:')); console.log(chalk_1.default.gray('You must restart Claude Code for updated commands to be visible')); console.log(chalk_1.default.white('- Run "claude --continue" to continue this conversation')); console.log(chalk_1.default.white('- Or run "claude" to start a fresh session')); console.log(); } console.log(chalk_1.default.yellow('Next steps:')); console.log(chalk_1.default.gray('1. Run: claude')); console.log(chalk_1.default.gray('2. For new features: /spec-create my-feature')); console.log(chalk_1.default.gray('3. For bug fixes: /bug-create my-bug')); console.log(); console.log(chalk_1.default.blue('For help, see the README or run /spec-list')); console.log(chalk_1.default.blue('To update later: npx @pimzino/claude-code-spec-workflow@latest')); } catch (error) { spinner.fail('Setup failed'); console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error); process.exit(1); } }); // Add test command program .command('test') .description('Test the setup in a temporary directory') .action(async () => { console.log(chalk_1.default.cyan('Testing setup...')); const os = await Promise.resolve().then(() => __importStar(require('os'))); const path = await Promise.resolve().then(() => __importStar(require('path'))); const fs = await Promise.resolve().then(() => __importStar(require('fs/promises'))); const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-spec-test-')); try { const setup = new setup_1.SpecWorkflowSetup(tempDir); await setup.runSetup(); console.log(chalk_1.default.green('Test completed successfully!')); console.log(chalk_1.default.gray(`Test directory: ${path.resolve(tempDir)}`)); console.log(chalk_1.default.blue('You can inspect the generated files in the test directory.')); } catch (error) { console.error(chalk_1.default.red('Test failed:'), error); process.exit(1); } }); // Add generate-task-commands command program .command('generate-task-commands') .description('Generate individual task commands for a spec') .argument('<spec-name>', 'Name of the spec to generate commands for') .option('-p, --project <path>', 'Project directory', process.cwd()) .action(async (specName, options) => { console.log(chalk_1.default.cyan('Generating task commands...')); const path = await Promise.resolve().then(() => __importStar(require('path'))); const fs = await Promise.resolve().then(() => __importStar(require('fs/promises'))); const projectPath = options.project; const specDir = path.join(projectPath, '.claude', 'specs', specName); const tasksFile = path.join(specDir, 'tasks.md'); const commandsSpecDir = path.join(projectPath, '.claude', 'commands', specName); const spinner = (0, ora_1.default)(`Generating commands for spec: ${specName}`).start(); try { // Check if tasks.md exists try { await fs.access(tasksFile); } catch { spinner.fail(`tasks.md not found at ${tasksFile}`); process.exit(1); } // Read tasks.md const tasksContent = await fs.readFile(tasksFile, 'utf8'); // Create spec commands directory await fs.mkdir(commandsSpecDir, { recursive: true }); // Parse tasks and generate commands const tasks = (0, task_generator_1.parseTasksFromMarkdown)(tasksContent); let taskCount = 0; for (const task of tasks) { await (0, task_generator_1.generateTaskCommand)(commandsSpecDir, specName, task); taskCount++; } spinner.succeed(`Generated ${taskCount} task commands for spec: ${specName}`); console.log(); console.log(chalk_1.default.green('Generated commands:')); for (const task of tasks) { console.log(chalk_1.default.gray(` /${specName}-task-${task.id} - ${task.description}`)); } console.log(); console.log(chalk_1.default.yellow.bold('RESTART REQUIRED: You must restart Claude Code for the new commands to be visible')); console.log(); console.log(chalk_1.default.cyan.bold('Instructions for the agent:')); console.log(chalk_1.default.gray('Tell the user they must exit Claude Code and restart it using:')); console.log(chalk_1.default.white('- Run "claude --continue" to continue this conversation with new commands')); console.log(chalk_1.default.white('- Or run "claude" to start a fresh session')); console.log(chalk_1.default.gray('The restart is absolutely necessary for the new task commands to appear.')); console.log(); console.log(chalk_1.default.blue('After restart, you can use commands like:')); if (tasks.length > 0) { console.log(chalk_1.default.gray(` /${specName}-task-${tasks[0].id}`)); if (tasks.length > 1) { console.log(chalk_1.default.gray(` /${specName}-task-${tasks[1].id}`)); } console.log(chalk_1.default.gray(' etc.')); } } catch (error) { spinner.fail('Command generation failed'); console.error(chalk_1.default.red('Error:'), error instanceof Error ? error.message : error); process.exit(1); } }); // Add get-content command program .command('get-content') .description('Read and print the contents of a file') .argument('<file-path>', 'Full path to the file to read') .action(async (filePath) => { await (0, get_content_1.getFileContent)(filePath); }); // Add using-agents command program .command('using-agents') .description('Check if agents are enabled in spec-config.json') .option('-p, --project <path>', 'Project directory', process.cwd()) .action(async (options) => { await (0, using_agents_1.getAgentsEnabled)(options.project); }); // Add get-tasks command program .command('get-tasks') .description('Get tasks from a specification') .argument('<spec-name>', 'Name of the spec to get tasks from') .argument('[task-id]', 'Specific task ID to retrieve') .option('-m, --mode <mode>', 'Mode: all, single, next-pending, or complete', 'all') .option('-p, --project <path>', 'Project directory', process.cwd()) .action(async (specName, taskId, options) => { let mode = options.mode; // Auto-detect mode if taskId is provided and mode is default if (taskId && mode === 'all') { mode = 'single'; } if (!['all', 'single', 'next-pending', 'complete'].includes(mode)) { console.error(chalk_1.default.red('Error: Invalid mode. Use: all, single, next-pending, or complete')); process.exit(1); } await (0, get_tasks_1.getTasks)(specName, taskId, mode, options.project); }); // Add error handling for unknown commands program.on('command:*', () => { const availableCommands = program.commands.map(cmd => cmd.name()).filter(name => name !== 'help'); console.error(chalk_1.default.red(`Error: Unknown command '${program.args[0]}'`)); console.log(); console.log(chalk_1.default.cyan('Available commands:')); availableCommands.forEach(cmd => { const command = program.commands.find(c => c.name() === cmd); if (command) { console.log(chalk_1.default.gray(` ${cmd} - ${command.description()}`)); } }); console.log(); console.log(chalk_1.default.yellow('For help with a specific command, run:')); console.log(chalk_1.default.gray(' npx @pimzino/claude-code-spec-workflow@latest <command> --help')); process.exit(1); }); // Check if we should add 'setup' as default command const args = process.argv.slice(2); if (args.length === 0 || (args.length > 0 && !args[0].startsWith('-') && !program.commands.some(cmd => cmd.name() === args[0]))) { // No command provided or first arg is not a known command and not a flag // Insert 'setup' as the command process.argv.splice(2, 0, 'setup'); } // Parse arguments normally - let Commander.js handle everything program.parse(); //# sourceMappingURL=cli.js.map