From 4cb0669a9cbe312ea5d0032b4a56e08d7ef4212e Mon Sep 17 00:00:00 2001 From: Alex Verkhovsky Date: Fri, 26 Dec 2025 02:29:22 -0800 Subject: [PATCH] refactor: convert cursor, windsurf, crush, and github-copilot to flat structure --- tools/cli/installers/lib/ide/crush.js | 275 +++++----- tools/cli/installers/lib/ide/cursor.js | 505 ++++++++---------- .../cli/installers/lib/ide/github-copilot.js | 228 ++++---- tools/cli/installers/lib/ide/windsurf.js | 314 +++++------ 4 files changed, 628 insertions(+), 694 deletions(-) diff --git a/tools/cli/installers/lib/ide/crush.js b/tools/cli/installers/lib/ide/crush.js index 0bef6952..4db7bf5c 100644 --- a/tools/cli/installers/lib/ide/crush.js +++ b/tools/cli/installers/lib/ide/crush.js @@ -4,10 +4,11 @@ const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); +const { getTasksFromBmad, getToolsFromBmad } = require('./shared/bmad-artifacts'); /** * Crush IDE setup handler - * Creates commands in .crush/commands/ directory structure + * Installs BMAD artifacts to .crush/commands with flattened naming */ class CrushSetup extends BaseIdeSetup { constructor() { @@ -25,123 +26,131 @@ class CrushSetup extends BaseIdeSetup { async setup(projectDir, bmadDir, options = {}) { console.log(chalk.cyan(`Setting up ${this.name}...`)); - // Create .crush/commands/bmad directory structure + // Create .crush/commands directory const crushDir = path.join(projectDir, this.configDir); - const commandsDir = path.join(crushDir, this.commandsDir, 'bmad'); + const commandsDir = path.join(crushDir, this.commandsDir); await this.ensureDir(commandsDir); - // Generate agent launchers - const agentGen = new AgentCommandGenerator(this.bmadFolderName); - const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + // Clear old BMAD files + await this.clearBmadPrefixedFiles(commandsDir); - // Get tasks, tools, and workflows (ALL workflows now generate commands) - const tasks = await this.getTasks(bmadDir, true); - const tools = await this.getTools(bmadDir, true); + // Collect all artifacts + const { artifacts, counts } = await this.collectCrushArtifacts(projectDir, bmadDir, options); - // Get ALL workflows using the new workflow command generator - const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName); - const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir); - - // Convert workflow artifacts to expected format for organizeByModule - const workflows = workflowArtifacts - .filter((artifact) => artifact.type === 'workflow-command') - .map((artifact) => ({ - module: artifact.module, - name: path.basename(artifact.relativePath, '.md'), - path: artifact.sourcePath, - content: artifact.content, - })); - - // Organize by module - const agentCount = await this.organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir); + // Write flattened files + const written = await this.writeFlattenedArtifacts(artifacts, commandsDir); console.log(chalk.green(`✓ ${this.name} configured:`)); - console.log(chalk.dim(` - ${agentCount.agents} agent commands created`)); - console.log(chalk.dim(` - ${agentCount.tasks} task commands created`)); - console.log(chalk.dim(` - ${agentCount.tools} tool commands created`)); - console.log(chalk.dim(` - ${agentCount.workflows} workflow commands created`)); - console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`)); - console.log(chalk.dim('\n Commands can be accessed via Crush command palette')); + console.log(chalk.dim(` - ${counts.agents} agent commands created`)); + console.log(chalk.dim(` - ${counts.tasks} task commands created`)); + console.log(chalk.dim(` - ${counts.tools} tool commands created`)); + console.log(chalk.dim(` - ${counts.workflows} workflow commands created`)); + if (counts.workflowLaunchers > 0) { + console.log(chalk.dim(` - ${counts.workflowLaunchers} workflow launchers created`)); + } + console.log(chalk.dim(` - ${written} files written to ${path.relative(projectDir, commandsDir)}`)); + + // Usage instructions + console.log(chalk.yellow('\n ⚠️ How to Use Crush Commands')); + console.log(chalk.cyan(' BMAD commands are available via the Crush command palette')); + console.log(chalk.dim(' Usage:')); + console.log(chalk.dim(' - All BMAD items start with "bmad-"')); + console.log(chalk.dim(' - Example: /bmad-bmm-agents-pm')); return { success: true, - ...agentCount, + agents: counts.agents, + tasks: counts.tasks, + tools: counts.tools, + workflows: counts.workflows, + workflowLaunchers: counts.workflowLaunchers, + written, }; } /** - * Organize commands by module + * Detect Crush installation by checking for .crush/commands directory */ - async organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir) { - // Get unique modules - const modules = new Set(); - for (const artifact of agentArtifacts) modules.add(artifact.module); - for (const task of tasks) modules.add(task.module); - for (const tool of tools) modules.add(tool.module); - for (const workflow of workflows) modules.add(workflow.module); + async detect(projectDir) { + const commandsDir = path.join(projectDir, this.configDir, this.commandsDir); - let agentCount = 0; - let taskCount = 0; - let toolCount = 0; - let workflowCount = 0; - - // Create module directories - for (const module of modules) { - const moduleDir = path.join(commandsDir, module); - const moduleAgentsDir = path.join(moduleDir, 'agents'); - const moduleTasksDir = path.join(moduleDir, 'tasks'); - const moduleToolsDir = path.join(moduleDir, 'tools'); - const moduleWorkflowsDir = path.join(moduleDir, 'workflows'); - - await this.ensureDir(moduleAgentsDir); - await this.ensureDir(moduleTasksDir); - await this.ensureDir(moduleToolsDir); - await this.ensureDir(moduleWorkflowsDir); - - // Write module-specific agent launchers - const moduleAgents = agentArtifacts.filter((a) => a.module === module); - for (const artifact of moduleAgents) { - const targetPath = path.join(moduleAgentsDir, `${artifact.name}.md`); - await this.writeFile(targetPath, artifact.content); - agentCount++; - } - - // Copy module-specific tasks - const moduleTasks = tasks.filter((t) => t.module === module); - for (const task of moduleTasks) { - const content = await this.readFile(task.path); - const commandContent = this.createTaskCommand(task, content); - const targetPath = path.join(moduleTasksDir, `${task.name}.md`); - await this.writeFile(targetPath, commandContent); - taskCount++; - } - - // Copy module-specific tools - const moduleTools = tools.filter((t) => t.module === module); - for (const tool of moduleTools) { - const content = await this.readFile(tool.path); - const commandContent = this.createToolCommand(tool, content); - const targetPath = path.join(moduleToolsDir, `${tool.name}.md`); - await this.writeFile(targetPath, commandContent); - toolCount++; - } - - // Copy module-specific workflow commands (already generated) - const moduleWorkflows = workflows.filter((w) => w.module === module); - for (const workflow of moduleWorkflows) { - // Use the pre-generated workflow command content - const targetPath = path.join(moduleWorkflowsDir, `${workflow.name}.md`); - await this.writeFile(targetPath, workflow.content); - workflowCount++; - } + if (!(await fs.pathExists(commandsDir))) { + return false; } + const entries = await fs.readdir(commandsDir); + return entries.some((entry) => entry.startsWith('bmad-')); + } + + /** + * Collect all artifacts for Crush export + */ + async collectCrushArtifacts(projectDir, bmadDir, options = {}) { + const selectedModules = options.selectedModules || []; + const artifacts = []; + + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules); + + // Process agent launchers + for (const agentArtifact of agentArtifacts) { + artifacts.push({ + type: 'agent', + module: agentArtifact.module, + sourcePath: agentArtifact.sourcePath, + relativePath: agentArtifact.relativePath, + content: agentArtifact.content, + }); + } + + // Get tasks + const tasks = await getTasksFromBmad(bmadDir, selectedModules); + for (const task of tasks) { + const rawContent = await this.readFile(task.path); + const content = this.createTaskCommand(task, rawContent); + + artifacts.push({ + type: 'task', + module: task.module, + sourcePath: task.path, + relativePath: path.join(task.module, 'tasks', `${task.name}.md`), + content, + }); + } + + // Get tools + const tools = await getToolsFromBmad(bmadDir, selectedModules); + for (const tool of tools) { + const rawContent = await this.readFile(tool.path); + const content = this.createToolCommand(tool, rawContent); + + artifacts.push({ + type: 'tool', + module: tool.module, + sourcePath: tool.path, + relativePath: path.join(tool.module, 'tools', `${tool.name}.md`), + content, + }); + } + + // Get workflows + const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName); + const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir); + + // Add workflow artifacts + artifacts.push(...workflowArtifacts); + return { - agents: agentCount, - tasks: taskCount, - tools: toolCount, - workflows: workflowCount, + artifacts, + counts: { + agents: agentArtifacts.length, + tasks: tasks.length, + tools: tools.length, + workflows: workflowCounts.commands, + workflowLaunchers: workflowCounts.launchers, + }, }; } @@ -153,7 +162,7 @@ class CrushSetup extends BaseIdeSetup { const nameMatch = content.match(/name="([^"]+)"/); const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name); - let commandContent = `# /task-${task.name} Command + return `# /task-${task.name} Command When this command is used, execute the following task: @@ -169,8 +178,6 @@ This command executes the ${taskName} task from the BMAD ${task.module.toUpperCa Part of the BMAD ${task.module.toUpperCase()} module. `; - - return commandContent; } /** @@ -181,7 +188,7 @@ Part of the BMAD ${task.module.toUpperCase()} module. const nameMatch = content.match(/name="([^"]+)"/); const toolName = nameMatch ? nameMatch[1] : this.formatTitle(tool.name); - let commandContent = `# /tool-${tool.name} Command + return `# /tool-${tool.name} Command When this command is used, execute the following tool: @@ -197,56 +204,18 @@ This command executes the ${toolName} tool from the BMAD ${tool.module.toUpperCa Part of the BMAD ${tool.module.toUpperCase()} module. `; - - return commandContent; } - /** - * Create workflow command content - */ - createWorkflowCommand(workflow, content) { - const workflowName = workflow.name ? this.formatTitle(workflow.name) : 'Workflow'; - - let commandContent = `# /${workflow.name} Command - -When this command is used, execute the following workflow: - -## ${workflowName} Workflow - -${content} - -## Command Usage - -This command executes the ${workflowName} workflow from the BMAD ${workflow.module.toUpperCase()} module. - -## Module - -Part of the BMAD ${workflow.module.toUpperCase()} module. -`; - - return commandContent; - } - - /** - * Format name as title - */ - formatTitle(name) { - return name - .split('-') - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' '); - } + // Uses inherited flattenFilename(), writeFlattenedArtifacts(), and clearBmadPrefixedFiles() from BaseIdeSetup /** * Cleanup Crush configuration */ async cleanup(projectDir) { - const fs = require('fs-extra'); - const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad'); - - if (await fs.pathExists(bmadCommandsDir)) { - await fs.remove(bmadCommandsDir); - console.log(chalk.dim(`Removed BMAD commands from Crush`)); + const commandsDir = path.join(projectDir, this.configDir, this.commandsDir); + const removedCount = await this.clearBmadPrefixedFiles(commandsDir); + if (removedCount > 0) { + console.log(chalk.dim(` Removed ${removedCount} old BMAD items from ${this.name}`)); } } @@ -259,11 +228,13 @@ Part of the BMAD ${workflow.module.toUpperCase()} module. * @returns {Object} Installation result */ async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { - const crushDir = path.join(projectDir, this.configDir); - const bmadCommandsDir = path.join(crushDir, this.commandsDir, 'bmad'); + const commandsDir = path.join(projectDir, this.configDir, this.commandsDir); - // Create .crush/commands/bmad directory if it doesn't exist - await fs.ensureDir(bmadCommandsDir); + if (!(await this.exists(path.join(projectDir, this.configDir)))) { + return null; // IDE not configured for this project + } + + await this.ensureDir(commandsDir); // Create custom agent launcher const launcherContent = `# ${agentName} Custom Agent @@ -282,8 +253,8 @@ The agent will follow the persona and instructions from the main agent file. *Generated by BMAD Method*`; - const fileName = `custom-${agentName.toLowerCase()}.md`; - const launcherPath = path.join(bmadCommandsDir, fileName); + const fileName = `bmad-custom-agents-${agentName.toLowerCase()}.md`; + const launcherPath = path.join(commandsDir, fileName); // Write the launcher file await fs.writeFile(launcherPath, launcherContent, 'utf8'); @@ -291,7 +262,7 @@ The agent will follow the persona and instructions from the main agent file. return { ide: 'crush', path: path.relative(projectDir, launcherPath), - command: agentName, + command: `bmad-custom-agents-${agentName}`, type: 'custom-agent-launcher', }; } diff --git a/tools/cli/installers/lib/ide/cursor.js b/tools/cli/installers/lib/ide/cursor.js index 183bbced..dd50051a 100644 --- a/tools/cli/installers/lib/ide/cursor.js +++ b/tools/cli/installers/lib/ide/cursor.js @@ -1,11 +1,14 @@ const path = require('node:path'); +const fs = require('fs-extra'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); +const { getTasksFromBmad, getToolsFromBmad } = require('./shared/bmad-artifacts'); /** * Cursor IDE setup handler + * Installs BMAD artifacts to .cursor/rules with flattened naming and MDC format */ class CursorSetup extends BaseIdeSetup { constructor() { @@ -14,20 +17,6 @@ class CursorSetup extends BaseIdeSetup { this.rulesDir = 'rules'; } - /** - * Cleanup old BMAD installation before reinstalling - * @param {string} projectDir - Project directory - */ - async cleanup(projectDir) { - const fs = require('fs-extra'); - const bmadRulesDir = path.join(projectDir, this.configDir, this.rulesDir, 'bmad'); - - if (await fs.pathExists(bmadRulesDir)) { - await fs.remove(bmadRulesDir); - console.log(chalk.dim(` Removed old BMAD rules from ${this.name}`)); - } - } - /** * Setup Cursor IDE configuration * @param {string} projectDir - Project directory @@ -37,138 +26,216 @@ class CursorSetup extends BaseIdeSetup { async setup(projectDir, bmadDir, options = {}) { console.log(chalk.cyan(`Setting up ${this.name}...`)); - // Clean up old BMAD installation first - await this.cleanup(projectDir); - - // Create .cursor/rules directory structure + // Create .cursor/rules directory const cursorDir = path.join(projectDir, this.configDir); const rulesDir = path.join(cursorDir, this.rulesDir); - const bmadRulesDir = path.join(rulesDir, 'bmad'); - await this.ensureDir(bmadRulesDir); + await this.ensureDir(rulesDir); - // Generate agent launchers first - const agentGen = new AgentCommandGenerator(this.bmadFolderName); - const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + // Clear old BMAD files + await this.clearBmadPrefixedFiles(rulesDir); - // Convert artifacts to agent format for index creation - const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name })); + // Collect all artifacts + const { artifacts, counts } = await this.collectCursorArtifacts(projectDir, bmadDir, options); - // Get tasks, tools, and workflows (ALL workflows now generate commands) - const tasks = await this.getTasks(bmadDir, true); - const tools = await this.getTools(bmadDir, true); + // Write flattened files + const written = await this.writeFlattenedArtifacts(artifacts, rulesDir); - // Get ALL workflows using the new workflow command generator - const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName); - const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir); - - // Convert artifacts to workflow objects for directory creation - const workflows = workflowArtifacts - .filter((artifact) => artifact.type === 'workflow-command') - .map((artifact) => ({ - module: artifact.module, - name: path.basename(artifact.relativePath, '.md'), - path: artifact.sourcePath, - })); - - // Create directories for each module - const modules = new Set(); - for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module); - - for (const module of modules) { - await this.ensureDir(path.join(bmadRulesDir, module)); - await this.ensureDir(path.join(bmadRulesDir, module, 'agents')); - await this.ensureDir(path.join(bmadRulesDir, module, 'tasks')); - await this.ensureDir(path.join(bmadRulesDir, module, 'tools')); - await this.ensureDir(path.join(bmadRulesDir, module, 'workflows')); - } - - // Process and write agent launchers with MDC format - let agentCount = 0; - for (const artifact of agentArtifacts) { - // Add MDC metadata header to launcher (but don't call processContent which adds activation headers) - const content = this.wrapLauncherWithMDC(artifact.content, { - module: artifact.module, - name: artifact.name, - }); - - const targetPath = path.join(bmadRulesDir, artifact.module, 'agents', `${artifact.name}.mdc`); - - await this.writeFile(targetPath, content); - agentCount++; - } - - // Process and copy tasks - let taskCount = 0; - for (const task of tasks) { - const content = await this.readAndProcess(task.path, { - module: task.module, - name: task.name, - }); - - const targetPath = path.join(bmadRulesDir, task.module, 'tasks', `${task.name}.mdc`); - - await this.writeFile(targetPath, content); - taskCount++; - } - - // Process and copy tools - let toolCount = 0; - for (const tool of tools) { - const content = await this.readAndProcess(tool.path, { - module: tool.module, - name: tool.name, - }); - - const targetPath = path.join(bmadRulesDir, tool.module, 'tools', `${tool.name}.mdc`); - - await this.writeFile(targetPath, content); - toolCount++; - } - - // Process and copy workflow commands (generated, not raw workflows) - let workflowCount = 0; - for (const artifact of workflowArtifacts) { - if (artifact.type === 'workflow-command') { - // Add MDC metadata header to workflow command - const content = this.wrapLauncherWithMDC(artifact.content, { - module: artifact.module, - name: path.basename(artifact.relativePath, '.md'), - }); - - const targetPath = path.join(bmadRulesDir, artifact.module, 'workflows', `${path.basename(artifact.relativePath, '.md')}.mdc`); - - await this.writeFile(targetPath, content); - workflowCount++; - } - } - - // Create BMAD index file (but NOT .cursorrules - user manages that) - await this.createBMADIndex(bmadRulesDir, agents, tasks, tools, workflows, modules); + // Create BMAD index file + await this.createBMADIndex(rulesDir, counts); console.log(chalk.green(`✓ ${this.name} configured:`)); - console.log(chalk.dim(` - ${agentCount} agents installed`)); - console.log(chalk.dim(` - ${taskCount} tasks installed`)); - console.log(chalk.dim(` - ${toolCount} tools installed`)); - console.log(chalk.dim(` - ${workflowCount} workflows installed`)); - console.log(chalk.dim(` - Rules directory: ${path.relative(projectDir, bmadRulesDir)}`)); + console.log(chalk.dim(` - ${counts.agents} agents installed`)); + console.log(chalk.dim(` - ${counts.tasks} tasks installed`)); + console.log(chalk.dim(` - ${counts.tools} tools installed`)); + console.log(chalk.dim(` - ${counts.workflows} workflow commands installed`)); + if (counts.workflowLaunchers > 0) { + console.log(chalk.dim(` - ${counts.workflowLaunchers} workflow launchers installed`)); + } + console.log(chalk.dim(` - ${written} files written to ${path.relative(projectDir, rulesDir)}`)); + + // Usage instructions + console.log(chalk.yellow('\n ⚠️ How to Use Cursor Rules')); + console.log(chalk.cyan(' BMAD rules are available as @ references in Cursor')); + console.log(chalk.dim(' Usage:')); + console.log(chalk.dim(' - Reference rules with @bmad-{module}-{type}-{name}')); + console.log(chalk.dim(' - All BMAD items start with "bmad-"')); + console.log(chalk.dim(' - Example: @bmad-bmm-agents-pm')); return { success: true, - agents: agentCount, - tasks: taskCount, - tools: toolCount, - workflows: workflowCount, + agents: counts.agents, + tasks: counts.tasks, + tools: counts.tools, + workflows: counts.workflows, + workflowLaunchers: counts.workflowLaunchers, + written, }; } + /** + * Detect Cursor installation by checking for .cursor/rules directory + */ + async detect(projectDir) { + const rulesDir = path.join(projectDir, this.configDir, this.rulesDir); + + if (!(await fs.pathExists(rulesDir))) { + return false; + } + + const entries = await fs.readdir(rulesDir); + return entries.some((entry) => entry.startsWith('bmad-')); + } + + /** + * Collect all artifacts for Cursor export + */ + async collectCursorArtifacts(projectDir, bmadDir, options = {}) { + const selectedModules = options.selectedModules || []; + const artifacts = []; + + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules); + + // Process agent launchers with Cursor MDC frontmatter + for (const agentArtifact of agentArtifacts) { + const content = this.addCursorMDCFrontmatter(agentArtifact.content, { + module: agentArtifact.module, + name: agentArtifact.name, + type: 'agent', + }); + + artifacts.push({ + type: 'agent', + module: agentArtifact.module, + sourcePath: agentArtifact.sourcePath, + relativePath: agentArtifact.relativePath.replace(/\.md$/, '.mdc'), + content, + }); + } + + // Get tasks + const tasks = await getTasksFromBmad(bmadDir, selectedModules); + for (const task of tasks) { + const rawContent = await this.readFile(task.path); + const content = this.addCursorMDCFrontmatter(rawContent, { + module: task.module, + name: task.name, + type: 'task', + }); + + artifacts.push({ + type: 'task', + module: task.module, + sourcePath: task.path, + relativePath: path.join(task.module, 'tasks', `${task.name}.mdc`), + content, + }); + } + + // Get tools + const tools = await getToolsFromBmad(bmadDir, selectedModules); + for (const tool of tools) { + const rawContent = await this.readFile(tool.path); + const content = this.addCursorMDCFrontmatter(rawContent, { + module: tool.module, + name: tool.name, + type: 'tool', + }); + + artifacts.push({ + type: 'tool', + module: tool.module, + sourcePath: tool.path, + relativePath: path.join(tool.module, 'tools', `${tool.name}.mdc`), + content, + }); + } + + // Get workflows + const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName); + const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir); + + // Process workflow artifacts with Cursor MDC frontmatter + for (const artifact of workflowArtifacts) { + const content = this.addCursorMDCFrontmatter(artifact.content, { + module: artifact.module, + name: artifact.name || path.basename(artifact.relativePath, '.md'), + type: 'workflow', + }); + + artifacts.push({ + ...artifact, + relativePath: artifact.relativePath.replace(/\.md$/, '.mdc'), + content, + }); + } + + return { + artifacts, + counts: { + agents: agentArtifacts.length, + tasks: tasks.length, + tools: tools.length, + workflows: workflowCounts.commands, + workflowLaunchers: workflowCounts.launchers, + }, + }; + } + + /** + * Add Cursor-specific MDC frontmatter + * @param {string} content - Original content + * @param {Object} metadata - Artifact metadata (module, name, type) + * @returns {string} Content with MDC frontmatter + */ + addCursorMDCFrontmatter(content, metadata) { + // Strip existing frontmatter + const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; + const contentWithoutFrontmatter = content.replace(frontmatterRegex, ''); + + // Build description based on type + let description; + switch (metadata.type) { + case 'agent': { + description = `BMAD ${metadata.module.toUpperCase()} Agent: ${this.formatTitle(metadata.name)}`; + break; + } + case 'task': { + description = `BMAD ${metadata.module.toUpperCase()} Task: ${this.formatTitle(metadata.name)}`; + break; + } + case 'tool': { + description = `BMAD ${metadata.module.toUpperCase()} Tool: ${this.formatTitle(metadata.name)}`; + break; + } + case 'workflow': { + description = `BMAD ${metadata.module.toUpperCase()} Workflow: ${this.formatTitle(metadata.name)}`; + break; + } + default: { + description = `BMAD ${metadata.module.toUpperCase()}: ${this.formatTitle(metadata.name)}`; + } + } + + // Create MDC frontmatter + return `--- +description: ${description} +globs: +alwaysApply: false +--- + +${contentWithoutFrontmatter}`; + } + /** * Create BMAD index file for easy navigation */ - async createBMADIndex(bmadRulesDir, agents, tasks, tools, workflows, modules) { - const indexPath = path.join(bmadRulesDir, 'index.mdc'); + async createBMADIndex(rulesDir, counts) { + const indexPath = path.join(rulesDir, 'bmad-index.mdc'); - let content = `--- + const content = `--- description: BMAD Method - Master Index globs: alwaysApply: true @@ -180,68 +247,25 @@ This is the master index for all BMAD agents, tasks, tools, and workflows availa ## Installation Complete! -BMAD rules have been installed to: \`.cursor/rules/bmad/\` +BMAD rules have been installed to: \`.cursor/rules/\` with flattened naming. **Note:** BMAD does not modify your \`.cursorrules\` file. You manage that separately. ## How to Use -- Reference specific agents: @bmad/{module}/agents/{agent-name} -- Reference specific tasks: @bmad/{module}/tasks/{task-name} -- Reference specific tools: @bmad/{module}/tools/{tool-name} -- Reference specific workflows: @bmad/{module}/workflows/{workflow-name} -- Reference entire modules: @bmad/{module} -- Reference this index: @bmad/index +- Reference specific rules: @bmad-{module}-{type}-{name} +- Examples: + - @bmad-bmm-agents-pm + - @bmad-core-tasks-workflow + - @bmad-bmm-workflows-create-prd -## Available Modules +## Statistics -`; +- **${counts.agents}** agents installed +- **${counts.tasks}** tasks installed +- **${counts.tools}** tools installed +- **${counts.workflows}** workflow commands installed - for (const module of modules) { - content += `### ${module.toUpperCase()}\n\n`; - - // List agents for this module - const moduleAgents = agents.filter((a) => a.module === module); - if (moduleAgents.length > 0) { - content += `**Agents:**\n`; - for (const agent of moduleAgents) { - content += `- @bmad/${module}/agents/${agent.name} - ${agent.name}\n`; - } - content += '\n'; - } - - // List tasks for this module - const moduleTasks = tasks.filter((t) => t.module === module); - if (moduleTasks.length > 0) { - content += `**Tasks:**\n`; - for (const task of moduleTasks) { - content += `- @bmad/${module}/tasks/${task.name} - ${task.name}\n`; - } - content += '\n'; - } - - // List tools for this module - const moduleTools = tools.filter((t) => t.module === module); - if (moduleTools.length > 0) { - content += `**Tools:**\n`; - for (const tool of moduleTools) { - content += `- @bmad/${module}/tools/${tool.name} - ${tool.name}\n`; - } - content += '\n'; - } - - // List workflows for this module - const moduleWorkflows = workflows.filter((w) => w.module === module); - if (moduleWorkflows.length > 0) { - content += `**Workflows:**\n`; - for (const workflow of moduleWorkflows) { - content += `- @bmad/${module}/workflows/${workflow.name} - ${workflow.name}\n`; - } - content += '\n'; - } - } - - content += ` ## Quick Reference - All BMAD rules are Manual type - reference them explicitly when needed @@ -261,107 +285,17 @@ specific agent expertise, task workflows, tools, or guided workflows. await this.writeFile(indexPath, content); } - /** - * Read and process file content - */ - async readAndProcess(filePath, metadata) { - const fs = require('fs-extra'); - const content = await fs.readFile(filePath, 'utf8'); - return this.processContent(content, metadata); - } + // Uses inherited flattenFilename(), writeFlattenedArtifacts(), and clearBmadPrefixedFiles() from BaseIdeSetup /** - * Override processContent to add MDC metadata header for Cursor - * @param {string} content - File content - * @param {Object} metadata - File metadata - * @returns {string} Processed content with MDC header + * Cleanup Cursor configuration */ - processContent(content, metadata = {}) { - // First apply base processing (includes activation injection for agents) - let processed = super.processContent(content, metadata); - - // Strip any existing frontmatter from the processed content - // This prevents duplicate frontmatter blocks - const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; - if (frontmatterRegex.test(processed)) { - processed = processed.replace(frontmatterRegex, ''); + async cleanup(projectDir) { + const rulesDir = path.join(projectDir, this.configDir, this.rulesDir); + const removedCount = await this.clearBmadPrefixedFiles(rulesDir); + if (removedCount > 0) { + console.log(chalk.dim(` Removed ${removedCount} old BMAD items from ${this.name}`)); } - - // Determine the type and description based on content - const isAgent = content.includes(' entry.startsWith('bmad-')); + } + + /** + * Collect all artifacts for GitHub Copilot export + */ + async collectCopilotArtifacts(projectDir, bmadDir, options = {}) { + const selectedModules = options.selectedModules || []; + const artifacts = []; + + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules); + + // Process agent launchers with GitHub Copilot frontmatter (tools array) + for (const agentArtifact of agentArtifacts) { + const content = this.addCopilotFrontmatter(agentArtifact.content, { + module: agentArtifact.module, + name: agentArtifact.name, + }); + + artifacts.push({ + type: 'agent', + module: agentArtifact.module, + sourcePath: agentArtifact.sourcePath, + relativePath: agentArtifact.relativePath.replace(/\.md$/, '.agent.md'), + content, + }); + } + + return { + artifacts, + counts: { + agents: agentArtifacts.length, + }, + }; + } + + /** + * Add GitHub Copilot-specific frontmatter with tools array + * @param {string} content - Original content + * @param {Object} metadata - Artifact metadata (module, name) + * @returns {string} Content with Copilot frontmatter + */ + addCopilotFrontmatter(content, metadata) { + // Strip existing frontmatter + const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; + const contentWithoutFrontmatter = content.replace(frontmatterRegex, '').trim(); + + const title = this.formatTitle(metadata.name); + const description = `Activates the ${title} agent persona.`; + + // Available GitHub Copilot tools (November 2025 - Official VS Code Documentation) + // Reference: https://code.visualstudio.com/docs/copilot/reference/copilot-vscode-features#_chat-tools + const tools = [ + 'changes', // List of source control changes + 'edit', // Edit files in your workspace including: createFile, createDirectory, editNotebook, newJupyterNotebook and editFiles + 'fetch', // Fetch content from web page + 'githubRepo', // Perform code search in GitHub repo + 'problems', // Add workspace issues from Problems panel + 'runCommands', // Runs commands in the terminal including: getTerminalOutput, terminalSelection, terminalLastCommand and runInTerminal + 'runTasks', // Runs tasks and gets their output for your workspace + 'runTests', // Run unit tests in workspace + 'search', // Search and read files in your workspace, including:fileSearch, textSearch, listDirectory, readFile, codebase and searchResults + 'runSubagent', // Runs a task within an isolated subagent context. Enables efficient organization of tasks and context window management. + 'testFailure', // Get unit test failure information + 'todos', // Tool for managing and tracking todo items for task planning + 'usages', // Find references and navigate definitions + ]; + + return `--- +description: "${description.replaceAll('"', String.raw`\"`)}" +tools: ${JSON.stringify(tools)} +--- + +# ${title} Agent + +${contentWithoutFrontmatter} + +`; + } + /** * Configure VS Code settings for GitHub Copilot */ async configureVsCodeSettings(projectDir, options) { - const fs = require('fs-extra'); const vscodeDir = path.join(projectDir, this.vscodeDir); const settingsPath = path.join(vscodeDir, 'settings.json'); @@ -207,72 +295,15 @@ class GitHubCopilotSetup extends BaseIdeSetup { console.log(chalk.green(' ✓ VS Code settings configured')); } - /** - * Create agent content - */ - async createAgentContent(agent, content) { - // Extract metadata from launcher frontmatter if present - const descMatch = content.match(/description:\s*"([^"]+)"/); - const title = descMatch ? descMatch[1] : this.formatTitle(agent.name); - - const description = `Activates the ${title} agent persona.`; - - // Strip any existing frontmatter from the content - const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; - let cleanContent = content; - if (frontmatterRegex.test(content)) { - cleanContent = content.replace(frontmatterRegex, '').trim(); - } - - // Available GitHub Copilot tools (November 2025 - Official VS Code Documentation) - // Reference: https://code.visualstudio.com/docs/copilot/reference/copilot-vscode-features#_chat-tools - const tools = [ - 'changes', // List of source control changes - 'edit', // Edit files in your workspace including: createFile, createDirectory, editNotebook, newJupyterNotebook and editFiles - 'fetch', // Fetch content from web page - 'githubRepo', // Perform code search in GitHub repo - 'problems', // Add workspace issues from Problems panel - 'runCommands', // Runs commands in the terminal including: getTerminalOutput, terminalSelection, terminalLastCommand and runInTerminal - 'runTasks', // Runs tasks and gets their output for your workspace - 'runTests', // Run unit tests in workspace - 'search', // Search and read files in your workspace, including:fileSearch, textSearch, listDirectory, readFile, codebase and searchResults - 'runSubagent', // Runs a task within an isolated subagent context. Enables efficient organization of tasks and context window management. - 'testFailure', // Get unit test failure information - 'todos', // Tool for managing and tracking todo items for task planning - 'usages', // Find references and navigate definitions - ]; - - let agentContent = `--- -description: "${description.replaceAll('"', String.raw`\"`)}" -tools: ${JSON.stringify(tools)} ---- - -# ${title} Agent - -${cleanContent} - -`; - - return agentContent; - } - - /** - * Format name as title - */ - formatTitle(name) { - return name - .split('-') - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' '); - } + // Uses inherited flattenFilename(), writeFlattenedArtifacts(), and clearBmadPrefixedFiles() from BaseIdeSetup /** * Cleanup GitHub Copilot configuration - surgically remove only BMAD files */ async cleanup(projectDir) { - const fs = require('fs-extra'); + const agentsDir = path.join(projectDir, this.configDir, this.agentsDir); - // Clean up old chatmodes directory + // Clean up old chatmodes directory (legacy) const chatmodesDir = path.join(projectDir, this.configDir, 'chatmodes'); if (await fs.pathExists(chatmodesDir)) { const files = await fs.readdir(chatmodesDir); @@ -291,21 +322,9 @@ ${cleanContent} } // Clean up new agents directory - const agentsDir = path.join(projectDir, this.configDir, this.agentsDir); - if (await fs.pathExists(agentsDir)) { - const files = await fs.readdir(agentsDir); - let removed = 0; - - for (const file of files) { - if (file.startsWith('bmd-') && file.endsWith('.agent.md')) { - await fs.remove(path.join(agentsDir, file)); - removed++; - } - } - - if (removed > 0) { - console.log(chalk.dim(` Cleaned up ${removed} existing BMAD agents`)); - } + const removedCount = await this.clearBmadPrefixedFiles(agentsDir); + if (removedCount > 0) { + console.log(chalk.dim(` Cleaned up ${removedCount} existing BMAD agents`)); } } @@ -374,12 +393,15 @@ tools: ${JSON.stringify(copilotTools)} ${launcherContent} `; - const agentFilePath = path.join(agentsDir, `bmd-custom-${agentName}.agent.md`); - await this.writeFile(agentFilePath, agentContent); + const fileName = `bmad-custom-agents-${agentName}.agent.md`; + const agentFilePath = path.join(agentsDir, fileName); + await fs.writeFile(agentFilePath, agentContent); return { - path: agentFilePath, - command: `bmd-custom-${agentName}`, + ide: 'github-copilot', + path: path.relative(projectDir, agentFilePath), + command: `@bmad-custom-agents-${agentName}`, + type: 'custom-agent-launcher', }; } } diff --git a/tools/cli/installers/lib/ide/windsurf.js b/tools/cli/installers/lib/ide/windsurf.js index 92596db3..76bff183 100644 --- a/tools/cli/installers/lib/ide/windsurf.js +++ b/tools/cli/installers/lib/ide/windsurf.js @@ -1,10 +1,14 @@ const path = require('node:path'); +const fs = require('fs-extra'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); const { AgentCommandGenerator } = require('./shared/agent-command-generator'); +const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); +const { getTasksFromBmad } = require('./shared/bmad-artifacts'); /** * Windsurf IDE setup handler + * Installs BMAD artifacts to .windsurf/workflows with flattened naming */ class WindsurfSetup extends BaseIdeSetup { constructor() { @@ -22,188 +26,186 @@ class WindsurfSetup extends BaseIdeSetup { async setup(projectDir, bmadDir, options = {}) { console.log(chalk.cyan(`Setting up ${this.name}...`)); - // Create .windsurf/workflows/bmad directory structure + // Create .windsurf/workflows directory const windsurfDir = path.join(projectDir, this.configDir); const workflowsDir = path.join(windsurfDir, this.workflowsDir); - const bmadWorkflowsDir = path.join(workflowsDir, 'bmad'); - await this.ensureDir(bmadWorkflowsDir); + await this.ensureDir(workflowsDir); - // Clean up any existing BMAD workflows before reinstalling - await this.cleanup(projectDir); + // Clear old BMAD files + await this.clearBmadPrefixedFiles(workflowsDir); - // Generate agent launchers - const agentGen = new AgentCommandGenerator(this.bmadFolderName); - const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + // Collect all artifacts + const { artifacts, counts } = await this.collectWindsurfArtifacts(projectDir, bmadDir, options); - // Convert artifacts to agent format for module organization - const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name })); - - // Get tasks, tools, and workflows (standalone only) - const tasks = await this.getTasks(bmadDir, true); - const tools = await this.getTools(bmadDir, true); - const workflows = await this.getWorkflows(bmadDir, true); - - // Create directories for each module under bmad/ - const modules = new Set(); - for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module); - - for (const module of modules) { - await this.ensureDir(path.join(bmadWorkflowsDir, module)); - await this.ensureDir(path.join(bmadWorkflowsDir, module, 'agents')); - await this.ensureDir(path.join(bmadWorkflowsDir, module, 'tasks')); - await this.ensureDir(path.join(bmadWorkflowsDir, module, 'tools')); - await this.ensureDir(path.join(bmadWorkflowsDir, module, 'workflows')); - } - - // Process agent launchers as workflows with organized structure - let agentCount = 0; - for (const artifact of agentArtifacts) { - const processedContent = this.createWorkflowContent({ module: artifact.module, name: artifact.name }, artifact.content); - - // Organized path: bmad/module/agents/agent-name.md - const targetPath = path.join(bmadWorkflowsDir, artifact.module, 'agents', `${artifact.name}.md`); - await this.writeFile(targetPath, processedContent); - agentCount++; - } - - // Process tasks as workflows with organized structure - let taskCount = 0; - for (const task of tasks) { - const content = await this.readFile(task.path); - const processedContent = this.createTaskWorkflowContent(task, content); - - // Organized path: bmad/module/tasks/task-name.md - const targetPath = path.join(bmadWorkflowsDir, task.module, 'tasks', `${task.name}.md`); - await this.writeFile(targetPath, processedContent); - taskCount++; - } - - // Process tools as workflows with organized structure - let toolCount = 0; - for (const tool of tools) { - const content = await this.readFile(tool.path); - const processedContent = this.createToolWorkflowContent(tool, content); - - // Organized path: bmad/module/tools/tool-name.md - const targetPath = path.join(bmadWorkflowsDir, tool.module, 'tools', `${tool.name}.md`); - await this.writeFile(targetPath, processedContent); - toolCount++; - } - - // Process workflows with organized structure - let workflowCount = 0; - for (const workflow of workflows) { - const content = await this.readFile(workflow.path); - const processedContent = this.createWorkflowWorkflowContent(workflow, content); - - // Organized path: bmad/module/workflows/workflow-name.md - const targetPath = path.join(bmadWorkflowsDir, workflow.module, 'workflows', `${workflow.name}.md`); - await this.writeFile(targetPath, processedContent); - workflowCount++; - } + // Write flattened files + const written = await this.writeFlattenedArtifacts(artifacts, workflowsDir); console.log(chalk.green(`✓ ${this.name} configured:`)); - console.log(chalk.dim(` - ${agentCount} agents installed`)); - console.log(chalk.dim(` - ${taskCount} tasks installed`)); - console.log(chalk.dim(` - ${toolCount} tools installed`)); - console.log(chalk.dim(` - ${workflowCount} workflows installed`)); - console.log(chalk.dim(` - Organized in modules: ${[...modules].join(', ')}`)); - console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, workflowsDir)}`)); + console.log(chalk.dim(` - ${counts.agents} agents installed`)); + console.log(chalk.dim(` - ${counts.tasks} tasks installed`)); + console.log(chalk.dim(` - ${counts.workflows} workflow commands installed`)); + if (counts.workflowLaunchers > 0) { + console.log(chalk.dim(` - ${counts.workflowLaunchers} workflow launchers installed`)); + } + console.log(chalk.dim(` - ${written} files written to ${path.relative(projectDir, workflowsDir)}`)); + + // Usage instructions + console.log(chalk.yellow('\n ⚠️ How to Use Windsurf Workflows')); + console.log(chalk.cyan(' BMAD workflows are available as slash commands in Windsurf')); + console.log(chalk.dim(' Usage:')); + console.log(chalk.dim(' - Type / to see available commands')); + console.log(chalk.dim(' - All BMAD items start with "bmad-"')); + console.log(chalk.dim(' - Example: /bmad-bmm-agents-pm')); // Provide additional configuration hints if (options.showHints !== false) { console.log(chalk.dim('\n Windsurf workflow settings:')); - console.log(chalk.dim(' - auto_execution_mode: 3 (recommended for agents)')); - console.log(chalk.dim(' - auto_execution_mode: 2 (recommended for tasks/tools)')); - console.log(chalk.dim(' - auto_execution_mode: 1 (recommended for workflows)')); - console.log(chalk.dim(' - Workflows can be triggered via the Windsurf menu')); + console.log(chalk.dim(' - auto_execution_mode: 3 (for agents)')); + console.log(chalk.dim(' - auto_execution_mode: 2 (for tasks)')); + console.log(chalk.dim(' - auto_execution_mode: 1 (for workflows)')); } return { success: true, - agents: agentCount, - tasks: taskCount, - tools: toolCount, - workflows: workflowCount, + agents: counts.agents, + tasks: counts.tasks, + workflows: counts.workflows, + workflowLaunchers: counts.workflowLaunchers, + written, }; } /** - * Create workflow content for an agent + * Detect Windsurf installation by checking for .windsurf/workflows directory */ - createWorkflowContent(agent, content) { - // Strip existing frontmatter from launcher + async detect(projectDir) { + const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir); + + if (!(await fs.pathExists(workflowsDir))) { + return false; + } + + const entries = await fs.readdir(workflowsDir); + return entries.some((entry) => entry.startsWith('bmad-')); + } + + /** + * Collect all artifacts for Windsurf export + */ + async collectWindsurfArtifacts(projectDir, bmadDir, options = {}) { + const selectedModules = options.selectedModules || []; + const artifacts = []; + + // Generate agent launchers + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules); + + // Process agent launchers with Windsurf frontmatter + for (const agentArtifact of agentArtifacts) { + const content = this.addWindsurfFrontmatter(agentArtifact.content, agentArtifact.name, 'agent'); + + artifacts.push({ + type: 'agent', + module: agentArtifact.module, + sourcePath: agentArtifact.sourcePath, + relativePath: agentArtifact.relativePath, + content, + }); + } + + // Get tasks + const tasks = await getTasksFromBmad(bmadDir, selectedModules); + for (const task of tasks) { + const rawContent = await this.readFile(task.path); + const content = this.addWindsurfFrontmatter(rawContent, `task-${task.name}`, 'task'); + + artifacts.push({ + type: 'task', + module: task.module, + sourcePath: task.path, + relativePath: path.join(task.module, 'tasks', `${task.name}.md`), + content, + }); + } + + // Get workflows + const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName); + const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir); + + // Process workflow artifacts with Windsurf frontmatter + for (const artifact of workflowArtifacts) { + const content = this.addWindsurfFrontmatter( + artifact.content, + artifact.name || path.basename(artifact.relativePath, '.md'), + 'workflow', + ); + + artifacts.push({ + ...artifact, + content, + }); + } + + return { + artifacts, + counts: { + agents: agentArtifacts.length, + tasks: tasks.length, + workflows: workflowCounts.commands, + workflowLaunchers: workflowCounts.launchers, + }, + }; + } + + /** + * Add Windsurf-specific frontmatter with auto_execution_mode + * @param {string} content - Original content + * @param {string} name - Artifact name for description + * @param {string} type - Artifact type (agent, task, workflow) + * @returns {string} Content with Windsurf frontmatter + */ + addWindsurfFrontmatter(content, name, type) { + // Strip existing frontmatter const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; const contentWithoutFrontmatter = content.replace(frontmatterRegex, ''); - // Create simple Windsurf frontmatter matching original format - let workflowContent = `--- -description: ${agent.name} -auto_execution_mode: 3 + // Determine auto_execution_mode based on type + let mode; + switch (type) { + case 'agent': { + mode = 3; + break; + } + case 'task': { + mode = 2; + break; + } + default: { + mode = 1; + break; + } + } + + // Create Windsurf frontmatter + return `--- +description: ${name} +auto_execution_mode: ${mode} --- ${contentWithoutFrontmatter}`; - - return workflowContent; } - /** - * Create workflow content for a task - */ - createTaskWorkflowContent(task, content) { - // Create simple Windsurf frontmatter matching original format - let workflowContent = `--- -description: task-${task.name} -auto_execution_mode: 2 ---- - -${content}`; - - return workflowContent; - } + // Uses inherited flattenFilename(), writeFlattenedArtifacts(), and clearBmadPrefixedFiles() from BaseIdeSetup /** - * Create workflow content for a tool - */ - createToolWorkflowContent(tool, content) { - // Create simple Windsurf frontmatter matching original format - let workflowContent = `--- -description: tool-${tool.name} -auto_execution_mode: 2 ---- - -${content}`; - - return workflowContent; - } - - /** - * Create workflow content for a workflow - */ - createWorkflowWorkflowContent(workflow, content) { - // Create simple Windsurf frontmatter matching original format - let workflowContent = `--- -description: ${workflow.name} -auto_execution_mode: 1 ---- - -${content}`; - - return workflowContent; - } - - /** - * Cleanup Windsurf configuration - surgically remove only BMAD files + * Cleanup Windsurf configuration */ async cleanup(projectDir) { - const fs = require('fs-extra'); - const bmadPath = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad'); - - if (await fs.pathExists(bmadPath)) { - // Remove the entire bmad folder - this is our territory - await fs.remove(bmadPath); - console.log(chalk.dim(` Cleaned up existing BMAD workflows`)); + const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir); + const removedCount = await this.clearBmadPrefixedFiles(workflowsDir); + if (removedCount > 0) { + console.log(chalk.dim(` Removed ${removedCount} old BMAD items from ${this.name}`)); } } @@ -216,14 +218,13 @@ ${content}`; * @returns {Object|null} Info about created command */ async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { - const fs = require('fs-extra'); - const customAgentsDir = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad', 'custom', 'agents'); + const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir); if (!(await this.exists(path.join(projectDir, this.configDir)))) { return null; // IDE not configured for this project } - await this.ensureDir(customAgentsDir); + await this.ensureDir(workflowsDir); const launcherContent = `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. @@ -245,12 +246,15 @@ auto_execution_mode: 3 ${launcherContent}`; - const launcherPath = path.join(customAgentsDir, `${agentName}.md`); + const fileName = `bmad-custom-agents-${agentName}.md`; + const launcherPath = path.join(workflowsDir, fileName); await fs.writeFile(launcherPath, workflowContent); return { - path: launcherPath, - command: `bmad/custom/agents/${agentName}`, + ide: 'windsurf', + path: path.relative(projectDir, launcherPath), + command: `bmad-custom-agents-${agentName}`, + type: 'custom-agent-launcher', }; } }