@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
233 lines • 10.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpecWorkflowUpdater = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const task_generator_1 = require("./task-generator");
class SpecWorkflowUpdater {
constructor(projectRoot = process.cwd()) {
this.projectRoot = projectRoot;
this.claudeDir = (0, path_1.join)(projectRoot, '.claude');
this.commandsDir = (0, path_1.join)(this.claudeDir, 'commands');
this.templatesDir = (0, path_1.join)(this.claudeDir, 'templates');
this.agentsDir = (0, path_1.join)(this.claudeDir, 'agents');
this.specsDir = (0, path_1.join)(this.claudeDir, 'specs');
// Initialize source markdown directories
this.markdownDir = (0, path_1.join)(__dirname, 'markdown');
this.markdownCommandsDir = (0, path_1.join)(this.markdownDir, 'commands');
this.markdownTemplatesDir = (0, path_1.join)(this.markdownDir, 'templates');
this.markdownAgentsDir = (0, path_1.join)(this.markdownDir, 'agents');
}
async updateCommands() {
// List of default command files to update (exclude task command folders)
const commandNames = [
'spec-create',
'spec-execute',
'spec-orchestrate',
'spec-status',
'spec-list',
'spec-completion-review',
'spec-steering-setup',
'bug-create',
'bug-analyze',
'bug-fix',
'bug-verify',
'bug-status'
];
// Delete existing default commands (but preserve task command folders)
const commandsEntries = await fs_1.promises.readdir(this.commandsDir, { withFileTypes: true });
for (const entry of commandsEntries) {
if (entry.isFile() && entry.name.endsWith('.md')) {
const commandPath = (0, path_1.join)(this.commandsDir, entry.name);
await fs_1.promises.unlink(commandPath);
}
// Preserve directories (which contain task commands for specific specs)
}
// Copy new command files
for (const commandName of commandNames) {
const sourceFile = (0, path_1.join)(this.markdownCommandsDir, `${commandName}.md`);
const destFile = (0, path_1.join)(this.commandsDir, `${commandName}.md`);
try {
const commandContent = await fs_1.promises.readFile(sourceFile, 'utf-8');
await fs_1.promises.writeFile(destFile, commandContent, 'utf-8');
}
catch (error) {
console.error(`Failed to update command ${commandName}:`, error);
throw error;
}
}
}
async updateTemplates() {
const templateNames = [
'requirements-template.md',
'design-template.md',
'tasks-template.md',
'product-template.md',
'tech-template.md',
'structure-template.md',
'bug-report-template.md',
'bug-analysis-template.md',
'bug-verification-template.md'
];
// Delete existing templates
try {
const templateEntries = await fs_1.promises.readdir(this.templatesDir);
for (const entry of templateEntries) {
if (entry.endsWith('.md')) {
const templatePath = (0, path_1.join)(this.templatesDir, entry);
await fs_1.promises.unlink(templatePath);
}
}
}
catch {
// Templates directory might not exist, continue
}
// Ensure templates directory exists
await fs_1.promises.mkdir(this.templatesDir, { recursive: true });
// Copy new template files
for (const templateName of templateNames) {
const sourceFile = (0, path_1.join)(this.markdownTemplatesDir, templateName);
const destFile = (0, path_1.join)(this.templatesDir, templateName);
try {
const templateContent = await fs_1.promises.readFile(sourceFile, 'utf-8');
await fs_1.promises.writeFile(destFile, templateContent, 'utf-8');
}
catch (error) {
console.error(`Failed to update template ${templateName}:`, error);
throw error;
}
}
}
async updateAgents() {
// Check if agents are enabled in config
const configFile = (0, path_1.join)(this.claudeDir, 'spec-config.json');
let agentsEnabled = false;
try {
const configContent = await fs_1.promises.readFile(configFile, 'utf-8');
const config = JSON.parse(configContent);
agentsEnabled = config.spec_workflow?.agents_enabled || false;
}
catch {
// Config might not exist or be malformed, skip agents update
return;
}
if (!agentsEnabled) {
// If agents are disabled, remove agents directory if it exists
try {
await fs_1.promises.rm(this.agentsDir, { recursive: true });
}
catch {
// Directory might not exist, ignore
}
return;
}
// List of available agent files
const agentFiles = [
'spec-requirements-validator.md',
'spec-design-validator.md',
'spec-task-validator.md',
'spec-task-executor.md',
'spec-task-implementation-reviewer.md',
'spec-integration-tester.md',
'spec-completion-reviewer.md',
'bug-root-cause-analyzer.md',
'steering-document-updater.md',
'spec-dependency-analyzer.md',
'spec-test-generator.md',
'spec-documentation-generator.md',
'spec-performance-analyzer.md',
'spec-duplication-detector.md',
'spec-breaking-change-detector.md'
];
// Delete existing agents
try {
const agentEntries = await fs_1.promises.readdir(this.agentsDir);
for (const entry of agentEntries) {
if (entry.endsWith('.md')) {
const agentPath = (0, path_1.join)(this.agentsDir, entry);
await fs_1.promises.unlink(agentPath);
}
}
}
catch {
// Agents directory might not exist, continue
}
// Ensure agents directory exists
await fs_1.promises.mkdir(this.agentsDir, { recursive: true });
// Copy new agent files
for (const agentFile of agentFiles) {
const sourceFile = (0, path_1.join)(this.markdownAgentsDir, agentFile);
const destFile = (0, path_1.join)(this.agentsDir, agentFile);
try {
const agentContent = await fs_1.promises.readFile(sourceFile, 'utf-8');
await fs_1.promises.writeFile(destFile, agentContent, 'utf-8');
}
catch (error) {
console.error(`Failed to update agent ${agentFile}:`, error);
throw error;
}
}
}
async regenerateTaskCommands() {
console.log('Scanning for existing specs...');
// Find all existing specs
let specDirs = [];
try {
const specsEntries = await fs_1.promises.readdir(this.specsDir, { withFileTypes: true });
specDirs = specsEntries
.filter(entry => entry.isDirectory())
.map(entry => entry.name);
if (specDirs.length === 0) {
console.log('No specs found to regenerate task commands for.');
return;
}
console.log(`Found ${specDirs.length} spec(s): ${specDirs.join(', ')}`);
}
catch {
console.log('No specs directory found, skipping task command regeneration.');
// Specs directory might not exist
return;
}
// For each spec, regenerate task commands if tasks.md exists
for (const specName of specDirs) {
const specDir = (0, path_1.join)(this.specsDir, specName);
const tasksFile = (0, path_1.join)(specDir, 'tasks.md');
const commandsSpecDir = (0, path_1.join)(this.commandsDir, specName);
try {
// Check if tasks.md exists
await fs_1.promises.access(tasksFile);
// Read tasks.md
const tasksContent = await fs_1.promises.readFile(tasksFile, 'utf8');
// Parse tasks and generate commands
const tasks = (0, task_generator_1.parseTasksFromMarkdown)(tasksContent);
if (tasks.length === 0) {
console.log(` ${specName}: No tasks found in tasks.md, skipping`);
continue;
}
console.log(` ${specName}: Regenerating ${tasks.length} task commands...`);
// Delete existing task commands for this spec
try {
await fs_1.promises.rm(commandsSpecDir, { recursive: true });
}
catch {
// Directory might not exist
}
// Create spec commands directory
await fs_1.promises.mkdir(commandsSpecDir, { recursive: true });
// Generate commands
for (const task of tasks) {
await (0, task_generator_1.generateTaskCommand)(commandsSpecDir, specName, task);
}
console.log(` ${specName}: Generated commands for tasks: ${tasks.map(t => t.id).join(', ')}`);
}
catch {
console.log(` ${specName}: No tasks.md found, skipping`);
// tasks.md doesn't exist for this spec, skip
continue;
}
}
console.log('Task command regeneration complete!');
}
}
exports.SpecWorkflowUpdater = SpecWorkflowUpdater;
//# sourceMappingURL=update.js.map