Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Verkhovsky 699256a0d2
Merge 82a776e45d into 3dd37bc94d 2026-02-19 10:35:35 +04:00
2 changed files with 50 additions and 35 deletions

View File

@ -44,7 +44,7 @@ class IdeManager {
/** /**
* Dynamically load all IDE handlers * Dynamically load all IDE handlers
* 1. Load custom installer files first (codex.js, github-copilot.js, kilo.js, rovodev.js) * 1. Load custom installer files first (codex.js, github-copilot.js, kilo.js)
* 2. Load config-driven handlers from platform-codes.yaml * 2. Load config-driven handlers from platform-codes.yaml
*/ */
async loadHandlers() { async loadHandlers() {

View File

@ -6,7 +6,6 @@ const prompts = require('../../../lib/prompts');
const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { toDashPath } = require('./shared/path-utils');
/** /**
* Rovo Dev IDE setup handler * Rovo Dev IDE setup handler
@ -26,6 +25,7 @@ class RovoDevSetup extends BaseIdeSetup {
this.rovoDir = '.rovodev'; this.rovoDir = '.rovodev';
this.workflowsDir = 'workflows'; this.workflowsDir = 'workflows';
this.promptsFile = 'prompts.yml'; this.promptsFile = 'prompts.yml';
this.detectionPaths = ['.rovodev/workflows', '.rovodev/prompts.yml'];
} }
/** /**
@ -51,13 +51,37 @@ class RovoDevSetup extends BaseIdeSetup {
const agentGen = new AgentCommandGenerator(this.bmadFolderName); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
const agentCount = await agentGen.writeDashArtifacts(workflowsPath, agentArtifacts); const agentCount = await agentGen.writeDashArtifacts(workflowsPath, agentArtifacts);
this._collectPromptEntries(writtenFiles, agentArtifacts, ['agent-launcher'], 'agent');
// Track written agent files for prompts.yml
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}`,
});
}
}
// Generate and write workflow commands // Generate and write workflow commands
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName); const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir); const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
const workflowCount = await workflowGen.writeDashArtifacts(workflowsPath, workflowArtifacts); const workflowCount = await workflowGen.writeDashArtifacts(workflowsPath, workflowArtifacts);
this._collectPromptEntries(writtenFiles, workflowArtifacts, ['workflow-command'], 'workflow');
// Track written workflow files for prompts.yml
for (const artifact of workflowArtifacts) {
if (artifact.type === 'workflow-command') {
const { toDashPath } = require('./shared/path-utils');
const flatName = toDashPath(artifact.relativePath);
writtenFiles.push({
name: path.basename(flatName, '.md'),
description: artifact.description || `${artifact.name} workflow`,
contentFile: `${this.workflowsDir}/${flatName}`,
});
}
}
// Generate and write task/tool commands // Generate and write task/tool commands
const taskToolGen = new TaskToolCommandGenerator(this.bmadFolderName); const taskToolGen = new TaskToolCommandGenerator(this.bmadFolderName);
@ -65,13 +89,23 @@ class RovoDevSetup extends BaseIdeSetup {
await taskToolGen.writeDashArtifacts(workflowsPath, taskToolArtifacts); await taskToolGen.writeDashArtifacts(workflowsPath, taskToolArtifacts);
const taskCount = taskToolCounts.tasks || 0; const taskCount = taskToolCounts.tasks || 0;
const toolCount = taskToolCounts.tools || 0; const toolCount = taskToolCounts.tools || 0;
this._collectPromptEntries(writtenFiles, taskToolArtifacts, ['task', 'tool']);
// Generate prompts.yml manifest (only if we have entries to write) // Track written task/tool files for prompts.yml
if (writtenFiles.length > 0) { for (const artifact of taskToolArtifacts) {
await this.generatePromptsYml(projectDir, writtenFiles); if (artifact.type === 'task' || artifact.type === 'tool') {
const { toDashPath } = require('./shared/path-utils');
const flatName = toDashPath(artifact.relativePath);
writtenFiles.push({
name: path.basename(flatName, '.md'),
description: artifact.description || `${artifact.name} ${artifact.type}`,
contentFile: `${this.workflowsDir}/${flatName}`,
});
}
} }
// Generate prompts.yml manifest
await this.generatePromptsYml(projectDir, writtenFiles);
if (!options.silent) { if (!options.silent) {
await prompts.log.success( await prompts.log.success(
`${this.name} configured: ${agentCount} agents, ${workflowCount} workflows, ${taskCount} tasks, ${toolCount} tools`, `${this.name} configured: ${agentCount} agents, ${workflowCount} workflows, ${taskCount} tasks, ${toolCount} tools`,
@ -89,25 +123,6 @@ class RovoDevSetup extends BaseIdeSetup {
}; };
} }
/**
* Collect prompt entries from artifacts into writtenFiles array
* @param {Array} writtenFiles - Target array to push entries into
* @param {Array} artifacts - Artifacts from a generator's collect method
* @param {string[]} acceptedTypes - Artifact types to include (e.g., ['agent-launcher'])
* @param {string} [fallbackSuffix] - Suffix for fallback description; defaults to artifact.type
*/
_collectPromptEntries(writtenFiles, artifacts, acceptedTypes, fallbackSuffix) {
for (const artifact of artifacts) {
if (!acceptedTypes.includes(artifact.type)) continue;
const flatName = toDashPath(artifact.relativePath);
writtenFiles.push({
name: path.basename(flatName, '.md'),
description: artifact.description || `${artifact.name} ${fallbackSuffix || artifact.type}`,
contentFile: `${this.workflowsDir}/${flatName}`,
});
}
}
/** /**
* Generate .rovodev/prompts.yml manifest * Generate .rovodev/prompts.yml manifest
* Merges with existing user entries -- strips entries with names starting 'bmad-', * Merges with existing user entries -- strips entries with names starting 'bmad-',
@ -159,11 +174,11 @@ class RovoDevSetup extends BaseIdeSetup {
async cleanup(projectDir, options = {}) { async cleanup(projectDir, options = {}) {
const workflowsPath = path.join(projectDir, this.rovoDir, this.workflowsDir); const workflowsPath = path.join(projectDir, this.rovoDir, this.workflowsDir);
// Remove all bmad-* entries from workflows dir (aligned with detect() predicate) // Remove bmad-* workflow files
if (await this.pathExists(workflowsPath)) { if (await fs.pathExists(workflowsPath)) {
const entries = await fs.readdir(workflowsPath); const entries = await fs.readdir(workflowsPath);
for (const entry of entries) { for (const entry of entries) {
if (entry.startsWith('bmad-')) { if (entry.startsWith('bmad-') && entry.endsWith('.md')) {
await fs.remove(path.join(workflowsPath, entry)); await fs.remove(path.join(workflowsPath, entry));
} }
} }
@ -171,9 +186,9 @@ class RovoDevSetup extends BaseIdeSetup {
// Clean BMAD entries from prompts.yml (preserve user entries) // Clean BMAD entries from prompts.yml (preserve user entries)
const promptsPath = path.join(projectDir, this.rovoDir, this.promptsFile); const promptsPath = path.join(projectDir, this.rovoDir, this.promptsFile);
if (await this.pathExists(promptsPath)) { if (await fs.pathExists(promptsPath)) {
try { try {
const content = await this.readFile(promptsPath); const content = await fs.readFile(promptsPath, 'utf8');
const parsed = yaml.parse(content) || {}; const parsed = yaml.parse(content) || {};
if (Array.isArray(parsed.prompts)) { if (Array.isArray(parsed.prompts)) {
@ -186,7 +201,7 @@ class RovoDevSetup extends BaseIdeSetup {
// If no entries remain, remove the file entirely // If no entries remain, remove the file entirely
await fs.remove(promptsPath); await fs.remove(promptsPath);
} else { } else {
await this.writeFile(promptsPath, yaml.stringify(parsed, { lineWidth: 0 })); await fs.writeFile(promptsPath, yaml.stringify(parsed, { lineWidth: 0 }));
} }
if (!options.silent) { if (!options.silent) {
await prompts.log.message(`Removed ${removedCount} BMAD entries from ${this.promptsFile}`); await prompts.log.message(`Removed ${removedCount} BMAD entries from ${this.promptsFile}`);
@ -202,7 +217,7 @@ class RovoDevSetup extends BaseIdeSetup {
} }
// Remove empty .rovodev directories // Remove empty .rovodev directories
if (await this.pathExists(workflowsPath)) { if (await fs.pathExists(workflowsPath)) {
const remaining = await fs.readdir(workflowsPath); const remaining = await fs.readdir(workflowsPath);
if (remaining.length === 0) { if (remaining.length === 0) {
await fs.remove(workflowsPath); await fs.remove(workflowsPath);
@ -210,7 +225,7 @@ class RovoDevSetup extends BaseIdeSetup {
} }
const rovoDirPath = path.join(projectDir, this.rovoDir); const rovoDirPath = path.join(projectDir, this.rovoDir);
if (await this.pathExists(rovoDirPath)) { if (await fs.pathExists(rovoDirPath)) {
const remaining = await fs.readdir(rovoDirPath); const remaining = await fs.readdir(rovoDirPath);
if (remaining.length === 0) { if (remaining.length === 0) {
await fs.remove(rovoDirPath); await fs.remove(rovoDirPath);