From 7a390a3a0854507e8d748c22122b76200f3a954a Mon Sep 17 00:00:00 2001 From: Alex Verkhovsky Date: Fri, 26 Dec 2025 02:10:28 -0800 Subject: [PATCH] refactor: convert claude-code and cline to flat structure with shared methods --- tools/cli/installers/lib/ide/claude-code.js | 59 ++++++------------ tools/cli/installers/lib/ide/cline.js | 66 +++------------------ 2 files changed, 25 insertions(+), 100 deletions(-) diff --git a/tools/cli/installers/lib/ide/claude-code.js b/tools/cli/installers/lib/ide/claude-code.js index 35e70b0b..4c2cb6d0 100644 --- a/tools/cli/installers/lib/ide/claude-code.js +++ b/tools/cli/installers/lib/ide/claude-code.js @@ -84,14 +84,15 @@ class ClaudeCodeSetup extends BaseIdeSetup { /** * Cleanup old BMAD installation before reinstalling + * Removes files and directories starting with 'bmad-' or named 'bmad' from commands dir * @param {string} projectDir - Project directory */ async cleanup(projectDir) { - const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad'); + const commandsDir = path.join(projectDir, this.configDir, this.commandsDir); + const removedCount = await this.clearBmadPrefixedFiles(commandsDir); - if (await fs.pathExists(bmadCommandsDir)) { - await fs.remove(bmadCommandsDir); - console.log(chalk.dim(` Removed old BMAD commands from ${this.name}`)); + if (removedCount > 0) { + console.log(chalk.dim(` Removed ${removedCount} old BMAD items from ${this.name}`)); } } @@ -113,28 +114,15 @@ class ClaudeCodeSetup extends BaseIdeSetup { // Create .claude/commands directory structure const claudeDir = path.join(projectDir, this.configDir); const commandsDir = path.join(claudeDir, this.commandsDir); - const bmadCommandsDir = path.join(commandsDir, 'bmad'); - await this.ensureDir(bmadCommandsDir); + await this.ensureDir(commandsDir); // Generate agent launchers using AgentCommandGenerator - // This creates small launcher files that reference the actual agents in _bmad/ const agentGen = new AgentCommandGenerator(this.bmadFolderName); - const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); - // Create directories for each module - const modules = new Set(); - for (const artifact of agentArtifacts) { - modules.add(artifact.module); - } - - for (const module of modules) { - await this.ensureDir(path.join(bmadCommandsDir, module)); - await this.ensureDir(path.join(bmadCommandsDir, module, 'agents')); - } - - // Write agent launcher files - const agentCount = await agentGen.writeAgentLaunchers(bmadCommandsDir, agentArtifacts); + // Write agent launcher files with flattened naming directly to commands dir + const agentCount = await this.writeFlattenedArtifacts(agentArtifacts, commandsDir); // Process Claude Code specific injections for installed modules // Use pre-collected configuration if available, or skip if already configured @@ -148,25 +136,13 @@ class ClaudeCodeSetup extends BaseIdeSetup { await this.processModuleInjections(projectDir, bmadDir, options); } - // Skip CLAUDE.md creation - let user manage their own CLAUDE.md file - // await this.createClaudeConfig(projectDir, modules); - // Generate workflow commands from manifest (if it exists) const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName); const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir); - // Write only workflow-command artifacts, skip workflow-launcher READMEs - let workflowCommandCount = 0; - for (const artifact of workflowArtifacts) { - if (artifact.type === 'workflow-command') { - const moduleWorkflowsDir = path.join(bmadCommandsDir, artifact.module, 'workflows'); - await this.ensureDir(moduleWorkflowsDir); - const commandPath = path.join(moduleWorkflowsDir, path.basename(artifact.relativePath)); - await this.writeFile(commandPath, artifact.content); - workflowCommandCount++; - } - // Skip workflow-launcher READMEs as they would be treated as slash commands - } + // Filter to workflow-command type and write with flattened naming + const workflowCommands = workflowArtifacts.filter((a) => a.type === 'workflow-command'); + const workflowCommandCount = await this.writeFlattenedArtifacts(workflowCommands, commandsDir); // Generate task and tool commands from manifests (if they exist) const taskToolGen = new TaskToolCommandGenerator(); @@ -184,7 +160,7 @@ class ClaudeCodeSetup extends BaseIdeSetup { ), ); } - console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, bmadCommandsDir)}`)); + console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`)); return { success: true, @@ -471,13 +447,13 @@ class ClaudeCodeSetup extends BaseIdeSetup { * @returns {Object|null} Info about created command */ async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { - const customAgentsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad', 'custom', 'agents'); + const commandsDir = path.join(projectDir, this.configDir, this.commandsDir); if (!(await this.exists(path.join(projectDir, this.configDir)))) { return null; // IDE not configured for this project } - await this.ensureDir(customAgentsDir); + await this.ensureDir(commandsDir); const launcherContent = `--- name: '${agentName}' @@ -496,12 +472,13 @@ You must fully embody this agent's persona and follow all activation instruction `; - const launcherPath = path.join(customAgentsDir, `${agentName}.md`); + const fileName = `bmad-custom-agents-${agentName}.md`; + const launcherPath = path.join(commandsDir, fileName); await this.writeFile(launcherPath, launcherContent); return { path: launcherPath, - command: `/bmad:custom:agents:${agentName}`, + command: `/bmad-custom-agents-${agentName}`, }; } } diff --git a/tools/cli/installers/lib/ide/cline.js b/tools/cli/installers/lib/ide/cline.js index 9a9ceba6..e0a7916a 100644 --- a/tools/cli/installers/lib/ide/cline.js +++ b/tools/cli/installers/lib/ide/cline.js @@ -33,13 +33,13 @@ class ClineSetup extends BaseIdeSetup { await this.ensureDir(workflowsDir); // Clear old BMAD files - await this.clearOldBmadFiles(workflowsDir); + await this.clearBmadPrefixedFiles(workflowsDir); // Collect all artifacts const { artifacts, counts } = await this.collectClineArtifacts(projectDir, bmadDir, options); // Write flattened files - const written = await this.flattenAndWriteArtifacts(artifacts, workflowsDir); + const written = await this.writeFlattenedArtifacts(artifacts, workflowsDir); console.log(chalk.green(`✓ ${this.name} configured:`)); console.log(chalk.dim(` - ${counts.agents} agents installed`)); @@ -143,54 +143,7 @@ class ClineSetup extends BaseIdeSetup { }; } - /** - * Flatten file path to bmad-module-type-name.md format - */ - flattenFilename(relativePath) { - const sanitized = relativePath.replaceAll(/[\\/]/g, '-'); - return `bmad-${sanitized}`; - } - - /** - * Write all artifacts with flattened names - */ - async flattenAndWriteArtifacts(artifacts, destDir) { - let written = 0; - - for (const artifact of artifacts) { - const flattenedName = this.flattenFilename(artifact.relativePath); - const targetPath = path.join(destDir, flattenedName); - await fs.writeFile(targetPath, artifact.content); - written++; - } - - return written; - } - - /** - * Clear old BMAD files from the workflows directory - */ - async clearOldBmadFiles(destDir) { - if (!(await fs.pathExists(destDir))) { - return; - } - - const entries = await fs.readdir(destDir); - - for (const entry of entries) { - if (!entry.startsWith('bmad-')) { - continue; - } - - const entryPath = path.join(destDir, entry); - const stat = await fs.stat(entryPath); - if (stat.isFile()) { - await fs.remove(entryPath); - } else if (stat.isDirectory()) { - await fs.remove(entryPath); - } - } - } + // Uses inherited flattenFilename(), writeFlattenedArtifacts(), and clearBmadPrefixedFiles() from BaseIdeSetup /** * Read and process file with project-specific paths @@ -205,8 +158,10 @@ class ClineSetup extends BaseIdeSetup { */ async cleanup(projectDir) { const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir); - await this.clearOldBmadFiles(workflowsDir); - console.log(chalk.dim(`Removed ${this.name} BMAD configuration`)); + const removedCount = await this.clearBmadPrefixedFiles(workflowsDir); + if (removedCount > 0) { + console.log(chalk.dim(` Removed ${removedCount} old BMAD items from ${this.name}`)); + } } /** @@ -257,13 +212,6 @@ The agent will follow the persona and instructions from the main agent file. type: 'custom-agent-launcher', }; } - - /** - * Utility: Ensure directory exists - */ - async ensureDir(dirPath) { - await fs.ensureDir(dirPath); - } } module.exports = { ClineSetup };