From 91b2d84ff8d9b482e82188c9d41e966cdfe429a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Sat, 31 Jan 2026 21:29:54 +0100 Subject: [PATCH 1/9] fix: support CRLF line endings and add task/tool templates for all IDEs --- .../lib/core/dependency-resolver.js | 2 +- .../installers/lib/core/manifest-generator.js | 6 +- .../cli/installers/lib/ide/_config-driven.js | 76 +++++++++++++++++-- .../ide/shared/task-tool-command-generator.js | 75 ++++++++++++++++++ .../ide/templates/combined/default-task.md | 10 +++ .../ide/templates/combined/default-tool.md | 10 +++ .../ide/templates/combined/gemini-task.toml | 11 +++ .../ide/templates/combined/gemini-tool.toml | 11 +++ 8 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 tools/cli/installers/lib/ide/templates/combined/default-task.md create mode 100644 tools/cli/installers/lib/ide/templates/combined/default-tool.md create mode 100644 tools/cli/installers/lib/ide/templates/combined/gemini-task.toml create mode 100644 tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml diff --git a/tools/cli/installers/lib/core/dependency-resolver.js b/tools/cli/installers/lib/core/dependency-resolver.js index 317b07f8..ee8a8a12 100644 --- a/tools/cli/installers/lib/core/dependency-resolver.js +++ b/tools/cli/installers/lib/core/dependency-resolver.js @@ -146,7 +146,7 @@ class DependencyResolver { const content = await fs.readFile(file.path, 'utf8'); // Parse YAML frontmatter for explicit dependencies - const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (frontmatterMatch) { try { // Pre-process to handle backticks in YAML values diff --git a/tools/cli/installers/lib/core/manifest-generator.js b/tools/cli/installers/lib/core/manifest-generator.js index 100164d5..33e5d0cb 100644 --- a/tools/cli/installers/lib/core/manifest-generator.js +++ b/tools/cli/installers/lib/core/manifest-generator.js @@ -161,7 +161,7 @@ class ManifestGenerator { workflow = yaml.parse(content); } else { // Parse MD workflow with YAML frontmatter - const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (!frontmatterMatch) { if (debug) { console.log(`[DEBUG] Skipped (no frontmatter): ${fullPath}`); @@ -392,7 +392,7 @@ class ManifestGenerator { if (file.endsWith('.md')) { // Parse YAML frontmatter for .md tasks - const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (frontmatterMatch) { try { const frontmatter = yaml.parse(frontmatterMatch[1]); @@ -481,7 +481,7 @@ class ManifestGenerator { if (file.endsWith('.md')) { // Parse YAML frontmatter for .md tools - const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (frontmatterMatch) { try { const frontmatter = yaml.parse(frontmatterMatch[1]); diff --git a/tools/cli/installers/lib/ide/_config-driven.js b/tools/cli/installers/lib/ide/_config-driven.js index 022bff7b..089d62fd 100644 --- a/tools/cli/installers/lib/ide/_config-driven.js +++ b/tools/cli/installers/lib/ide/_config-driven.js @@ -86,10 +86,11 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup { results.workflows = await this.writeWorkflowArtifacts(targetPath, artifacts, template_type, config); } - // Install tasks and tools + // Install tasks and tools using template system (supports TOML for Gemini, MD for others) if (!artifact_types || artifact_types.includes('tasks') || artifact_types.includes('tools')) { - const taskToolGen = new TaskToolCommandGenerator(); - const taskToolResult = await taskToolGen.generateDashTaskToolCommands(projectDir, bmadDir, targetPath); + const taskToolGen = new TaskToolCommandGenerator(this.bmadFolderName); + const { artifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir); + const taskToolResult = await this.writeTaskToolArtifacts(targetPath, artifacts, template_type, config); results.tasks = taskToolResult.tasks || 0; results.tools = taskToolResult.tools || 0; } @@ -180,6 +181,53 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup { return count; } + /** + * Write task/tool artifacts to target directory using templates + * @param {string} targetPath - Target directory path + * @param {Array} artifacts - Task/tool artifacts + * @param {string} templateType - Template type to use + * @param {Object} config - Installation configuration + * @returns {Promise} Counts of tasks and tools written + */ + async writeTaskToolArtifacts(targetPath, artifacts, templateType, config = {}) { + let taskCount = 0; + let toolCount = 0; + + // Pre-load templates to avoid repeated file I/O in the loop + const taskTemplate = await this.loadTemplate(templateType, 'task', config, 'default-task'); + const toolTemplate = await this.loadTemplate(templateType, 'tool', config, 'default-tool'); + + const { artifact_types } = config; + + for (const artifact of artifacts) { + if (artifact.type !== 'task' && artifact.type !== 'tool') { + continue; + } + + // Skip if the specific artifact type is not requested in config + if (artifact_types) { + if (artifact.type === 'task' && !artifact_types.includes('tasks')) continue; + if (artifact.type === 'tool' && !artifact_types.includes('tools')) continue; + } + + // Use pre-loaded template based on artifact type + const { content: template, extension } = artifact.type === 'task' ? taskTemplate : toolTemplate; + + const content = this.renderTemplate(template, artifact); + const filename = this.generateFilename(artifact, artifact.type, extension); + const filePath = path.join(targetPath, filename); + await this.writeFile(filePath, content); + + if (artifact.type === 'task') { + taskCount++; + } else { + toolCount++; + } + } + + return { tasks: taskCount, tools: toolCount }; + } + /** * Load template based on type and configuration * @param {string} templateType - Template type (claude, windsurf, etc.) @@ -314,10 +362,24 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}} renderTemplate(template, artifact) { // Use the appropriate path property based on artifact type let pathToUse = artifact.relativePath || ''; - if (artifact.type === 'agent-launcher') { - pathToUse = artifact.agentPath || artifact.relativePath || ''; - } else if (artifact.type === 'workflow-command') { - pathToUse = artifact.workflowPath || artifact.relativePath || ''; + switch (artifact.type) { + case 'agent-launcher': { + pathToUse = artifact.agentPath || artifact.relativePath || ''; + + break; + } + case 'workflow-command': { + pathToUse = artifact.workflowPath || artifact.relativePath || ''; + + break; + } + case 'task': + case 'tool': { + pathToUse = artifact.path || artifact.relativePath || ''; + + break; + } + // No default } let rendered = template diff --git a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js index 6b90de9f..d92ae080 100644 --- a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js @@ -8,6 +8,81 @@ const { toColonName, toColonPath, toDashPath } = require('./path-utils'); * Generates command files for standalone tasks and tools */ class TaskToolCommandGenerator { + /** + * @param {string} bmadFolderName - Name of the BMAD folder for template rendering (default: 'bmad') + * Note: This parameter is accepted for API consistency with AgentCommandGenerator and + * WorkflowCommandGenerator, but is not used for path stripping. The manifest always stores + * filesystem paths with '_bmad/' prefix (the actual folder name), while bmadFolderName is + * used for template placeholder rendering ({{bmadFolderName}}). + */ + constructor(bmadFolderName = 'bmad') { + this.bmadFolderName = bmadFolderName; + } + + /** + * Collect task and tool artifacts for IDE installation + * @param {string} bmadDir - BMAD installation directory + * @returns {Promise} Artifacts array with metadata + */ + async collectTaskToolArtifacts(bmadDir) { + const tasks = await this.loadTaskManifest(bmadDir); + const tools = await this.loadToolManifest(bmadDir); + + // Filter to only standalone items + const standaloneTasks = tasks ? tasks.filter((t) => t.standalone === 'true' || t.standalone === true) : []; + const standaloneTools = tools ? tools.filter((t) => t.standalone === 'true' || t.standalone === true) : []; + + const artifacts = []; + + // Collect task artifacts + for (const task of standaloneTasks) { + let taskPath = (task.path || '').replaceAll('\\', '/'); + // Remove _bmad/ prefix if present to get relative path within bmad folder + if (taskPath.startsWith('_bmad/')) { + taskPath = taskPath.slice(6); // Remove '_bmad/' + } + + artifacts.push({ + type: 'task', + name: task.name, + displayName: task.displayName || task.name, + description: task.description || `Execute ${task.displayName || task.name}`, + module: task.module, + // Use forward slashes for cross-platform consistency (not path.join which uses backslashes on Windows) + relativePath: `${task.module}/tasks/${task.name}.md`, + path: taskPath, + }); + } + + // Collect tool artifacts + for (const tool of standaloneTools) { + let toolPath = (tool.path || '').replaceAll('\\', '/'); + // Remove _bmad/ prefix if present to get relative path within bmad folder + if (toolPath.startsWith('_bmad/')) { + toolPath = toolPath.slice(6); // Remove '_bmad/' + } + + artifacts.push({ + type: 'tool', + name: tool.name, + displayName: tool.displayName || tool.name, + description: tool.description || `Execute ${tool.displayName || tool.name}`, + module: tool.module, + // Use forward slashes for cross-platform consistency (not path.join which uses backslashes on Windows) + relativePath: `${tool.module}/tools/${tool.name}.md`, + path: toolPath, + }); + } + + return { + artifacts, + counts: { + tasks: standaloneTasks.length, + tools: standaloneTools.length, + }, + }; + } + /** * Generate task and tool commands from manifest CSVs * @param {string} projectDir - Project directory diff --git a/tools/cli/installers/lib/ide/templates/combined/default-task.md b/tools/cli/installers/lib/ide/templates/combined/default-task.md new file mode 100644 index 00000000..b865d6ff --- /dev/null +++ b/tools/cli/installers/lib/ide/templates/combined/default-task.md @@ -0,0 +1,10 @@ +--- +name: '{{name}}' +description: '{{description}}' +--- + +# {{name}} + +Read the entire task file at: {project-root}/{{bmadFolderName}}/{{path}} + +Follow all instructions in the task file exactly as written. diff --git a/tools/cli/installers/lib/ide/templates/combined/default-tool.md b/tools/cli/installers/lib/ide/templates/combined/default-tool.md new file mode 100644 index 00000000..11c6aac8 --- /dev/null +++ b/tools/cli/installers/lib/ide/templates/combined/default-tool.md @@ -0,0 +1,10 @@ +--- +name: '{{name}}' +description: '{{description}}' +--- + +# {{name}} + +Read the entire tool file at: {project-root}/{{bmadFolderName}}/{{path}} + +Follow all instructions in the tool file exactly as written. diff --git a/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml b/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml new file mode 100644 index 00000000..e8d08818 --- /dev/null +++ b/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml @@ -0,0 +1,11 @@ +description = "Executes the {{name}} task from the BMad Method." +prompt = """ +Execute the BMAD '{{name}}' task. + +TASK INSTRUCTIONS: +1. LOAD the task file from {project-root}/{{bmadFolderName}}/{{path}} +2. READ its entire contents +3. FOLLOW every instruction precisely as specified + +TASK FILE: {project-root}/{{bmadFolderName}}/{{path}} +""" diff --git a/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml b/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml new file mode 100644 index 00000000..08410f04 --- /dev/null +++ b/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml @@ -0,0 +1,11 @@ +description = "Executes the {{name}} tool from the BMad Method." +prompt = """ +Execute the BMAD '{{name}}' tool. + +TOOL INSTRUCTIONS: +1. LOAD the tool file from {project-root}/{{bmadFolderName}}/{{path}} +2. READ its entire contents +3. FOLLOW every instruction precisely as specified + +TOOL FILE: {project-root}/{{bmadFolderName}}/{{path}} +""" From c0f51350c25b76d78e0c2afb3fe19c3e292c9e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Sat, 31 Jan 2026 21:43:42 +0100 Subject: [PATCH 2/9] fix: preserve file extensions in IDE task/tool paths and update BMAD branding --- .../lib/ide/shared/task-tool-command-generator.js | 10 ++++++---- .../lib/ide/templates/combined/gemini-task.toml | 2 +- .../lib/ide/templates/combined/gemini-tool.toml | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js index d92ae080..664b0cfb 100644 --- a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js @@ -42,6 +42,7 @@ class TaskToolCommandGenerator { taskPath = taskPath.slice(6); // Remove '_bmad/' } + const taskExt = path.extname(taskPath) || '.md'; artifacts.push({ type: 'task', name: task.name, @@ -49,7 +50,7 @@ class TaskToolCommandGenerator { description: task.description || `Execute ${task.displayName || task.name}`, module: task.module, // Use forward slashes for cross-platform consistency (not path.join which uses backslashes on Windows) - relativePath: `${task.module}/tasks/${task.name}.md`, + relativePath: `${task.module}/tasks/${task.name}${taskExt}`, path: taskPath, }); } @@ -62,6 +63,7 @@ class TaskToolCommandGenerator { toolPath = toolPath.slice(6); // Remove '_bmad/' } + const toolExt = path.extname(toolPath) || '.md'; artifacts.push({ type: 'tool', name: tool.name, @@ -69,7 +71,7 @@ class TaskToolCommandGenerator { description: tool.description || `Execute ${tool.displayName || tool.name}`, module: tool.module, // Use forward slashes for cross-platform consistency (not path.join which uses backslashes on Windows) - relativePath: `${tool.module}/tools/${tool.name}.md`, + relativePath: `${tool.module}/tools/${tool.name}${toolExt}`, path: toolPath, }); } @@ -261,7 +263,7 @@ Follow all instructions in the ${type} file exactly as written. // Generate command files for tasks for (const task of standaloneTasks) { const commandContent = this.generateCommandContent(task, 'task'); - // Use underscore format: bmad_bmm_name.md + // Use dash format: bmad-bmm-name.md const flatName = toDashPath(`${task.module}/tasks/${task.name}.md`); const commandPath = path.join(baseCommandsDir, flatName); await fs.ensureDir(path.dirname(commandPath)); @@ -272,7 +274,7 @@ Follow all instructions in the ${type} file exactly as written. // Generate command files for tools for (const tool of standaloneTools) { const commandContent = this.generateCommandContent(tool, 'tool'); - // Use underscore format: bmad_bmm_name.md + // Use dash format: bmad-bmm-name.md const flatName = toDashPath(`${tool.module}/tools/${tool.name}.md`); const commandPath = path.join(baseCommandsDir, flatName); await fs.ensureDir(path.dirname(commandPath)); diff --git a/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml b/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml index e8d08818..7d15e216 100644 --- a/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml +++ b/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml @@ -1,4 +1,4 @@ -description = "Executes the {{name}} task from the BMad Method." +description = "Executes the {{name}} task from the BMAD Method." prompt = """ Execute the BMAD '{{name}}' task. diff --git a/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml b/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml index 08410f04..fc78c6b7 100644 --- a/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml +++ b/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml @@ -1,4 +1,4 @@ -description = "Executes the {{name}} tool from the BMad Method." +description = "Executes the {{name}} tool from the BMAD Method." prompt = """ Execute the BMAD '{{name}}' tool. From 9e50f015a3fd3074cbf04d1e0770ab95bbd52580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Sat, 31 Jan 2026 22:25:00 +0100 Subject: [PATCH 3/9] fix: double extension issue in wrapper filename generation --- pr.md | 81 +++++++++++++++++++ .../cli/installers/lib/ide/_config-driven.js | 5 +- .../installers/lib/ide/shared/path-utils.js | 7 +- 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 pr.md diff --git a/pr.md b/pr.md new file mode 100644 index 00000000..11fb53ce --- /dev/null +++ b/pr.md @@ -0,0 +1,81 @@ +## Summary + +This PR fixes bugs affecting task/tool installation across IDEs: + +1. **CRLF Line Ending Bug** - Frontmatter parsing failed on Windows due to CRLF (`\r\n`) line endings +2. **Gemini CLI TOML Support** - Tasks/tools were generated as `.md` files instead of `.toml` for Gemini CLI +3. **File Extension Preservation** - `.xml` task/tool files had incorrect paths (hardcoded `.md`) + +## Problem + +### Issue 1: Tasks not installed on Windows +The manifest generator's regex `^---\n` expected LF-only line endings, but Windows files have CRLF. This caused: +- YAML frontmatter parsing to silently fail +- All `.md` tasks defaulting to `standalone: false` +- Tasks like `bmad-help` not being installed despite having `standalone: true` in their frontmatter + +### Issue 2: Gemini CLI incompatibility +The `TaskToolCommandGenerator` hardcoded markdown format for all IDEs, but Gemini CLI requires TOML format. Agents and workflows already used the template system correctly, but tasks/tools did not. + +### Issue 3: Incorrect file extensions in paths +The `relativePath` property was hardcoded to `.md`, so tasks/tools with `.xml` extension got incorrect paths like `bmm/tasks/foo.md` instead of `bmm/tasks/foo.xml`. + +## Solution + +### Fix 1: CRLF-aware regex (4 files) +Changed frontmatter regex from `^---\n` to `^---\r?\n` to handle both Windows (CRLF) and Unix (LF) line endings. + +**Files modified:** +- `tools/cli/installers/lib/core/manifest-generator.js` (3 occurrences) +- `tools/cli/installers/lib/core/dependency-resolver.js` (1 occurrence) + +### Fix 2: Template-based task/tool generation +Extended the existing template system (used by agents/workflows) to also handle tasks/tools. + +**New files:** +- `tools/cli/installers/lib/ide/templates/combined/gemini-task.toml` +- `tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml` +- `tools/cli/installers/lib/ide/templates/combined/default-task.md` +- `tools/cli/installers/lib/ide/templates/combined/default-tool.md` + +**Modified files:** +- `tools/cli/installers/lib/ide/shared/task-tool-command-generator.js` + - Added `collectTaskToolArtifacts()` method + - Added constructor with `bmadFolderName` parameter +- `tools/cli/installers/lib/ide/_config-driven.js` + - Added `writeTaskToolArtifacts()` method with `artifact_types` filtering + - Updated `installToTarget()` to use template system + - Updated `renderTemplate()` to handle task/tool paths + +### Fix 3: File extension preservation (4d7ca00) +The `relativePath` property was hardcoded to `.md` extension, causing incorrect paths for `.xml` task/tool files. + +**Modified files:** +- `tools/cli/installers/lib/ide/shared/task-tool-command-generator.js` + - Extract actual extension from source path with `.md` fallback + - Fixed misleading comments ("underscore format" → "dash format") +- `tools/cli/installers/lib/ide/templates/combined/gemini-task.toml` + - Fixed branding: "BMad" → "BMAD" +- `tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml` + - Fixed branding: "BMad" → "BMAD" + +## Test Plan + +- [x] Windows install (CMD) - tasks installed with correct frontmatter parsing +- [x] WSL/Linux install - tasks installed correctly +- [x] Gemini CLI generates `.toml` files for tasks/tools +- [x] Claude Code generates `.md` files for tasks/tools +- [x] All other IDEs (Cursor, Windsurf, Trae, etc.) generate `.md` files +- [x] `bmad-help` task now correctly has `standalone: true` in manifest +- [x] Existing agent/workflow installation unaffected +- [x] `.xml` tasks/tools get correct extension in `relativePath` + +## Breaking Changes + +None - this is purely a bug fix. Existing installations will work correctly after reinstall. + +--- + +Generated with [Claude Code](https://claude.ai/code) + +Co-Authored-By: Claude Opus 4.5 diff --git a/tools/cli/installers/lib/ide/_config-driven.js b/tools/cli/installers/lib/ide/_config-driven.js index 089d62fd..26b76577 100644 --- a/tools/cli/installers/lib/ide/_config-driven.js +++ b/tools/cli/installers/lib/ide/_config-driven.js @@ -411,8 +411,9 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}} // 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'); + // Clean up potential double extensions from source files (e.g. .yaml.md, .xml.md -> .md) + // This handles any extensions that might slip through toDashPath() + const baseName = standardName.replace(/\.(md|yaml|yml|json|xml|toml)\.md$/i, '.md'); // If using default markdown, preserve the bmad-agent- prefix for agents if (extension === '.md') { diff --git a/tools/cli/installers/lib/ide/shared/path-utils.js b/tools/cli/installers/lib/ide/shared/path-utils.js index d6ad00f5..561a9029 100644 --- a/tools/cli/installers/lib/ide/shared/path-utils.js +++ b/tools/cli/installers/lib/ide/shared/path-utils.js @@ -59,7 +59,9 @@ function toDashPath(relativePath) { return 'bmad-unknown.md'; } - const withoutExt = relativePath.replace('.md', ''); + // Strip common file extensions to avoid double extensions in generated filenames + // e.g., 'create-story.xml' → 'create-story', 'workflow.yaml' → 'workflow' + const withoutExt = relativePath.replace(/\.(md|yaml|yml|json|xml|toml)$/i, ''); const parts = withoutExt.split(/[/\\]/); const module = parts[0]; @@ -183,7 +185,8 @@ function toUnderscoreName(module, type, name) { * @deprecated Use toDashPath instead */ function toUnderscorePath(relativePath) { - const withoutExt = relativePath.replace('.md', ''); + // Strip common file extensions (same as toDashPath for consistency) + const withoutExt = relativePath.replace(/\.(md|yaml|yml|json|xml|toml)$/i, ''); const parts = withoutExt.split(/[/\\]/); const module = parts[0]; From f334f66164db68981da3f30dc079bca0ec597108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Sat, 31 Jan 2026 23:24:24 +0100 Subject: [PATCH 4/9] fix: correct path handling and variable reference in task/tool command generator --- pr.md | 81 ------------------- tools/cli/installers/lib/ide/codex.js | 8 +- .../ide/shared/task-tool-command-generator.js | 28 ++++++- tools/cli/lib/agent/installer.js | 2 +- 4 files changed, 34 insertions(+), 85 deletions(-) delete mode 100644 pr.md diff --git a/pr.md b/pr.md deleted file mode 100644 index 11fb53ce..00000000 --- a/pr.md +++ /dev/null @@ -1,81 +0,0 @@ -## Summary - -This PR fixes bugs affecting task/tool installation across IDEs: - -1. **CRLF Line Ending Bug** - Frontmatter parsing failed on Windows due to CRLF (`\r\n`) line endings -2. **Gemini CLI TOML Support** - Tasks/tools were generated as `.md` files instead of `.toml` for Gemini CLI -3. **File Extension Preservation** - `.xml` task/tool files had incorrect paths (hardcoded `.md`) - -## Problem - -### Issue 1: Tasks not installed on Windows -The manifest generator's regex `^---\n` expected LF-only line endings, but Windows files have CRLF. This caused: -- YAML frontmatter parsing to silently fail -- All `.md` tasks defaulting to `standalone: false` -- Tasks like `bmad-help` not being installed despite having `standalone: true` in their frontmatter - -### Issue 2: Gemini CLI incompatibility -The `TaskToolCommandGenerator` hardcoded markdown format for all IDEs, but Gemini CLI requires TOML format. Agents and workflows already used the template system correctly, but tasks/tools did not. - -### Issue 3: Incorrect file extensions in paths -The `relativePath` property was hardcoded to `.md`, so tasks/tools with `.xml` extension got incorrect paths like `bmm/tasks/foo.md` instead of `bmm/tasks/foo.xml`. - -## Solution - -### Fix 1: CRLF-aware regex (4 files) -Changed frontmatter regex from `^---\n` to `^---\r?\n` to handle both Windows (CRLF) and Unix (LF) line endings. - -**Files modified:** -- `tools/cli/installers/lib/core/manifest-generator.js` (3 occurrences) -- `tools/cli/installers/lib/core/dependency-resolver.js` (1 occurrence) - -### Fix 2: Template-based task/tool generation -Extended the existing template system (used by agents/workflows) to also handle tasks/tools. - -**New files:** -- `tools/cli/installers/lib/ide/templates/combined/gemini-task.toml` -- `tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml` -- `tools/cli/installers/lib/ide/templates/combined/default-task.md` -- `tools/cli/installers/lib/ide/templates/combined/default-tool.md` - -**Modified files:** -- `tools/cli/installers/lib/ide/shared/task-tool-command-generator.js` - - Added `collectTaskToolArtifacts()` method - - Added constructor with `bmadFolderName` parameter -- `tools/cli/installers/lib/ide/_config-driven.js` - - Added `writeTaskToolArtifacts()` method with `artifact_types` filtering - - Updated `installToTarget()` to use template system - - Updated `renderTemplate()` to handle task/tool paths - -### Fix 3: File extension preservation (4d7ca00) -The `relativePath` property was hardcoded to `.md` extension, causing incorrect paths for `.xml` task/tool files. - -**Modified files:** -- `tools/cli/installers/lib/ide/shared/task-tool-command-generator.js` - - Extract actual extension from source path with `.md` fallback - - Fixed misleading comments ("underscore format" → "dash format") -- `tools/cli/installers/lib/ide/templates/combined/gemini-task.toml` - - Fixed branding: "BMad" → "BMAD" -- `tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml` - - Fixed branding: "BMad" → "BMAD" - -## Test Plan - -- [x] Windows install (CMD) - tasks installed with correct frontmatter parsing -- [x] WSL/Linux install - tasks installed correctly -- [x] Gemini CLI generates `.toml` files for tasks/tools -- [x] Claude Code generates `.md` files for tasks/tools -- [x] All other IDEs (Cursor, Windsurf, Trae, etc.) generate `.md` files -- [x] `bmad-help` task now correctly has `standalone: true` in manifest -- [x] Existing agent/workflow installation unaffected -- [x] `.xml` tasks/tools get correct extension in `relativePath` - -## Breaking Changes - -None - this is purely a bug fix. Existing installations will work correctly after reinstall. - ---- - -Generated with [Claude Code](https://claude.ai/code) - -Co-Authored-By: Claude Opus 4.5 diff --git a/tools/cli/installers/lib/ide/codex.js b/tools/cli/installers/lib/ide/codex.js index 60250a39..86fd0240 100644 --- a/tools/cli/installers/lib/ide/codex.js +++ b/tools/cli/installers/lib/ide/codex.js @@ -104,7 +104,10 @@ class CodexSetup extends BaseIdeSetup { ); taskArtifacts.push({ type: 'task', + name: task.name, + displayName: task.name, module: task.module, + path: task.path, sourcePath: task.path, relativePath: path.join(task.module, 'tasks', `${task.name}.md`), content, @@ -116,7 +119,7 @@ class CodexSetup extends BaseIdeSetup { const workflowCount = await workflowGenerator.writeDashArtifacts(destDir, workflowArtifacts); // Also write tasks using underscore format - const ttGen = new TaskToolCommandGenerator(); + const ttGen = new TaskToolCommandGenerator(this.bmadFolderName); const tasksWritten = await ttGen.writeDashArtifacts(destDir, taskArtifacts); const written = agentCount + workflowCount + tasksWritten; @@ -214,7 +217,10 @@ class CodexSetup extends BaseIdeSetup { artifacts.push({ type: 'task', + name: task.name, + displayName: task.name, module: task.module, + path: task.path, sourcePath: task.path, relativePath: path.join(task.module, 'tasks', `${task.name}.md`), content, diff --git a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js index 664b0cfb..aea043e4 100644 --- a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js @@ -142,9 +142,33 @@ class TaskToolCommandGenerator { const description = item.description || `Execute ${item.displayName || item.name}`; // Convert path to use {project-root} placeholder + // Handle undefined/missing path by constructing from module and name let itemPath = item.path; - if (itemPath && typeof itemPath === 'string' && itemPath.startsWith('bmad/')) { - itemPath = `{project-root}/${itemPath}`; + if (!itemPath || typeof itemPath !== 'string') { + // Fallback: construct path from module and name if path is missing + const typePlural = type === 'task' ? 'tasks' : 'tools'; + itemPath = `{project-root}/${this.bmadFolderName}/${item.module}/${typePlural}/${item.name}.md`; + } else { + // Normalize path separators to forward slashes + itemPath = itemPath.replaceAll('\\', '/'); + + // Extract relative path from absolute paths (Windows or Unix) + // Look for _bmad/ or bmad/ in the path and extract everything after it + // Match patterns like: /_bmad/core/tasks/... or /bmad/core/tasks/... + const bmadMatch = itemPath.match(/\/_bmad\/(.+)$/) || itemPath.match(/\/bmad\/(.+)$/); + if (bmadMatch) { + // Found /_bmad/ or /bmad/ - use relative path after it + itemPath = `{project-root}/${this.bmadFolderName}/${bmadMatch[1]}`; + } else if (itemPath.startsWith('_bmad/')) { + // Relative path starting with _bmad/ + itemPath = `{project-root}/${this.bmadFolderName}/${itemPath.slice(6)}`; + } else if (itemPath.startsWith('bmad/')) { + // Relative path starting with bmad/ + itemPath = `{project-root}/${this.bmadFolderName}/${itemPath.slice(5)}`; + } else if (!itemPath.startsWith('{project-root}')) { + // For other relative paths, prefix with project root and bmad folder + itemPath = `{project-root}/${this.bmadFolderName}/${itemPath}`; + } } return `--- diff --git a/tools/cli/lib/agent/installer.js b/tools/cli/lib/agent/installer.js index b55502ed..a7650453 100644 --- a/tools/cli/lib/agent/installer.js +++ b/tools/cli/lib/agent/installer.js @@ -42,7 +42,7 @@ function findBmadConfig(startPath = process.cwd()) { * @returns {string} Resolved path */ function resolvePath(pathStr, context) { - return pathStr.replaceAll('{project-root}', context.projectRoot).replaceAll('{bmad-folder}', context_bmadFolder); + return pathStr.replaceAll('{project-root}', context.projectRoot).replaceAll('{bmad-folder}', context.bmadFolder); } /** From 74b9f22f2005736a422d2c8a4ef1ca02f5e6a9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Sun, 1 Feb 2026 00:06:03 +0100 Subject: [PATCH 5/9] fix: change default BMAD folder name from 'bmad' to '_bmad' across all IDE components --- tools/cli/installers/lib/ide/_base-ide.js | 2 +- tools/cli/installers/lib/ide/manager.js | 3 ++- tools/cli/installers/lib/ide/shared/agent-command-generator.js | 2 +- .../installers/lib/ide/shared/task-tool-command-generator.js | 2 +- .../installers/lib/ide/shared/workflow-command-generator.js | 2 +- tools/cli/installers/lib/modules/manager.js | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/cli/installers/lib/ide/_base-ide.js b/tools/cli/installers/lib/ide/_base-ide.js index b16ee518..8cf48601 100644 --- a/tools/cli/installers/lib/ide/_base-ide.js +++ b/tools/cli/installers/lib/ide/_base-ide.js @@ -18,7 +18,7 @@ class BaseIdeSetup { this.configFile = null; // Override in subclasses when detection is file-based this.detectionPaths = []; // Additional paths that indicate the IDE is configured this.xmlHandler = new XmlHandler(); - this.bmadFolderName = 'bmad'; // Default, can be overridden + this.bmadFolderName = '_bmad'; // Default, can be overridden } /** diff --git a/tools/cli/installers/lib/ide/manager.js b/tools/cli/installers/lib/ide/manager.js index 2b68dfad..94573d53 100644 --- a/tools/cli/installers/lib/ide/manager.js +++ b/tools/cli/installers/lib/ide/manager.js @@ -14,7 +14,7 @@ class IdeManager { constructor() { this.handlers = new Map(); this._initialized = false; - this.bmadFolderName = 'bmad'; // Default, can be overridden + this.bmadFolderName = '_bmad'; // Default, can be overridden } /** @@ -73,6 +73,7 @@ class IdeManager { if (HandlerClass) { const instance = new HandlerClass(); if (instance.name && typeof instance.name === 'string') { + instance.setBmadFolderName(this.bmadFolderName); this.handlers.set(instance.name, instance); } } 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 dec22a12..a0e48fbb 100644 --- a/tools/cli/installers/lib/ide/shared/agent-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/agent-command-generator.js @@ -8,7 +8,7 @@ const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = r * Similar to WorkflowCommandGenerator but for agents */ class AgentCommandGenerator { - constructor(bmadFolderName = 'bmad') { + constructor(bmadFolderName = '_bmad') { this.templatePath = path.join(__dirname, '../templates/agent-command-template.md'); this.bmadFolderName = bmadFolderName; } diff --git a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js index aea043e4..68177339 100644 --- a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js @@ -15,7 +15,7 @@ class TaskToolCommandGenerator { * filesystem paths with '_bmad/' prefix (the actual folder name), while bmadFolderName is * used for template placeholder rendering ({{bmadFolderName}}). */ - constructor(bmadFolderName = 'bmad') { + constructor(bmadFolderName = '_bmad') { this.bmadFolderName = bmadFolderName; } 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 6dab1a3f..c3f804c4 100644 --- a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js @@ -8,7 +8,7 @@ const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = r * Generates command files for each workflow in the manifest */ class WorkflowCommandGenerator { - constructor(bmadFolderName = 'bmad') { + constructor(bmadFolderName = '_bmad') { this.templatePath = path.join(__dirname, '../templates/workflow-command-template.md'); this.bmadFolderName = bmadFolderName; } diff --git a/tools/cli/installers/lib/modules/manager.js b/tools/cli/installers/lib/modules/manager.js index 60c087b1..4ba49bf6 100644 --- a/tools/cli/installers/lib/modules/manager.js +++ b/tools/cli/installers/lib/modules/manager.js @@ -27,7 +27,7 @@ const { ExternalModuleManager } = require('./external-manager'); class ModuleManager { constructor(options = {}) { this.xmlHandler = new XmlHandler(); - this.bmadFolderName = 'bmad'; // Default, can be overridden + this.bmadFolderName = '_bmad'; // Default, can be overridden this.customModulePaths = new Map(); // Initialize custom module paths this.externalModuleManager = new ExternalModuleManager(); // For external official modules } From 8ed36d9f0dd862d458f838849595bdab5c960e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Sun, 1 Feb 2026 12:24:09 +0100 Subject: [PATCH 6/9] refactor: centralize BMAD_FOLDER_NAME constant in path-utils --- tools/cli/installers/lib/core/installer.js | 4 +--- tools/cli/installers/lib/ide/_base-ide.js | 5 +++-- .../lib/ide/shared/agent-command-generator.js | 4 ++-- .../installers/lib/ide/shared/path-utils.js | 4 ++++ .../ide/shared/task-tool-command-generator.js | 19 ++++++++++--------- .../ide/shared/workflow-command-generator.js | 4 ++-- 6 files changed, 22 insertions(+), 18 deletions(-) diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index a14c3d19..cb146270 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -17,9 +17,7 @@ const { ManifestGenerator } = require('./manifest-generator'); const { IdeConfigManager } = require('./ide-config-manager'); const { CustomHandler } = require('../custom/handler'); const prompts = require('../../../lib/prompts'); - -// BMAD installation folder name - this is constant and should never change -const BMAD_FOLDER_NAME = '_bmad'; +const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils'); class Installer { constructor() { diff --git a/tools/cli/installers/lib/ide/_base-ide.js b/tools/cli/installers/lib/ide/_base-ide.js index 8cf48601..b3ce3af3 100644 --- a/tools/cli/installers/lib/ide/_base-ide.js +++ b/tools/cli/installers/lib/ide/_base-ide.js @@ -3,6 +3,7 @@ const fs = require('fs-extra'); const chalk = require('chalk'); const { XmlHandler } = require('../../../lib/xml-handler'); const { getSourcePath } = require('../../../lib/project-root'); +const { BMAD_FOLDER_NAME } = require('./shared/path-utils'); /** * Base class for IDE-specific setup @@ -18,7 +19,7 @@ class BaseIdeSetup { this.configFile = null; // Override in subclasses when detection is file-based this.detectionPaths = []; // Additional paths that indicate the IDE is configured this.xmlHandler = new XmlHandler(); - this.bmadFolderName = '_bmad'; // Default, can be overridden + this.bmadFolderName = BMAD_FOLDER_NAME; // Default, can be overridden } /** @@ -57,7 +58,7 @@ class BaseIdeSetup { if (this.configDir) { const configPath = path.join(projectDir, this.configDir); if (await fs.pathExists(configPath)) { - const bmadRulesPath = path.join(configPath, 'bmad'); + const bmadRulesPath = path.join(configPath, BMAD_FOLDER_NAME); if (await fs.pathExists(bmadRulesPath)) { await fs.remove(bmadRulesPath); console.log(chalk.dim(`Removed ${this.name} BMAD configuration`)); 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 a0e48fbb..caf60614 100644 --- a/tools/cli/installers/lib/ide/shared/agent-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/agent-command-generator.js @@ -1,14 +1,14 @@ const path = require('node:path'); const fs = require('fs-extra'); const chalk = require('chalk'); -const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = require('./path-utils'); +const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils'); /** * Generates launcher command files for each agent * Similar to WorkflowCommandGenerator but for agents */ class AgentCommandGenerator { - constructor(bmadFolderName = '_bmad') { + constructor(bmadFolderName = BMAD_FOLDER_NAME) { this.templatePath = path.join(__dirname, '../templates/agent-command-template.md'); this.bmadFolderName = bmadFolderName; } diff --git a/tools/cli/installers/lib/ide/shared/path-utils.js b/tools/cli/installers/lib/ide/shared/path-utils.js index 561a9029..51966923 100644 --- a/tools/cli/installers/lib/ide/shared/path-utils.js +++ b/tools/cli/installers/lib/ide/shared/path-utils.js @@ -18,6 +18,9 @@ const TYPE_SEGMENTS = ['workflows', 'tasks', 'tools']; const AGENT_SEGMENT = 'agents'; +// BMAD installation folder name - centralized constant for all installers +const BMAD_FOLDER_NAME = '_bmad'; + /** * Convert hierarchical path to flat dash-separated name (NEW STANDARD) * Converts: 'bmm', 'agents', 'pm' → 'bmad-agent-bmm-pm.md' @@ -292,4 +295,5 @@ module.exports = { TYPE_SEGMENTS, AGENT_SEGMENT, + BMAD_FOLDER_NAME, }; diff --git a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js index 4f1f589e..7edb130b 100644 --- a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js @@ -2,20 +2,20 @@ const path = require('node:path'); const fs = require('fs-extra'); const csv = require('csv-parse/sync'); const chalk = require('chalk'); -const { toColonName, toColonPath, toDashPath } = require('./path-utils'); +const { toColonName, toColonPath, toDashPath, BMAD_FOLDER_NAME } = require('./path-utils'); /** * Generates command files for standalone tasks and tools */ class TaskToolCommandGenerator { /** - * @param {string} bmadFolderName - Name of the BMAD folder for template rendering (default: 'bmad') + * @param {string} bmadFolderName - Name of the BMAD folder for template rendering (default: '_bmad') * Note: This parameter is accepted for API consistency with AgentCommandGenerator and * WorkflowCommandGenerator, but is not used for path stripping. The manifest always stores * filesystem paths with '_bmad/' prefix (the actual folder name), while bmadFolderName is * used for template placeholder rendering ({{bmadFolderName}}). */ - constructor(bmadFolderName = '_bmad') { + constructor(bmadFolderName = BMAD_FOLDER_NAME) { this.bmadFolderName = bmadFolderName; } @@ -33,13 +33,14 @@ class TaskToolCommandGenerator { const standaloneTools = tools ? tools.filter((t) => t.standalone === 'true' || t.standalone === true) : []; const artifacts = []; + const bmadPrefix = `${BMAD_FOLDER_NAME}/`; // Collect task artifacts for (const task of standaloneTasks) { let taskPath = (task.path || '').replaceAll('\\', '/'); // Remove _bmad/ prefix if present to get relative path within bmad folder - if (taskPath.startsWith('_bmad/')) { - taskPath = taskPath.slice(6); // Remove '_bmad/' + if (taskPath.startsWith(bmadPrefix)) { + taskPath = taskPath.slice(bmadPrefix.length); } const taskExt = path.extname(taskPath) || '.md'; @@ -59,8 +60,8 @@ class TaskToolCommandGenerator { for (const tool of standaloneTools) { let toolPath = (tool.path || '').replaceAll('\\', '/'); // Remove _bmad/ prefix if present to get relative path within bmad folder - if (toolPath.startsWith('_bmad/')) { - toolPath = toolPath.slice(6); // Remove '_bmad/' + if (toolPath.startsWith(bmadPrefix)) { + toolPath = toolPath.slice(bmadPrefix.length); } const toolExt = path.extname(toolPath) || '.md'; @@ -159,9 +160,9 @@ class TaskToolCommandGenerator { if (bmadMatch) { // Found /_bmad/ or /bmad/ - use relative path after it itemPath = `{project-root}/${this.bmadFolderName}/${bmadMatch[1]}`; - } else if (itemPath.startsWith('_bmad/')) { + } else if (itemPath.startsWith(`${BMAD_FOLDER_NAME}/`)) { // Relative path starting with _bmad/ - itemPath = `{project-root}/${this.bmadFolderName}/${itemPath.slice(6)}`; + itemPath = `{project-root}/${this.bmadFolderName}/${itemPath.slice(BMAD_FOLDER_NAME.length + 1)}`; } else if (itemPath.startsWith('bmad/')) { // Relative path starting with bmad/ itemPath = `{project-root}/${this.bmadFolderName}/${itemPath.slice(5)}`; 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 c3f804c4..5a23fda2 100644 --- a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js @@ -2,13 +2,13 @@ const path = require('node:path'); const fs = require('fs-extra'); const csv = require('csv-parse/sync'); const chalk = require('chalk'); -const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = require('./path-utils'); +const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils'); /** * Generates command files for each workflow in the manifest */ class WorkflowCommandGenerator { - constructor(bmadFolderName = '_bmad') { + constructor(bmadFolderName = BMAD_FOLDER_NAME) { this.templatePath = path.join(__dirname, '../templates/workflow-command-template.md'); this.bmadFolderName = bmadFolderName; } From a6a960f88c170fc441498d40cd89bc2d052940ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Sun, 1 Feb 2026 12:42:10 +0100 Subject: [PATCH 7/9] fix: Replace the rest of BMAD_FOLDER magic values --- tools/cli/installers/lib/ide/manager.js | 3 ++- tools/cli/installers/lib/modules/manager.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/cli/installers/lib/ide/manager.js b/tools/cli/installers/lib/ide/manager.js index 94573d53..065754a0 100644 --- a/tools/cli/installers/lib/ide/manager.js +++ b/tools/cli/installers/lib/ide/manager.js @@ -1,6 +1,7 @@ const fs = require('fs-extra'); const path = require('node:path'); const chalk = require('chalk'); +const { BMAD_FOLDER_NAME } = require('./shared/path-utils'); /** * IDE Manager - handles IDE-specific setup @@ -14,7 +15,7 @@ class IdeManager { constructor() { this.handlers = new Map(); this._initialized = false; - this.bmadFolderName = '_bmad'; // Default, can be overridden + this.bmadFolderName = BMAD_FOLDER_NAME; // Default, can be overridden } /** diff --git a/tools/cli/installers/lib/modules/manager.js b/tools/cli/installers/lib/modules/manager.js index 4ba49bf6..dbe280f7 100644 --- a/tools/cli/installers/lib/modules/manager.js +++ b/tools/cli/installers/lib/modules/manager.js @@ -7,6 +7,7 @@ const { XmlHandler } = require('../../../lib/xml-handler'); const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root'); const { filterCustomizationData } = require('../../../lib/agent/compiler'); const { ExternalModuleManager } = require('./external-manager'); +const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils'); /** * Manages the installation, updating, and removal of BMAD modules. @@ -27,7 +28,7 @@ const { ExternalModuleManager } = require('./external-manager'); class ModuleManager { constructor(options = {}) { this.xmlHandler = new XmlHandler(); - this.bmadFolderName = '_bmad'; // Default, can be overridden + this.bmadFolderName = BMAD_FOLDER_NAME; // Default, can be overridden this.customModulePaths = new Map(); // Initialize custom module paths this.externalModuleManager = new ExternalModuleManager(); // For external official modules } From 1d505b17b42ab66c0ce7409069979128db3da8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Sun, 1 Feb 2026 13:23:19 +0100 Subject: [PATCH 8/9] fix: add safety checks for setBmadFolderName method calls in IdeManager --- tools/cli/installers/lib/ide/manager.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/cli/installers/lib/ide/manager.js b/tools/cli/installers/lib/ide/manager.js index 065754a0..7d00588c 100644 --- a/tools/cli/installers/lib/ide/manager.js +++ b/tools/cli/installers/lib/ide/manager.js @@ -74,7 +74,9 @@ class IdeManager { if (HandlerClass) { const instance = new HandlerClass(); if (instance.name && typeof instance.name === 'string') { - instance.setBmadFolderName(this.bmadFolderName); + if (typeof instance.setBmadFolderName === 'function') { + instance.setBmadFolderName(this.bmadFolderName); + } this.handlers.set(instance.name, instance); } } @@ -102,7 +104,9 @@ class IdeManager { if (!platformInfo.installer) continue; const handler = new ConfigDrivenIdeSetup(platformCode, platformInfo); - handler.setBmadFolderName(this.bmadFolderName); + if (typeof handler.setBmadFolderName === 'function') { + handler.setBmadFolderName(this.bmadFolderName); + } this.handlers.set(platformCode, handler); } } From a69810afe5e469ed647b6ed3d83e3cd3cf1e7df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Sun, 1 Feb 2026 13:39:09 +0100 Subject: [PATCH 9/9] fix: convert absolute paths to relative in task-tool-command-generator --- .../lib/ide/shared/task-tool-command-generator.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js index 7edb130b..18f37770 100644 --- a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js @@ -38,6 +38,10 @@ class TaskToolCommandGenerator { // Collect task artifacts for (const task of standaloneTasks) { let taskPath = (task.path || '').replaceAll('\\', '/'); + // Convert absolute paths to relative paths + if (path.isAbsolute(taskPath)) { + taskPath = path.relative(bmadDir, taskPath).replaceAll('\\', '/'); + } // Remove _bmad/ prefix if present to get relative path within bmad folder if (taskPath.startsWith(bmadPrefix)) { taskPath = taskPath.slice(bmadPrefix.length); @@ -59,6 +63,10 @@ class TaskToolCommandGenerator { // Collect tool artifacts for (const tool of standaloneTools) { let toolPath = (tool.path || '').replaceAll('\\', '/'); + // Convert absolute paths to relative paths + if (path.isAbsolute(toolPath)) { + toolPath = path.relative(bmadDir, toolPath).replaceAll('\\', '/'); + } // Remove _bmad/ prefix if present to get relative path within bmad folder if (toolPath.startsWith(bmadPrefix)) { toolPath = toolPath.slice(bmadPrefix.length);