fix(installer): add custom Rovo Dev installer with prompts.yml generation#1701
Conversation
…tion Rovo Dev CLI requires a .rovodev/prompts.yml manifest to register prompts for /prompts access. The config-driven installer was writing .md files but never generating this manifest, so /prompts showed nothing. - Create custom rovodev.js installer extending BaseIdeSetup - Generate prompts.yml indexing all written workflow files - Merge with existing user entries (only touch bmad- prefixed entries) - Remove stale rovo entry from tools/platform-codes.yaml Closes bmad-code-org#1466
The /prompts list in Rovo Dev only shows descriptions, making it hard to identify entries. Prefix each description with the bmad entry name so users see e.g. "bmad-bmm-create-prd - PRD workflow..." instead of just the description text.
🤖 Augment PR SummarySummary: This PR adds a dedicated Rovo Dev installer so BMAD prompt files show up in the Rovo Dev CLI Changes:
Technical Notes: The manifest uses Rovo’s 🤖 Was this summary useful? React with 👍 or 👎 |
📝 WalkthroughWalkthroughIntroduces a custom IDE installer for Rovo Dev that generates BMAD workflow artifacts and a prompts.yml manifest file instead of using standard configuration-driven installation. Adds rovodev.js to the custom installer loader and removes the rovo platform entry from platform configuration. Changes
Sequence DiagramsequenceDiagram
participant ProjectDir
participant RovoDevSetup
participant AgentGen as Agent Generator
participant WorkflowGen as Workflow Generator
participant TaskToolGen as TaskTool Generator
participant PromptsYml as prompts.yml
ProjectDir->>RovoDevSetup: setup(projectDir, bmadDir, options)
RovoDevSetup->>RovoDevSetup: cleanup old BMAD artifacts
RovoDevSetup->>RovoDevSetup: ensure .rovodev/workflows/ exists
RovoDevSetup->>AgentGen: generate agent artifacts
AgentGen->>RovoDevSetup: return files & metadata
RovoDevSetup->>WorkflowGen: generate workflow artifacts
WorkflowGen->>RovoDevSetup: return files & metadata
RovoDevSetup->>TaskToolGen: generate task/tool artifacts
TaskToolGen->>RovoDevSetup: return files & metadata
RovoDevSetup->>RovoDevSetup: accumulate artifact counts
RovoDevSetup->>PromptsYml: generatePromptsYml(writtenFiles)
PromptsYml->>PromptsYml: read existing entries
PromptsYml->>PromptsYml: preserve non-BMAD entries
PromptsYml->>PromptsYml: merge new BMAD entries
RovoDevSetup->>ProjectDir: return { success, results }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tools/cli/installers/lib/ide/manager.js (1)
45-48:⚠️ Potential issue | 🟡 MinorJSDoc for
loadHandlers()still lists only three custom files —rovodev.jsis absent.Line 11 (the class-level comment) was updated, but the method-level JSDoc on lines 47–48 was not. It still reads
(codex.js, github-copilot.js, kilo.js). This will confuse anyone reading the method in isolation.🐛 Proposed fix
- * 1. Load custom installer files first (codex.js, github-copilot.js, kilo.js) + * 1. Load custom installer files first (codex.js, github-copilot.js, kilo.js, rovodev.js)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/cli/installers/lib/ide/manager.js` around lines 45 - 48, The JSDoc for the loadHandlers() method is out of sync: it still lists only "codex.js, github-copilot.js, kilo.js" while the class-level comment and code also load "rovodev.js"; update the method-level JSDoc on loadHandlers() to include rovodev.js (and ensure the wording matches the class-level comment) so readers inspecting the method see the correct list of custom installer files.
🧹 Nitpick comments (4)
tools/cli/installers/lib/ide/rovodev.js (3)
56-104:requireinside loops — hoisttoDashPathto the module top level.
toDashPathis destructured fromrequire('./shared/path-utils')on lines 58, 76, and 96 — once per loop iteration, three separate loops. While Node.js caches the module, this is unnecessary overhead and obscures the module's true dependency graph. The import belongs at the top of the file alongside all other requires.♻️ Proposed fix
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); +const { toDashPath } = require('./shared/path-utils'); ... for (const artifact of agentArtifacts) { if (artifact.type === 'agent-launcher') { - const { toDashPath } = require('./shared/path-utils'); const flatName = toDashPath(artifact.relativePath);Apply the same removal to lines 76 and 96.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/cli/installers/lib/ide/rovodev.js` around lines 56 - 104, Hoist the destructured require for toDashPath out of the loops by adding a single top-level import like const { toDashPath } = require('./shared/path-utils'); alongside the other module requires, and remove the in-loop requires that currently appear inside the agentArtifacts, workflowArtifacts, and taskToolArtifacts loops (references: toDashPath, './shared/path-utils', writtenFiles push blocks in the code that build name/description/contentFile). This preserves behavior while avoiding repeated requires and clarifies the module dependency.
56-104: Three near-identical artifact-to-prompt-entry blocks — extract a shared helper.Lines 56–66, 74–84, and 94–104 follow the exact same pattern: check artifact type, derive flat filename, push a
{ name, description, contentFile }object. The only variation is the set of accepted type strings and the fallback description suffix. Extract a helper (e.g.,artifactToPromptEntry) to eliminate the repetition and centralise the mapping logic.♻️ Proposed refactor sketch
+ /** + * `@param` {Object} artifact + * `@param` {string[]} acceptedTypes + * `@returns` {{ name, description, contentFile }|null} + */ + _toPromptEntry(artifact, acceptedTypes) { + if (!acceptedTypes.includes(artifact.type)) return null; + const flatName = toDashPath(artifact.relativePath); + return { + name: path.basename(flatName, '.md'), + description: artifact.description || `${artifact.name} ${artifact.type}`, + contentFile: `${this.workflowsDir}/${flatName}`, + }; + } // In setup(): - for (const artifact of agentArtifacts) { - if (artifact.type === 'agent-launcher') { - const { toDashPath } = require('./shared/path-utils'); - const flatName = toDashPath(artifact.relativePath); - writtenFiles.push({ - name: path.basename(flatName, '.md'), - description: artifact.description || `${artifact.name} agent`, - contentFile: `${this.workflowsDir}/${flatName}`, - }); - } - } + writtenFiles.push(...agentArtifacts.map(a => this._toPromptEntry(a, ['agent-launcher'])).filter(Boolean));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/cli/installers/lib/ide/rovodev.js` around lines 56 - 104, There are three duplicated loops that map artifacts into prompt entries; extract a helper function (e.g., artifactToPromptEntry) to centralise the logic used by the loops that populate writtenFiles. Implement artifactToPromptEntry(relativePath, artifact, typeMatchers, fallbackSuffix) which uses require('./shared/path-utils').toDashPath to compute flatName, path.basename to derive name, builds description from artifact.description || `${artifact.name} ${fallbackSuffix}`, and returns the { name, description, contentFile } object (contentFile should be `${this.workflowsDir}/${flatName}`). Replace the three inline blocks that iterate agentArtifacts, workflowArtifacts, and taskToolArtifacts with calls to this helper (pass appropriate typeMatchers or simple type checks for 'agent-launcher', 'workflow-command', and ['task','tool'] and the respective suffixes like "agent", "workflow", or the artifact.type), and push the returned object into writtenFiles when the type matches.
174-234:cleanup()bypassesBaseIdeSetupfile I/O abstractions whilegeneratePromptsYml()uses them — breaks mock-based unit testing for half the class.
generatePromptsYmlcallsthis.pathExists,this.readFile,this.writeFile(presumably provided byBaseIdeSetupand replaceable in tests), butcleanupcallsfs.pathExists,fs.readFile,fs.writeFile, andfs.removedirectly. Any test harness that stubs theBaseIdeSetupmethods will not interceptcleanup's I/O, making it impossible to unit-test cleanup in isolation without touching the real filesystem. Standardise on one approach — preferably theBaseIdeSetupabstraction throughout.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/cli/installers/lib/ide/rovodev.js` around lines 174 - 234, cleanup() currently uses direct fs.* calls which bypass the BaseIdeSetup file I/O abstraction used by generatePromptsYml(); replace those direct fs calls with the class's abstracted methods so tests can stub them. Specifically, in cleanup() replace fs.pathExists with this.pathExists, fs.readdir with the corresponding this.readdir/this.readDir wrapper (or add one to BaseIdeSetup if missing), fs.readFile with this.readFile, fs.writeFile with this.writeFile, and fs.remove with a this.remove (or add a remove/unlink wrapper to BaseIdeSetup); keep the existing logic and logging (prompts.log.message/warn and options.silent) intact while using these instance methods.tools/cli/installers/lib/ide/manager.js (1)
70-86: Establish a convention: each IDE handler file must export the handler class as adefaultexport.The current pattern
HandlerModule.default || Object.values(HandlerModule)[0]at line 72 is fragile. Today it works because all handler files (codex.js, github-copilot.js, kilo.js, rovodev.js) export exactly one class, and Object.values() happens to pick it. But this silently breaks if anyone adds a second export (a helper constant, utility function, etc.)—the wrong value gets instantiated with no error. Either enforcedefaultexports in all handler files, or explicitly check for the handler class by name instead of relying on enumeration order.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/cli/installers/lib/ide/manager.js` around lines 70 - 86, The loader currently uses HandlerModule.default || Object.values(HandlerModule)[0], which is fragile; change the logic in manager.js to require a proper default export: only use HandlerModule.default as the handler class, and if HandlerModule.default is missing or not a constructor, log/warn and skip loading that file. Specifically, update the block that derives HandlerClass (replace the fallback to Object.values) to validate HandlerModule.default is a function/constructor before instantiating, and adjust the error message to indicate a missing or invalid default export; keep honoring instance.setBmadFolderName and this.handlers.set(instance.name, instance) as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tools/cli/installers/lib/ide/rovodev.js`:
- Around line 143-150: The current filter on parsed.prompts that drops entries
whose name starts with 'bmad-' (the existingPrompts assignment) can silently
delete user-owned prompts; change the logic to detect which entries would be
removed by the bmad- filter and, for each removed entry not present in this
run's writtenFiles set, emit a warning (e.g., console.warn or the module's
logger) listing the prompt name and that it was removed; only then continue to
set existingPrompts to the filtered array. Ensure you reference parsed.prompts,
the bmad- name check, existingPrompts, and writtenFiles when implementing the
detection and warning.
- Line 107: The code unconditionally calls generatePromptsYml(projectDir,
writtenFiles) even when no artifacts were collected, which creates/overwrites a
prompts manifest with an empty BMAD block; guard the call so it only runs when
there are artifacts to write. Modify the caller to check that writtenFiles is a
non-empty array (e.g., if (Array.isArray(writtenFiles) && writtenFiles.length >
0) await this.generatePromptsYml(projectDir, writtenFiles);), leaving
generatePromptsYml unchanged so existing user entries aren't touched when no new
artifacts are found.
- Around line 178-185: The cleanup() implementation only removes files matching
'bmad-*.md' under workflowsPath but detect() considers any entry that
startsWith('bmad-') (including directories or non-.md files), causing detect()
to still return true after cleanup; update cleanup() to remove any filesystem
entry whose name startsWith('bmad-') (use fs.stat or fs.lstat to handle
directories and files and call fs.remove on each matching entry) so its
predicate aligns with detect(), and keep references to the existing functions
and variable names (cleanup(), detect(), workflowsPath, entries) when making the
change.
- Around line 22-29: Remove the unused detectionPaths assignment from the
RovoDevSetup constructor: the class currently sets this.detectionPaths =
['.rovodev/workflows', '.rovodev/prompts.yml'] but its detect() method
(RovoDevSetup.detect) overrides detection logic and never uses the
BaseIdeSetup.detect mechanism that reads this property, so delete the
this.detectionPaths line from the constructor to eliminate the redundant field.
- Around line 88-91: The fix must not destructure a non-object from
taskToolGen.writeDashArtifacts; call await
taskToolGen.writeDashArtifacts(bmadDir) and, if you need the number of files
written capture it as a single numeric writtenCount, but keep taskCount and
toolCount coming from taskToolCounts returned by
taskToolGen.collectTaskToolArtifacts (use taskToolCounts.tasks || 0 and
taskToolCounts.tools || 0); alternatively, if you only track a combined written
total, assign that single writtenCount to both taskCount and toolCount, but do
not attempt to destructure {tasks, tools} from writeDashArtifacts (see
taskToolGen.writeDashArtifacts, taskToolGen.collectTaskToolArtifacts, and the
analogous agentGen.writeDashArtifacts/workflowGen.writeDashArtifacts behavior).
- Around line 52-104: The workflow and task/tool collectors are not receiving
the user's selectedModules while agentGen.collectAgentArtifacts does, so
workflows and tasks are being installed unfiltered; update the calls to
WorkflowCommandGenerator.collectWorkflowArtifacts(...) and
TaskToolCommandGenerator.collectTaskToolArtifacts(...) to pass the
selectedModules argument (same variable used with
agentGen.collectAgentArtifacts), ensuring the collectors receive selectedModules
so getTasksFromBmad/module filtering is applied and prompts.yml only lists
selected modules' artifacts.
- Around line 53-104: The manifest references stale files because writtenFiles
is populated from agentArtifacts/workflowArtifacts/taskToolArtifacts instead of
the actual results of writeDashArtifacts; change writeDashArtifacts (used by
agentGen.writeDashArtifacts, workflowGen.writeDashArtifacts,
taskToolGen.writeDashArtifacts) to return the list of successfully written
artifact objects (not just a count), then update the callers to use those
returned arrays to build writtenFiles (replace loops over
agentArtifacts/workflowArtifacts/taskToolArtifacts with loops over the returned
written-artifacts arrays) before calling generatePromptsYml so prompts.yml only
lists confirmed-on-disk files.
---
Outside diff comments:
In `@tools/cli/installers/lib/ide/manager.js`:
- Around line 45-48: The JSDoc for the loadHandlers() method is out of sync: it
still lists only "codex.js, github-copilot.js, kilo.js" while the class-level
comment and code also load "rovodev.js"; update the method-level JSDoc on
loadHandlers() to include rovodev.js (and ensure the wording matches the
class-level comment) so readers inspecting the method see the correct list of
custom installer files.
---
Nitpick comments:
In `@tools/cli/installers/lib/ide/manager.js`:
- Around line 70-86: The loader currently uses HandlerModule.default ||
Object.values(HandlerModule)[0], which is fragile; change the logic in
manager.js to require a proper default export: only use HandlerModule.default as
the handler class, and if HandlerModule.default is missing or not a constructor,
log/warn and skip loading that file. Specifically, update the block that derives
HandlerClass (replace the fallback to Object.values) to validate
HandlerModule.default is a function/constructor before instantiating, and adjust
the error message to indicate a missing or invalid default export; keep honoring
instance.setBmadFolderName and this.handlers.set(instance.name, instance) as
before.
In `@tools/cli/installers/lib/ide/rovodev.js`:
- Around line 56-104: Hoist the destructured require for toDashPath out of the
loops by adding a single top-level import like const { toDashPath } =
require('./shared/path-utils'); alongside the other module requires, and remove
the in-loop requires that currently appear inside the agentArtifacts,
workflowArtifacts, and taskToolArtifacts loops (references: toDashPath,
'./shared/path-utils', writtenFiles push blocks in the code that build
name/description/contentFile). This preserves behavior while avoiding repeated
requires and clarifies the module dependency.
- Around line 56-104: There are three duplicated loops that map artifacts into
prompt entries; extract a helper function (e.g., artifactToPromptEntry) to
centralise the logic used by the loops that populate writtenFiles. Implement
artifactToPromptEntry(relativePath, artifact, typeMatchers, fallbackSuffix)
which uses require('./shared/path-utils').toDashPath to compute flatName,
path.basename to derive name, builds description from artifact.description ||
`${artifact.name} ${fallbackSuffix}`, and returns the { name, description,
contentFile } object (contentFile should be `${this.workflowsDir}/${flatName}`).
Replace the three inline blocks that iterate agentArtifacts, workflowArtifacts,
and taskToolArtifacts with calls to this helper (pass appropriate typeMatchers
or simple type checks for 'agent-launcher', 'workflow-command', and
['task','tool'] and the respective suffixes like "agent", "workflow", or the
artifact.type), and push the returned object into writtenFiles when the type
matches.
- Around line 174-234: cleanup() currently uses direct fs.* calls which bypass
the BaseIdeSetup file I/O abstraction used by generatePromptsYml(); replace
those direct fs calls with the class's abstracted methods so tests can stub
them. Specifically, in cleanup() replace fs.pathExists with this.pathExists,
fs.readdir with the corresponding this.readdir/this.readDir wrapper (or add one
to BaseIdeSetup if missing), fs.readFile with this.readFile, fs.writeFile with
this.writeFile, and fs.remove with a this.remove (or add a remove/unlink wrapper
to BaseIdeSetup); keep the existing logic and logging (prompts.log.message/warn
and options.silent) intact while using these instance methods.
- Hoist toDashPath import to module top level - Extract _collectPromptEntries helper replacing 3 duplicated loops - Remove unused detectionPaths (detect() is overridden) - Guard generatePromptsYml when writtenFiles is empty - Align cleanup() with detect() predicate (remove any bmad-*, not just .md) - Use BaseIdeSetup abstractions (this.pathExists/readFile/writeFile) in cleanup() - Update loadHandlers() JSDoc to include rovodev.js

Summary
rovodev.jsinstaller that generates.rovodev/prompts.ymlmanifest, fixing/promptsshowing nothing in Rovo Dev CLIrovoentry fromtools/platform-codes.yaml(onlyrovo-devshould exist)rovo-devfrom config-driven to custom installer inplatform-codes.yamlCloses #1466
Test plan
node -e "require('./tools/cli/installers/lib/ide/manager.js')"loads without errorsRovoDevSetuphandler forrovo-devbmad-cli installselectingrovo-dev, confirmed.rovodev/workflows/containsbmad-*.mdfiles and.rovodev/prompts.ymlexists with correct entriesManual test results
Full end-to-end test run against a fresh git repo at
/tmp/bmad-rovo-test:.rovodev/workflows/content_filepaths inprompts.ymlresolve on disk (56/56)prompts.ymlafter second installbmad-*workflow files removedprompts.ymlretains only user's custom entry; file deleted entirely if no user entriesRovoDevSetup.detect()returnsfalseNote: Rovo Dev's UI displays only the
descriptionfield — thenamefield is ignored for display. Thebmad-<name> - <title>description format is intentional to keep entries identifiable in the UI.