@waltcow/claude-code-spec-workflow
Version:
Automated spec-driven workflow for Claude Code. Transforms feature ideas into complete implementations through Requirements → Design → Tasks → Implementation.
158 lines • 7.18 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpecWorkflowSetup = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const commands_1 = require("./commands");
const templates_1 = require("./templates");
const claude_md_1 = require("./claude-md");
const scripts_1 = require("./scripts");
class SpecWorkflowSetup {
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.specsDir = (0, path_1.join)(this.claudeDir, 'specs');
this.templatesDir = (0, path_1.join)(this.claudeDir, 'templates');
this.scriptsDir = (0, path_1.join)(this.claudeDir, 'scripts');
}
async claudeDirectoryExists() {
try {
await fs_1.promises.access(this.claudeDir);
return true;
}
catch {
return false;
}
}
async setupDirectories() {
const directories = [
this.claudeDir,
this.commandsDir,
this.specsDir,
this.templatesDir,
this.scriptsDir
];
for (const dir of directories) {
await fs_1.promises.mkdir(dir, { recursive: true });
}
}
async createSlashCommands() {
const commands = {
'spec-create': (0, commands_1.getSpecCreateCommand)(),
'spec-requirements': (0, commands_1.getSpecRequirementsCommand)(),
'spec-design': (0, commands_1.getSpecDesignCommand)(),
'spec-tasks': (0, commands_1.getSpecTasksCommand)(),
'spec-execute': (0, commands_1.getSpecExecuteCommand)(),
'spec-status': (0, commands_1.getSpecStatusCommand)(),
'spec-list': (0, commands_1.getSpecListCommand)()
};
for (const [commandName, commandContent] of Object.entries(commands)) {
const commandFile = (0, path_1.join)(this.commandsDir, `${commandName}.md`);
await fs_1.promises.writeFile(commandFile, commandContent, 'utf-8');
}
}
async createTemplates() {
const templates = {
'requirements-template.md': (0, templates_1.getRequirementsTemplate)(),
'design-template.md': (0, templates_1.getDesignTemplate)(),
'tasks-template.md': (0, templates_1.getTasksTemplate)()
};
for (const [templateName, templateContent] of Object.entries(templates)) {
const templateFile = (0, path_1.join)(this.templatesDir, templateName);
await fs_1.promises.writeFile(templateFile, templateContent, 'utf-8');
}
}
async createScripts() {
const scripts = {
'generate-commands.bat': (0, scripts_1.getWindowsCommandGenerationScript)(),
'generate-commands.sh': (0, scripts_1.getUnixCommandGenerationScript)(),
'generate-commands-launcher.sh': (0, scripts_1.getOSDetectionScript)(),
'README.md': (0, scripts_1.getCommandGenerationInstructions)()
};
for (const [scriptName, scriptContent] of Object.entries(scripts)) {
const scriptFile = (0, path_1.join)(this.scriptsDir, scriptName);
await fs_1.promises.writeFile(scriptFile, scriptContent, 'utf-8');
// Make shell scripts executable on Unix-like systems
if (scriptName.endsWith('.sh')) {
try {
await fs_1.promises.chmod(scriptFile, 0o755);
}
catch (error) {
// Ignore chmod errors on Windows
console.warn(`Warning: Could not set execute permissions for ${scriptName}`);
}
}
}
}
async createConfigFile() {
const config = {
spec_workflow: {
version: '1.0.0',
auto_create_directories: true,
auto_reference_requirements: true,
enforce_approval_workflow: true,
default_feature_prefix: 'feature-',
supported_formats: ['markdown', 'mermaid']
}
};
const configFile = (0, path_1.join)(this.claudeDir, 'spec-config.json');
await fs_1.promises.writeFile(configFile, JSON.stringify(config, null, 2), 'utf-8');
}
async createClaudeMd() {
const claudeMdContent = (0, claude_md_1.getClaudeMdContent)();
const claudeMdFile = (0, path_1.join)(this.projectRoot, 'CLAUDE.md');
// Check if CLAUDE.md exists
try {
const existingContent = await fs_1.promises.readFile(claudeMdFile, 'utf-8');
if (!existingContent.includes('# Spec Workflow')) {
// Append to existing file - preserve all existing content
const separator = existingContent.trim().length > 0 ? '\n\n---\n\n' : '';
const updatedContent = existingContent.trim() + separator + claudeMdContent;
await fs_1.promises.writeFile(claudeMdFile, updatedContent, 'utf-8');
}
else {
// Replace existing spec workflow section while preserving everything else
const lines = existingContent.split('\n');
const startIndex = lines.findIndex(line => line.trim() === '# Spec Workflow');
if (startIndex !== -1) {
// Find the end of the spec workflow section (next # header or end of file)
let endIndex = lines.length;
for (let i = startIndex + 1; i < lines.length; i++) {
if (lines[i].startsWith('# ') && !lines[i].includes('Spec Workflow')) {
endIndex = i;
break;
}
}
// Preserve content before and after the spec workflow section
const beforeSection = lines.slice(0, startIndex).join('\n').trim();
const afterSection = lines.slice(endIndex).join('\n').trim();
// Reconstruct the file with preserved content
let updatedContent = '';
if (beforeSection) {
updatedContent += beforeSection + '\n\n';
}
updatedContent += claudeMdContent;
if (afterSection) {
updatedContent += '\n\n' + afterSection;
}
await fs_1.promises.writeFile(claudeMdFile, updatedContent, 'utf-8');
}
}
}
catch {
// File doesn't exist, create it
await fs_1.promises.writeFile(claudeMdFile, claudeMdContent, 'utf-8');
}
}
async runSetup() {
await this.setupDirectories();
await this.createSlashCommands();
await this.createTemplates();
await this.createScripts();
await this.createConfigFile();
await this.createClaudeMd();
}
}
exports.SpecWorkflowSetup = SpecWorkflowSetup;
//# sourceMappingURL=setup.js.map