UNPKG

@pimzino/claude-code-spec-workflow

Version:

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

397 lines (396 loc) 20.3 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 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 get_steering_context_1 = require("./get-steering-context"); const get_spec_context_1 = require("./get-spec-context"); const get_template_context_1 = require("./get-template-context"); const get_tasks_1 = require("./get-tasks"); const auto_update_1 = require("./auto-update"); 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 claude-code-spec-workflow get-content <file> # Read file content claude-code-spec-workflow get-steering-context # Get formatted steering documents claude-code-spec-workflow get-spec-context <spec> # Get formatted spec documents claude-code-spec-workflow get-template-context [type] # Get formatted templates claude-code-spec-workflow 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') .option('--no-update', 'Skip automatic update check') .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 task execution')); console.log(); // Check for updates and auto-update if available (unless disabled) if (options.update !== false) { await (0, auto_update_1.autoUpdate)(); } 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 and installation completeness let setup = new setup_1.SpecWorkflowSetup(projectPath); const claudeExists = await setup.claudeDirectoryExists(); let isComplete = false; if (claudeExists) { // Check if installation is complete (agents are now mandatory) isComplete = await setup.isInstallationComplete(); } // Handle incomplete installations - auto-complete without prompts if (claudeExists && !isComplete) { console.log(chalk_1.default.yellow('Incomplete installation detected - completing automatically...')); console.log(chalk_1.default.gray('Some required files or directories are missing from your .claude installation.')); console.log(chalk_1.default.green('Your spec documents (requirements.md, design.md, tasks.md) will not be modified')); console.log(); // For incomplete installations, we'll proceed with fresh setup to add missing files // The setup process will only create missing files/directories } if (claudeExists && isComplete) { console.log(chalk_1.default.cyan('Existing installation detected - updating automatically...')); console.log(chalk_1.default.green('Your spec documents (requirements.md, design.md, tasks.md) will not be modified')); console.log(); console.log(chalk_1.default.yellow('🔄 Update Process:')); console.log(chalk_1.default.gray('- Complete .claude directory will be backed up automatically')); console.log(chalk_1.default.gray('- Fresh installation will be created with latest versions')); console.log(chalk_1.default.gray('- Your specs and task commands will be restored from backup')); console.log(chalk_1.default.green('✓ Your spec documents (requirements.md, design.md, tasks.md) will be preserved')); console.log(); } // Setup for new installations - agents are now mandatory if (!claudeExists || !isComplete) { 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 for existing specs')); 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(' Complete workflow instructions embedded in each command')); console.log(chalk_1.default.gray(' Claude Code sub-agents (mandatory)')); console.log(); // Create setup instance (agents are now mandatory) setup = new setup_1.SpecWorkflowSetup(process.cwd()); } // Run setup or update if (claudeExists && isComplete) { // This is an update scenario (complete installation being updated) const updateSpinner = (0, ora_1.default)('Starting fresh installation update...').start(); const { SpecWorkflowUpdater } = await Promise.resolve().then(() => __importStar(require('./update'))); const updater = new SpecWorkflowUpdater(projectPath); // Agents are now mandatory for all installations try { await updater.updateWithFreshInstall(); // Agents are now mandatory } catch (error) { updateSpinner.fail('Fresh installation update failed'); console.error(chalk_1.default.red('Fresh installation update failed. Error:'), error instanceof Error ? error.message : error); process.exit(1); } // Clean up old backups (keep 5 most recent) await updater.cleanupOldBackups(5); updateSpinner.succeed('Update complete!'); } else { // This is a fresh setup or completing an incomplete installation const isCompletion = claudeExists && !isComplete; const setupSpinner = (0, ora_1.default)(isCompletion ? 'Completing installation...' : 'Setting up spec workflow...').start(); await setup.runSetup(); setupSpinner.succeed(isCompletion ? 'Installation completed!' : 'Setup complete!'); } // Success message console.log(); if (claudeExists && isComplete) { console.log(chalk_1.default.green.bold('Spec Workflow updated successfully!')); console.log(chalk_1.default.gray('A backup of your previous installation was created automatically.')); } else if (claudeExists && !isComplete) { console.log(chalk_1.default.green.bold('Spec Workflow installation completed successfully!')); console.log(chalk_1.default.gray('Missing files and directories have been added to your existing installation.')); } else { console.log(chalk_1.default.green.bold('Spec Workflow installed successfully!')); } console.log(); // Show restart message if it was an update or installation completion if ((claudeExists && isComplete) || (claudeExists && !isComplete)) { 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 feature-name "description"')); console.log(chalk_1.default.gray('3. For bug fixes: /bug-create bug-name "description"')); console.log(); console.log(chalk_1.default.blue('For help, see the README')); console.log(chalk_1.default.blue('To update later: npm install -g @pimzino/claude-code-spec-workflow')); } 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 get-steering-context command program .command('get-steering-context') .description('Load and format all steering documents for context passing') .option('-p, --project <path>', 'Project directory', process.cwd()) .action(async (options) => { await (0, get_steering_context_1.getSteeringContext)(options.project); }); // Add get-spec-context command program .command('get-spec-context') .description('Load and format all specification documents for context passing') .argument('<spec-name>', 'Name of the specification') .option('-p, --project <path>', 'Project directory', process.cwd()) .action(async (specName, options) => { await (0, get_spec_context_1.getSpecContext)(specName, options.project); }); // Add get-template-context command program .command('get-template-context') .description('Load and format templates for context passing') .argument('[template-type]', 'Template type: spec, steering, bug, or all (default: all)') .option('-p, --project <path>', 'Project directory', process.cwd()) .action(async (templateType, options) => { await (0, get_template_context_1.getTemplateContext)(templateType, 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