From 181aeac04a0c00452ae5a0444ec565fa8ea32500 Mon Sep 17 00:00:00 2001 From: Davor Racic Date: Wed, 28 Jan 2026 06:56:12 +0100 Subject: [PATCH] fix(ide): add support for Gemini CLI TOML format (#1431) * fix(ide): add support for Gemini CLI TOML format * fix(ide): Added normalization for config.extension, added .yml to the extension array --------- Co-authored-by: Brian --- .../cli/installers/lib/ide/_config-driven.js | 65 +++++++++++++------ .../lib/ide/shared/agent-command-generator.js | 4 +- .../ide/shared/workflow-command-generator.js | 14 +++- .../ide/templates/combined/gemini-agent.toml | 14 ++++ .../combined/gemini-workflow-yaml.toml | 16 +++++ .../templates/combined/gemini-workflow.toml | 14 ++++ .../ide/templates/gemini-agent-command.toml | 14 ---- .../ide/templates/gemini-task-command.toml | 12 ---- .../lib/ide/templates/split/gemini/body.md | 10 --- .../ide/templates/split/gemini/header.toml | 2 - 10 files changed, 104 insertions(+), 61 deletions(-) create mode 100644 tools/cli/installers/lib/ide/templates/combined/gemini-agent.toml create mode 100644 tools/cli/installers/lib/ide/templates/combined/gemini-workflow-yaml.toml create mode 100644 tools/cli/installers/lib/ide/templates/combined/gemini-workflow.toml delete mode 100644 tools/cli/installers/lib/ide/templates/gemini-agent-command.toml delete mode 100644 tools/cli/installers/lib/ide/templates/gemini-task-command.toml delete mode 100644 tools/cli/installers/lib/ide/templates/split/gemini/body.md delete mode 100644 tools/cli/installers/lib/ide/templates/split/gemini/header.toml diff --git a/tools/cli/installers/lib/ide/_config-driven.js b/tools/cli/installers/lib/ide/_config-driven.js index 7d637faf..4ebd173e 100644 --- a/tools/cli/installers/lib/ide/_config-driven.js +++ b/tools/cli/installers/lib/ide/_config-driven.js @@ -132,12 +132,12 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup { */ async writeAgentArtifacts(targetPath, artifacts, templateType, config = {}) { // Try to load platform-specific template, fall back to default-agent - const template = await this.loadTemplate(templateType, 'agent', config, 'default-agent'); + const { content: template, extension } = await this.loadTemplate(templateType, 'agent', config, 'default-agent'); let count = 0; for (const artifact of artifacts) { const content = this.renderTemplate(template, artifact); - const filename = this.generateFilename(artifact, 'agent'); + const filename = this.generateFilename(artifact, 'agent', extension); const filePath = path.join(targetPath, filename); await this.writeFile(filePath, content); count++; @@ -167,9 +167,10 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup { // Fall back to default templates if specific ones don't exist const finalTemplateType = artifact.isYamlWorkflow ? 'default-workflow-yaml' : 'default-workflow'; - const template = await this.loadTemplate(workflowTemplateType, 'workflow', config, finalTemplateType); + // workflowTemplateType already contains full name (e.g., 'gemini-workflow-yaml'), so pass empty artifactType + const { content: template, extension } = await this.loadTemplate(workflowTemplateType, '', config, finalTemplateType); const content = this.renderTemplate(template, artifact); - const filename = this.generateFilename(artifact, 'workflow'); + const filename = this.generateFilename(artifact, 'workflow', extension); const filePath = path.join(targetPath, filename); await this.writeFile(filePath, content); count++; @@ -185,34 +186,47 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup { * @param {string} artifactType - Artifact type (agent, workflow, task, tool) * @param {Object} config - Installation configuration * @param {string} fallbackTemplateType - Fallback template type if requested template not found - * @returns {Promise} Template content + * @returns {Promise<{content: string, extension: string}>} Template content and extension */ async loadTemplate(templateType, artifactType, config = {}, fallbackTemplateType = null) { const { header_template, body_template } = config; // Check for separate header/body templates if (header_template || body_template) { - return await this.loadSplitTemplates(templateType, artifactType, header_template, body_template); + const content = await this.loadSplitTemplates(templateType, artifactType, header_template, body_template); + // Allow config to override extension, default to .md + const ext = config.extension || '.md'; + const normalizedExt = ext.startsWith('.') ? ext : `.${ext}`; + return { content, extension: normalizedExt }; } - // Load combined template - const templateName = `${templateType}-${artifactType}.md`; - const templatePath = path.join(__dirname, 'templates', 'combined', templateName); + // Load combined template - try multiple extensions + // If artifactType is empty, templateType already contains full name (e.g., 'gemini-workflow-yaml') + const templateBaseName = artifactType ? `${templateType}-${artifactType}` : templateType; + const templateDir = path.join(__dirname, 'templates', 'combined'); + const extensions = ['.md', '.toml', '.yaml', '.yml']; - if (await fs.pathExists(templatePath)) { - return await fs.readFile(templatePath, 'utf8'); + for (const ext of extensions) { + const templatePath = path.join(templateDir, templateBaseName + ext); + if (await fs.pathExists(templatePath)) { + const content = await fs.readFile(templatePath, 'utf8'); + return { content, extension: ext }; + } } // Fall back to default template (if provided) if (fallbackTemplateType) { - const fallbackPath = path.join(__dirname, 'templates', 'combined', `${fallbackTemplateType}.md`); - if (await fs.pathExists(fallbackPath)) { - return await fs.readFile(fallbackPath, 'utf8'); + for (const ext of extensions) { + const fallbackPath = path.join(templateDir, `${fallbackTemplateType}${ext}`); + if (await fs.pathExists(fallbackPath)) { + const content = await fs.readFile(fallbackPath, 'utf8'); + return { content, extension: ext }; + } } } // Ultimate fallback - minimal template - return this.getDefaultTemplate(artifactType); + return { content: this.getDefaultTemplate(artifactType), extension: '.md' }; } /** @@ -326,13 +340,26 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}} * Generate filename for artifact * @param {Object} artifact - Artifact data * @param {string} artifactType - Artifact type (agent, workflow, task, tool) + * @param {string} extension - File extension to use (e.g., '.md', '.toml') * @returns {string} Generated filename */ - generateFilename(artifact, artifactType) { + generateFilename(artifact, artifactType, extension = '.md') { const { toDashPath } = require('./shared/path-utils'); - // toDashPath already handles the .agent.md suffix for agents correctly - // No need to add it again here - return toDashPath(artifact.relativePath); + + // Reuse central logic to ensure consistent naming conventions + const standardName = toDashPath(artifact.relativePath); + + // Clean up potential double extensions from source files (e.g. .yaml.md -> .md) + const baseName = standardName.replace(/\.(yaml|yml)\.md$/, '.md'); + + // If using default markdown, preserve original behavior (keeps .agent.md suffix) + if (extension === '.md') { + return baseName; + } + + // For other extensions (e.g., .toml), remove .agent suffix as well + // Gemini doesn't support double-dot patterns like .agent.toml + return baseName.replace(/(\.agent)?(\.md|\.yaml|\.yml)$/, extension); } /** diff --git a/tools/cli/installers/lib/ide/shared/agent-command-generator.js b/tools/cli/installers/lib/ide/shared/agent-command-generator.js index 084ed048..da8c1ef0 100644 --- a/tools/cli/installers/lib/ide/shared/agent-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/agent-command-generator.js @@ -32,7 +32,9 @@ class AgentCommandGenerator { // Use relativePath if available (for nested agents), otherwise just name with .md const agentPathInModule = agent.relativePath || `${agent.name}.md`; // Calculate the relative agent path (e.g., bmm/agents/pm.md) - let agentRelPath = agent.path; + let agentRelPath = agent.path || ''; + // Normalize path separators for cross-platform compatibility + agentRelPath = agentRelPath.replaceAll('\\', '/'); // Remove _bmad/ prefix if present to get relative path from project root // Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...) if (agentRelPath.includes('_bmad/')) { 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 b3410b0f..42bad24a 100644 --- a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js @@ -68,7 +68,9 @@ class WorkflowCommandGenerator { for (const workflow of allWorkflows) { const commandContent = await this.generateCommandContent(workflow, bmadDir); // Calculate the relative workflow path (e.g., bmm/workflows/4-implementation/sprint-planning/workflow.yaml) - let workflowRelPath = workflow.path; + let workflowRelPath = workflow.path || ''; + // Normalize path separators for cross-platform compatibility + workflowRelPath = workflowRelPath.replaceAll('\\', '/'); // Remove _bmad/ prefix if present to get relative path from project root // Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...) if (workflowRelPath.includes('_bmad/')) { @@ -76,9 +78,15 @@ class WorkflowCommandGenerator { if (parts.length > 1) { workflowRelPath = parts.slice(1).join('/'); } + } else if (workflowRelPath.includes('/src/')) { + // Normalize source paths (e.g. .../src/bmm/...) to relative module path (e.g. bmm/...) + const match = workflowRelPath.match(/\/src\/([^/]+)\/(.+)/); + if (match) { + workflowRelPath = `${match[1]}/${match[2]}`; + } } - // Determine if this is a YAML workflow - const isYamlWorkflow = workflow.path.endsWith('.yaml') || workflow.path.endsWith('.yml'); + // Determine if this is a YAML workflow (use normalized path which is guaranteed to be a string) + const isYamlWorkflow = workflowRelPath.endsWith('.yaml') || workflowRelPath.endsWith('.yml'); artifacts.push({ type: 'workflow-command', isYamlWorkflow: isYamlWorkflow, // For template selection diff --git a/tools/cli/installers/lib/ide/templates/combined/gemini-agent.toml b/tools/cli/installers/lib/ide/templates/combined/gemini-agent.toml new file mode 100644 index 00000000..ae5f791c --- /dev/null +++ b/tools/cli/installers/lib/ide/templates/combined/gemini-agent.toml @@ -0,0 +1,14 @@ +description = "Activates the {{name}} agent from the BMad Method." +prompt = """ +CRITICAL: You are now the BMad '{{name}}' agent. + +PRE-FLIGHT CHECKLIST: +1. [ ] IMMEDIATE ACTION: Load and parse {project-root}/{{bmadFolderName}}/{{module}}/config.yaml - store ALL config values in memory for use throughout the session. +2. [ ] IMMEDIATE ACTION: Read and internalize the full agent definition at {project-root}/{{bmadFolderName}}/{{path}}. +3. [ ] CONFIRM: The user's name from config is {user_name}. + +Only after all checks are complete, greet the user by name and display the menu. +Acknowledge this checklist is complete in your first response. + +AGENT DEFINITION: {project-root}/{{bmadFolderName}}/{{path}} +""" diff --git a/tools/cli/installers/lib/ide/templates/combined/gemini-workflow-yaml.toml b/tools/cli/installers/lib/ide/templates/combined/gemini-workflow-yaml.toml new file mode 100644 index 00000000..063ca0d0 --- /dev/null +++ b/tools/cli/installers/lib/ide/templates/combined/gemini-workflow-yaml.toml @@ -0,0 +1,16 @@ +description = """{{description}}""" +prompt = """ +Execute the BMAD '{{name}}' workflow. + +CRITICAL: This is a structured YAML workflow. Follow these steps precisely: + +1. LOAD the workflow definition from {project-root}/{{bmadFolderName}}/{{workflow_path}} +2. PARSE the YAML structure to understand: + - Workflow phases and steps + - Required inputs and outputs + - Dependencies between steps +3. EXECUTE each step in order +4. VALIDATE outputs before proceeding to next step + +WORKFLOW FILE: {project-root}/{{bmadFolderName}}/{{workflow_path}} +""" diff --git a/tools/cli/installers/lib/ide/templates/combined/gemini-workflow.toml b/tools/cli/installers/lib/ide/templates/combined/gemini-workflow.toml new file mode 100644 index 00000000..52624106 --- /dev/null +++ b/tools/cli/installers/lib/ide/templates/combined/gemini-workflow.toml @@ -0,0 +1,14 @@ +description = """{{description}}""" +prompt = """ +Execute the BMAD '{{name}}' workflow. + +CRITICAL: You must load and follow the workflow definition exactly. + +WORKFLOW INSTRUCTIONS: +1. LOAD the workflow file from {project-root}/{{bmadFolderName}}/{{workflow_path}} +2. READ its entire contents +3. FOLLOW every step precisely as specified +4. DO NOT skip or modify any steps + +WORKFLOW FILE: {project-root}/{{bmadFolderName}}/{{workflow_path}} +""" diff --git a/tools/cli/installers/lib/ide/templates/gemini-agent-command.toml b/tools/cli/installers/lib/ide/templates/gemini-agent-command.toml deleted file mode 100644 index 2dc4c2c5..00000000 --- a/tools/cli/installers/lib/ide/templates/gemini-agent-command.toml +++ /dev/null @@ -1,14 +0,0 @@ -description = "Activates the {{title}} agent from the BMad Method." -prompt = """ -CRITICAL: You are now the BMad '{{title}}' agent. - -PRE-FLIGHT CHECKLIST: -1. [ ] IMMEDIATE ACTION: Load and parse @{_bmad}/{{module}}/config.yaml - store ALL config values in memory for use throughout the session. -2. [ ] IMMEDIATE ACTION: Read and internalize the full agent definition at @{_bmad}/{{module}}/agents/{{name}}.md. -3. [ ] CONFIRM: The user's name from config is {user_name}. - -Only after all checks are complete, greet the user by name and display the menu. -Acknowledge this checklist is complete in your first response. - -AGENT DEFINITION: @{_bmad}/{{module}}/agents/{{name}}.md -""" diff --git a/tools/cli/installers/lib/ide/templates/gemini-task-command.toml b/tools/cli/installers/lib/ide/templates/gemini-task-command.toml deleted file mode 100644 index 2dbedaaa..00000000 --- a/tools/cli/installers/lib/ide/templates/gemini-task-command.toml +++ /dev/null @@ -1,12 +0,0 @@ -description = "Executes the {{taskName}} task from the BMad Method." -prompt = """ -Execute the following BMad Method task workflow: - -PRE-FLIGHT CHECKLIST: -1. [ ] IMMEDIATE ACTION: Load and parse @{_bmad}/{{module}}/config.yaml. -2. [ ] IMMEDIATE ACTION: Read and load the task definition at @{_bmad}/{{module}}/tasks/{{filename}}. - -Follow all instructions and complete the task as defined. - -TASK DEFINITION: @{_bmad}/{{module}}/tasks/{{filename}} -""" diff --git a/tools/cli/installers/lib/ide/templates/split/gemini/body.md b/tools/cli/installers/lib/ide/templates/split/gemini/body.md deleted file mode 100644 index b20f6651..00000000 --- a/tools/cli/installers/lib/ide/templates/split/gemini/body.md +++ /dev/null @@ -1,10 +0,0 @@ -You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. - - -1. LOAD the FULL agent file from {project-root}/_bmad/{{path}} -2. READ its entire contents - this contains the complete agent persona, menu, and instructions -3. FOLLOW every step in the section precisely -4. DISPLAY the welcome/greeting as instructed -5. PRESENT the numbered menu -6. WAIT for user input before proceeding - diff --git a/tools/cli/installers/lib/ide/templates/split/gemini/header.toml b/tools/cli/installers/lib/ide/templates/split/gemini/header.toml deleted file mode 100644 index 099b1e26..00000000 --- a/tools/cli/installers/lib/ide/templates/split/gemini/header.toml +++ /dev/null @@ -1,2 +0,0 @@ -name = "{{name}}" -description = "{{description}}"