diff --git a/src/utility/agent-components/handler-workflow.txt b/src/utility/agent-components/handler-workflow.txt
index 1be1dcbe5..f9ada8a34 100644
--- a/src/utility/agent-components/handler-workflow.txt
+++ b/src/utility/agent-components/handler-workflow.txt
@@ -1,10 +1,10 @@
- When menu item has: workflow="path/to/workflow.yaml":
+ When menu item has: workflow="path/to/workflow-config":
1. CRITICAL: Always LOAD {project-root}/_bmad/core/tasks/workflow.xml
2. Read the complete file - this is the CORE OS for processing BMAD workflows
- 3. Pass the yaml path as 'workflow-config' parameter to those instructions
+ 3. Pass the workflow-config path as 'workflow-config' parameter to those instructions
4. Follow workflow.xml instructions precisely following all steps
5. Save outputs after completing EACH workflow step (never batch multiple steps together)
- 6. If workflow.yaml path is "todo", inform user the workflow hasn't been implemented yet
-
\ No newline at end of file
+ 6. If workflow-config path normalizes to "todo" (trimmed, case-insensitive), inform user the workflow hasn't been implemented yet
+
diff --git a/tools/cli/installers/lib/ide/_config-driven.js b/tools/cli/installers/lib/ide/_config-driven.js
index d1552700f..47bb7768b 100644
--- a/tools/cli/installers/lib/ide/_config-driven.js
+++ b/tools/cli/installers/lib/ide/_config-driven.js
@@ -361,6 +361,42 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
`;
}
+ /**
+ * Normalize artifact path values to a relative path inside the BMAD folder.
+ * @param {string} rawPath - Raw path value from artifact metadata
+ * @returns {string} Normalized relative path
+ */
+ normalizeArtifactPath(rawPath) {
+ if (!rawPath || typeof rawPath !== 'string') {
+ return '';
+ }
+
+ let normalized = rawPath.replaceAll('\\', '/').trim();
+
+ // Strip {project-root}/ prefix if present.
+ normalized = normalized.replace(/^\{project-root\}\//, '');
+
+ // Strip configured/legacy BMAD folder prefixes from absolute or relative paths.
+ const escapedFolderName = this.bmadFolderName.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
+ const configuredFolderMatch = normalized.match(new RegExp(`(?:^|/)${escapedFolderName}/(.+)$`));
+ if (configuredFolderMatch) {
+ normalized = configuredFolderMatch[1];
+ } else {
+ const legacyFolderMatch = normalized.match(/(?:^|\/)_bmad\/(.+)$/);
+ if (legacyFolderMatch) {
+ normalized = legacyFolderMatch[1];
+ }
+ }
+
+ // Final cleanup for relative-only path contracts.
+ normalized = normalized.replace(/^[A-Za-z]:\//, ''); // Windows drive prefix
+ normalized = normalized.replace(/^\.\/+/, '');
+ normalized = normalized.replace(/^\/+/, '');
+ normalized = normalized.replaceAll(/\/{2,}/g, '/');
+
+ return normalized;
+ }
+
/**
* Render template with artifact data
* @param {string} template - Template content
@@ -372,18 +408,18 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
let pathToUse = artifact.relativePath || '';
switch (artifact.type) {
case 'agent-launcher': {
- pathToUse = artifact.agentPath || artifact.relativePath || '';
+ pathToUse = this.normalizeArtifactPath(artifact.agentPath || artifact.relativePath || '');
break;
}
case 'workflow-command': {
- pathToUse = artifact.workflowPath || artifact.relativePath || '';
+ pathToUse = this.normalizeArtifactPath(artifact.workflowPath || artifact.relativePath || '');
break;
}
case 'task':
case 'tool': {
- pathToUse = artifact.path || artifact.relativePath || '';
+ pathToUse = this.normalizeArtifactPath(artifact.path || artifact.relativePath || '');
break;
}
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 0915c306b..d39edc055 100644
--- a/tools/cli/installers/lib/ide/shared/agent-command-generator.js
+++ b/tools/cli/installers/lib/ide/shared/agent-command-generator.js
@@ -79,8 +79,8 @@ class AgentCommandGenerator {
.replaceAll('{{module}}', agent.module)
.replaceAll('{{path}}', agentPathInModule)
.replaceAll('{{description}}', agent.description || `${agent.name} agent`)
- .replaceAll('_bmad', this.bmadFolderName)
- .replaceAll('_bmad', '_bmad');
+ .replaceAll('{{bmadFolderName}}', this.bmadFolderName)
+ .replaceAll('_bmad', this.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 d94e77db1..324361bde 100644
--- a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js
+++ b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js
@@ -13,6 +13,57 @@ class WorkflowCommandGenerator {
this.bmadFolderName = bmadFolderName;
}
+ /**
+ * Escape regex metacharacters in dynamic path segments.
+ * @param {string} value - Raw string value
+ * @returns {string} Regex-escaped value
+ */
+ escapeRegex(value) {
+ return String(value).replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
+ }
+
+ /**
+ * Normalize workflow paths to a relative path inside the BMAD folder.
+ * Result never starts with '/', '{project-root}/', or '{bmadFolderName}/'.
+ * @param {string} workflowPath - Raw workflow path
+ * @returns {string} Normalized relative workflow path
+ */
+ normalizeWorkflowRelativePath(workflowPath) {
+ if (!workflowPath || typeof workflowPath !== 'string') {
+ return '';
+ }
+
+ let normalized = workflowPath.replaceAll('\\', '/').trim();
+
+ // Strip {project-root}/ prefix if present.
+ normalized = normalized.replace(/^\{project-root\}\//, '');
+
+ // Normalize source paths (e.g. .../src/bmm/... -> bmm/...).
+ const srcMatch = normalized.match(/(?:^|\/)src\/([^/]+)\/(.+)/);
+ if (srcMatch) {
+ normalized = `${srcMatch[1]}/${srcMatch[2]}`;
+ } else {
+ // Strip configured/legacy BMAD folder prefixes from absolute or relative paths.
+ const configuredFolderMatch = normalized.match(new RegExp(`(?:^|/)${this.escapeRegex(this.bmadFolderName)}/(.+)$`));
+ if (configuredFolderMatch) {
+ normalized = configuredFolderMatch[1];
+ } else {
+ const legacyFolderMatch = normalized.match(/(?:^|\/)_bmad\/(.+)$/);
+ if (legacyFolderMatch) {
+ normalized = legacyFolderMatch[1];
+ }
+ }
+ }
+
+ // Final cleanup for relative-only contract.
+ normalized = normalized.replace(/^[A-Za-z]:\//, ''); // Windows drive prefix
+ normalized = normalized.replace(/^\.\/+/, '');
+ normalized = normalized.replace(/^\/+/, '');
+ normalized = normalized.replaceAll(/\/{2,}/g, '/');
+
+ return normalized;
+ }
+
/**
* Generate workflow commands from the manifest CSV
* @param {string} projectDir - Project directory
@@ -67,24 +118,8 @@ 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 || '';
- // 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/')) {
- const parts = workflowRelPath.split(/_bmad\//);
- 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]}`;
- }
- }
+ // Calculate a relative workflow path (e.g., bmm/workflows/.../workflow.yaml).
+ const workflowRelPath = this.normalizeWorkflowRelativePath(workflow.path || '');
// 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({
@@ -124,39 +159,23 @@ class WorkflowCommandGenerator {
* Generate command content for a workflow
*/
async generateCommandContent(workflow, bmadDir) {
+ const normalizedWorkflowPath = this.normalizeWorkflowRelativePath(workflow.path || '');
+
// Determine template based on workflow file type
- const isMarkdownWorkflow = workflow.path.endsWith('workflow.md');
+ const isMarkdownWorkflow = normalizedWorkflowPath.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/bmm/workflows/.../workflow.yaml
- // To: {project-root}/_bmad/bmm/workflows/.../workflow.yaml
- let workflowPath = workflow.path;
-
- // Extract the relative path from source
- if (workflowPath.includes('/src/bmm/')) {
- // bmm is directly under src/
- const match = workflowPath.match(/\/src\/bmm\/(.+)/);
- if (match) {
- workflowPath = `${this.bmadFolderName}/bmm/${match[1]}`;
- }
- } else if (workflowPath.includes('/src/core/')) {
- const match = workflowPath.match(/\/src\/core\/(.+)/);
- if (match) {
- workflowPath = `${this.bmadFolderName}/core/${match[1]}`;
- }
- }
-
// Replace template variables
return template
.replaceAll('{{name}}', workflow.name)
.replaceAll('{{module}}', workflow.module)
.replaceAll('{{description}}', workflow.description)
- .replaceAll('{{workflow_path}}', workflowPath)
+ .replaceAll('{{workflow_path}}', normalizedWorkflowPath)
+ .replaceAll('{{bmadFolderName}}', this.bmadFolderName)
.replaceAll('_bmad', this.bmadFolderName);
}
diff --git a/tools/cli/installers/lib/ide/templates/agent-command-template.md b/tools/cli/installers/lib/ide/templates/agent-command-template.md
index 897136317..2e25ff7f7 100644
--- a/tools/cli/installers/lib/ide/templates/agent-command-template.md
+++ b/tools/cli/installers/lib/ide/templates/agent-command-template.md
@@ -6,7 +6,7 @@ description: '{{description}}'
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 @_bmad/{{module}}/agents/{{path}}
+1. LOAD the FULL agent file from {project-root}/{{bmadFolderName}}/{{module}}/agents/{{path}}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. Execute ALL activation steps exactly as written in the agent file
4. Follow the agent's persona and menu system precisely
diff --git a/tools/cli/installers/lib/ide/templates/combined/antigravity.md b/tools/cli/installers/lib/ide/templates/combined/antigravity.md
index 88e806e9d..1c11eb246 100644
--- a/tools/cli/installers/lib/ide/templates/combined/antigravity.md
+++ b/tools/cli/installers/lib/ide/templates/combined/antigravity.md
@@ -3,6 +3,6 @@ name: '{{name}}'
description: '{{description}}'
---
-Read the entire workflow file at: {project-root}/_bmad/{{workflow_path}}
+Read the entire workflow file at: {project-root}/{{bmadFolderName}}/{{workflow_path}}
Follow all instructions in the workflow file exactly as written.
diff --git a/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md b/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md
index eca904370..01dc763e5 100644
--- a/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md
+++ b/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md
@@ -6,9 +6,9 @@ description: '{{description}}'
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
-1. Always LOAD the FULL @{project-root}/{{bmadFolderName}}/core/tasks/workflow.xml
-2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @{project-root}/{{bmadFolderName}}/{{path}}
-3. Pass the yaml path @{project-root}/{{bmadFolderName}}/{{path}} as 'workflow-config' parameter to the workflow.xml instructions
+1. Always LOAD the FULL {project-root}/{{bmadFolderName}}/core/tasks/workflow.xml
+2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config "{project-root}/{{bmadFolderName}}/{{path}}" (where {{path}} is relative and does not start with `/`)
+3. Pass the workflow-config path "{project-root}/{{bmadFolderName}}/{{path}}" as 'workflow-config' parameter to the workflow.xml instructions
4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions
5. Save outputs after EACH section when generating any documents from templates
diff --git a/tools/cli/installers/lib/ide/templates/combined/default-workflow.md b/tools/cli/installers/lib/ide/templates/combined/default-workflow.md
index afb0dea58..c5311914e 100644
--- a/tools/cli/installers/lib/ide/templates/combined/default-workflow.md
+++ b/tools/cli/installers/lib/ide/templates/combined/default-workflow.md
@@ -3,4 +3,4 @@ name: '{{name}}'
description: '{{description}}'
---
-IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{project-root}/{{bmadFolderName}}/{{path}}, READ its entire contents and follow its directions exactly!
+IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL "{project-root}/{{bmadFolderName}}/{{path}}", READ its entire contents and follow its directions exactly! ({{path}} must be relative and must not start with `/`)
diff --git a/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md b/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md
index 4ee4e0824..20fd0c789 100644
--- a/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md
+++ b/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md
@@ -9,7 +9,7 @@ IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the c
1. Always LOAD the FULL #[[file:{{bmadFolderName}}/core/tasks/workflow.xml]]
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config #[[file:{{bmadFolderName}}/{{path}}]]
-3. Pass the yaml path {{bmadFolderName}}/{{path}} as 'workflow-config' parameter to the workflow.xml instructions
+3. Pass the workflow-config path {{bmadFolderName}}/{{path}} as 'workflow-config' parameter to the workflow.xml instructions (Kiro intentionally uses a project-relative workflow-config path for #[[file:...]] compatibility; other templates may use {project-root}/... form)
4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions
5. Save outputs after EACH section when generating any documents from templates
diff --git a/tools/cli/installers/lib/ide/templates/combined/rovodev.md b/tools/cli/installers/lib/ide/templates/combined/rovodev.md
index 066945ee5..77aff07b0 100644
--- a/tools/cli/installers/lib/ide/templates/combined/rovodev.md
+++ b/tools/cli/installers/lib/ide/templates/combined/rovodev.md
@@ -4,6 +4,6 @@
---
-Read the entire workflow file at: {project-root}/_bmad/{{workflow_path}}
+Read the entire workflow file at: {project-root}/{{bmadFolderName}}/{{workflow_path}}
Follow all instructions in the workflow file exactly as written.
diff --git a/tools/cli/installers/lib/ide/templates/combined/trae.md b/tools/cli/installers/lib/ide/templates/combined/trae.md
index b4d43d7af..d496a2bd6 100644
--- a/tools/cli/installers/lib/ide/templates/combined/trae.md
+++ b/tools/cli/installers/lib/ide/templates/combined/trae.md
@@ -4,6 +4,6 @@
## Instructions
-Read the entire workflow file at: {project-root}/_bmad/{{workflow_path}}
+Read the entire workflow file at: {project-root}/{{bmadFolderName}}/{{workflow_path}}
Follow all instructions in the workflow file exactly as written.
diff --git a/tools/cli/installers/lib/ide/templates/combined/windsurf-workflow.md b/tools/cli/installers/lib/ide/templates/combined/windsurf-workflow.md
index 6366425c7..b60c4340e 100644
--- a/tools/cli/installers/lib/ide/templates/combined/windsurf-workflow.md
+++ b/tools/cli/installers/lib/ide/templates/combined/windsurf-workflow.md
@@ -5,6 +5,6 @@ auto_execution_mode: "iterate"
# {{name}}
-Read the entire workflow file at {project-root}/_bmad/{{workflow_path}}
+Read the entire workflow file at {project-root}/{{bmadFolderName}}/{{workflow_path}}
Follow all instructions in the workflow file exactly as written.
diff --git a/tools/cli/installers/lib/ide/templates/workflow-command-template.md b/tools/cli/installers/lib/ide/templates/workflow-command-template.md
index 5c9e436c7..56ae997ad 100644
--- a/tools/cli/installers/lib/ide/templates/workflow-command-template.md
+++ b/tools/cli/installers/lib/ide/templates/workflow-command-template.md
@@ -5,9 +5,9 @@ description: '{{description}}'
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
-1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml
-2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @{{workflow_path}}
-3. Pass the yaml path {{workflow_path}} as 'workflow-config' parameter to the workflow.xml instructions
+1. Always LOAD the FULL "{project-root}/{{bmadFolderName}}/core/tasks/workflow.xml"
+2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config "{project-root}/{{bmadFolderName}}/{{workflow_path}}" (where {{workflow_path}} is relative and does not start with `/`)
+3. Pass the workflow-config path "{project-root}/{{bmadFolderName}}/{{workflow_path}}" as 'workflow-config' parameter to the workflow.xml instructions
4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions
5. Save outputs after EACH section when generating any documents from templates
diff --git a/tools/cli/installers/lib/ide/templates/workflow-commander.md b/tools/cli/installers/lib/ide/templates/workflow-commander.md
index 3645c1a2f..9078b4cb3 100644
--- a/tools/cli/installers/lib/ide/templates/workflow-commander.md
+++ b/tools/cli/installers/lib/ide/templates/workflow-commander.md
@@ -2,4 +2,4 @@
description: '{{description}}'
---
-IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{{workflow_path}}, READ its entire contents and follow its directions exactly!
+IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL "{project-root}/{{bmadFolderName}}/{{workflow_path}}", READ its entire contents and follow its directions exactly! ({{workflow_path}} must be relative and must not start with `/`)