@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
JavaScript
;
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