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