From 686af5b0ee7811d0c56b5bba453077a0e95fc4f1 Mon Sep 17 00:00:00 2001 From: Alex Verkhovsky Date: Wed, 3 Dec 2025 11:14:36 -0700 Subject: [PATCH 01/11] feat: add intelligent routing to quick-dev workflow (#1019) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add escalation threshold and scale-adaptive routing to quick-dev: - Simple requests get standard [t]/[e] choice - Complex requests evaluated against project-levels.yaml - Level 1-2 or uncertain โ†’ tech-spec recommended - Level 3+ โ†’ BMad Method (workflow-init) recommended ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Co-authored-by: Brian --- .../bmad-quick-flow/quick-dev/instructions.md | 111 ++++++++++++++++-- .../bmad-quick-flow/quick-dev/workflow.yaml | 4 + 2 files changed, 108 insertions(+), 7 deletions(-) diff --git a/src/modules/bmm/workflows/bmad-quick-flow/quick-dev/instructions.md b/src/modules/bmm/workflows/bmad-quick-flow/quick-dev/instructions.md index b2b8a331..b1635173 100644 --- a/src/modules/bmm/workflows/bmad-quick-flow/quick-dev/instructions.md +++ b/src/modules/bmm/workflows/bmad-quick-flow/quick-dev/instructions.md @@ -32,18 +32,115 @@ - **[t] Plan first** - Create tech-spec then implement + + + +Evaluate escalation threshold against user input (minimal tokens, no file loading): + +**Triggers escalation** (if 2+ signals present): + +- Multiple components mentioned (e.g., dashboard + api + database) +- System-level language (e.g., platform, integration, architecture) +- Uncertainty about approach (e.g., "how should I", "best way to") +- Multi-layer scope (e.g., UI + backend + data together) +- Extended timeframe (e.g., "this week", "over the next few days") + +**Reduces signal:** + +- Simplicity markers (e.g., "just", "quickly", "fix", "bug", "typo", "simple", "basic", "minor") +- Single file/component focus +- Confident, specific request + +Use holistic judgment, not mechanical keyword matching. + + + + **[t] Plan first** - Create tech-spec then implement **[e] Execute directly** - Start now - - Load and execute {create_tech_spec_workflow} - Continue to implementation after spec complete + + Load and execute {create_tech_spec_workflow} + Continue to implementation after spec complete + + + + Any additional guidance before I begin? (patterns, files, constraints) Or "go" to start. + step_2 + + - - Any additional guidance before I begin? (patterns, files, constraints) Or "go" to start. - step_2 + + + Load {project_levels} and evaluate user input against detection_hints.keywords + Determine level (0-4) using scale-adaptive definitions + + + + **[t] Plan first** - Create tech-spec then implement + +**[e] Execute directly** - Start now + + + Load and execute {create_tech_spec_workflow} + Continue to implementation after spec complete + + + + Any additional guidance before I begin? (patterns, files, constraints) Or "go" to start. + step_2 + + + + + This looks like a focused feature with multiple components. + +**[t] Create tech-spec first** (recommended) +**[w] Seems bigger than quick-dev** โ€” see what BMad Method recommends (workflow-init) +**[e] Execute directly** + + + Load and execute {create_tech_spec_workflow} + Continue to implementation after spec complete + + + + Load and execute {workflow_init} + EXIT quick-dev - user has been routed to BMad Method + + + + Any additional guidance before I begin? (patterns, files, constraints) Or "go" to start. + step_2 + + + + + + This sounds like platform/system work. + +**[w] Start BMad Method** (recommended) (workflow-init) +**[t] Create tech-spec** (lighter planning) +**[e] Execute directly** - feeling lucky + + + Load and execute {workflow_init} + EXIT quick-dev - user has been routed to BMad Method + + + + Load and execute {create_tech_spec_workflow} + Continue to implementation after spec complete + + + + Any additional guidance before I begin? (patterns, files, constraints) Or "go" to start. + step_2 + + + + diff --git a/src/modules/bmm/workflows/bmad-quick-flow/quick-dev/workflow.yaml b/src/modules/bmm/workflows/bmad-quick-flow/quick-dev/workflow.yaml index 3f9ca77e..7c2de639 100644 --- a/src/modules/bmm/workflows/bmad-quick-flow/quick-dev/workflow.yaml +++ b/src/modules/bmm/workflows/bmad-quick-flow/quick-dev/workflow.yaml @@ -25,5 +25,9 @@ create_tech_spec_workflow: "{project-root}/{bmad_folder}/bmm/workflows/bmad-quic party_mode_exec: "{project-root}/{bmad_folder}/core/workflows/party-mode/workflow.md" advanced_elicitation: "{project-root}/{bmad_folder}/core/tasks/advanced-elicitation.xml" +# Routing resources (lazy-loaded) +project_levels: "{project-root}/{bmad_folder}/bmm/workflows/workflow-status/project-levels.yaml" +workflow_init: "{project-root}/{bmad_folder}/bmm/workflows/workflow-status/init/workflow.yaml" + standalone: true web_bundle: false From 41f9cc19134521f944f4b1d320fa7a661859ec6d Mon Sep 17 00:00:00 2001 From: Philip Louw <35452556+igknot@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:17:02 +0200 Subject: [PATCH 02/11] feat: add kiro-cli installer with BMad Core compliance (#993) - Implement KiroCliSetup class extending BaseIdeSetup - Generate 21 agents from YAML sources with JSON configs and markdown prompts - Add runtime resource loading and numbered menu formatting - Include BMad Core validation for required agent fields - Fix agent naming conventions to prevent double prefixes - Add .kiro/ directory to gitignore Follows BMad Method standards for IDE installer integration. Co-authored-by: Brian --- .gitignore | 3 +- tools/cli/installers/lib/ide/kiro-cli.js | 302 +++++++++++++++++++++++ 2 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 tools/cli/installers/lib/ide/kiro-cli.js diff --git a/.gitignore b/.gitignore index cef2ce1a..01026ba6 100644 --- a/.gitignore +++ b/.gitignore @@ -70,4 +70,5 @@ z*/ .codex .github/chatmodes .agent -.agentvibes/ \ No newline at end of file +.agentvibes/ +.kiro/ \ No newline at end of file diff --git a/tools/cli/installers/lib/ide/kiro-cli.js b/tools/cli/installers/lib/ide/kiro-cli.js new file mode 100644 index 00000000..5ea9bc5f --- /dev/null +++ b/tools/cli/installers/lib/ide/kiro-cli.js @@ -0,0 +1,302 @@ +const path = require('node:path'); +const { BaseIdeSetup } = require('./_base-ide'); +const chalk = require('chalk'); +const fs = require('fs-extra'); +const yaml = require('js-yaml'); + +/** + * Kiro CLI setup handler for BMad Method + */ +class KiroCliSetup extends BaseIdeSetup { + constructor() { + super('kiro-cli', 'Kiro CLI', false); + this.configDir = '.kiro'; + this.agentsDir = 'agents'; + } + + /** + * Cleanup old BMAD installation before reinstalling + * @param {string} projectDir - Project directory + */ + async cleanup(projectDir) { + const bmadAgentsDir = path.join(projectDir, this.configDir, this.agentsDir); + + if (await fs.pathExists(bmadAgentsDir)) { + // Remove existing BMad agents + const files = await fs.readdir(bmadAgentsDir); + for (const file of files) { + if (file.startsWith('bmad-') || file.includes('bmad')) { + await fs.remove(path.join(bmadAgentsDir, file)); + } + } + console.log(chalk.dim(` Cleaned old BMAD agents from ${this.name}`)); + } + } + + /** + * Setup Kiro CLI configuration with BMad agents + * @param {string} projectDir - Project directory + * @param {string} bmadDir - BMAD installation directory + * @param {Object} options - Setup options + */ + async setup(projectDir, bmadDir, options = {}) { + console.log(chalk.cyan(`Setting up ${this.name}...`)); + + await this.cleanup(projectDir); + + const kiroDir = path.join(projectDir, this.configDir); + const agentsDir = path.join(kiroDir, this.agentsDir); + + await this.ensureDir(agentsDir); + + // Create BMad agents from source YAML files + await this.createBmadAgentsFromSource(agentsDir, projectDir); + + console.log(chalk.green(`โœ“ ${this.name} configured with BMad agents`)); + } + + /** + * Create BMad agent definitions from source YAML files + * @param {string} agentsDir - Agents directory + * @param {string} projectDir - Project directory + */ + async createBmadAgentsFromSource(agentsDir, projectDir) { + const sourceDir = path.join(__dirname, '../../../../../src/modules'); + + // Find all agent YAML files + const agentFiles = await this.findAgentFiles(sourceDir); + + for (const agentFile of agentFiles) { + try { + await this.processAgentFile(agentFile, agentsDir, projectDir); + } catch (error) { + console.warn(chalk.yellow(`โš ๏ธ Failed to process ${agentFile}: ${error.message}`)); + } + } + } + + /** + * Find all agent YAML files in modules and core + * @param {string} sourceDir - Source modules directory + * @returns {Array} Array of agent file paths + */ + async findAgentFiles(sourceDir) { + const agentFiles = []; + + // Check core agents + const coreAgentsDir = path.join(__dirname, '../../../../../src/core/agents'); + if (await fs.pathExists(coreAgentsDir)) { + const files = await fs.readdir(coreAgentsDir); + + for (const file of files) { + if (file.endsWith('.agent.yaml')) { + agentFiles.push(path.join(coreAgentsDir, file)); + } + } + } + + // Check module agents + if (!(await fs.pathExists(sourceDir))) { + return agentFiles; + } + + const modules = await fs.readdir(sourceDir); + + for (const module of modules) { + const moduleAgentsDir = path.join(sourceDir, module, 'agents'); + + if (await fs.pathExists(moduleAgentsDir)) { + const files = await fs.readdir(moduleAgentsDir); + + for (const file of files) { + if (file.endsWith('.agent.yaml')) { + agentFiles.push(path.join(moduleAgentsDir, file)); + } + } + } + } + + return agentFiles; + } + + /** + * Validate BMad Core compliance + * @param {Object} agentData - Agent YAML data + * @returns {boolean} True if compliant + */ + validateBmadCompliance(agentData) { + const requiredFields = ['agent.metadata.id', 'agent.persona.role', 'agent.persona.principles']; + + for (const field of requiredFields) { + const keys = field.split('.'); + let current = agentData; + + for (const key of keys) { + if (!current || !current[key]) { + return false; + } + current = current[key]; + } + } + + return true; + } + + /** + * Process individual agent YAML file + * @param {string} agentFile - Path to agent YAML file + * @param {string} agentsDir - Target agents directory + * @param {string} projectDir - Project directory + */ + async processAgentFile(agentFile, agentsDir, projectDir) { + const yamlContent = await fs.readFile(agentFile, 'utf8'); + const agentData = yaml.load(yamlContent); + + if (!this.validateBmadCompliance(agentData)) { + 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}`; + + // Create JSON definition + await this.createAgentDefinitionFromYaml(agentsDir, agentName, agentData); + + // Create prompt file + await this.createAgentPromptFromYaml(agentsDir, agentName, agentData, projectDir); + } + + /** + * Sanitize agent name for file naming + * @param {string} name - Agent name + * @returns {string} Sanitized name + */ + sanitizeAgentName(name) { + return name + .toLowerCase() + .replaceAll(/\s+/g, '-') + .replaceAll(/[^a-z0-9-]/g, ''); + } + + /** + * Create agent JSON definition from YAML data + * @param {string} agentsDir - Agents directory + * @param {string} agentName - Agent name (role-based) + * @param {Object} agentData - Agent YAML data + */ + async createAgentDefinitionFromYaml(agentsDir, agentName, agentData) { + const personName = agentData.agent.metadata.name; + const role = agentData.agent.persona.role; + + const agentConfig = { + name: agentName, + description: `${personName} - ${role}`, + prompt: `file://./${agentName}-prompt.md`, + tools: ['*'], + mcpServers: {}, + useLegacyMcpJson: true, + resources: [], + }; + + const agentPath = path.join(agentsDir, `${agentName}.json`); + await fs.writeJson(agentPath, agentConfig, { spaces: 2 }); + } + + /** + * Create agent prompt from YAML data + * @param {string} agentsDir - Agents directory + * @param {string} agentName - Agent name (role-based) + * @param {Object} agentData - Agent YAML data + * @param {string} projectDir - Project directory + */ + async createAgentPromptFromYaml(agentsDir, agentName, agentData, projectDir) { + const promptPath = path.join(agentsDir, `${agentName}-prompt.md`); + + // Generate prompt from YAML data + const prompt = this.generatePromptFromYaml(agentData); + await fs.writeFile(promptPath, prompt); + } + + /** + * Generate prompt content from YAML data + * @param {Object} agentData - Agent YAML data + * @returns {string} Generated prompt + */ + generatePromptFromYaml(agentData) { + const agent = agentData.agent; + const name = agent.metadata.name; + const icon = agent.metadata.icon || '๐Ÿค–'; + const role = agent.persona.role; + const identity = agent.persona.identity; + const style = agent.persona.communication_style; + const principles = agent.persona.principles; + + let prompt = `# ${name} ${icon}\n\n`; + prompt += `## Role\n${role}\n\n`; + + if (identity) { + prompt += `## Identity\n${identity}\n\n`; + } + + if (style) { + prompt += `## Communication Style\n${style}\n\n`; + } + + if (principles) { + prompt += `## Principles\n`; + if (typeof principles === 'string') { + // Handle multi-line string principles + prompt += principles + '\n\n'; + } else if (Array.isArray(principles)) { + // Handle array principles + for (const principle of principles) { + prompt += `- ${principle}\n`; + } + prompt += '\n'; + } + } + + // Add menu items if available + if (agent.menu && agent.menu.length > 0) { + prompt += `## Available Workflows\n`; + for (let i = 0; i < agent.menu.length; i++) { + const item = agent.menu[i]; + prompt += `${i + 1}. **${item.trigger}**: ${item.description}\n`; + } + prompt += '\n'; + } + + prompt += `## Instructions\nYou are ${name}, part of the BMad Method. Follow your role and principles while assisting users with their development needs.\n`; + + return prompt; + } + + /** + * Check if Kiro CLI is available + * @returns {Promise} True if available + */ + async isAvailable() { + try { + const { execSync } = require('node:child_process'); + execSync('kiro-cli --version', { stdio: 'ignore' }); + return true; + } catch { + return false; + } + } + + /** + * Get installation instructions + * @returns {string} Installation instructions + */ + getInstallInstructions() { + return `Install Kiro CLI: + curl -fsSL https://github.com/aws/kiro-cli/releases/latest/download/install.sh | bash + + Or visit: https://github.com/aws/kiro-cli`; + } +} + +module.exports = { KiroCliSetup }; From 73db5538bf50cf08402444f24b9a82e91c3ac5df Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Wed, 3 Dec 2025 19:41:12 -0600 Subject: [PATCH 03/11] roo installer improovement --- .gitignore | 3 +- tools/cli/installers/lib/ide/roo.js | 304 +++++++----------- .../lib/ide/shared/bmad-artifacts.js | 10 + 3 files changed, 132 insertions(+), 185 deletions(-) diff --git a/.gitignore b/.gitignore index 01026ba6..47a82e6e 100644 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,5 @@ z*/ .github/chatmodes .agent .agentvibes/ -.kiro/ \ No newline at end of file +.kiro/ +.roo diff --git a/tools/cli/installers/lib/ide/roo.js b/tools/cli/installers/lib/ide/roo.js index 22f333f6..1352b311 100644 --- a/tools/cli/installers/lib/ide/roo.js +++ b/tools/cli/installers/lib/ide/roo.js @@ -5,34 +5,13 @@ const { AgentCommandGenerator } = require('./shared/agent-command-generator'); /** * Roo IDE setup handler - * Creates custom modes in .roomodes file + * Creates custom commands in .roo/commands directory */ class RooSetup extends BaseIdeSetup { constructor() { super('roo', 'Roo Code'); - this.configFile = '.roomodes'; - this.defaultPermissions = { - dev: { - description: 'Development files', - fileRegex: String.raw`.*\.(js|jsx|ts|tsx|py|java|cpp|c|h|cs|go|rs|php|rb|swift)$`, - }, - config: { - description: 'Configuration files', - fileRegex: String.raw`.*\.(json|yaml|yml|toml|xml|ini|env|config)$`, - }, - docs: { - description: 'Documentation files', - fileRegex: String.raw`.*\.(md|mdx|rst|txt|doc|docx)$`, - }, - styles: { - description: 'Style and design files', - fileRegex: String.raw`.*\.(css|scss|sass|less|stylus)$`, - }, - all: { - description: 'All files', - fileRegex: '.*', - }, - }; + this.configDir = '.roo'; + this.commandsDir = 'commands'; } /** @@ -44,94 +23,96 @@ class RooSetup extends BaseIdeSetup { async setup(projectDir, bmadDir, options = {}) { console.log(chalk.cyan(`Setting up ${this.name}...`)); - // Check for existing .roomodes file - const roomodesPath = path.join(projectDir, this.configFile); - let existingModes = []; - let existingContent = ''; + // Create .roo/commands directory + const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir); + await this.ensureDir(rooCommandsDir); - if (await this.pathExists(roomodesPath)) { - existingContent = await this.readFile(roomodesPath); - // Parse existing modes to avoid duplicates - const modeMatches = existingContent.matchAll(/- slug: ([\w-]+)/g); - for (const match of modeMatches) { - existingModes.push(match[1]); - } - console.log(chalk.yellow(`Found existing .roomodes file with ${existingModes.length} modes`)); - } - - // Generate agent launchers (though Roo will reference the actual .bmad agents) + // Generate agent launchers const agentGen = new AgentCommandGenerator(this.bmadFolderName); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); - // Always use 'all' permissions - users can customize in .roomodes file - const permissionChoice = 'all'; - - // Create modes content - let newModesContent = ''; let addedCount = 0; let skippedCount = 0; for (const artifact of agentArtifacts) { - const slug = `bmad-${artifact.module}-${artifact.name}`; + const commandName = `bmad-${artifact.module}-agent-${artifact.name}`; + const commandPath = path.join(rooCommandsDir, `${commandName}.md`); // Skip if already exists - if (existingModes.includes(slug)) { - console.log(chalk.dim(` Skipping ${slug} - already exists`)); + if (await this.pathExists(commandPath)) { + console.log(chalk.dim(` Skipping ${commandName} - already exists`)); skippedCount++; continue; } - // Read the actual agent file from .bmad for metadata extraction + // Read the actual agent file from .bmad for metadata extraction (installed agents are .md files) const agentPath = path.join(bmadDir, artifact.module, 'agents', `${artifact.name}.md`); const content = await this.readFile(agentPath); - // Create mode entry that references the actual .bmad agent - const modeEntry = await this.createModeEntry( - { module: artifact.module, name: artifact.name, path: agentPath }, - content, - permissionChoice, - projectDir, - ); + // Create command file that references the actual .bmad agent + await this.createCommandFile({ module: artifact.module, name: artifact.name, path: agentPath }, content, commandPath, projectDir); - newModesContent += modeEntry; addedCount++; - console.log(chalk.green(` โœ“ Added mode: ${slug}`)); + console.log(chalk.green(` โœ“ Added command: ${commandName}`)); } - // Build final content - let finalContent = ''; - if (existingContent) { - // Append to existing content - finalContent = existingContent.trim() + '\n' + newModesContent; - } else { - // Create new .roomodes file - finalContent = 'customModes:\n' + newModesContent; - } - - // Write .roomodes file - await this.writeFile(roomodesPath, finalContent); - console.log(chalk.green(`โœ“ ${this.name} configured:`)); - console.log(chalk.dim(` - ${addedCount} modes added`)); + console.log(chalk.dim(` - ${addedCount} commands added`)); if (skippedCount > 0) { - console.log(chalk.dim(` - ${skippedCount} modes skipped (already exist)`)); + console.log(chalk.dim(` - ${skippedCount} commands skipped (already exist)`)); } - console.log(chalk.dim(` - Configuration file: ${this.configFile}`)); - console.log(chalk.dim(` - Permission level: all (unrestricted)`)); - console.log(chalk.yellow(`\n ๐Ÿ’ก Tip: Edit ${this.configFile} to customize file permissions per agent`)); - console.log(chalk.dim(` Modes will be available when you open this project in Roo Code`)); + console.log(chalk.dim(` - Commands directory: ${this.configDir}/${this.commandsDir}/bmad/`)); + console.log(chalk.dim(` Commands will be available when you open this project in Roo Code`)); return { success: true, - modes: addedCount, + commands: addedCount, skipped: skippedCount, }; } /** - * Create a mode entry for an agent + * Create a unified command file for agents + * @param {string} commandPath - Path where to write the command file + * @param {Object} options - Command options + * @param {string} options.name - Display name for the command + * @param {string} options.description - Description for the command + * @param {string} options.agentPath - Path to the agent file (relative to project root) + * @param {string} [options.icon] - Icon emoji (defaults to ๐Ÿค–) + * @param {string} [options.extraContent] - Additional content to include before activation */ - async createModeEntry(agent, content, permissionChoice, projectDir) { + async createAgentCommandFile(commandPath, options) { + const { name, description, agentPath, icon = '๐Ÿค–', extraContent = '' } = options; + + // Build command content with YAML frontmatter + let commandContent = `---\n`; + commandContent += `name: '${icon} ${name}'\n`; + commandContent += `description: '${description}'\n`; + commandContent += `---\n\n`; + + commandContent += `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.\n\n`; + + // Add any extra content (e.g., warnings for custom agents) + if (extraContent) { + commandContent += `${extraContent}\n\n`; + } + + commandContent += `\n`; + commandContent += `1. LOAD the FULL agent file from @${agentPath}\n`; + commandContent += `2. READ its entire contents - this contains the complete agent persona, menu, and instructions\n`; + commandContent += `3. Execute ALL activation steps exactly as written in the agent file\n`; + commandContent += `4. Follow the agent's persona and menu system precisely\n`; + commandContent += `5. Stay in character throughout the session\n`; + commandContent += `\n`; + + // Write command file + await this.writeFile(commandPath, commandContent); + } + + /** + * Create a command file for an agent + */ + async createCommandFile(agent, content, commandPath, projectDir) { // Extract metadata from agent content const titleMatch = content.match(/title="([^"]+)"/); const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); @@ -142,66 +123,16 @@ class RooSetup extends BaseIdeSetup { const whenToUseMatch = content.match(/whenToUse="([^"]+)"/); const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`; - // Get the activation header from central template - const activationHeader = await this.getAgentCommandHeader(); - - const roleDefinitionMatch = content.match(/roleDefinition="([^"]+)"/); - const roleDefinition = roleDefinitionMatch - ? roleDefinitionMatch[1] - : `You are a ${title} specializing in ${title.toLowerCase()} tasks and responsibilities.`; - // Get relative path const relativePath = path.relative(projectDir, agent.path).replaceAll('\\', '/'); - // Determine permissions - const permissions = this.getPermissionsForAgent(agent, permissionChoice); - - // Build mode entry - const slug = `bmad-${agent.module}-${agent.name}`; - let modeEntry = ` - slug: ${slug}\n`; - modeEntry += ` name: '${icon} ${title}'\n`; - - if (permissions && permissions.description) { - modeEntry += ` description: '${permissions.description}'\n`; - } - - modeEntry += ` roleDefinition: ${roleDefinition}\n`; - modeEntry += ` whenToUse: ${whenToUse}\n`; - modeEntry += ` customInstructions: ${activationHeader} Read the full YAML from ${relativePath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`; - modeEntry += ` groups:\n`; - modeEntry += ` - read\n`; - - if (permissions && permissions.fileRegex) { - modeEntry += ` - - edit\n`; - modeEntry += ` - fileRegex: ${permissions.fileRegex}\n`; - modeEntry += ` description: ${permissions.description}\n`; - } else { - modeEntry += ` - edit\n`; - } - - return modeEntry; - } - - /** - * Get permissions configuration for an agent - */ - getPermissionsForAgent(agent, permissionChoice) { - if (permissionChoice === 'custom') { - // Custom logic based on agent name/module - if (agent.name.includes('dev') || agent.name.includes('code')) { - return this.defaultPermissions.dev; - } else if (agent.name.includes('doc') || agent.name.includes('write')) { - return this.defaultPermissions.docs; - } else if (agent.name.includes('config') || agent.name.includes('setup')) { - return this.defaultPermissions.config; - } else if (agent.name.includes('style') || agent.name.includes('css')) { - return this.defaultPermissions.styles; - } - // Default to all for custom agents - return this.defaultPermissions.all; - } - - return this.defaultPermissions[permissionChoice] || null; + // Use unified method + await this.createAgentCommandFile(commandPath, { + name: title, + description: whenToUse, + agentPath: relativePath, + icon: icon, + }); } /** @@ -219,8 +150,26 @@ class RooSetup extends BaseIdeSetup { */ async cleanup(projectDir) { const fs = require('fs-extra'); - const roomodesPath = path.join(projectDir, this.configFile); + const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir); + if (await fs.pathExists(rooCommandsDir)) { + const files = await fs.readdir(rooCommandsDir); + let removedCount = 0; + + for (const file of files) { + if (file.startsWith('bmad-') && file.endsWith('.md')) { + await fs.remove(path.join(rooCommandsDir, file)); + removedCount++; + } + } + + if (removedCount > 0) { + console.log(chalk.dim(`Removed ${removedCount} BMAD commands from .roo/commands/`)); + } + } + + // Also clean up old .roomodes file if it exists + const roomodesPath = path.join(projectDir, '.roomodes'); if (await fs.pathExists(roomodesPath)) { const content = await fs.readFile(roomodesPath, 'utf8'); @@ -245,7 +194,9 @@ class RooSetup extends BaseIdeSetup { // Write back filtered content await fs.writeFile(roomodesPath, filteredLines.join('\n')); - console.log(chalk.dim(`Removed ${removedCount} BMAD modes from .roomodes`)); + if (removedCount > 0) { + console.log(chalk.dim(`Removed ${removedCount} BMAD modes from legacy .roomodes file`)); + } } } @@ -254,68 +205,53 @@ class RooSetup extends BaseIdeSetup { * @param {string} projectDir - Project directory * @param {string} agentName - Agent name (e.g., "fred-commit-poet") * @param {string} agentPath - Path to compiled agent (relative to project root) - * @param {Object} metadata - Agent metadata + * @param {Object} metadata - Agent metadata (unused, kept for compatibility) * @returns {Object} Installation result */ async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { - const roomodesPath = path.join(projectDir, this.configFile); - let existingContent = ''; + const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir); + await this.ensureDir(rooCommandsDir); - // Read existing .roomodes file - if (await this.pathExists(roomodesPath)) { - existingContent = await this.readFile(roomodesPath); - } + const commandName = `bmad-custom-agent-${agentName.toLowerCase()}`; + const commandPath = path.join(rooCommandsDir, `${commandName}.md`); - // Create custom agent mode entry - const slug = `bmad-custom-${agentName.toLowerCase()}`; - const modeEntry = ` - slug: ${slug} - name: 'BMAD Custom: ${agentName}' - description: | - Custom BMAD agent: ${agentName} - - **โš ๏ธ IMPORTANT**: Run @${agentPath} first to load the complete agent! - - This is a launcher for the custom BMAD agent "${agentName}". The agent will follow the persona and instructions from the main agent file. - prompt: | - @${agentPath} - always: false - permissions: all -`; - - // Check if mode already exists - if (existingContent.includes(slug)) { + // Check if command already exists + if (await this.pathExists(commandPath)) { return { ide: 'roo', - path: this.configFile, - command: agentName, + path: path.join(this.configDir, this.commandsDir, `${commandName}.md`), + command: commandName, type: 'custom-agent-launcher', alreadyExists: true, }; } - // Build final content - let finalContent = ''; - if (existingContent) { - // Find customModes section or add it - if (existingContent.includes('customModes:')) { - // Append to existing customModes - finalContent = existingContent + modeEntry; - } else { - // Add customModes section - finalContent = existingContent.trim() + '\n\ncustomModes:\n' + modeEntry; - } - } else { - // Create new .roomodes file with customModes - finalContent = 'customModes:\n' + modeEntry; - } + // Read the custom agent file to extract metadata (same as regular agents) + const fullAgentPath = path.join(projectDir, agentPath); + const content = await this.readFile(fullAgentPath); - // Write .roomodes file - await this.writeFile(roomodesPath, finalContent); + // Extract metadata from agent content + const titleMatch = content.match(/title="([^"]+)"/); + const title = titleMatch ? titleMatch[1] : this.formatTitle(agentName); + + const iconMatch = content.match(/icon="([^"]+)"/); + const icon = iconMatch ? iconMatch[1] : '๐Ÿค–'; + + const whenToUseMatch = content.match(/whenToUse="([^"]+)"/); + const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`; + + // Use unified method without extra content (clean) + await this.createAgentCommandFile(commandPath, { + name: title, + description: whenToUse, + agentPath: agentPath, + icon: icon, + }); return { ide: 'roo', - path: this.configFile, - command: slug, + path: path.join(this.configDir, this.commandsDir, `${commandName}.md`), + command: commandName, type: 'custom-agent-launcher', }; } diff --git a/tools/cli/installers/lib/ide/shared/bmad-artifacts.js b/tools/cli/installers/lib/ide/shared/bmad-artifacts.js index 27184bc9..d05b985e 100644 --- a/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +++ b/tools/cli/installers/lib/ide/shared/bmad-artifacts.js @@ -90,6 +90,11 @@ async function getAgentsFromDir(dirPath, moduleName) { continue; } + // Skip README files and other non-agent files + if (file.toLowerCase() === 'readme.md' || file.toLowerCase().startsWith('readme-')) { + continue; + } + if (file.includes('.customize.')) { continue; } @@ -101,6 +106,11 @@ async function getAgentsFromDir(dirPath, moduleName) { continue; } + // Only include files that have agent-specific content (compiled agents have tag) + if (!content.includes(' Date: Wed, 3 Dec 2025 19:22:59 -0700 Subject: [PATCH 04/11] feat(discord): compact plain-text notifications with bug fixes (#1021) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix esc() bracket expression (] must be first in POSIX regex) - Fix delete job: inline helper to avoid checkout of deleted ref - Fix issue notifications: attribute close/reopen to actor, not author - Simplify trunc() comment (remove false Unicode-safe claim) - Smart truncation with wall-of-text detection - Escape markdown and @mentions for safe display ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Co-authored-by: Brian --- .github/scripts/discord-helpers.sh | 15 ++ .github/workflows/discord.yaml | 288 ++++++++++++++++++++++++++++- 2 files changed, 294 insertions(+), 9 deletions(-) create mode 100644 .github/scripts/discord-helpers.sh diff --git a/.github/scripts/discord-helpers.sh b/.github/scripts/discord-helpers.sh new file mode 100644 index 00000000..191b9037 --- /dev/null +++ b/.github/scripts/discord-helpers.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Discord notification helper functions + +# Escape markdown special chars and @mentions for safe Discord display +# Bracket expression: ] must be first, then other chars. In POSIX bracket expr, \ is literal. +esc() { sed -e 's/[][\*_()~`>]/\\&/g' -e 's/@/@ /g'; } + +# Truncate to $1 chars (or 80 if wall-of-text with <3 spaces) +trunc() { + local max=$1 + local txt=$(tr '\n\r' ' ' | cut -c1-"$max") + local spaces=$(printf '%s' "$txt" | tr -cd ' ' | wc -c) + [ "$spaces" -lt 3 ] && [ ${#txt} -gt 80 ] && txt=$(printf '%s' "$txt" | cut -c1-80) + printf '%s' "$txt" +} diff --git a/.github/workflows/discord.yaml b/.github/workflows/discord.yaml index 13316da7..109bbb16 100644 --- a/.github/workflows/discord.yaml +++ b/.github/workflows/discord.yaml @@ -1,16 +1,286 @@ name: Discord Notification -"on": [pull_request, release, create, delete, issue_comment, pull_request_review, pull_request_review_comment] +on: + pull_request: + types: [opened, closed, reopened, ready_for_review] + release: + types: [published] + create: + delete: + issue_comment: + types: [created] + pull_request_review: + types: [submitted] + pull_request_review_comment: + types: [created] + issues: + types: [opened, closed, reopened] + +env: + MAX_TITLE: 100 + MAX_BODY: 250 jobs: - notify: + pull_request: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + sparse-checkout: .github/scripts + sparse-checkout-cone-mode: false + - name: Notify Discord + env: + WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + ACTION: ${{ github.event.action }} + MERGED: ${{ github.event.pull_request.merged }} + PR_NUM: ${{ github.event.pull_request.number }} + PR_URL: ${{ github.event.pull_request.html_url }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_USER: ${{ github.event.pull_request.user.login }} + PR_BODY: ${{ github.event.pull_request.body }} + run: | + set -o pipefail + source .github/scripts/discord-helpers.sh + [ -z "$WEBHOOK" ] && exit 0 + + if [ "$ACTION" = "opened" ]; then ICON="๐Ÿ”€"; LABEL="New PR" + elif [ "$ACTION" = "closed" ] && [ "$MERGED" = "true" ]; then ICON="๐ŸŽ‰"; LABEL="Merged" + elif [ "$ACTION" = "closed" ]; then ICON="โŒ"; LABEL="Closed" + elif [ "$ACTION" = "reopened" ]; then ICON="๐Ÿ”„"; LABEL="Reopened" + else ICON="๐Ÿ“‹"; LABEL="Ready"; fi + + TITLE=$(printf '%s' "$PR_TITLE" | trunc $MAX_TITLE | esc) + [ ${#PR_TITLE} -gt $MAX_TITLE ] && TITLE="${TITLE}..." + BODY=$(printf '%s' "$PR_BODY" | trunc $MAX_BODY | esc) + [ -n "$PR_BODY" ] && [ ${#PR_BODY} -gt $MAX_BODY ] && BODY="${BODY}..." + [ -n "$BODY" ] && BODY=" ยท $BODY" + USER=$(printf '%s' "$PR_USER" | esc) + + MSG="$ICON **[$LABEL #$PR_NUM: $TITLE](<$PR_URL>)**"$'\n'"by @$USER$BODY" + jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @- + + issues: + if: github.event_name == 'issues' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + sparse-checkout: .github/scripts + sparse-checkout-cone-mode: false + - name: Notify Discord + env: + WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + ACTION: ${{ github.event.action }} + ISSUE_NUM: ${{ github.event.issue.number }} + ISSUE_URL: ${{ github.event.issue.html_url }} + ISSUE_TITLE: ${{ github.event.issue.title }} + ISSUE_USER: ${{ github.event.issue.user.login }} + ISSUE_BODY: ${{ github.event.issue.body }} + ACTOR: ${{ github.actor }} + run: | + set -o pipefail + source .github/scripts/discord-helpers.sh + [ -z "$WEBHOOK" ] && exit 0 + + if [ "$ACTION" = "opened" ]; then ICON="๐Ÿ›"; LABEL="New Issue"; USER="$ISSUE_USER" + elif [ "$ACTION" = "closed" ]; then ICON="โœ…"; LABEL="Closed"; USER="$ACTOR" + else ICON="๐Ÿ”„"; LABEL="Reopened"; USER="$ACTOR"; fi + + TITLE=$(printf '%s' "$ISSUE_TITLE" | trunc $MAX_TITLE | esc) + [ ${#ISSUE_TITLE} -gt $MAX_TITLE ] && TITLE="${TITLE}..." + BODY=$(printf '%s' "$ISSUE_BODY" | trunc $MAX_BODY | esc) + [ -n "$ISSUE_BODY" ] && [ ${#ISSUE_BODY} -gt $MAX_BODY ] && BODY="${BODY}..." + [ -n "$BODY" ] && BODY=" ยท $BODY" + USER=$(printf '%s' "$USER" | esc) + + MSG="$ICON **[$LABEL #$ISSUE_NUM: $TITLE](<$ISSUE_URL>)**"$'\n'"by @$USER$BODY" + jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @- + + issue_comment: + if: github.event_name == 'issue_comment' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + sparse-checkout: .github/scripts + sparse-checkout-cone-mode: false + - name: Notify Discord + env: + WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + IS_PR: ${{ github.event.issue.pull_request && 'true' || 'false' }} + ISSUE_NUM: ${{ github.event.issue.number }} + ISSUE_TITLE: ${{ github.event.issue.title }} + COMMENT_URL: ${{ github.event.comment.html_url }} + COMMENT_USER: ${{ github.event.comment.user.login }} + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + set -o pipefail + source .github/scripts/discord-helpers.sh + [ -z "$WEBHOOK" ] && exit 0 + + [ "$IS_PR" = "true" ] && TYPE="PR" || TYPE="Issue" + + TITLE=$(printf '%s' "$ISSUE_TITLE" | trunc $MAX_TITLE | esc) + [ ${#ISSUE_TITLE} -gt $MAX_TITLE ] && TITLE="${TITLE}..." + BODY=$(printf '%s' "$COMMENT_BODY" | trunc $MAX_BODY | esc) + [ ${#COMMENT_BODY} -gt $MAX_BODY ] && BODY="${BODY}..." + USER=$(printf '%s' "$COMMENT_USER" | esc) + + MSG="๐Ÿ’ฌ **[Comment on $TYPE #$ISSUE_NUM: $TITLE](<$COMMENT_URL>)**"$'\n'"@$USER: $BODY" + jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @- + + pull_request_review: + if: github.event_name == 'pull_request_review' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + sparse-checkout: .github/scripts + sparse-checkout-cone-mode: false + - name: Notify Discord + env: + WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + STATE: ${{ github.event.review.state }} + PR_NUM: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + REVIEW_URL: ${{ github.event.review.html_url }} + REVIEW_USER: ${{ github.event.review.user.login }} + REVIEW_BODY: ${{ github.event.review.body }} + run: | + set -o pipefail + source .github/scripts/discord-helpers.sh + [ -z "$WEBHOOK" ] && exit 0 + + if [ "$STATE" = "approved" ]; then ICON="โœ…"; LABEL="Approved" + elif [ "$STATE" = "changes_requested" ]; then ICON="๐Ÿ”ง"; LABEL="Changes Requested" + else ICON="๐Ÿ‘€"; LABEL="Reviewed"; fi + + TITLE=$(printf '%s' "$PR_TITLE" | trunc $MAX_TITLE | esc) + [ ${#PR_TITLE} -gt $MAX_TITLE ] && TITLE="${TITLE}..." + BODY=$(printf '%s' "$REVIEW_BODY" | trunc $MAX_BODY | esc) + [ -n "$REVIEW_BODY" ] && [ ${#REVIEW_BODY} -gt $MAX_BODY ] && BODY="${BODY}..." + [ -n "$BODY" ] && BODY=": $BODY" + USER=$(printf '%s' "$REVIEW_USER" | esc) + + MSG="$ICON **[$LABEL PR #$PR_NUM: $TITLE](<$REVIEW_URL>)**"$'\n'"@$USER$BODY" + jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @- + + pull_request_review_comment: + if: github.event_name == 'pull_request_review_comment' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + sparse-checkout: .github/scripts + sparse-checkout-cone-mode: false + - name: Notify Discord + env: + WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + PR_NUM: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + COMMENT_URL: ${{ github.event.comment.html_url }} + COMMENT_USER: ${{ github.event.comment.user.login }} + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + set -o pipefail + source .github/scripts/discord-helpers.sh + [ -z "$WEBHOOK" ] && exit 0 + + TITLE=$(printf '%s' "$PR_TITLE" | trunc $MAX_TITLE | esc) + [ ${#PR_TITLE} -gt $MAX_TITLE ] && TITLE="${TITLE}..." + BODY=$(printf '%s' "$COMMENT_BODY" | trunc $MAX_BODY | esc) + [ ${#COMMENT_BODY} -gt $MAX_BODY ] && BODY="${BODY}..." + USER=$(printf '%s' "$COMMENT_USER" | esc) + + MSG="๐Ÿ’ญ **[Review Comment PR #$PR_NUM: $TITLE](<$COMMENT_URL>)**"$'\n'"@$USER: $BODY" + jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @- + + release: + if: github.event_name == 'release' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + sparse-checkout: .github/scripts + sparse-checkout-cone-mode: false + - name: Notify Discord + env: + WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + TAG: ${{ github.event.release.tag_name }} + NAME: ${{ github.event.release.name }} + URL: ${{ github.event.release.html_url }} + RELEASE_BODY: ${{ github.event.release.body }} + run: | + set -o pipefail + source .github/scripts/discord-helpers.sh + [ -z "$WEBHOOK" ] && exit 0 + + REL_NAME=$(printf '%s' "$NAME" | trunc $MAX_TITLE | esc) + [ ${#NAME} -gt $MAX_TITLE ] && REL_NAME="${REL_NAME}..." + BODY=$(printf '%s' "$RELEASE_BODY" | trunc $MAX_BODY | esc) + [ -n "$RELEASE_BODY" ] && [ ${#RELEASE_BODY} -gt $MAX_BODY ] && BODY="${BODY}..." + [ -n "$BODY" ] && BODY=" ยท $BODY" + TAG_ESC=$(printf '%s' "$TAG" | esc) + + MSG="๐Ÿš€ **[Release $TAG_ESC: $REL_NAME](<$URL>)**"$'\n'"$BODY" + jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @- + + create: + if: github.event_name == 'create' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + sparse-checkout: .github/scripts + sparse-checkout-cone-mode: false + - name: Notify Discord + env: + WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + REF_TYPE: ${{ github.event.ref_type }} + REF: ${{ github.event.ref }} + ACTOR: ${{ github.actor }} + REPO_URL: ${{ github.event.repository.html_url }} + run: | + set -o pipefail + source .github/scripts/discord-helpers.sh + [ -z "$WEBHOOK" ] && exit 0 + + [ "$REF_TYPE" = "branch" ] && ICON="๐ŸŒฟ" || ICON="๐Ÿท๏ธ" + REF_TRUNC=$(printf '%s' "$REF" | trunc $MAX_TITLE) + [ ${#REF} -gt $MAX_TITLE ] && REF_TRUNC="${REF_TRUNC}..." + REF_ESC=$(printf '%s' "$REF_TRUNC" | esc) + REF_URL=$(jq -rn --arg ref "$REF" '$ref | @uri') + ACTOR_ESC=$(printf '%s' "$ACTOR" | esc) + MSG="$ICON **${REF_TYPE^} created: [$REF_ESC](<$REPO_URL/tree/$REF_URL>)** by @$ACTOR_ESC" + jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @- + + delete: + if: github.event_name == 'delete' runs-on: ubuntu-latest steps: - name: Notify Discord - uses: sarisia/actions-status-discord@v1 - if: always() - with: - webhook: ${{ secrets.DISCORD_WEBHOOK }} - status: ${{ job.status }} - title: "Triggered by ${{ github.event_name }}" - color: 0x5865F2 + env: + WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + REF_TYPE: ${{ github.event.ref_type }} + REF: ${{ github.event.ref }} + ACTOR: ${{ github.actor }} + run: | + set -o pipefail + [ -z "$WEBHOOK" ] && exit 0 + esc() { sed -e 's/[][\*_()~`>]/\\&/g' -e 's/@/@ /g'; } + trunc() { tr '\n\r' ' ' | cut -c1-"$1"; } + + REF_TRUNC=$(printf '%s' "$REF" | trunc 100) + [ ${#REF} -gt 100 ] && REF_TRUNC="${REF_TRUNC}..." + REF_ESC=$(printf '%s' "$REF_TRUNC" | esc) + ACTOR_ESC=$(printf '%s' "$ACTOR" | esc) + MSG="๐Ÿ—‘๏ธ **${REF_TYPE^} deleted: $REF_ESC** by @$ACTOR_ESC" + jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @- From aa1cf76f884fecbf03e82076a7d4bc4da94a57e1 Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Wed, 3 Dec 2025 21:35:44 -0600 Subject: [PATCH 05/11] new workflow types generate slash commands --- src/core/workflows/brainstorming/workflow.md | 2 +- src/core/workflows/party-mode/workflow.md | 2 +- .../bmb/workflows/create-agent/workflow.md | 2 +- .../bmb/workflows/create-workflow/workflow.md | 2 +- .../bmb/workflows/edit-agent/workflow.md | 2 +- .../bmb/workflows/edit-workflow/workflow.md | 2 +- .../workflow-compliance-check/workflow.md | 2 +- .../1-analysis/product-brief/workflow.md | 2 +- .../workflows/1-analysis/research/workflow.md | 3 +- .../create-ux-design/workflow.md | 6 +++ .../2-plan-workflows/prd/workflow.md | 2 +- .../3-solutioning/architecture/workflow.md | 3 +- .../create-epics-and-stories/workflow.md | 2 +- .../implementation-readiness/workflow.md | 2 +- .../generate-project-context/workflow.md | 2 +- .../installers/lib/core/manifest-generator.js | 37 ++++++++++++------- .../ide/shared/workflow-command-generator.js | 33 +++++++++-------- .../lib/ide/templates/workflow-commander.md | 5 +++ 18 files changed, 68 insertions(+), 43 deletions(-) create mode 100644 tools/cli/installers/lib/ide/templates/workflow-commander.md diff --git a/src/core/workflows/brainstorming/workflow.md b/src/core/workflows/brainstorming/workflow.md index f2793fe7..156a9bb5 100644 --- a/src/core/workflows/brainstorming/workflow.md +++ b/src/core/workflows/brainstorming/workflow.md @@ -1,5 +1,5 @@ --- -name: Brainstorming Session +name: brainstorming-session description: Facilitate interactive brainstorming sessions using diverse creative techniques and ideation methods context_file: '' # Optional context file path for project-specific guidance --- diff --git a/src/core/workflows/party-mode/workflow.md b/src/core/workflows/party-mode/workflow.md index 26d7a507..b3147ad0 100644 --- a/src/core/workflows/party-mode/workflow.md +++ b/src/core/workflows/party-mode/workflow.md @@ -1,5 +1,5 @@ --- -name: Party Mode +name: party-mode description: Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations --- diff --git a/src/modules/bmb/workflows/create-agent/workflow.md b/src/modules/bmb/workflows/create-agent/workflow.md index 0893ff68..503df318 100644 --- a/src/modules/bmb/workflows/create-agent/workflow.md +++ b/src/modules/bmb/workflows/create-agent/workflow.md @@ -1,5 +1,5 @@ --- -name: Create Agent +name: create-agent description: Interactive workflow to build BMAD Core compliant agents with optional brainstorming, persona development, and command structure web_bundle: true --- diff --git a/src/modules/bmb/workflows/create-workflow/workflow.md b/src/modules/bmb/workflows/create-workflow/workflow.md index 55d80e94..6b4140d5 100644 --- a/src/modules/bmb/workflows/create-workflow/workflow.md +++ b/src/modules/bmb/workflows/create-workflow/workflow.md @@ -1,5 +1,5 @@ --- -name: Create Workflow +name: create-workflow description: Create structured standalone workflows using markdown-based step architecture web_bundle: true --- diff --git a/src/modules/bmb/workflows/edit-agent/workflow.md b/src/modules/bmb/workflows/edit-agent/workflow.md index 0c7927fd..81462cbb 100644 --- a/src/modules/bmb/workflows/edit-agent/workflow.md +++ b/src/modules/bmb/workflows/edit-agent/workflow.md @@ -1,5 +1,5 @@ --- -name: Edit Agent +name: edit-agent description: Edit existing BMAD agents while following all best practices and conventions web_bundle: false --- diff --git a/src/modules/bmb/workflows/edit-workflow/workflow.md b/src/modules/bmb/workflows/edit-workflow/workflow.md index 9a275bc3..d4d62f96 100644 --- a/src/modules/bmb/workflows/edit-workflow/workflow.md +++ b/src/modules/bmb/workflows/edit-workflow/workflow.md @@ -1,5 +1,5 @@ --- -name: Edit Workflow +name: edit-workflow description: Intelligent workflow editor that helps modify existing workflows while following best practices web_bundle: true --- diff --git a/src/modules/bmb/workflows/workflow-compliance-check/workflow.md b/src/modules/bmb/workflows/workflow-compliance-check/workflow.md index 049366b4..2fb39bd2 100644 --- a/src/modules/bmb/workflows/workflow-compliance-check/workflow.md +++ b/src/modules/bmb/workflows/workflow-compliance-check/workflow.md @@ -1,5 +1,5 @@ --- -name: Workflow Compliance Check +name: workflow-compliance-check description: Systematic validation of workflows against BMAD standards with adversarial analysis and detailed reporting web_bundle: false --- diff --git a/src/modules/bmm/workflows/1-analysis/product-brief/workflow.md b/src/modules/bmm/workflows/1-analysis/product-brief/workflow.md index d2a7ab71..a070d3ce 100644 --- a/src/modules/bmm/workflows/1-analysis/product-brief/workflow.md +++ b/src/modules/bmm/workflows/1-analysis/product-brief/workflow.md @@ -1,5 +1,5 @@ --- -name: Product Brief Workflow +name: create-product-brief description: Create comprehensive product briefs through collaborative step-by-step discovery as creative Business Analyst working with the user as peers. web_bundle: true --- diff --git a/src/modules/bmm/workflows/1-analysis/research/workflow.md b/src/modules/bmm/workflows/1-analysis/research/workflow.md index 8ca1ea3e..cbbacfd9 100644 --- a/src/modules/bmm/workflows/1-analysis/research/workflow.md +++ b/src/modules/bmm/workflows/1-analysis/research/workflow.md @@ -1,6 +1,7 @@ --- -name: Research Workflow +name: research description: Conduct comprehensive research across multiple domains using current web data and verified sources - Market, Technical, Domain and other research types. +web_bundle: true --- # Research Workflow diff --git a/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md b/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md index 03514f8d..1810e94d 100644 --- a/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +++ b/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md @@ -1,3 +1,9 @@ +--- +name: create-ux-design +description: Work with a peer UX Design expert to plan your applications UX patterns, look and feel. +web_bundle: true +--- + # Create UX Design Workflow **Goal:** Create comprehensive UX design specifications through collaborative visual exploration and informed decision-making where you act as a UX facilitator working with a product stakeholder. 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 6cee6a80..b9d738d6 100644 --- a/src/modules/bmm/workflows/2-plan-workflows/prd/workflow.md +++ b/src/modules/bmm/workflows/2-plan-workflows/prd/workflow.md @@ -1,5 +1,5 @@ --- -name: PRD Workflow +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` web_bundle: true diff --git a/src/modules/bmm/workflows/3-solutioning/architecture/workflow.md b/src/modules/bmm/workflows/3-solutioning/architecture/workflow.md index b59b48e2..7d5deeb7 100644 --- a/src/modules/bmm/workflows/3-solutioning/architecture/workflow.md +++ b/src/modules/bmm/workflows/3-solutioning/architecture/workflow.md @@ -1,6 +1,7 @@ --- -name: Architecture Workflow +name: create-architecture description: Collaborative architectural decision facilitation for AI-agent consistency. Replaces template-driven architecture with intelligent, adaptive conversation that produces a decision-focused architecture document optimized for preventing agent conflicts. +web_bundle: true --- # Architecture Workflow diff --git a/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md b/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md index 2590627a..2975980a 100644 --- a/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +++ b/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md @@ -1,5 +1,5 @@ --- -name: 'Create Epics and Stories' +name: create-epics-stories description: 'Transform PRD requirements and Architecture decisions into comprehensive stories organized by user value. This workflow requires completed PRD + Architecture documents (UX recommended if UI exists) and breaks down requirements into implementation-ready epics and user stories that incorporate all available technical and design context. Creates detailed, actionable stories with complete acceptance criteria for development teams.' web_bundle: true --- diff --git a/src/modules/bmm/workflows/3-solutioning/implementation-readiness/workflow.md b/src/modules/bmm/workflows/3-solutioning/implementation-readiness/workflow.md index 989b659d..2483cde8 100644 --- a/src/modules/bmm/workflows/3-solutioning/implementation-readiness/workflow.md +++ b/src/modules/bmm/workflows/3-solutioning/implementation-readiness/workflow.md @@ -1,5 +1,5 @@ --- -name: 'Implementation Readiness' +name: check-implementation-readiness description: 'Critical validation workflow that assesses PRD, Architecture, and Epics & Stories for completeness and alignment before implementation. Uses adversarial review approach to find gaps and issues.' web_bundle: false --- diff --git a/src/modules/bmm/workflows/generate-project-context/workflow.md b/src/modules/bmm/workflows/generate-project-context/workflow.md index a9c463e9..934ebff9 100644 --- a/src/modules/bmm/workflows/generate-project-context/workflow.md +++ b/src/modules/bmm/workflows/generate-project-context/workflow.md @@ -1,5 +1,5 @@ --- -name: Generate Project Context +name: generate-project-context description: Creates a concise project_context.md file with critical rules and patterns that AI agents must follow when implementing code. Optimized for LLM context efficiency. --- diff --git a/tools/cli/installers/lib/core/manifest-generator.js b/tools/cli/installers/lib/core/manifest-generator.js index 1dbb8ea6..644fd494 100644 --- a/tools/cli/installers/lib/core/manifest-generator.js +++ b/tools/cli/installers/lib/core/manifest-generator.js @@ -105,7 +105,7 @@ class ManifestGenerator { } /** - * Recursively find and parse workflow.yaml files + * Recursively find and parse workflow.yaml and workflow.md files */ async getWorkflowsFromPath(basePath, moduleName) { const workflows = []; @@ -126,11 +126,23 @@ class ManifestGenerator { // Recurse into subdirectories const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name; await findWorkflows(fullPath, newRelativePath); - } else if (entry.name === 'workflow.yaml') { - // Parse workflow file + } else if (entry.name === 'workflow.yaml' || entry.name === 'workflow.md') { + // Parse workflow file (both YAML and MD formats) try { const content = await fs.readFile(fullPath, 'utf8'); - const workflow = yaml.load(content); + + let workflow; + if (entry.name === 'workflow.yaml') { + // Parse YAML workflow + workflow = yaml.load(content); + } else { + // Parse MD workflow with YAML frontmatter + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + if (!frontmatterMatch) { + continue; // Skip MD files without frontmatter + } + workflow = yaml.load(frontmatterMatch[1]); + } // Skip template workflows (those with placeholder values) if (workflow.name && workflow.name.includes('{') && workflow.name.includes('}')) { @@ -141,18 +153,15 @@ class ManifestGenerator { // Build relative path for installation const installPath = moduleName === 'core' - ? `${this.bmadFolderName}/core/workflows/${relativePath}/workflow.yaml` - : `${this.bmadFolderName}/${moduleName}/workflows/${relativePath}/workflow.yaml`; - - // Check for standalone property (default: false) - const standalone = workflow.standalone === true; + ? `${this.bmadFolderName}/core/workflows/${relativePath}/${entry.name}` + : `${this.bmadFolderName}/${moduleName}/workflows/${relativePath}/${entry.name}`; + // ALL workflows now generate commands - no standalone property needed workflows.push({ name: workflow.name, description: workflow.description.replaceAll('"', '""'), // Escape quotes for CSV module: moduleName, path: installPath, - standalone: standalone, }); // Add to files list @@ -541,12 +550,12 @@ class ManifestGenerator { async writeWorkflowManifest(cfgDir) { const csvPath = path.join(cfgDir, 'workflow-manifest.csv'); - // Create CSV header with standalone column - let csv = 'name,description,module,path,standalone\n'; + // Create CSV header - removed standalone column as ALL workflows now generate commands + let csv = 'name,description,module,path\n'; - // Add all workflows + // Add all workflows - no standalone property needed anymore for (const workflow of this.workflows) { - csv += `"${workflow.name}","${workflow.description}","${workflow.module}","${workflow.path}","${workflow.standalone}"\n`; + csv += `"${workflow.name}","${workflow.description}","${workflow.module}","${workflow.path}"\n`; } await fs.writeFile(csvPath, csv); diff --git a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js index e7a2fe9a..e3280e8c 100644 --- a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js @@ -25,16 +25,16 @@ class WorkflowCommandGenerator { return { generated: 0 }; } - // Filter to only standalone workflows - const standaloneWorkflows = workflows.filter((w) => w.standalone === 'true' || w.standalone === true); + // ALL workflows now generate commands - no standalone filtering + const allWorkflows = workflows; // Base commands directory const baseCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad'); let generatedCount = 0; - // Generate a command file for each standalone workflow, organized by module - for (const workflow of standaloneWorkflows) { + // Generate a command file for each workflow, organized by module + for (const workflow of allWorkflows) { const moduleWorkflowsDir = path.join(baseCommandsDir, workflow.module, 'workflows'); await fs.ensureDir(moduleWorkflowsDir); @@ -46,7 +46,7 @@ class WorkflowCommandGenerator { } // Also create a workflow launcher README in each module - const groupedWorkflows = this.groupWorkflowsByModule(standaloneWorkflows); + const groupedWorkflows = this.groupWorkflowsByModule(allWorkflows); await this.createModuleWorkflowLaunchers(baseCommandsDir, groupedWorkflows); return { generated: generatedCount }; @@ -59,12 +59,12 @@ class WorkflowCommandGenerator { return { artifacts: [], counts: { commands: 0, launchers: 0 } }; } - // Filter to only standalone workflows - const standaloneWorkflows = workflows.filter((w) => w.standalone === 'true' || w.standalone === true); + // ALL workflows now generate commands - no standalone filtering + const allWorkflows = workflows; const artifacts = []; - for (const workflow of standaloneWorkflows) { + for (const workflow of allWorkflows) { const commandContent = await this.generateCommandContent(workflow, bmadDir); artifacts.push({ type: 'workflow-command', @@ -75,7 +75,7 @@ class WorkflowCommandGenerator { }); } - const groupedWorkflows = this.groupWorkflowsByModule(standaloneWorkflows); + const groupedWorkflows = this.groupWorkflowsByModule(allWorkflows); for (const [module, launcherContent] of Object.entries(this.buildModuleWorkflowLaunchers(groupedWorkflows))) { artifacts.push({ type: 'workflow-launcher', @@ -89,7 +89,7 @@ class WorkflowCommandGenerator { return { artifacts, counts: { - commands: standaloneWorkflows.length, + commands: allWorkflows.length, launchers: Object.keys(groupedWorkflows).length, }, }; @@ -99,8 +99,13 @@ class WorkflowCommandGenerator { * Generate command content for a workflow */ async generateCommandContent(workflow, bmadDir) { - // Load the template - const template = await fs.readFile(this.templatePath, 'utf8'); + // Determine template based on workflow file type + const isMarkdownWorkflow = workflow.path.endsWith('workflow.md'); + const templateName = isMarkdownWorkflow ? 'workflow-commander.md' : 'workflow-command-template.md'; + const templatePath = path.join(path.dirname(this.templatePath), templateName); + + // Load the appropriate template + const template = await fs.readFile(templatePath, 'utf8'); // Convert source path to installed path // From: /Users/.../src/modules/bmm/workflows/.../workflow.yaml @@ -127,9 +132,7 @@ class WorkflowCommandGenerator { .replaceAll('{{description}}', workflow.description) .replaceAll('{{workflow_path}}', workflowPath) .replaceAll('{bmad_folder}', this.bmadFolderName) - .replaceAll('{*bmad_folder*}', '{bmad_folder}') - .replaceAll('{{interactive}}', workflow.interactive) - .replaceAll('{{author}}', workflow.author || 'BMAD'); + .replaceAll('{*bmad_folder*}', '{bmad_folder}'); } /** diff --git a/tools/cli/installers/lib/ide/templates/workflow-commander.md b/tools/cli/installers/lib/ide/templates/workflow-commander.md new file mode 100644 index 00000000..3645c1a2 --- /dev/null +++ b/tools/cli/installers/lib/ide/templates/workflow-commander.md @@ -0,0 +1,5 @@ +--- +description: '{{description}}' +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{{workflow_path}}, READ its entire contents and follow its directions exactly! From 0b9290789eef9ed9d1602732c4b0e77c1787298d Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Wed, 3 Dec 2025 22:44:13 -0600 Subject: [PATCH 06/11] 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++; } From f99e192e74d2b0242e3750db9f85199a67fe05c6 Mon Sep 17 00:00:00 2001 From: Murat K Ozcan <34237651+muratkeremozcan@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:30:20 -0600 Subject: [PATCH 07/11] fix: tea ci nvmrc (#1036) --- .../testarch/ci/github-actions-template.yaml | 39 +++++++++++++++++-- .../testarch/ci/gitlab-ci-template.yaml | 29 ++++++++++++-- .../bmm/workflows/testarch/ci/instructions.md | 4 +- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/modules/bmm/workflows/testarch/ci/github-actions-template.yaml b/src/modules/bmm/workflows/testarch/ci/github-actions-template.yaml index 0eefd180..9f09a73f 100644 --- a/src/modules/bmm/workflows/testarch/ci/github-actions-template.yaml +++ b/src/modules/bmm/workflows/testarch/ci/github-actions-template.yaml @@ -27,10 +27,21 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Determine Node version + id: node-version + run: | + if [ -f .nvmrc ]; then + echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT" + echo "Using Node from .nvmrc" + else + echo "value=24" >> "$GITHUB_OUTPUT" + echo "Using default Node 24 (current LTS)" + fi + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version-file: ".nvmrc" + node-version: ${{ steps.node-version.outputs.value }} cache: "npm" - name: Install dependencies @@ -54,10 +65,21 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Determine Node version + id: node-version + run: | + if [ -f .nvmrc ]; then + echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT" + echo "Using Node from .nvmrc" + else + echo "value=22" >> "$GITHUB_OUTPUT" + echo "Using default Node 22 (current LTS)" + fi + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version-file: ".nvmrc" + node-version: ${{ steps.node-version.outputs.value }} cache: "npm" - name: Cache Playwright browsers @@ -99,10 +121,21 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Determine Node version + id: node-version + run: | + if [ -f .nvmrc ]; then + echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT" + echo "Using Node from .nvmrc" + else + echo "value=22" >> "$GITHUB_OUTPUT" + echo "Using default Node 22 (current LTS)" + fi + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version-file: ".nvmrc" + node-version: ${{ steps.node-version.outputs.value }} cache: "npm" - name: Cache Playwright browsers diff --git a/src/modules/bmm/workflows/testarch/ci/gitlab-ci-template.yaml b/src/modules/bmm/workflows/testarch/ci/gitlab-ci-template.yaml index e3b433da..f5336de4 100644 --- a/src/modules/bmm/workflows/testarch/ci/gitlab-ci-template.yaml +++ b/src/modules/bmm/workflows/testarch/ci/gitlab-ci-template.yaml @@ -15,6 +15,8 @@ variables: npm_config_cache: "$CI_PROJECT_DIR/.npm" # Playwright browser cache PLAYWRIGHT_BROWSERS_PATH: "$CI_PROJECT_DIR/.cache/ms-playwright" + # Default Node version when .nvmrc is missing + DEFAULT_NODE_VERSION: "24" # Caching configuration cache: @@ -29,19 +31,32 @@ cache: # Lint stage - Code quality checks lint: stage: lint - image: node:20 - script: + image: node:$DEFAULT_NODE_VERSION + before_script: + - | + NODE_VERSION=$(cat .nvmrc 2>/dev/null || echo "$DEFAULT_NODE_VERSION") + echo "Using Node $NODE_VERSION" + npm install -g n + n "$NODE_VERSION" + node -v - npm ci + script: - npm run lint timeout: 5 minutes # Test stage - Parallel execution with sharding .test-template: &test-template stage: test - image: node:20 + image: node:$DEFAULT_NODE_VERSION needs: - lint before_script: + - | + NODE_VERSION=$(cat .nvmrc 2>/dev/null || echo "$DEFAULT_NODE_VERSION") + echo "Using Node $NODE_VERSION" + npm install -g n + n "$NODE_VERSION" + node -v - npm ci - npx playwright install --with-deps chromium artifacts: @@ -75,7 +90,7 @@ test:shard-4: # Burn-in stage - Flaky test detection burn-in: stage: burn-in - image: node:20 + image: node:$DEFAULT_NODE_VERSION needs: - test:shard-1 - test:shard-2 @@ -86,6 +101,12 @@ burn-in: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_PIPELINE_SOURCE == "schedule"' before_script: + - | + NODE_VERSION=$(cat .nvmrc 2>/dev/null || echo "$DEFAULT_NODE_VERSION") + echo "Using Node $NODE_VERSION" + npm install -g n + n "$NODE_VERSION" + node -v - npm ci - npx playwright install --with-deps chromium script: diff --git a/src/modules/bmm/workflows/testarch/ci/instructions.md b/src/modules/bmm/workflows/testarch/ci/instructions.md index 9bd87940..9241e93c 100644 --- a/src/modules/bmm/workflows/testarch/ci/instructions.md +++ b/src/modules/bmm/workflows/testarch/ci/instructions.md @@ -61,8 +61,8 @@ Scaffolds a production-ready CI/CD quality pipeline with test execution, burn-in - Ask user if unable to auto-detect 5. **Read Environment Configuration** - - Check for `.nvmrc` to determine Node version - - Default to Node 20 LTS if not found + - Use `.nvmrc` for Node version if present + - If missing, default to a current LTS (Node 24) or newer instead of a fixed old version - Read `package.json` to identify dependencies (affects caching strategy) **Halt Condition:** If preflight checks fail, stop immediately and report which requirement failed. From 8265bbf29564496bb7c81ab1fb7f0dc91cb30875 Mon Sep 17 00:00:00 2001 From: Paul Preibisch Date: Fri, 5 Dec 2025 17:54:03 -0700 Subject: [PATCH 08/11] feat(installer): Enhanced TTS injection summary with tracking and documentation (#1037) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Track all files with TTS injection applied during installation - Display informative summary explaining what TTS injection does - Show backup location and restore command for recovery ## What is TTS Injection? TTS (Text-to-Speech) injection adds voice instructions to BMAD agents, enabling them to speak their responses aloud using AgentVibes. Example: When you activate the PM agent, it will greet you with spoken audio like "Hey! I'm your Project Manager. How can I help?" ## Changes - **installer.js**: Track files in `processAgentFiles()`, `buildStandaloneAgents()`, and `rebuildAgentFiles()` when TTS markers are processed - **compiler.js**: Add TTS injection support for custom agent compilation - **ui.js**: Enhanced installation summary showing: - Explanation of what TTS injection is with example - List of all files with TTS injection applied (grouped by type) - Backup location (~/.bmad-tts-backups/) - Restore command for recovery ## Example Output ``` โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• AgentVibes TTS Injection Summary โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• What is TTS Injection? TTS (Text-to-Speech) injection adds voice instructions to BMAD agents, enabling them to speak their responses aloud using AgentVibes. Example: When you activate the PM agent, it will greet you with spoken audio like "Hey! I'm your Project Manager. How can I help?" โœ… TTS injection applied to 11 file(s): Party Mode (multi-agent conversations): โ€ข .bmad/core/workflows/party-mode/instructions.md Agent TTS (individual agent voices): โ€ข .bmad/bmm/agents/analyst.md โ€ข .bmad/bmm/agents/architect.md ... Backups & Recovery: Pre-injection backups are stored in: ~/.bmad-tts-backups/ To restore original files (removes TTS instructions): bmad-tts-injector.sh --restore /path/to/.bmad ``` ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Paul Preibisch Co-authored-by: Claude --- tools/cli/installers/lib/core/installer.js | 34 ++++++++++++--- tools/cli/lib/agent/compiler.js | 44 ++++++++++++++++++- tools/cli/lib/ui.js | 49 ++++++++++++++++++++++ 3 files changed, 119 insertions(+), 8 deletions(-) diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index c43a197b..db8333bb 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -51,6 +51,7 @@ class Installer { this.configCollector = new ConfigCollector(); this.ideConfigManager = new IdeConfigManager(); this.installedFiles = []; // Track all installed files + this.ttsInjectedFiles = []; // Track files with TTS injection applied } /** @@ -146,8 +147,8 @@ class Installer { content = content.replaceAll('{*bmad_folder*}', '{bmad_folder}'); } - // Process AgentVibes injection points - content = this.processTTSInjectionPoints(content); + // Process AgentVibes injection points (pass targetPath for tracking) + content = this.processTTSInjectionPoints(content, targetPath); // Write to target with replaced content await fs.ensureDir(path.dirname(targetPath)); @@ -226,10 +227,14 @@ class Installer { * - src/modules/bmm/agents/*.md (rules sections) * - TTS Hook: .claude/hooks/bmad-speak.sh (in AgentVibes repo) */ - processTTSInjectionPoints(content) { + processTTSInjectionPoints(content, targetPath = null) { // Check if AgentVibes is enabled (set during installation configuration) const enableAgentVibes = this.enableAgentVibes || false; + // Check if content contains any TTS injection markers + const hasPartyMode = content.includes(''); + const hasAgentTTS = content.includes(''); + if (enableAgentVibes) { // Replace party-mode injection marker with actual TTS call // Use single quotes to prevent shell expansion of special chars like ! @@ -253,6 +258,12 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: IMPORTANT: Use single quotes as shown - do NOT escape special characters like ! or $ inside single quotes Run in background (&) to avoid blocking`, ); + + // Track files that had TTS injection applied + if (targetPath && (hasPartyMode || hasAgentTTS)) { + const injectionType = hasPartyMode ? 'party-mode' : 'agent-tts'; + this.ttsInjectedFiles.push({ path: targetPath, type: injectionType }); + } } else { // Strip injection markers cleanly when AgentVibes is disabled content = content.replaceAll(/\n?/g, ''); @@ -1021,6 +1032,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: modules: config.modules, ides: config.ides, customFiles: customFiles.length > 0 ? customFiles : undefined, + ttsInjectedFiles: this.enableAgentVibes && this.ttsInjectedFiles.length > 0 ? this.ttsInjectedFiles : undefined, + agentVibesEnabled: this.enableAgentVibes || false, }); // Offer cleanup for legacy files (only for updates, not fresh installs, and only if not skipped) @@ -1526,13 +1539,16 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: // Build YAML + customize to .md const customizeExists = await fs.pathExists(customizePath); - const xmlContent = await this.xmlHandler.buildFromYaml(yamlPath, customizeExists ? customizePath : null, { + let xmlContent = await this.xmlHandler.buildFromYaml(yamlPath, customizeExists ? customizePath : null, { includeMetadata: true, }); // DO NOT replace {project-root} - LLMs understand this placeholder at runtime // const processedContent = xmlContent.replaceAll('{project-root}', projectDir); + // Process TTS injection points (pass targetPath for tracking) + xmlContent = this.processTTSInjectionPoints(xmlContent, mdPath); + // Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n'; await fs.writeFile(mdPath, content, 'utf8'); @@ -1628,13 +1644,16 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: } // Build YAML to XML .md - const xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, { + let xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, { includeMetadata: true, }); // DO NOT replace {project-root} - LLMs understand this placeholder at runtime // const processedContent = xmlContent.replaceAll('{project-root}', projectDir); + // Process TTS injection points (pass targetPath for tracking) + xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath); + // Write the built .md file with POSIX-compliant final newline const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n'; await fs.writeFile(targetMdPath, content, 'utf8'); @@ -1722,13 +1741,16 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice: } // Build YAML + customize to .md - const xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, { + let xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, { includeMetadata: true, }); // DO NOT replace {project-root} - LLMs understand this placeholder at runtime // const processedContent = xmlContent.replaceAll('{project-root}', projectDir); + // Process TTS injection points (pass targetPath for tracking) + xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath); + // Write the rebuilt .md file with POSIX-compliant final newline const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n'; await fs.writeFile(targetMdPath, content, 'utf8'); diff --git a/tools/cli/lib/agent/compiler.js b/tools/cli/lib/agent/compiler.js index ba9b1557..3df6845b 100644 --- a/tools/cli/lib/agent/compiler.js +++ b/tools/cli/lib/agent/compiler.js @@ -482,10 +482,39 @@ function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '' }; } +/** + * Process TTS injection markers in content + * @param {string} content - Content to process + * @param {boolean} enableAgentVibes - Whether AgentVibes is enabled + * @returns {Object} { content: string, hadInjection: boolean } + */ +function processTTSInjectionPoints(content, enableAgentVibes) { + const hasAgentTTS = content.includes(''); + + if (enableAgentVibes && hasAgentTTS) { + // Replace agent-tts injection marker with TTS rule + content = content.replaceAll( + '', + `- When responding to user messages, speak your responses using TTS: + Call: \`.claude/hooks/bmad-speak.sh '{agent-id}' '{response-text}'\` after each response + Replace {agent-id} with YOUR agent ID from tag at top of this file + Replace {response-text} with the text you just output to the user + IMPORTANT: Use single quotes as shown - do NOT escape special characters like ! or $ inside single quotes + Run in background (&) to avoid blocking`, + ); + return { content, hadInjection: true }; + } else if (!enableAgentVibes && hasAgentTTS) { + // Strip injection markers when disabled + content = content.replaceAll(/\n?/g, ''); + } + + return { content, hadInjection: false }; +} + /** * Compile agent file to .md * @param {string} yamlPath - Path to agent YAML file - * @param {Object} options - { answers: {}, outputPath: string } + * @param {Object} options - { answers: {}, outputPath: string, enableAgentVibes: boolean } * @returns {Object} Compilation result */ function compileAgentFile(yamlPath, options = {}) { @@ -501,13 +530,24 @@ function compileAgentFile(yamlPath, options = {}) { outputPath = path.join(dir, `${basename}.md`); } + // Process TTS injection points if enableAgentVibes option is provided + let xml = result.xml; + let ttsInjected = false; + if (options.enableAgentVibes !== undefined) { + const ttsResult = processTTSInjectionPoints(xml, options.enableAgentVibes); + xml = ttsResult.content; + ttsInjected = ttsResult.hadInjection; + } + // Write compiled XML - fs.writeFileSync(outputPath, result.xml, 'utf8'); + fs.writeFileSync(outputPath, xml, 'utf8'); return { ...result, + xml, outputPath, sourcePath: yamlPath, + ttsInjected, }; } diff --git a/tools/cli/lib/ui.js b/tools/cli/lib/ui.js index 32e8dfc0..4c5b3379 100644 --- a/tools/cli/lib/ui.js +++ b/tools/cli/lib/ui.js @@ -363,11 +363,60 @@ class UI { `๐Ÿ”ง Tools Configured: ${result.ides?.length > 0 ? result.ides.join(', ') : 'none'}`, ]; + // Add AgentVibes TTS info if enabled + if (result.agentVibesEnabled) { + summary.push(`๐ŸŽค AgentVibes TTS: Enabled`); + } + CLIUtils.displayBox(summary.join('\n\n'), { borderColor: 'green', borderStyle: 'round', }); + // Display TTS injection details if present + if (result.ttsInjectedFiles && result.ttsInjectedFiles.length > 0) { + console.log('\n' + chalk.cyan.bold('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•')); + console.log(chalk.cyan.bold(' AgentVibes TTS Injection Summary')); + console.log(chalk.cyan.bold('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n')); + + // Explain what TTS injection is + console.log(chalk.white.bold('What is TTS Injection?\n')); + console.log(chalk.dim(' TTS (Text-to-Speech) injection adds voice instructions to BMAD agents,')); + console.log(chalk.dim(' enabling them to speak their responses aloud using AgentVibes.\n')); + console.log(chalk.dim(' Example: When you activate the PM agent, it will greet you with')); + console.log(chalk.dim(' spoken audio like "Hey! I\'m your Project Manager. How can I help?"\n')); + + console.log(chalk.green(`โœ… TTS injection applied to ${result.ttsInjectedFiles.length} file(s):\n`)); + + // Group by type + const partyModeFiles = result.ttsInjectedFiles.filter((f) => f.type === 'party-mode'); + const agentTTSFiles = result.ttsInjectedFiles.filter((f) => f.type === 'agent-tts'); + + if (partyModeFiles.length > 0) { + console.log(chalk.yellow(' Party Mode (multi-agent conversations):')); + for (const file of partyModeFiles) { + console.log(chalk.dim(` โ€ข ${file.path}`)); + } + } + + if (agentTTSFiles.length > 0) { + console.log(chalk.yellow(' Agent TTS (individual agent voices):')); + for (const file of agentTTSFiles) { + console.log(chalk.dim(` โ€ข ${file.path}`)); + } + } + + // Show backup info and restore command + console.log('\n' + chalk.white.bold('Backups & Recovery:\n')); + console.log(chalk.dim(' Pre-injection backups are stored in:')); + console.log(chalk.cyan(' ~/.bmad-tts-backups/\n')); + console.log(chalk.dim(' To restore original files (removes TTS instructions):')); + console.log(chalk.cyan(` bmad-tts-injector.sh --restore ${result.path}\n`)); + + console.log(chalk.cyan('๐Ÿ’ก BMAD agents will now speak when activated!')); + console.log(chalk.dim(' Ensure AgentVibes is installed: https://agentvibes.org')); + } + console.log('\n' + chalk.green.bold('โœจ BMAD is ready to use!')); } From 72ef9e97225c494296cf9bf7716ffff5860f4e93 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Huy <31732865+huynguyen03dev@users.noreply.github.com> Date: Sat, 6 Dec 2025 10:26:04 +0700 Subject: [PATCH 09/11] fix: use backticks for quoted phrase in code-review description (#1025) Replace 'looks good' with `looks good` to avoid nested single quote issues when IDEs generate command files from workflow YAML. Co-authored-by: Brian --- .../bmm/workflows/4-implementation/code-review/workflow.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/bmm/workflows/4-implementation/code-review/workflow.yaml b/src/modules/bmm/workflows/4-implementation/code-review/workflow.yaml index bea3b9dc..c055db20 100644 --- a/src/modules/bmm/workflows/4-implementation/code-review/workflow.yaml +++ b/src/modules/bmm/workflows/4-implementation/code-review/workflow.yaml @@ -1,6 +1,6 @@ # Review Story Workflow name: code-review -description: "Perform an ADVERSARIAL Senior Developer code review that finds 3-10 specific problems in every story. Challenges everything: code quality, test coverage, architecture compliance, security, performance. NEVER accepts 'looks good' - must find minimum issues and can auto-fix with user approval." +description: "Perform an ADVERSARIAL Senior Developer code review that finds 3-10 specific problems in every story. Challenges everything: code quality, test coverage, architecture compliance, security, performance. NEVER accepts `looks good` - must find minimum issues and can auto-fix with user approval." author: "BMad" # Critical variables from config From c95b65f462f93ef3c4443975ff2bcbf71b83cfc3 Mon Sep 17 00:00:00 2001 From: Alex Verkhovsky Date: Fri, 5 Dec 2025 19:27:11 -0800 Subject: [PATCH 10/11] fix(bmm): correct code-review workflow status logic and checklist (#1015) (#1028) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix checklist to only accept 'review' status (not 'ready-for-review') - Include MEDIUM issues in done/in-progress status determination - Initialize and track fixed_count/action_count variables for summary - Add sprint-status.yaml sync when story status changes ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude --- .../code-review/checklist.md | 3 +- .../code-review/instructions.xml | 54 +++++++++++++++++-- 2 files changed, 53 insertions(+), 4 deletions(-) rename src/modules/{bmgd/workflows/4-production => bmm/workflows/4-implementation}/code-review/checklist.md (90%) diff --git a/src/modules/bmgd/workflows/4-production/code-review/checklist.md b/src/modules/bmm/workflows/4-implementation/code-review/checklist.md similarity index 90% rename from src/modules/bmgd/workflows/4-production/code-review/checklist.md rename to src/modules/bmm/workflows/4-implementation/code-review/checklist.md index ce903701..f213a6b9 100644 --- a/src/modules/bmgd/workflows/4-production/code-review/checklist.md +++ b/src/modules/bmm/workflows/4-implementation/code-review/checklist.md @@ -1,7 +1,7 @@ # Senior Developer Review - Validation Checklist - [ ] Story file loaded from `{{story_path}}` -- [ ] Story Status verified as one of: {{allow_status_values}} +- [ ] Story Status verified as reviewable (review) - [ ] Epic and Story IDs resolved ({{epic_num}}.{{story_num}}) - [ ] Story Context located or warning recorded - [ ] Epic Tech Spec located or warning recorded @@ -17,6 +17,7 @@ - [ ] Review notes appended under "Senior Developer Review (AI)" - [ ] Change Log updated with review entry - [ ] Status updated according to settings (if enabled) +- [ ] Sprint status synced (if sprint tracking enabled) - [ ] Story saved successfully _Reviewer: {{user_name}} on {{date}}_ diff --git a/src/modules/bmm/workflows/4-implementation/code-review/instructions.xml b/src/modules/bmm/workflows/4-implementation/code-review/instructions.xml index 6e896a97..bf8b7d69 100644 --- a/src/modules/bmm/workflows/4-implementation/code-review/instructions.xml +++ b/src/modules/bmm/workflows/4-implementation/code-review/instructions.xml @@ -16,6 +16,7 @@ Use provided {{story_path}} or ask user which story file to review Read COMPLETE story file + Set {{story_key}} = extracted key from filename (e.g., "1-2-user-authentication.md" โ†’ "1-2-user-authentication") or story metadata Parse sections: Story, Acceptance Criteria, Tasks/Subtasks, Dev Agent Record โ†’ File List, Change Log @@ -106,6 +107,8 @@ Categorize findings: HIGH (must fix), MEDIUM (should fix), LOW (nice to fix) + Set {{fixed_count}} = 0 + Set {{action_count}} = 0 **๐Ÿ”ฅ CODE REVIEW FINDINGS, {user_name}!** @@ -145,11 +148,15 @@ Add/update tests as needed Update File List in story if files changed Update story Dev Agent Record with fixes applied + Set {{fixed_count}} = number of HIGH and MEDIUM issues fixed + Set {{action_count}} = 0 Add "Review Follow-ups (AI)" subsection to Tasks/Subtasks For each issue: `- [ ] [AI-Review][Severity] Description [file:line]` + Set {{action_count}} = number of action items created + Set {{fixed_count}} = 0 @@ -158,11 +165,52 @@ - - If all HIGH issues fixed and ACs implemented โ†’ Update story Status to "done" - If issues remain โ†’ Update story Status to "in-progress" + + + + Set {{new_status}} = "done" + Update story Status field to "done" + + + Set {{new_status}} = "in-progress" + Update story Status field to "in-progress" + Save story file + + + Set {{current_sprint_status}} = "enabled" + + + Set {{current_sprint_status}} = "no-sprint-tracking" + + + + + Load the FULL file: {sprint_status} + Find development_status key matching {{story_key}} + + + Update development_status[{{story_key}}] = "done" + Save file, preserving ALL comments and structure + โœ… Sprint status synced: {{story_key}} โ†’ done + + + + Update development_status[{{story_key}}] = "in-progress" + Save file, preserving ALL comments and structure + ๐Ÿ”„ Sprint status synced: {{story_key}} โ†’ in-progress + + + + โš ๏ธ Story file updated, but sprint-status sync failed: {{story_key}} not found in sprint-status.yaml + + + + + โ„น๏ธ Story status updated (no sprint tracking configured) + + **โœ… Review Complete!** **Story Status:** {{new_status}} From 5ee1551b5b3008e83ee865bbfcb380176a028388 Mon Sep 17 00:00:00 2001 From: Alex Verkhovsky Date: Fri, 5 Dec 2025 19:35:46 -0800 Subject: [PATCH 11/11] fix(bmm): remove stale validate-prd references (fixes #1030) (#1038) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove validate-prd workflow references from all workflow path YAML files - Update Excalidraw diagram: remove Validate PRD box and zombie JSON elements - Re-export SVG at 1x scale - Standardize implementation-readiness descriptions across all docs - Add validation script (validate-svg-changes.sh) and README for SVG export process - Correct Excalidraw timestamps ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Co-authored-by: Brian --- src/modules/bmm/docs/agents-guide.md | 23 +- src/modules/bmm/docs/faq.md | 2 +- src/modules/bmm/docs/glossary.md | 2 +- src/modules/bmm/docs/images/README.md | 37 ++ .../workflow-method-greenfield.excalidraw | 264 +++---------- .../images/workflow-method-greenfield.svg | 4 +- .../paths/enterprise-brownfield.yaml | 7 +- .../paths/enterprise-greenfield.yaml | 7 +- .../paths/method-brownfield.yaml | 7 +- .../paths/method-greenfield.yaml | 8 +- tools/validate-svg-changes.sh | 356 ++++++++++++++++++ 11 files changed, 474 insertions(+), 243 deletions(-) create mode 100644 src/modules/bmm/docs/images/README.md create mode 100755 tools/validate-svg-changes.sh diff --git a/src/modules/bmm/docs/agents-guide.md b/src/modules/bmm/docs/agents-guide.md index 89010bb6..16e5d633 100644 --- a/src/modules/bmm/docs/agents-guide.md +++ b/src/modules/bmm/docs/agents-guide.md @@ -76,8 +76,7 @@ The BMad Method Module (BMM) provides a comprehensive team of specialized AI age - `create-prd` - Create PRD for Level 2-4 projects (creates FRs/NFRs only) - `tech-spec` - Quick spec for Level 0-1 projects - `create-epics-and-stories` - Break PRD into implementable pieces (runs AFTER architecture) -- `validate-prd` - Validate PRD completeness -- `validate-tech-spec` - Validate Technical Specification +- `implementation-readiness` - Validate PRD + Architecture + Epics + UX (optional) - `correct-course` - Handle mid-project changes - `workflow-init` - Initialize workflow tracking @@ -146,7 +145,7 @@ The BMad Method Module (BMM) provides a comprehensive team of specialized AI age - `workflow-status` - Check what to do next - `create-architecture` - Produce a Scale Adaptive Architecture - `validate-architecture` - Validate architecture document -- `implementation-readiness` - Validate readiness for Phase 4 +- `implementation-readiness` - Validate PRD + Architecture + Epics + UX (optional) **Communication Style:** Comprehensive yet pragmatic. Uses architectural metaphors. Balances technical depth with accessibility. Connects decisions to business value. @@ -642,13 +641,12 @@ Some workflows are available to multiple agents: Many workflows have optional validation workflows that perform independent review: -| Validation | Agent | Validates | -| ----------------------- | ----------- | -------------------------------- | -| `validate-prd` | PM | PRD completeness (FRs/NFRs only) | -| `validate-tech-spec` | PM | Technical specification quality | -| `validate-architecture` | Architect | Architecture document | -| `validate-design` | UX Designer | UX specification and artifacts | -| `validate-create-story` | SM | Story draft | +| Validation | Agent | Validates | +| -------------------------- | ----------- | ------------------------------------------ | +| `implementation-readiness` | Architect | PRD + Architecture + Epics + UX (optional) | +| `validate-architecture` | Architect | Architecture document | +| `validate-design` | UX Designer | UX specification and artifacts | +| `validate-create-story` | SM | Story draft | **When to use validation:** @@ -945,9 +943,8 @@ Agent analyzes project state โ†’ recommends next workflow ``` Each phase has validation gates: -- Phase 2 to 3: validate-prd, validate-tech-spec -- Phase 3 to 4: implementation-readiness -Run validation before advancing +- Phase 3 to 4: implementation-readiness (validates PRD + Architecture + Epics + UX (optional)) +Run validation before advancing to implementation ``` **Course correction:** diff --git a/src/modules/bmm/docs/faq.md b/src/modules/bmm/docs/faq.md index 7d041b87..3270f9c4 100644 --- a/src/modules/bmm/docs/faq.md +++ b/src/modules/bmm/docs/faq.md @@ -147,7 +147,7 @@ If status file exists, use workflow-status. If not, use workflow-init. ### Q: How do I know when Phase 3 is complete and I can start Phase 4? -**A:** For Level 3-4, run the implementation-readiness workflow. It validates that PRD (FRs/NFRs), architecture, epics+stories, and UX (if applicable) are cohesive before implementation. Pass the gate check = ready for Phase 4. +**A:** For Level 3-4, run the implementation-readiness workflow. It validates PRD + Architecture + Epics + UX (optional) are aligned before implementation. Pass the gate check = ready for Phase 4. ### Q: Can I run workflows in parallel or do they have to be sequential? diff --git a/src/modules/bmm/docs/glossary.md b/src/modules/bmm/docs/glossary.md index 813cdf72..62735532 100644 --- a/src/modules/bmm/docs/glossary.md +++ b/src/modules/bmm/docs/glossary.md @@ -246,7 +246,7 @@ Workflow that initializes Phase 4 implementation by creating sprint-status.yaml, ### Gate Check -Validation workflow (implementation-readiness) run before Phase 4 to ensure PRD, architecture, and UX documents are cohesive with no gaps or contradictions. Required for BMad Method and Enterprise Method tracks. +Validation workflow (implementation-readiness) run before Phase 4 to ensure PRD + Architecture + Epics + UX (optional) are aligned with no gaps or contradictions. Required for BMad Method and Enterprise Method tracks. ### DoD (Definition of Done) diff --git a/src/modules/bmm/docs/images/README.md b/src/modules/bmm/docs/images/README.md new file mode 100644 index 00000000..cc943e47 --- /dev/null +++ b/src/modules/bmm/docs/images/README.md @@ -0,0 +1,37 @@ +# Workflow Diagram Maintenance + +## Regenerating SVG from Excalidraw + +When you edit `workflow-method-greenfield.excalidraw`, regenerate the SVG: + +1. Open https://excalidraw.com/ +2. Load the `.excalidraw` file +3. Click menu (โ˜ฐ) โ†’ Export image โ†’ SVG +4. **Set "Scale" to 1x** (default is 2x) +5. Click "Export" +6. Save as `workflow-method-greenfield.svg` +7. **Validate the changes** (see below) +8. Commit both files together + +**Important:** + +- Always use **1x scale** to maintain consistent dimensions +- Automated export tools (`excalidraw-to-svg`) are broken - use manual export only + +## Visual Validation + +After regenerating the SVG, validate that it renders correctly: + +```bash +./tools/validate-svg-changes.sh src/modules/bmm/docs/images/workflow-method-greenfield.svg +``` + +This script: + +- Checks for required dependencies (Playwright, ImageMagick) +- Installs Playwright locally if needed (no package.json pollution) +- Renders old vs new SVG using browser-accurate rendering +- Compares pixel-by-pixel and generates a diff image +- Outputs a prompt for AI visual analysis (paste into Gemini/Claude) + +**Threshold**: <0.01% difference is acceptable (anti-aliasing variations) diff --git a/src/modules/bmm/docs/images/workflow-method-greenfield.excalidraw b/src/modules/bmm/docs/images/workflow-method-greenfield.excalidraw index de98315c..f4d2411f 100644 --- a/src/modules/bmm/docs/images/workflow-method-greenfield.excalidraw +++ b/src/modules/bmm/docs/images/workflow-method-greenfield.excalidraw @@ -1036,10 +1036,6 @@ "type": "arrow", "id": "arrow-discovery-no" }, - { - "type": "arrow", - "id": "arrow-prd-validate" - }, { "id": "arrow-phase1-to-phase2", "type": "arrow" @@ -1055,17 +1051,21 @@ { "id": "arrow-has-ui-no", "type": "arrow" + }, + { + "id": "arrow-prd-hasui", + "type": "arrow" } ], "locked": false, - "version": 107, - "versionNonce": 930129274, + "version": 108, + "versionNonce": 930129275, "index": "aN", "isDeleted": false, "strokeStyle": "solid", "seed": 1, "frameId": null, - "updated": 1764191563350, + "updated": 1764952855000, "link": null }, { @@ -1107,197 +1107,6 @@ "autoResize": true, "lineHeight": 1.25 }, - { - "id": "arrow-prd-validate", - "type": "arrow", - "x": 439.4640518625828, - "y": 331.0450590268819, - "width": 0.17283039375342923, - "height": 28.50332681186643, - "angle": 0, - "strokeColor": "#1976d2", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "roughness": 0, - "opacity": 100, - "groupIds": [], - "startBinding": { - "elementId": "proc-prd", - "focus": 0, - "gap": 1 - }, - "endBinding": { - "elementId": "proc-validate-prd", - "focus": 0, - "gap": 1 - }, - "points": [ - [ - 0, - 0 - ], - [ - 0.17283039375342923, - 28.50332681186643 - ] - ], - "lastCommittedPoint": null, - "version": 102, - "versionNonce": 1274591910, - "index": "aP", - "isDeleted": false, - "strokeStyle": "solid", - "seed": 1, - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1764191023838, - "link": null, - "locked": false, - "startArrowhead": null, - "endArrowhead": "arrow" - }, - { - "id": "proc-validate-prd", - "type": "rectangle", - "x": 360, - "y": 360, - "width": 160, - "height": 80, - "angle": 0, - "strokeColor": "#43a047", - "backgroundColor": "#c8e6c9", - "fillStyle": "solid", - "strokeWidth": 2, - "roughness": 0, - "opacity": 100, - "roundness": { - "type": 3, - "value": 8 - }, - "groupIds": [ - "proc-validate-prd-group" - ], - "boundElements": [ - { - "type": "text", - "id": "proc-validate-prd-text" - }, - { - "type": "arrow", - "id": "arrow-prd-validate" - }, - { - "type": "arrow", - "id": "arrow-validate-prd-hasui" - }, - { - "id": "jv0rnlK2D9JKIGTO7pUtT", - "type": "arrow" - } - ], - "locked": false, - "version": 3, - "versionNonce": 894806650, - "index": "aQ", - "isDeleted": false, - "strokeStyle": "solid", - "seed": 1, - "frameId": null, - "updated": 1764191341774, - "link": null - }, - { - "id": "proc-validate-prd-text", - "type": "text", - "x": 370, - "y": 375, - "width": 140, - "height": 50, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "roughness": 0, - "opacity": 100, - "groupIds": [ - "proc-validate-prd-group" - ], - "fontSize": 14, - "fontFamily": 1, - "text": "Validate PRD\n<>", - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "proc-validate-prd", - "locked": false, - "version": 2, - "versionNonce": 944332155, - "index": "aR", - "isDeleted": false, - "strokeStyle": "solid", - "seed": 1, - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1763522171080, - "link": null, - "originalText": "Validate PRD\n<>", - "autoResize": true, - "lineHeight": 1.7857142857142858 - }, - { - "id": "arrow-validate-prd-hasui", - "type": "arrow", - "x": 440, - "y": 440, - "width": 0, - "height": 30, - "angle": 0, - "strokeColor": "#1976d2", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "roughness": 0, - "opacity": 100, - "groupIds": [], - "startBinding": { - "elementId": "proc-validate-prd", - "focus": 0, - "gap": 1 - }, - "endBinding": { - "elementId": "decision-has-ui", - "focus": 0, - "gap": 1 - }, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 30 - ] - ], - "lastCommittedPoint": null, - "version": 2, - "versionNonce": 1369541557, - "index": "aS", - "isDeleted": false, - "strokeStyle": "solid", - "seed": 1, - "frameId": null, - "roundness": null, - "boundElements": [], - "updated": 1763522171080, - "link": null, - "locked": false, - "startArrowhead": null, - "endArrowhead": "arrow" - }, { "id": "decision-has-ui", "type": "diamond", @@ -1322,7 +1131,7 @@ }, { "type": "arrow", - "id": "arrow-validate-prd-hasui" + "id": "arrow-prd-hasui" }, { "type": "arrow", @@ -1334,15 +1143,15 @@ } ], "locked": false, - "version": 2, - "versionNonce": 1003877915, + "version": 3, + "versionNonce": 1003877916, "index": "aT", "isDeleted": false, "strokeStyle": "solid", "seed": 1, "frameId": null, "roundness": null, - "updated": 1763522171080, + "updated": 1764952855000, "link": null }, { @@ -5162,6 +4971,57 @@ "startArrowhead": null, "endArrowhead": "arrow", "elbowed": false + }, + { + "id": "arrow-prd-hasui", + "type": "arrow", + "x": 440, + "y": 330, + "width": 0, + "height": 140, + "angle": 0, + "strokeColor": "#1976d2", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "startBinding": { + "elementId": "proc-prd", + "focus": 0, + "gap": 1 + }, + "endBinding": { + "elementId": "decision-has-ui", + "focus": 0, + "gap": 1 + }, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 140 + ] + ], + "lastCommittedPoint": null, + "version": 1, + "versionNonce": 1, + "index": "b1J", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1764952855000, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" } ], "appState": { diff --git a/src/modules/bmm/docs/images/workflow-method-greenfield.svg b/src/modules/bmm/docs/images/workflow-method-greenfield.svg index 1b7dd76c..6522b695 100644 --- a/src/modules/bmm/docs/images/workflow-method-greenfield.svg +++ b/src/modules/bmm/docs/images/workflow-method-greenfield.svg @@ -1,2 +1,4 @@ + + BMad Method Workflow - Standard GreenfieldStartPHASE 1Discovery(Optional)IncludeDiscovery?YesBrainstorm<<optional>>Research<<optional>>Product Brief<<optional>>NoPHASE 2Planning (Required)PRDValidate PRD<<optional>>Has UI?YesCreate UXNoPHASE 3Solutioning (Required)ArchitectureEpics/StoriesTest Design<<optional>>Validate Arch<<optional>>ImplementationReadinessPHASE 4Implementation (Required)Sprint PlanSTORY LOOPCreate StoryValidate Story<<optional>>Develop StoryCode ReviewPass?FailPassCode Review<<use differentLLM>>More Storiesin Epic?YesNoRetrospectiveMore Epics?YesNoEndAgent LegendAnalystPMUX DesignerArchitectTEASMDEVDecision \ No newline at end of file + @font-face { font-family: Virgil; src: url(data:font/woff2;base64,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); }BMad Method Workflow - Standard GreenfieldStartPHASE 1Discovery(Optional)IncludeDiscovery?YesBrainstorm<<optional>>Research<<optional>>Product Brief<<optional>>NoPHASE 2Planning (Required)PRDHas UI?YesCreate UXNoPHASE 3Solutioning (Required)ArchitectureEpics/StoriesTest Design<<optional>>Validate Arch<<optional>>ImplementationReadinessPHASE 4Implementation (Required)Sprint PlanSTORY LOOPCreate StoryValidate Story<<optional>>Develop StoryCode ReviewPass?FailPassCode Review<<use differentLLM>>More Storiesin Epic?YesNoRetrospectiveMore Epics?YesNoEndAgent LegendAnalystPMUX DesignerArchitectTEASMDEVDecision \ No newline at end of file diff --git a/src/modules/bmm/workflows/workflow-status/paths/enterprise-brownfield.yaml b/src/modules/bmm/workflows/workflow-status/paths/enterprise-brownfield.yaml index 7ac0d490..5064030d 100644 --- a/src/modules/bmm/workflows/workflow-status/paths/enterprise-brownfield.yaml +++ b/src/modules/bmm/workflows/workflow-status/paths/enterprise-brownfield.yaml @@ -56,11 +56,6 @@ phases: output: "Enterprise PRD with compliance requirements" note: "Must address existing system constraints and migration strategy" - - id: "validate-prd" - recommended: true - agent: "pm" - command: "validate-prd" - - id: "create-ux-design" recommended: true agent: "ux-designer" @@ -114,7 +109,7 @@ phases: required: true agent: "architect" command: "implementation-readiness" - note: "Critical gate - validates all planning + Epics before touching production system" + note: "Validates PRD + Architecture + Epics + UX (optional)" - phase: 3 name: "Implementation" diff --git a/src/modules/bmm/workflows/workflow-status/paths/enterprise-greenfield.yaml b/src/modules/bmm/workflows/workflow-status/paths/enterprise-greenfield.yaml index de15e3c3..94757114 100644 --- a/src/modules/bmm/workflows/workflow-status/paths/enterprise-greenfield.yaml +++ b/src/modules/bmm/workflows/workflow-status/paths/enterprise-greenfield.yaml @@ -44,11 +44,6 @@ phases: output: "Comprehensive Product Requirements Document" note: "Enterprise-level requirements with compliance considerations" - - id: "validate-prd" - recommended: true - agent: "pm" - command: "validate-prd" - - id: "create-ux-design" recommended: true agent: "ux-designer" @@ -102,7 +97,7 @@ phases: required: true agent: "architect" command: "implementation-readiness" - note: "Validates all planning artifacts + Epics + testability align before implementation" + note: "Validates PRD + Architecture + Epics + UX (optional)" - phase: 3 name: "Implementation" diff --git a/src/modules/bmm/workflows/workflow-status/paths/method-brownfield.yaml b/src/modules/bmm/workflows/workflow-status/paths/method-brownfield.yaml index ed9cbd32..67ee6cd0 100644 --- a/src/modules/bmm/workflows/workflow-status/paths/method-brownfield.yaml +++ b/src/modules/bmm/workflows/workflow-status/paths/method-brownfield.yaml @@ -55,11 +55,6 @@ phases: output: "PRD focused on new features/changes" note: "Must consider existing system constraints" - - id: "validate-prd" - optional: true - agent: "pm" - command: "validate-prd" - - id: "create-ux-design" conditional: "if_has_ui" agent: "ux-designer" @@ -98,7 +93,7 @@ phases: required: true agent: "architect" command: "implementation-readiness" - note: "Validates PRD + UX + Architecture + Epics cohesion before implementation" + note: "Validates PRD + Architecture + Epics + UX (optional)" - phase: 3 name: "Implementation" diff --git a/src/modules/bmm/workflows/workflow-status/paths/method-greenfield.yaml b/src/modules/bmm/workflows/workflow-status/paths/method-greenfield.yaml index bef2a839..aca183e9 100644 --- a/src/modules/bmm/workflows/workflow-status/paths/method-greenfield.yaml +++ b/src/modules/bmm/workflows/workflow-status/paths/method-greenfield.yaml @@ -43,12 +43,6 @@ phases: command: "prd" output: "Product Requirements Document with FRs and NFRs" - - id: "validate-prd" - optional: true - agent: "pm" - command: "validate-prd" - note: "Quality check for PRD completeness" - - id: "create-ux-design" conditional: "if_has_ui" agent: "ux-designer" @@ -89,7 +83,7 @@ phases: required: true agent: "architect" command: "implementation-readiness" - note: "Validates PRD + UX + Architecture + Epics + Testability cohesion before implementation" + note: "Validates PRD + Architecture + Epics + UX (optional)" - phase: 3 name: "Implementation" diff --git a/tools/validate-svg-changes.sh b/tools/validate-svg-changes.sh new file mode 100755 index 00000000..07c68375 --- /dev/null +++ b/tools/validate-svg-changes.sh @@ -0,0 +1,356 @@ +#!/bin/bash +# +# Visual SVG Validation Script +# +# Compares old vs new SVG files using browser-accurate rendering (Playwright) +# and pixel-level comparison (ImageMagick), then generates a prompt for AI analysis. +# +# Usage: ./tools/validate-svg-changes.sh +# + +set -e + +SVG_FILE="${1:-src/modules/bmm/docs/images/workflow-method-greenfield.svg}" +TMP_DIR="/tmp/svg-validation-$$" + +echo "๐ŸŽจ Visual SVG Validation" +echo "" + +# Check if file exists +if [ ! -f "$SVG_FILE" ]; then + echo "โŒ Error: SVG file not found: $SVG_FILE" + exit 1 +fi + +# Check for ImageMagick +if ! command -v magick &> /dev/null; then + echo "โŒ ImageMagick not found" + echo "" + echo "Install with:" + echo " brew install imagemagick" + echo "" + exit 1 +fi + +echo "โœ“ ImageMagick found" + +# Check for Node.js +if ! command -v node &> /dev/null; then + echo "โŒ Node.js not found" + exit 1 +fi + +echo "โœ“ Node.js found ($(node -v))" + +# Check for Playwright (local install) +if [ ! -d "node_modules/playwright" ]; then + echo "" + echo "๐Ÿ“ฆ Playwright not found locally" + echo "Installing Playwright (local to this project, no package.json changes)..." + echo "" + npm install --no-save playwright + echo "" + echo "โœ“ Playwright installed" +else + echo "โœ“ Playwright found" +fi + +echo "" +echo "๐Ÿ”„ Rendering SVGs to PNG..." +echo "" + +# Create temp directory +mkdir -p "$TMP_DIR" + +# Extract old SVG from git +git show HEAD:"$SVG_FILE" > "$TMP_DIR/old.svg" 2>/dev/null || { + echo "โŒ Could not extract old SVG from git HEAD" + echo " Make sure you have uncommitted changes to compare" + exit 1 +} + +# Copy new SVG +cp "$SVG_FILE" "$TMP_DIR/new.svg" + +# Create Node.js renderer script in project directory (so it can find node_modules) +cat > "tools/render-svg-temp.js" << 'EOJS' +const { chromium } = require('playwright'); +const fs = require('fs'); + +async function renderSVG(svgPath, pngPath) { + const browser = await chromium.launch({ headless: true }); + const page = await browser.newPage(); + + const svgContent = fs.readFileSync(svgPath, 'utf8'); + const widthMatch = svgContent.match(/width="([^"]+)"/); + const heightMatch = svgContent.match(/height="([^"]+)"/); + const width = Math.ceil(parseFloat(widthMatch[1])); + const height = Math.ceil(parseFloat(heightMatch[1])); + + const html = ` + + + + + + ${svgContent} + + `; + + await page.setContent(html); + await page.setViewportSize({ width, height }); + await page.waitForTimeout(1000); + await page.screenshot({ path: pngPath, fullPage: true }); + await browser.close(); + + console.log(`โœ“ Rendered ${pngPath}`); +} + +(async () => { + await renderSVG(process.argv[2], process.argv[3]); + await renderSVG(process.argv[4], process.argv[5]); +})(); +EOJS + +# Render both SVGs (run from project dir so node_modules is accessible) +node tools/render-svg-temp.js \ + "$TMP_DIR/old.svg" "$TMP_DIR/old.png" \ + "$TMP_DIR/new.svg" "$TMP_DIR/new.png" + +# Clean up temp script +rm tools/render-svg-temp.js + +echo "" +echo "๐Ÿ” Comparing pixels..." +echo "" + +# Compare using ImageMagick +DIFF_OUTPUT=$(magick compare -metric AE "$TMP_DIR/old.png" "$TMP_DIR/new.png" "$TMP_DIR/diff.png" 2>&1 || true) +DIFF_PIXELS=$(echo "$DIFF_OUTPUT" | awk '{print $1}') + +# Get image dimensions +DIMENSIONS=$(magick identify -format "%wx%h" "$TMP_DIR/old.png") +WIDTH=$(echo "$DIMENSIONS" | cut -d'x' -f1) +HEIGHT=$(echo "$DIMENSIONS" | cut -d'x' -f2) +TOTAL_PIXELS=$((WIDTH * HEIGHT)) + +# Calculate percentage +DIFF_PERCENT=$(echo "scale=4; $DIFF_PIXELS / $TOTAL_PIXELS * 100" | bc) + +echo "๐Ÿ“Š Results:" +echo " Dimensions: ${WIDTH} ร— ${HEIGHT}" +echo " Total pixels: $(printf "%'d" $TOTAL_PIXELS)" +echo " Different pixels: $(printf "%'d" $DIFF_PIXELS)" +echo " Difference: ${DIFF_PERCENT}%" +echo "" + +if (( $(echo "$DIFF_PERCENT < 0.01" | bc -l) )); then + echo "โœ… ESSENTIALLY IDENTICAL (< 0.01% difference)" + VERDICT="essentially identical" +elif (( $(echo "$DIFF_PERCENT < 0.1" | bc -l) )); then + echo "โš ๏ธ MINOR DIFFERENCES (< 0.1%)" + VERDICT="minor differences detected" +else + echo "โŒ SIGNIFICANT DIFFERENCES (โ‰ฅ 0.1%)" + VERDICT="significant differences detected" +fi + +echo "" +echo "๐Ÿ“ Output files:" +echo " Old render: $TMP_DIR/old.png" +echo " New render: $TMP_DIR/new.png" +echo " Diff image: $TMP_DIR/diff.png" +echo "" + +# Generate HTML comparison page +cat > "$TMP_DIR/comparison.html" << 'EOHTML' + + + + SVG Comparison + + + +
+

๐ŸŽจ SVG Visual Comparison

+

File: FILENAME_PLACEHOLDER

+
+
+
Dimensions
+
DIMENSIONS_PLACEHOLDER
+
+
+
Different Pixels
+
DIFF_PIXELS_PLACEHOLDER
+
+
+
Difference
+
DIFF_PERCENT_PLACEHOLDER%
+
+
+
Verdict
+
VERDICT_PLACEHOLDER
+
+
+
+ +
+
+

๐Ÿ“„ Old (HEAD)

+
+ Old SVG +
+
+ +
+

๐Ÿ“ New (Working)

+
+ New SVG +
+
+ +
+

๐Ÿ” Diff (Red = Changes)

+
+ Diff +
+
+
+ + +EOHTML + +# Determine verdict class for styling +if (( $(echo "$DIFF_PERCENT < 0.01" | bc -l) )); then + VERDICT_CLASS="good" +elif (( $(echo "$DIFF_PERCENT < 0.1" | bc -l) )); then + VERDICT_CLASS="warning" +else + VERDICT_CLASS="bad" +fi + +# Replace placeholders in HTML +sed -i '' "s|FILENAME_PLACEHOLDER|$SVG_FILE|g" "$TMP_DIR/comparison.html" +sed -i '' "s|DIMENSIONS_PLACEHOLDER|${WIDTH} ร— ${HEIGHT}|g" "$TMP_DIR/comparison.html" +sed -i '' "s|DIFF_PIXELS_PLACEHOLDER|$(printf "%'d" $DIFF_PIXELS) / $(printf "%'d" $TOTAL_PIXELS)|g" "$TMP_DIR/comparison.html" +sed -i '' "s|DIFF_PERCENT_PLACEHOLDER|$DIFF_PERCENT|g" "$TMP_DIR/comparison.html" +sed -i '' "s|VERDICT_PLACEHOLDER|$VERDICT|g" "$TMP_DIR/comparison.html" +sed -i '' "s|VERDICT_CLASS_PLACEHOLDER|$VERDICT_CLASS|g" "$TMP_DIR/comparison.html" + +echo "โœ“ Generated comparison page: $TMP_DIR/comparison.html" +echo "" +echo "๐ŸŒ Opening comparison in browser..." +open "$TMP_DIR/comparison.html" +echo "" + +echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" +echo "" +echo "๐Ÿค– AI VISUAL ANALYSIS PROMPT" +echo "" +echo "Copy and paste this into Gemini/Claude with the diff image attached:" +echo "" +echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" +cat << PROMPT + +I've made changes to an Excalidraw diagram SVG file. Please analyze the visual differences between the old and new versions. + +**Automated Analysis:** +- Dimensions: ${WIDTH} ร— ${HEIGHT} pixels +- Different pixels: $(printf "%'d" $DIFF_PIXELS) out of $(printf "%'d" $TOTAL_PIXELS) +- Difference: ${DIFF_PERCENT}% +- Verdict: ${VERDICT} + +**Attached Image:** +The attached image shows the pixel-level diff (red = differences). + +**Questions:** +1. Are the differences purely anti-aliasing/rendering artifacts, or are there actual content changes? +2. If there are content changes, what specifically changed? +3. Do the changes align with the intent to remove zombie Excalidraw elements (elements marked as deleted but left in the JSON)? +4. Is this safe to commit? + +**Context:** +- File: $SVG_FILE +- Changes: Removed 191 lines of zombie JSON from Excalidraw source +- Expected: Visual output should be identical (zombie elements were already marked as deleted) + +PROMPT +echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" +echo "" +echo "๐Ÿ“Ž Attach this file to your AI prompt:" +echo " $TMP_DIR/diff.png" +echo "" +echo "๐Ÿ’ก To open the diff image:" +echo " open $TMP_DIR/diff.png" +echo ""