From 0b9290789eef9ed9d1602732c4b0e77c1787298d Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Wed, 3 Dec 2025 22:44:13 -0600 Subject: [PATCH] installer fixes --- .../2-plan-workflows/prd/workflow.md | 2 +- tools/cli/installers/lib/ide/auggie.js | 26 +++++++--- tools/cli/installers/lib/ide/crush.js | 25 +++++++--- tools/cli/installers/lib/ide/cursor.js | 38 ++++++++++----- tools/cli/installers/lib/ide/gemini.js | 48 ++++++++++++++++++- tools/cli/installers/lib/ide/iflow.js | 21 +++++++- tools/cli/installers/lib/ide/kiro-cli.js | 37 +++++++++++--- tools/cli/installers/lib/ide/opencode.js | 6 +-- 8 files changed, 167 insertions(+), 36 deletions(-) diff --git a/src/modules/bmm/workflows/2-plan-workflows/prd/workflow.md b/src/modules/bmm/workflows/2-plan-workflows/prd/workflow.md index b9d738d6..224f24fe 100644 --- a/src/modules/bmm/workflows/2-plan-workflows/prd/workflow.md +++ b/src/modules/bmm/workflows/2-plan-workflows/prd/workflow.md @@ -1,7 +1,7 @@ --- name: create-prd description: Creates a comprehensive PRDs through collaborative step-by-step discovery between two product managers working as peers. -main_config: `{project-root}/{bmad_folder}/bmm/config.yaml` +main_config: '{project-root}/{bmad_folder}/bmm/config.yaml' web_bundle: true --- diff --git a/tools/cli/installers/lib/ide/auggie.js b/tools/cli/installers/lib/ide/auggie.js index 24b809ca..04e08788 100644 --- a/tools/cli/installers/lib/ide/auggie.js +++ b/tools/cli/installers/lib/ide/auggie.js @@ -3,6 +3,7 @@ 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'); /** * Auggie CLI setup handler @@ -33,10 +34,23 @@ class AuggieSetup extends BaseIdeSetup { const agentGen = new AgentCommandGenerator(this.bmadFolderName); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); - // Get tasks, tools, and workflows (standalone only) + // Get tasks, tools, and workflows (ALL workflows now generate commands) const tasks = await this.getTasks(bmadDir, true); const tools = await this.getTools(bmadDir, true); - const workflows = await this.getWorkflows(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 workflow artifacts to expected format + 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, + })); const bmadCommandsDir = path.join(location, 'bmad'); const agentsDir = path.join(bmadCommandsDir, 'agents'); @@ -73,13 +87,11 @@ class AuggieSetup extends BaseIdeSetup { await this.writeFile(targetPath, commandContent); } - // Install workflows + // Install workflows (already generated commands) for (const workflow of workflows) { - const content = await this.readFile(workflow.path); - const commandContent = this.createWorkflowCommand(workflow, content); - + // Use the pre-generated workflow command content const targetPath = path.join(workflowsDir, `${workflow.module}-${workflow.name}.md`); - await this.writeFile(targetPath, commandContent); + await this.writeFile(targetPath, workflow.content); } const totalInstalled = agentArtifacts.length + tasks.length + tools.length + workflows.length; diff --git a/tools/cli/installers/lib/ide/crush.js b/tools/cli/installers/lib/ide/crush.js index c49424bf..0bef6952 100644 --- a/tools/cli/installers/lib/ide/crush.js +++ b/tools/cli/installers/lib/ide/crush.js @@ -3,6 +3,7 @@ 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'); /** * Crush IDE setup handler @@ -34,10 +35,23 @@ class CrushSetup extends BaseIdeSetup { const agentGen = new AgentCommandGenerator(this.bmadFolderName); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); - // Get tasks, tools, and workflows (standalone only) + // Get tasks, tools, and workflows (ALL workflows now generate commands) const tasks = await this.getTasks(bmadDir, true); const tools = await this.getTools(bmadDir, true); - const workflows = await this.getWorkflows(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 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); @@ -113,13 +127,12 @@ class CrushSetup extends BaseIdeSetup { toolCount++; } - // Copy module-specific workflows + // Copy module-specific workflow commands (already generated) const moduleWorkflows = workflows.filter((w) => w.module === module); for (const workflow of moduleWorkflows) { - const content = await this.readFile(workflow.path); - const commandContent = this.createWorkflowCommand(workflow, content); + // Use the pre-generated workflow command content const targetPath = path.join(moduleWorkflowsDir, `${workflow.name}.md`); - await this.writeFile(targetPath, commandContent); + await this.writeFile(targetPath, workflow.content); workflowCount++; } } diff --git a/tools/cli/installers/lib/ide/cursor.js b/tools/cli/installers/lib/ide/cursor.js index e7d92838..183bbced 100644 --- a/tools/cli/installers/lib/ide/cursor.js +++ b/tools/cli/installers/lib/ide/cursor.js @@ -2,6 +2,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); const { AgentCommandGenerator } = require('./shared/agent-command-generator'); +const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); /** * Cursor IDE setup handler @@ -53,10 +54,22 @@ class CursorSetup extends BaseIdeSetup { // Convert artifacts to agent format for index creation const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name })); - // Get tasks, tools, and workflows (standalone only) + // Get tasks, tools, and workflows (ALL workflows now generate commands) const tasks = await this.getTasks(bmadDir, true); const tools = await this.getTools(bmadDir, true); - const workflows = await this.getWorkflows(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, + })); // Create directories for each module const modules = new Set(); @@ -113,18 +126,21 @@ class CursorSetup extends BaseIdeSetup { toolCount++; } - // Process and copy workflows + // Process and copy workflow commands (generated, not raw workflows) let workflowCount = 0; - for (const workflow of workflows) { - const content = await this.readAndProcess(workflow.path, { - module: workflow.module, - name: workflow.name, - }); + 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, workflow.module, 'workflows', `${workflow.name}.mdc`); + const targetPath = path.join(bmadRulesDir, artifact.module, 'workflows', `${path.basename(artifact.relativePath, '.md')}.mdc`); - await this.writeFile(targetPath, content); - workflowCount++; + await this.writeFile(targetPath, content); + workflowCount++; + } } // Create BMAD index file (but NOT .cursorrules - user manages that) diff --git a/tools/cli/installers/lib/ide/gemini.js b/tools/cli/installers/lib/ide/gemini.js index 7de51742..10dd04b9 100644 --- a/tools/cli/installers/lib/ide/gemini.js +++ b/tools/cli/installers/lib/ide/gemini.js @@ -4,6 +4,7 @@ const yaml = require('js-yaml'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); const { AgentCommandGenerator } = require('./shared/agent-command-generator'); +const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); /** * Gemini CLI setup handler @@ -68,9 +69,13 @@ class GeminiSetup extends BaseIdeSetup { const agentGen = new AgentCommandGenerator(this.bmadFolderName); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); - // Get tasks + // Get tasks and workflows (ALL workflows now generate commands) const tasks = await this.getTasks(bmadDir); + // Get ALL workflows using the new workflow command generator + const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName); + const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir); + // Install agents as TOML files with bmad- prefix (flat structure) let agentCount = 0; for (const artifact of agentArtifacts) { @@ -98,17 +103,37 @@ class GeminiSetup extends BaseIdeSetup { console.log(chalk.green(` ✓ Added task: /bmad:tasks:${task.module}:${task.name}`)); } + // Install workflows as TOML files with bmad- prefix (flat structure) + let workflowCount = 0; + for (const artifact of workflowArtifacts) { + if (artifact.type === 'workflow-command') { + // Create TOML wrapper around workflow command content + const tomlContent = await this.createWorkflowToml(artifact); + + // Flat structure: bmad-workflow-{module}-{name}.toml + const workflowName = path.basename(artifact.relativePath, '.md'); + const tomlPath = path.join(commandsDir, `bmad-workflow-${artifact.module}-${workflowName}.toml`); + await this.writeFile(tomlPath, tomlContent); + workflowCount++; + + console.log(chalk.green(` ✓ Added workflow: /bmad:workflows:${artifact.module}:${workflowName}`)); + } + } + console.log(chalk.green(`✓ ${this.name} configured:`)); console.log(chalk.dim(` - ${agentCount} agents configured`)); console.log(chalk.dim(` - ${taskCount} tasks configured`)); + console.log(chalk.dim(` - ${workflowCount} workflows configured`)); console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`)); console.log(chalk.dim(` - Agent activation: /bmad:agents:{agent-name}`)); console.log(chalk.dim(` - Task activation: /bmad:tasks:{task-name}`)); + console.log(chalk.dim(` - Workflow activation: /bmad:workflows:{workflow-name}`)); return { success: true, agents: agentCount, tasks: taskCount, + workflows: workflowCount, }; } @@ -179,6 +204,27 @@ ${contentWithoutFrontmatter} return tomlContent; } + /** + * Create workflow TOML content from artifact + */ + async createWorkflowToml(artifact) { + // Extract description from artifact content + const descriptionMatch = artifact.content.match(/description:\s*"([^"]+)"/); + const description = descriptionMatch + ? descriptionMatch[1] + : `BMAD ${artifact.module.toUpperCase()} Workflow: ${path.basename(artifact.relativePath, '.md')}`; + + // Strip frontmatter from command content + const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; + const contentWithoutFrontmatter = artifact.content.replace(frontmatterRegex, '').trim(); + + return `description = "${description}" +prompt = """ +${contentWithoutFrontmatter} +""" +`; + } + /** * Cleanup Gemini configuration - surgically remove only BMAD files */ diff --git a/tools/cli/installers/lib/ide/iflow.js b/tools/cli/installers/lib/ide/iflow.js index df32d1e7..bbe6d470 100644 --- a/tools/cli/installers/lib/ide/iflow.js +++ b/tools/cli/installers/lib/ide/iflow.js @@ -3,6 +3,7 @@ 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'); /** * iFlow CLI setup handler @@ -29,9 +30,11 @@ class IFlowSetup extends BaseIdeSetup { const commandsDir = path.join(iflowDir, this.commandsDir, 'bmad'); const agentsDir = path.join(commandsDir, 'agents'); const tasksDir = path.join(commandsDir, 'tasks'); + const workflowsDir = path.join(commandsDir, 'workflows'); await this.ensureDir(agentsDir); await this.ensureDir(tasksDir); + await this.ensureDir(workflowsDir); // Generate agent launchers const agentGen = new AgentCommandGenerator(this.bmadFolderName); @@ -47,9 +50,13 @@ class IFlowSetup extends BaseIdeSetup { agentCount++; } - // Get tasks + // Get tasks and workflows (ALL workflows now generate commands) const tasks = await this.getTasks(bmadDir); + // Get ALL workflows using the new workflow command generator + const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName); + const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir); + // Setup tasks as commands let taskCount = 0; for (const task of tasks) { @@ -61,15 +68,27 @@ class IFlowSetup extends BaseIdeSetup { taskCount++; } + // Setup workflows as commands (already generated) + let workflowCount = 0; + for (const artifact of workflowArtifacts) { + if (artifact.type === 'workflow-command') { + const targetPath = path.join(workflowsDir, `${artifact.module}-${path.basename(artifact.relativePath, '.md')}.md`); + await this.writeFile(targetPath, artifact.content); + workflowCount++; + } + } + console.log(chalk.green(`✓ ${this.name} configured:`)); console.log(chalk.dim(` - ${agentCount} agent commands created`)); console.log(chalk.dim(` - ${taskCount} task commands created`)); + console.log(chalk.dim(` - ${workflowCount} workflow commands created`)); console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`)); return { success: true, agents: agentCount, tasks: taskCount, + workflows: workflowCount, }; } diff --git a/tools/cli/installers/lib/ide/kiro-cli.js b/tools/cli/installers/lib/ide/kiro-cli.js index 5ea9bc5f..c2702900 100644 --- a/tools/cli/installers/lib/ide/kiro-cli.js +++ b/tools/cli/installers/lib/ide/kiro-cli.js @@ -156,16 +156,41 @@ class KiroCliSetup extends BaseIdeSetup { return; } - // Extract agent name from ID path (e.g., "{bmad_folder}/bmm/agents/analyst.md" -> "analyst") - const idPath = agentData.agent.metadata.id; - const basename = path.basename(idPath, '.md'); - const agentName = basename.startsWith('bmad-') ? basename : `bmad-${basename}`; + // Extract module from file path + const normalizedPath = path.normalize(agentFile); + const pathParts = normalizedPath.split(path.sep); + const basename = path.basename(agentFile, '.agent.yaml'); + + // Find the module name from path + let moduleName = 'unknown'; + if (pathParts.includes('src')) { + const srcIndex = pathParts.indexOf('src'); + if (srcIndex + 3 < pathParts.length) { + const folderAfterSrc = pathParts[srcIndex + 1]; + // Handle both src/core/agents and src/modules/[module]/agents patterns + if (folderAfterSrc === 'core') { + moduleName = 'core'; + } else if (folderAfterSrc === 'modules') { + moduleName = pathParts[srcIndex + 2]; // The actual module name + } + } + } + + // Extract the agent name from the ID path in YAML if available + let agentBaseName = basename; + if (agentData.agent && agentData.agent.metadata && agentData.agent.metadata.id) { + const idPath = agentData.agent.metadata.id; + agentBaseName = path.basename(idPath, '.md'); + } + + const agentName = `bmad-${moduleName}-${agentBaseName}`; + const sanitizedAgentName = this.sanitizeAgentName(agentName); // Create JSON definition - await this.createAgentDefinitionFromYaml(agentsDir, agentName, agentData); + await this.createAgentDefinitionFromYaml(agentsDir, sanitizedAgentName, agentData); // Create prompt file - await this.createAgentPromptFromYaml(agentsDir, agentName, agentData, projectDir); + await this.createAgentPromptFromYaml(agentsDir, sanitizedAgentName, agentData, projectDir); } /** diff --git a/tools/cli/installers/lib/ide/opencode.js b/tools/cli/installers/lib/ide/opencode.js index b3cf03f3..e6c861a7 100644 --- a/tools/cli/installers/lib/ide/opencode.js +++ b/tools/cli/installers/lib/ide/opencode.js @@ -47,7 +47,7 @@ class OpenCodeSetup extends BaseIdeSetup { agentCount++; } - // Install workflow commands with flat naming: bmad-workflow-{module}-{name}.md + // Install workflow commands with flat naming: bmad-{module}-{workflow-name} const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName); const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir); @@ -55,10 +55,10 @@ class OpenCodeSetup extends BaseIdeSetup { for (const artifact of workflowArtifacts) { if (artifact.type === 'workflow-command') { const commandContent = artifact.content; - // Flat structure: bmad-workflow-{module}-{name}.md + // Flat structure: bmad-{module}-{workflow-name}.md // artifact.relativePath is like: bmm/workflows/plan-project.md const workflowName = path.basename(artifact.relativePath, '.md'); - const targetPath = path.join(commandsBaseDir, `bmad-workflow-${artifact.module}-${workflowName}.md`); + const targetPath = path.join(commandsBaseDir, `bmad-${artifact.module}-${workflowName}.md`); await this.writeFile(targetPath, commandContent); workflowCommandCount++; }