From 677a00280b42651719296227bcefc526e7fc5a56 Mon Sep 17 00:00:00 2001 From: Phil Date: Fri, 9 Jan 2026 03:39:32 -0500 Subject: [PATCH] feat: refactor Cursor IDE setup to do command generation and cleanup instead of rules (#1283) * feat: refactor Cursor IDE setup to do command generation and cleanup instead of rules - Added support for command generation in the Cursor IDE setup, including the creation of a new commands directory. - Implemented cleanup for old BMAD commands alongside existing rules. - Integrated TaskToolCommandGenerator for generating task and tool commands. - Updated logging to reflect the number of agents, tasks, tools, and workflow commands generated during setup. * style: adjust constructor formatting and update command path in Cursor IDE setup - Reformatted the constructor method for consistency. - Updated the command path syntax in the Cursor IDE setup to use a more standard format. * fix: update Cursor command paths in documentation - Changed the command path for Cursor IDE setup from `.cursor/rules/bmad/` to `.cursor/commands/bmad/` in both installers.md and modules.md. - Updated file extension references to use `.md` instead of `.mdc` for consistency. --- .../toolsmith-sidecar/knowledge/installers.md | 2 +- .../toolsmith-sidecar/knowledge/modules.md | 2 +- tools/cli/installers/lib/ide/cursor.js | 363 +++--------------- 3 files changed, 60 insertions(+), 307 deletions(-) diff --git a/samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md b/samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md index b6f8be22..d3bb907f 100644 --- a/samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md +++ b/samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md @@ -61,7 +61,7 @@ | -------------- | -------------- | ------------------------- | ----------------------------- | | claude-code | Claude Code | .claude/commands/ | .md with frontmatter | | codex | Codex | (varies) | .md | -| cursor | Cursor | .cursor/rules/bmad/ | .mdc with MDC frontmatter | +| cursor | Cursor | .cursor/commands/bmad/ | .md with YAML frontmatter | | github-copilot | GitHub Copilot | .github/ | .md | | opencode | OpenCode | .opencode/ | .md | | windsurf | Windsurf | .windsurf/workflows/bmad/ | .md with workflow frontmatter | diff --git a/samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md b/samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md index 663fcc60..fa03b247 100644 --- a/samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md +++ b/samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md @@ -128,7 +128,7 @@ module.exports = { NewIdeSetup }; | IDE | Config Pattern | File Extension | | -------------- | ------------------------- | -------------- | | Claude Code | .claude/commands/bmad/ | .md | -| Cursor | .cursor/rules/bmad/ | .mdc | +| Cursor | .cursor/commands/bmad/ | .md | | Windsurf | .windsurf/workflows/bmad/ | .md | | GitHub Copilot | .github/ | .md | diff --git a/tools/cli/installers/lib/ide/cursor.js b/tools/cli/installers/lib/ide/cursor.js index 183bbced..61f374a4 100644 --- a/tools/cli/installers/lib/ide/cursor.js +++ b/tools/cli/installers/lib/ide/cursor.js @@ -3,6 +3,7 @@ const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); +const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); /** * Cursor IDE setup handler @@ -12,6 +13,7 @@ class CursorSetup extends BaseIdeSetup { super('cursor', 'Cursor', true); // preferred IDE this.configDir = '.cursor'; this.rulesDir = 'rules'; + this.commandsDir = 'commands'; } /** @@ -21,11 +23,17 @@ class CursorSetup extends BaseIdeSetup { async cleanup(projectDir) { const fs = require('fs-extra'); const bmadRulesDir = path.join(projectDir, this.configDir, this.rulesDir, 'bmad'); + const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad'); if (await fs.pathExists(bmadRulesDir)) { await fs.remove(bmadRulesDir); console.log(chalk.dim(` Removed old BMAD rules from ${this.name}`)); } + + if (await fs.pathExists(bmadCommandsDir)) { + await fs.remove(bmadCommandsDir); + console.log(chalk.dim(` Removed old BMAD commands from ${this.name}`)); + } } /** @@ -40,330 +48,76 @@ class CursorSetup extends BaseIdeSetup { // Clean up old BMAD installation first await this.cleanup(projectDir); - // Create .cursor/rules directory structure + // Create .cursor/commands directory structure const cursorDir = path.join(projectDir, this.configDir); - const rulesDir = path.join(cursorDir, this.rulesDir); - const bmadRulesDir = path.join(rulesDir, 'bmad'); + const commandsDir = path.join(cursorDir, this.commandsDir); + const bmadCommandsDir = path.join(commandsDir, 'bmad'); - await this.ensureDir(bmadRulesDir); + await this.ensureDir(bmadCommandsDir); - // Generate agent launchers first + // 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 } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); - - // Convert artifacts to agent format for index creation - const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name })); - - // Get tasks, tools, and workflows (ALL workflows now generate commands) - const tasks = await this.getTasks(bmadDir, true); - const tools = await this.getTools(bmadDir, true); - - // 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, - })); + const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); // Create directories for each module const modules = new Set(); - for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module); + for (const artifact of agentArtifacts) { + modules.add(artifact.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')); + await this.ensureDir(path.join(bmadCommandsDir, module)); + await this.ensureDir(path.join(bmadCommandsDir, module, 'agents')); } - // 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, - }); + // Write agent launcher files + const agentCount = await agentGen.writeAgentLaunchers(bmadCommandsDir, agentArtifacts); - const targetPath = path.join(bmadRulesDir, artifact.module, 'agents', `${artifact.name}.mdc`); + // Generate workflow commands from manifest (if it exists) + const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName); + const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir); - 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; + // Write only workflow-command artifacts, skip workflow-launcher READMEs + let workflowCommandCount = 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++; + 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 } - // Create BMAD index file (but NOT .cursorrules - user manages that) - await this.createBMADIndex(bmadRulesDir, agents, tasks, tools, workflows, modules); + // Generate task and tool commands from manifests (if they exist) + const taskToolGen = new TaskToolCommandGenerator(); + const taskToolResult = await taskToolGen.generateTaskToolCommands(projectDir, bmadDir, bmadCommandsDir); 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)}`)); + if (workflowCommandCount > 0) { + console.log(chalk.dim(` - ${workflowCommandCount} workflow commands generated`)); + } + if (taskToolResult.generated > 0) { + console.log( + chalk.dim( + ` - ${taskToolResult.generated} task/tool commands generated (${taskToolResult.tasks} tasks, ${taskToolResult.tools} tools)`, + ), + ); + } + console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, bmadCommandsDir)}`)); return { success: true, agents: agentCount, - tasks: taskCount, - tools: toolCount, - workflows: workflowCount, + tasks: taskToolResult.tasks || 0, + tools: taskToolResult.tools || 0, + workflows: workflowCommandCount, }; } - /** - * Create BMAD index file for easy navigation - */ - async createBMADIndex(bmadRulesDir, agents, tasks, tools, workflows, modules) { - const indexPath = path.join(bmadRulesDir, 'index.mdc'); - - let content = `--- -description: BMAD Method - Master Index -globs: -alwaysApply: true ---- - -# BMAD Method - Cursor Rules Index - -This is the master index for all BMAD agents, tasks, tools, and workflows available in your project. - -## Installation Complete! - -BMAD rules have been installed to: \`.cursor/rules/bmad/\` - -**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 - -## Available Modules - -`; - - 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 -- Agents provide persona-based assistance with specific expertise -- Tasks are reusable workflows for common operations -- Tools provide specialized functionality -- Workflows orchestrate multi-step processes -- Each agent includes an activation block for proper initialization - -## Configuration - -BMAD rules are configured as Manual rules (alwaysApply: false) to give you control -over when they're included in your context. Reference them explicitly when you need -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); - } - - /** - * 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 - */ - 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, ''); - } - - // Determine the type and description based on content - const isAgent = content.includes(' `; - // Cursor uses MDC format with metadata header - const mdcContent = `--- -description: "${agentName} agent" -globs: -alwaysApply: false + // Cursor uses YAML frontmatter matching Claude Code format + const commandContent = `--- +name: '${agentName}' +description: '${agentName} agent' --- ${launcherContent} `; - const launcherPath = path.join(customAgentsDir, `${agentName}.mdc`); - await this.writeFile(launcherPath, mdcContent); + const launcherPath = path.join(customAgentsDir, `${agentName}.md`); + await this.writeFile(launcherPath, commandContent); return { path: launcherPath, - command: `@${agentName}`, + command: `/bmad/custom/agents/${agentName}`, }; } }