diff --git a/tools/cli/installers/lib/ide/_base-ide.js b/tools/cli/installers/lib/ide/_base-ide.js index b53eb977..5e295f98 100644 --- a/tools/cli/installers/lib/ide/_base-ide.js +++ b/tools/cli/installers/lib/ide/_base-ide.js @@ -629,6 +629,58 @@ class BaseIdeSetup { return `bmad-${sanitized}`; } + /** + * Clear old BMAD files from a directory (prefix-based cleanup) + * Removes files and directories starting with 'bmad-' or named 'bmad' + * Used by IDEs with flat slash command structure (Antigravity, Codex) + * @param {string} dir - Directory to clean + * @returns {number} Number of items removed + */ + async clearBmadPrefixedFiles(dir) { + if (!(await fs.pathExists(dir))) { + return 0; + } + + const entries = await fs.readdir(dir); + let removedCount = 0; + + for (const entry of entries) { + if (!entry.startsWith('bmad-') && entry !== 'bmad') { + continue; + } + + const entryPath = path.join(dir, entry); + const stat = await fs.stat(entryPath); + if (stat.isFile() || stat.isDirectory()) { + await fs.remove(entryPath); + removedCount++; + } + } + + return removedCount; + } + + /** + * Write artifacts with flattened naming to a directory + * Used by IDEs with flat slash command structure (Antigravity, Codex) + * @param {Array} artifacts - Array of artifact objects with relativePath and content + * @param {string} destDir - Destination directory + * @returns {number} Number of files written + */ + async writeFlattenedArtifacts(artifacts, destDir) { + await this.ensureDir(destDir); + let written = 0; + + for (const artifact of artifacts) { + const flattenedName = this.flattenFilename(artifact.relativePath); + const targetPath = path.join(destDir, flattenedName); + await this.writeFile(targetPath, artifact.content); + written++; + } + + return written; + } + /** * Create agent configuration file * @param {string} bmadDir - BMAD installation directory diff --git a/tools/cli/installers/lib/ide/antigravity.js b/tools/cli/installers/lib/ide/antigravity.js index a7fed6ea..311fc209 100644 --- a/tools/cli/installers/lib/ide/antigravity.js +++ b/tools/cli/installers/lib/ide/antigravity.js @@ -85,14 +85,15 @@ class AntigravitySetup extends BaseIdeSetup { /** * Cleanup old BMAD installation before reinstalling + * Removes files and directories starting with 'bmad-' from workflows dir * @param {string} projectDir - Project directory */ async cleanup(projectDir) { - const bmadWorkflowsDir = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad'); + const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir); + const removedCount = await this.clearBmadPrefixedFiles(workflowsDir); - if (await fs.pathExists(bmadWorkflowsDir)) { - await fs.remove(bmadWorkflowsDir); - console.log(chalk.dim(` Removed old BMAD workflows from ${this.name}`)); + if (removedCount > 0) { + console.log(chalk.dim(` Removed ${removedCount} old BMAD items from ${this.name}`)); } } @@ -114,25 +115,16 @@ class AntigravitySetup extends BaseIdeSetup { // Create .agent/workflows directory structure const agentDir = path.join(projectDir, this.configDir); const workflowsDir = path.join(agentDir, this.workflowsDir); - const bmadWorkflowsDir = path.join(workflowsDir, 'bmad'); - await this.ensureDir(bmadWorkflowsDir); + await this.ensureDir(workflowsDir); // 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 || []); - // Write agent launcher files with FLATTENED naming - // Antigravity ignores directory structure, so we flatten to: bmad-module-agents-name.md - // This creates slash commands like /bmad-bmm-agents-dev instead of /dev - let agentCount = 0; - for (const artifact of agentArtifacts) { - const flattenedName = this.flattenFilename(artifact.relativePath); - const targetPath = path.join(bmadWorkflowsDir, flattenedName); - await this.writeFile(targetPath, artifact.content); - agentCount++; - } + // Write agent launcher files with flattened naming directly to workflows dir + const agentCount = await this.writeFlattenedArtifacts(agentArtifacts, workflowsDir); // Process Antigravity specific injections for installed modules // Use pre-collected configuration if available, or skip if already configured @@ -150,16 +142,9 @@ class AntigravitySetup extends BaseIdeSetup { const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName); const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir); - // Write workflow-command artifacts with FLATTENED naming - let workflowCommandCount = 0; - for (const artifact of workflowArtifacts) { - if (artifact.type === 'workflow-command') { - const flattenedName = this.flattenFilename(artifact.relativePath); - const targetPath = path.join(bmadWorkflowsDir, flattenedName); - await this.writeFile(targetPath, artifact.content); - workflowCommandCount++; - } - } + // 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, workflowsDir); // Generate task and tool commands from manifests (if they exist) const taskToolGen = new TaskToolCommandGenerator(); @@ -177,7 +162,7 @@ class AntigravitySetup extends BaseIdeSetup { ), ); } - console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, bmadWorkflowsDir)}`)); + console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, workflowsDir)}`)); console.log(chalk.yellow(`\n Note: Antigravity uses flattened slash commands (e.g., /bmad-module-agents-name)`)); return { @@ -463,12 +448,11 @@ class AntigravitySetup extends BaseIdeSetup { * @returns {Object} Installation result */ async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { - // Create .agent/workflows/bmad directory structure (same as regular agents) + // Create .agent/workflows directory structure (flat, no bmad/ subdirectory) const agentDir = path.join(projectDir, this.configDir); const workflowsDir = path.join(agentDir, this.workflowsDir); - const bmadWorkflowsDir = path.join(workflowsDir, 'bmad'); - await fs.ensureDir(bmadWorkflowsDir); + await fs.ensureDir(workflowsDir); // Create custom agent launcher with same pattern as regular agents const launcherContent = `name: '${agentName}' @@ -490,7 +474,7 @@ usage: | ⚠️ **IMPORTANT**: Run @${agentPath} to load the complete agent before using this launcher!`; const fileName = `bmad-custom-agents-${agentName}.md`; - const launcherPath = path.join(bmadWorkflowsDir, fileName); + const launcherPath = path.join(workflowsDir, fileName); // Write the launcher file await fs.writeFile(launcherPath, launcherContent, 'utf8'); diff --git a/tools/cli/installers/lib/ide/codex.js b/tools/cli/installers/lib/ide/codex.js index 9967057a..d20ee6e6 100644 --- a/tools/cli/installers/lib/ide/codex.js +++ b/tools/cli/installers/lib/ide/codex.js @@ -95,8 +95,8 @@ class CodexSetup extends BaseIdeSetup { const destDir = this.getCodexPromptDir(projectDir, installLocation); await fs.ensureDir(destDir); - await this.clearOldBmadFiles(destDir); - const written = await this.flattenAndWriteArtifacts(artifacts, destDir); + await this.clearBmadPrefixedFiles(destDir); + const written = await this.writeFlattenedArtifacts(artifacts, destDir); console.log(chalk.green(`✓ ${this.name} configured:`)); console.log(chalk.dim(` - Mode: CLI`)); @@ -212,40 +212,7 @@ class CodexSetup extends BaseIdeSetup { return path.join(os.homedir(), '.codex', 'prompts'); } - 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; - } - - 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 writeFlattenedArtifacts() and clearBmadPrefixedFiles() from BaseIdeSetup async readAndProcessWithProject(filePath, metadata, projectDir) { const content = await fs.readFile(filePath, 'utf8'); @@ -337,11 +304,11 @@ class CodexSetup extends BaseIdeSetup { async cleanup(projectDir = null) { // Clean both global and project-specific locations const globalDir = this.getCodexPromptDir(null, 'global'); - await this.clearOldBmadFiles(globalDir); + await this.clearBmadPrefixedFiles(globalDir); if (projectDir) { const projectSpecificDir = this.getCodexPromptDir(projectDir, 'project'); - await this.clearOldBmadFiles(projectSpecificDir); + await this.clearBmadPrefixedFiles(projectSpecificDir); } }