Skip to content

feat: refactor Cursor IDE setup to do command generation and cleanup instead of rules#1283

Merged
bmadcode merged 3 commits intobmad-code-org:mainfrom
thecontstruct:cursor-ide-updates
Jan 9, 2026
Merged

feat: refactor Cursor IDE setup to do command generation and cleanup instead of rules#1283
bmadcode merged 3 commits intobmad-code-org:mainfrom
thecontstruct:cursor-ide-updates

Conversation

@thecontstruct
Copy link
Copy Markdown
Contributor

What

Updated Cursor IDE setup to create slash commands in .cursor/commands/bmad/ instead of rules in .cursor/rules/bmad/. Commands now use plain Markdown files with YAML frontmatter (matching Claude Code format) instead of MDC format.

Why

Cursor IDE supports slash commands as plain Markdown files in .cursor/commands/ directory that users explicitly invoke, rather than rules that auto-apply. This change aligns with Cursor's command model and matches Claude Code's implementation pattern for consistency across IDE handlers.

How

  • Refactored CursorSetup.setup() to follow Claude Code's exact pattern: use AgentCommandGenerator.writeAgentLaunchers() directly, write workflow artifacts with their existing YAML frontmatter, and use TaskToolCommandGenerator for tasks/tools
  • Removed all MDC-specific processing methods (processContent() override, wrapLauncherWithMDC(), readAndProcess()) since the generators already provide correct YAML frontmatter
  • Updated cleanup to remove both old .cursor/rules/bmad/ and .cursor/commands/bmad/ directories to support migration from rules to commands
  • Removed index file generation to match Claude Code's simpler approach

Testing

Verified that commands appear in Cursor's slash command palette (/), file naming matches Claude Code exactly, frontmatter uses YAML format (not MDC), and cleanup removes both old rules and commands directories.

…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.
…r IDE setup

- Reformatted the constructor method for consistency.
- Updated the command path syntax in the Cursor IDE setup to use a more standard format.
- 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.
@thecontstruct thecontstruct marked this pull request as ready for review January 9, 2026 03:28
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Migrates Cursor IDE configuration from rules-based to commands-based directory structure. Updates file paths from .cursor/rules/bmad/ to .cursor/commands/bmad/ and changes artifact formats from MDC to YAML-frontmatter markdown. Refactors the Cursor installer to use TaskToolCommandGenerator for unified command generation instead of discrete artifact generation.

Changes

Cohort / File(s) Summary
Documentation Updates
samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md, samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md
Updated Cursor IDE configuration references: changed file path from .cursor/rules/bmad/ to .cursor/commands/bmad/ and file format from .mdc (MDC) to .md (YAML frontmatter)
Cursor Installer Implementation
tools/cli/installers/lib/ide/cursor.js
Replaced rules-based artifact generation with TaskToolCommandGenerator-driven commands approach. Migrated setup flow to create unified command files under .cursor/commands directory. Updated installCustomAgentLauncher to return command reference /bmad/custom/agents/${agentName} instead of @${agentName}. Added commandsDir property and new cleanup/logging for command generation

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Use slash commands in Cursor integration #1049: Directly addresses the migration from .cursor/rules/bmad/ to .cursor/commands/bmad/ structure, including MDC to YAML format conversion and TaskToolCommandGenerator integration in the installer logic.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: refactoring Cursor IDE setup from rules-based to command-based generation, which is the primary focus across all modified files.
Description check ✅ Passed The description comprehensively explains what changed (rules to commands), why (alignment with Cursor's command model), and how (refactored setup flow, removed MDC processing, updated cleanup), all directly related to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @tools/cli/installers/lib/ide/cursor.js:
- Around line 23-37: In the cleanup function, filesystem removes can throw and
currently propagate; wrap each await fs.remove(bmadRulesDir) and await
fs.remove(bmadCommandsDir) (or the whole removal block) in try/catch, log
failures (e.g., console.error or chalk.red with the path and error.message) and
continue so a single failure doesn’t abort the rest of the cleanup; ensure you
still report successful removals with the existing console.log calls.
- Around line 61-75: The destructuring from collectAgentArtifacts currently
pulls counts: agentCounts which is never used; remove the unused agentCounts
from the destructuring (keep artifacts: agentArtifacts) or if you intend to
validate/log the count, use agentCounts (e.g., compare to or log alongside the
agentCount returned by writeAgentLaunchers). Update the line that calls
collectAgentArtifacts and any related logging/validation to either drop
agentCounts or consume it, referencing collectAgentArtifacts, agentArtifacts,
agentCounts, and writeAgentLaunchers in your changes.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d19cca7 and b450b38.

📒 Files selected for processing (3)
  • samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md
  • samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md
  • tools/cli/installers/lib/ide/cursor.js
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Focus on inconsistencies, contradictions, edge cases and serious issues.
Avoid commenting on minor issues such as linting, formatting and style issues.
When providing code suggestions, use GitHub's suggestion format:

<code changes>

Files:

  • samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md
  • samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md
  • tools/cli/installers/lib/ide/cursor.js
**/*.js

⚙️ CodeRabbit configuration file

**/*.js: CLI tooling code. Check for: missing error handling on fs operations,
path.join vs string concatenation, proper cleanup in error paths.
Flag any process.exit() without error message.

Files:

  • tools/cli/installers/lib/ide/cursor.js
🧬 Code graph analysis (1)
tools/cli/installers/lib/ide/cursor.js (1)
tools/cli/installers/lib/ide/shared/task-tool-command-generator.js (2)
  • projectDir (16-58)
  • item (63-82)
🔇 Additional comments (7)
tools/cli/installers/lib/ide/cursor.js (5)

6-6: LGTM!

The import and property additions are consistent with the migration to a commands-based structure.

Also applies to: 16-16


77-92: Good filtering logic for workflow artifacts.

The selective processing of only workflow-command artifacts while skipping workflow-launcher READMEs is appropriate for the Cursor command structure.

However, note that the writeFile() operation on Line 88 lacks error handling, consistent with the broader pattern in this method that should be addressed.


94-119: Comprehensive logging and return structure.

The conditional logging for workflows and task/tool commands provides good visibility into what was generated, and the return object properly includes all relevant counts with defensive fallbacks.


138-148: @${agentPath} reference format is correct and supported in Cursor.

Cursor IDE supports @ symbols for file path references in interactive contexts (Composer, Chat, Cmd-K). The @${agentPath} syntax on line 141 is embedded as instructional guidance within the slash command's markdown content, directing the user or AI to reference the agent file using Cursor's standard @ syntax. This pattern is consistently used across all IDE setup files in the codebase (windsurf, codex, opencode, claude-code, github-copilot, etc.), indicating it's an established and correct approach.


164-164: The createIdeSlashCommands() function is exported from installer.js but is not currently called anywhere in the codebase. The API change to return /bmad/custom/agents/${agentName} is consistently implemented across all IDE handlers with proper error handling in the manager. However, since this function has no active callers, there are no downstream consumers to verify at this time. This may indicate the function is prepared for future integration or is unused code.

samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md (1)

131-131: LGTM!

Documentation accurately reflects the migration from .cursor/rules/bmad/ with .mdc files to .cursor/commands/bmad/ with .md files, consistent with the implementation changes.

samples/sample-custom-modules/sample-unitary-module/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md (1)

64-64: LGTM!

Documentation correctly updated to reflect both the path migration and the frontmatter format change from MDC to YAML, aligning with the implementation.

Comment on lines 23 to 37
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}`));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for filesystem operations.

Per coding guidelines for CLI tooling, filesystem operations should include proper error handling. If either fs.remove() call fails, the error propagates without recovery, potentially leaving the cleanup in a partial state.

🛡️ Proposed fix with error handling
 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}`));
-  }
+  try {
+    if (await fs.pathExists(bmadRulesDir)) {
+      await fs.remove(bmadRulesDir);
+      console.log(chalk.dim(`  Removed old BMAD rules from ${this.name}`));
+    }
+  } catch (error) {
+    console.error(chalk.yellow(`  Warning: Failed to remove BMAD rules: ${error.message}`));
+  }
+
+  try {
+    if (await fs.pathExists(bmadCommandsDir)) {
+      await fs.remove(bmadCommandsDir);
+      console.log(chalk.dim(`  Removed old BMAD commands from ${this.name}`));
+    }
+  } catch (error) {
+    console.error(chalk.yellow(`  Warning: Failed to remove BMAD commands: ${error.message}`));
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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}`));
}
}
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');
try {
if (await fs.pathExists(bmadRulesDir)) {
await fs.remove(bmadRulesDir);
console.log(chalk.dim(` Removed old BMAD rules from ${this.name}`));
}
} catch (error) {
console.error(chalk.yellow(` Warning: Failed to remove BMAD rules: ${error.message}`));
}
try {
if (await fs.pathExists(bmadCommandsDir)) {
await fs.remove(bmadCommandsDir);
console.log(chalk.dim(` Removed old BMAD commands from ${this.name}`));
}
} catch (error) {
console.error(chalk.yellow(` Warning: Failed to remove BMAD commands: ${error.message}`));
}
}
🤖 Prompt for AI Agents
In @tools/cli/installers/lib/ide/cursor.js around lines 23 - 37, In the cleanup
function, filesystem removes can throw and currently propagate; wrap each await
fs.remove(bmadRulesDir) and await fs.remove(bmadCommandsDir) (or the whole
removal block) in try/catch, log failures (e.g., console.error or chalk.red with
the path and error.message) and continue so a single failure doesn’t abort the
rest of the cleanup; ensure you still report successful removals with the
existing console.log calls.

Comment on lines +61 to +75
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 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++;
modules.add(artifact.module);
}

// 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++;
for (const module of modules) {
await this.ensureDir(path.join(bmadCommandsDir, module));
await this.ensureDir(path.join(bmadCommandsDir, module, 'agents'));
}

// 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`);
// Write agent launcher files
const agentCount = await agentGen.writeAgentLaunchers(bmadCommandsDir, agentArtifacts);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the full context of the file to understand the setup() method
fd cursor.js --type f | head -5

Repository: bmad-code-org/BMAD-METHOD

Length of output: 107


🏁 Script executed:

# Get the file size to determine how to read it
wc -l tools/cli/installers/lib/ide/cursor.js

Repository: bmad-code-org/BMAD-METHOD

Length of output: 111


🏁 Script executed:

# Read the file to understand the full context
cat -n tools/cli/installers/lib/ide/cursor.js

Repository: bmad-code-org/BMAD-METHOD

Length of output: 7732


🏁 Script executed:

# Search for any usage of agentCounts variable in the entire codebase
rg 'agentCounts' --type=js -B 2 -A 2

Repository: bmad-code-org/BMAD-METHOD

Length of output: 1734


🏁 Script executed:

# Check the collectAgentArtifacts method to understand what it returns
rg 'collectAgentArtifacts' --type=js -B 2 -A 5

Repository: bmad-code-org/BMAD-METHOD

Length of output: 13347


🏁 Script executed:

# Look for writeAgentLaunchers to understand its signature and return value
rg 'writeAgentLaunchers' --type=js -B 2 -A 8

Repository: bmad-code-org/BMAD-METHOD

Length of output: 3280


Remove unused agentCounts variable from destructuring.

Line 61 destructures counts: agentCounts from collectAgentArtifacts(), but this variable is never used anywhere in the method. The actual agent count comes from writeAgentLaunchers() on line 75. Either remove agentCounts from the destructuring or use it for validation/logging if it serves a purpose.

🤖 Prompt for AI Agents
In @tools/cli/installers/lib/ide/cursor.js around lines 61 - 75, The
destructuring from collectAgentArtifacts currently pulls counts: agentCounts
which is never used; remove the unused agentCounts from the destructuring (keep
artifacts: agentArtifacts) or if you intend to validate/log the count, use
agentCounts (e.g., compare to or log alongside the agentCount returned by
writeAgentLaunchers). Update the line that calls collectAgentArtifacts and any
related logging/validation to either drop agentCounts or consume it, referencing
collectAgentArtifacts, agentArtifacts, agentCounts, and writeAgentLaunchers in
your changes.

@bmadcode
Copy link
Copy Markdown
Collaborator

bmadcode commented Jan 9, 2026

thank you @thecontstruct and also @wsmoak for the input and help here!

@bmadcode bmadcode merged commit 677a002 into bmad-code-org:main Jan 9, 2026
20 checks passed
@wsmoak
Copy link
Copy Markdown
Contributor

wsmoak commented Jan 9, 2026

I see it's already merged, just tried it out by installing from main and ✅ we have commands in Cursor now!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants