Compare commits
5 Commits
40b9edd791
...
9dd136373d
| Author | SHA1 | Date |
|---|---|---|
|
|
9dd136373d | |
|
|
e8756996b9 | |
|
|
50b1dfd272 | |
|
|
43c87acd7c | |
|
|
97e2eba95b |
|
|
@ -1,10 +1,10 @@
|
|||
<handler type="workflow">
|
||||
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
|
||||
</handler>
|
||||
6. If workflow-config path normalizes to "todo" (trimmed, case-insensitive), inform user the workflow hasn't been implemented yet
|
||||
</handler>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
<agent-activation CRITICAL="TRUE">
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
<steps CRITICAL="TRUE">
|
||||
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
|
||||
</steps>
|
||||
|
|
|
|||
|
|
@ -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 `/`)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the c
|
|||
<steps CRITICAL="TRUE">
|
||||
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
|
||||
</steps>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
<steps CRITICAL="TRUE">
|
||||
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
|
||||
</steps>
|
||||
|
|
|
|||
|
|
@ -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 `/`)
|
||||
|
|
|
|||
Loading…
Reference in New Issue