refactor: remove legacy YAML/XML workflow engine plumbing (#1864)
* refactor(augment): remove legacy YAML/XML workflow rules from code review guidelines All workflows have been converted to markdown. Remove workflow.yaml, workflow.xml, and config_source references from Augment review rules. Drop the entire xml_workflows section (5 rules) and the YAML-specific standard_workflow_instructions rule. * refactor: extract discover_inputs protocol from workflow.xml into co-located markdown Convert the discover_inputs XML protocol (FULL_LOAD, SELECTIVE_LOAD, INDEX_GUIDED strategies) into standalone markdown files placed alongside the two workflows that use it (create-story, code-review). Replace <invoke-protocol> tags with explicit file references. This decouples the workflows from workflow.xml, enabling its deletion in a follow-up. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: delete dead YAML/XML workflow engine files Remove 5 files made obsolete by the workflow.yaml → workflow.md migration: - workflow.xml (the YAML workflow interpreter engine) - dev-story/instructions.xml (superseded by workflow.md) - 3 installer templates for YAML workflow command generation References in CLI code will be cleaned up in follow-up commits. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: delete obsolete workflow handler fragments Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: remove YAML workflow code paths from CLI installer pipeline Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: remove workflow.xml references from manifests and checklists Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: remove workflow.xml references from English command docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: update fixtures to remove workflow.yaml references Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: update workflow.yaml example path to workflow.md in handler-multi Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: stop tracking workflow/validate-workflow as handler attributes These handler fragments were deleted — the exec handler already covers loading .md workflow files directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: rename workflow attribute to exec in agent menu items Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review findings from triage - Fix regex capture group index in module manager workflow path parsing - Remove stale workflow handler references from handler-multi.txt - Replace workflow with multi in activation-steps dispatch contract - Remove dead validate-workflow emission from compiler and xml-builder - Align commands.md wording to remove engine references - Fix relativePath anchoring in _base-ide.js recursive directory scans - Remove dead code from workflow-command-generator (unused template, generateCommandContent, writeColonArtifacts, writeDashArtifacts) - Delete unused workflow-commander.md template - Add regression test for workflow path regex --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4c470d9948
commit
ee25fcca6f
|
|
@ -56,17 +56,13 @@ areas:
|
|||
- "src/**/workflows/**"
|
||||
rules:
|
||||
- id: "workflow_entry_point_required"
|
||||
description: "Every workflow folder must have workflow.yaml, workflow.md, or workflow.xml as entry point"
|
||||
description: "Every workflow folder must have workflow.md as entry point"
|
||||
severity: "high"
|
||||
|
||||
- id: "sharded_workflow_steps_folder"
|
||||
description: "Sharded workflows (using workflow.md) must have steps/ folder with numbered files (step-01-*.md, step-02-*.md)"
|
||||
severity: "high"
|
||||
|
||||
- id: "standard_workflow_instructions"
|
||||
description: "Standard workflows using workflow.yaml must include instructions.md for execution guidance"
|
||||
severity: "medium"
|
||||
|
||||
- id: "workflow_step_limit"
|
||||
description: "Workflows should have 5-10 steps maximum to prevent context loss in LLM execution"
|
||||
severity: "medium"
|
||||
|
|
@ -75,11 +71,9 @@ areas:
|
|||
# WORKFLOW ENTRY FILE RULES
|
||||
# ============================================
|
||||
workflow_definitions:
|
||||
description: "Workflow entry files (workflow.yaml, workflow.md, workflow.xml)"
|
||||
description: "Workflow entry files (workflow.md)"
|
||||
globs:
|
||||
- "src/**/workflows/**/workflow.yaml"
|
||||
- "src/**/workflows/**/workflow.md"
|
||||
- "src/**/workflows/**/workflow.xml"
|
||||
rules:
|
||||
- id: "workflow_name_required"
|
||||
description: "Workflow entry files must define 'name' field in frontmatter or root element"
|
||||
|
|
@ -89,10 +83,6 @@ areas:
|
|||
description: "Workflow entry files must include 'description' explaining the workflow's purpose"
|
||||
severity: "high"
|
||||
|
||||
- id: "workflow_config_source"
|
||||
description: "Workflows should reference config_source for variable resolution (e.g., {project-root}/_bmad/module/config.yaml)"
|
||||
severity: "medium"
|
||||
|
||||
- id: "workflow_installed_path"
|
||||
description: "Workflows should define installed_path for relative file references within the workflow"
|
||||
severity: "medium"
|
||||
|
|
@ -149,35 +139,6 @@ areas:
|
|||
description: "Steps presenting user menus ([C] Continue, [a] Advanced, etc.) must HALT and wait for response"
|
||||
severity: "high"
|
||||
|
||||
# ============================================
|
||||
# XML WORKFLOW/TASK RULES
|
||||
# ============================================
|
||||
xml_workflows:
|
||||
description: "XML-based workflows and tasks"
|
||||
globs:
|
||||
- "src/**/workflows/**/*.xml"
|
||||
- "src/**/tasks/**/*.xml"
|
||||
rules:
|
||||
- id: "xml_task_id_required"
|
||||
description: "XML tasks must have unique 'id' attribute on root task element"
|
||||
severity: "high"
|
||||
|
||||
- id: "xml_llm_instructions"
|
||||
description: "XML workflows should include <llm> section with critical execution instructions for the agent"
|
||||
severity: "medium"
|
||||
|
||||
- id: "xml_step_numbering"
|
||||
description: "XML steps should use n='X' attribute for sequential numbering"
|
||||
severity: "medium"
|
||||
|
||||
- id: "xml_action_tags"
|
||||
description: "Use <action> for required actions, <ask> for user input (must HALT), <goto> for jumps, <check if='...'> for conditionals"
|
||||
severity: "medium"
|
||||
|
||||
- id: "xml_ask_must_halt"
|
||||
description: "<ask> tags require agent to HALT and wait for user response before continuing"
|
||||
severity: "high"
|
||||
|
||||
# ============================================
|
||||
# WORKFLOW CONTENT QUALITY
|
||||
# ============================================
|
||||
|
|
@ -185,7 +146,6 @@ areas:
|
|||
description: "Content quality and consistency rules for all workflow files"
|
||||
globs:
|
||||
- "src/**/workflows/**/*.md"
|
||||
- "src/**/workflows/**/*.yaml"
|
||||
rules:
|
||||
- id: "communication_language_variable"
|
||||
description: "Workflows should use {communication_language} variable for agent output language consistency"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ The installer uses templates for each skill type:
|
|||
| Skill type | What the generated file does |
|
||||
| --- | --- |
|
||||
| **Agent launcher** | Loads the agent persona file, activates its menu, and stays in character |
|
||||
| **Workflow skill** | Loads the workflow engine (`workflow.xml`) and passes the workflow config |
|
||||
| **Workflow skill** | Loads the workflow config and follows its steps |
|
||||
| **Task skill** | Loads a standalone task file and follows its instructions |
|
||||
| **Tool skill** | Loads a standalone tool file and follows its instructions |
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ See [Agents](./agents.md) for the full list of default agents and their triggers
|
|||
|
||||
### Workflow Skills
|
||||
|
||||
Workflow skills run a structured, multi-step process without loading an agent persona first. They load the workflow engine and pass a specific workflow configuration.
|
||||
Workflow skills run a structured, multi-step process without loading an agent persona first. They load a workflow configuration and follow its steps.
|
||||
|
||||
| Example skill | Purpose |
|
||||
| --- | --- |
|
||||
|
|
|
|||
|
|
@ -39,5 +39,5 @@ agent:
|
|||
description: "[CB] Create Brief: A guided experience to nail down your product idea into an executive brief"
|
||||
|
||||
- trigger: DP or fuzzy match on document-project
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/document-project/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/document-project/workflow.md"
|
||||
description: "[DP] Document Project: Analyze an existing project to produce useful documentation for both human and LLM"
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ agent:
|
|||
|
||||
menu:
|
||||
- trigger: DS or fuzzy match on dev-story
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/4-implementation/dev-story/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/4-implementation/dev-story/workflow.md"
|
||||
description: "[DS] Dev Story: Write the next or specified stories tests and code."
|
||||
|
||||
- trigger: CR or fuzzy match on code-review
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/4-implementation/code-review/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/4-implementation/code-review/workflow.md"
|
||||
description: "[CR] Code Review: Initiate a comprehensive code review across multiple quality facets. For best results, use a fresh context and a different quality LLM if available"
|
||||
|
|
|
|||
|
|
@ -40,5 +40,5 @@ agent:
|
|||
description: "[IR] Implementation Readiness: Ensure the PRD, UX, and Architecture and Epics and Stories List are all aligned"
|
||||
|
||||
- trigger: CC or fuzzy match on correct-course
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/4-implementation/correct-course/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/4-implementation/correct-course/workflow.md"
|
||||
description: "[CC] Course Correction: Use this so we can determine how to proceed if major need for change is discovered mid implementation"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ agent:
|
|||
|
||||
menu:
|
||||
- trigger: QA or fuzzy match on qa-automate
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/qa-generate-e2e-tests/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/qa-generate-e2e-tests/workflow.md"
|
||||
description: "[QA] Automate - Generate tests for existing features (simplified)"
|
||||
|
||||
prompts:
|
||||
|
|
|
|||
|
|
@ -32,5 +32,5 @@ agent:
|
|||
description: "[QQ] Quick Dev New (Preview): Unified quick flow — clarify intent, plan, implement, review, present (experimental)"
|
||||
|
||||
- trigger: CR or fuzzy match on code-review
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/4-implementation/code-review/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/4-implementation/code-review/workflow.md"
|
||||
description: "[CR] Code Review: Initiate a comprehensive code review across multiple quality facets. For best results, use a fresh context and a different quality LLM if available"
|
||||
|
|
|
|||
|
|
@ -20,18 +20,18 @@ agent:
|
|||
|
||||
menu:
|
||||
- trigger: SP or fuzzy match on sprint-planning
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/4-implementation/sprint-planning/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/4-implementation/sprint-planning/workflow.md"
|
||||
description: "[SP] Sprint Planning: Generate or update the record that will sequence the tasks to complete the full project that the dev agent will follow"
|
||||
|
||||
- trigger: CS or fuzzy match on create-story
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/4-implementation/create-story/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/4-implementation/create-story/workflow.md"
|
||||
description: "[CS] Context Story: Prepare a story with all required context for implementation for the developer agent"
|
||||
|
||||
- trigger: ER or fuzzy match on epic-retrospective
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/4-implementation/retrospective/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/4-implementation/retrospective/workflow.md"
|
||||
data: "{project-root}/_bmad/_config/agent-manifest.csv"
|
||||
description: "[ER] Epic Retrospective: Party Mode review of all work completed across an epic."
|
||||
|
||||
- trigger: CC or fuzzy match on correct-course
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/4-implementation/correct-course/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/4-implementation/correct-course/workflow.md"
|
||||
description: "[CC] Course Correction: Use this so we can determine how to proceed if major need for change is discovered mid implementation"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ agent:
|
|||
|
||||
menu:
|
||||
- trigger: DP or fuzzy match on document-project
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/document-project/workflow.md"
|
||||
exec: "{project-root}/_bmad/bmm/workflows/document-project/workflow.md"
|
||||
description: "[DP] Document Project: Generate comprehensive project documentation (brownfield analysis, architecture scanning)"
|
||||
|
||||
- trigger: WD or fuzzy match on write-document
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
# Discover Inputs Protocol
|
||||
|
||||
**Objective:** Intelligently load project files (whole or sharded) based on the workflow's Input Files configuration.
|
||||
|
||||
**Prerequisite:** Only execute this protocol if the workflow defines an Input Files section. If no input file patterns are configured, skip this entirely.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Parse Input File Patterns
|
||||
|
||||
- Read the Input Files table from the workflow configuration.
|
||||
- For each input group (prd, architecture, epics, ux, etc.), note the **load strategy** if specified.
|
||||
|
||||
## Step 2: Load Files Using Smart Strategies
|
||||
|
||||
For each pattern in the Input Files table, work through the following substeps in order:
|
||||
|
||||
### 2a: Try Sharded Documents First
|
||||
|
||||
If a sharded pattern exists for this input, determine the load strategy (defaults to **FULL_LOAD** if not specified), then apply the matching strategy:
|
||||
|
||||
#### FULL_LOAD Strategy
|
||||
|
||||
Load ALL files in the sharded directory. Use this for PRD, Architecture, UX, brownfield docs, or whenever the full picture is needed.
|
||||
|
||||
1. Use the glob pattern to find ALL `.md` files (e.g., `{planning_artifacts}/*architecture*/*.md`).
|
||||
2. Load EVERY matching file completely.
|
||||
3. Concatenate content in logical order: `index.md` first if it exists, then alphabetical.
|
||||
4. Store the combined result in a variable named `{pattern_name_content}` (e.g., `{architecture_content}`).
|
||||
|
||||
#### SELECTIVE_LOAD Strategy
|
||||
|
||||
Load a specific shard using a template variable. Example: used for epics with `{{epic_num}}`.
|
||||
|
||||
1. Check for template variables in the sharded pattern (e.g., `{{epic_num}}`).
|
||||
2. If the variable is undefined, ask the user for the value OR infer it from context.
|
||||
3. Resolve the template to a specific file path.
|
||||
4. Load that specific file.
|
||||
5. Store in variable: `{pattern_name_content}`.
|
||||
|
||||
#### INDEX_GUIDED Strategy
|
||||
|
||||
Load index.md, analyze the structure and description of each doc in the index, then intelligently load relevant docs.
|
||||
|
||||
**DO NOT BE LAZY** -- use best judgment to load documents that might have relevant information, even if there is only a 5% chance of relevance.
|
||||
|
||||
1. Load `index.md` from the sharded directory.
|
||||
2. Parse the table of contents, links, and section headers.
|
||||
3. Analyze the workflow's purpose and objective.
|
||||
4. Identify which linked/referenced documents are likely relevant.
|
||||
- *Example:* If the workflow is about authentication and the index shows "Auth Overview", "Payment Setup", "Deployment" -- load the auth docs, consider deployment docs, skip payment.
|
||||
5. Load all identified relevant documents.
|
||||
6. Store combined content in variable: `{pattern_name_content}`.
|
||||
|
||||
**When in doubt, LOAD IT** -- context is valuable, and being thorough is better than missing critical info.
|
||||
|
||||
---
|
||||
|
||||
After applying the matching strategy, mark the pattern as **RESOLVED** and move to the next pattern.
|
||||
|
||||
### 2b: Try Whole Document if No Sharded Found
|
||||
|
||||
If no sharded matches were found OR no sharded pattern exists for this input:
|
||||
|
||||
1. Attempt a glob match on the "whole" pattern (e.g., `{planning_artifacts}/*prd*.md`).
|
||||
2. If matches are found, load ALL matching files completely (no offset/limit).
|
||||
3. Store content in variable: `{pattern_name_content}` (e.g., `{prd_content}`).
|
||||
4. Mark pattern as **RESOLVED** and move to the next pattern.
|
||||
|
||||
### 2c: Handle Not Found
|
||||
|
||||
If no matches were found for either sharded or whole patterns:
|
||||
|
||||
1. Set `{pattern_name_content}` to empty string.
|
||||
2. Note in session: "No {pattern_name} files found" -- this is not an error, just unavailable. Offer the user a chance to provide the file.
|
||||
|
||||
## Step 3: Report Discovery Results
|
||||
|
||||
List all loaded content variables with file counts. Example:
|
||||
|
||||
```
|
||||
OK Loaded {prd_content} from 5 sharded files: prd/index.md, prd/requirements.md, ...
|
||||
OK Loaded {architecture_content} from 1 file: Architecture.md
|
||||
OK Loaded {epics_content} from selective load: epics/epic-3.md
|
||||
-- No ux_design files found
|
||||
```
|
||||
|
||||
This gives the workflow transparency into what context is available.
|
||||
|
|
@ -81,7 +81,7 @@ Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
|
|||
- Missing documentation of what was actually changed
|
||||
</action>
|
||||
|
||||
<invoke-protocol name="discover_inputs" />
|
||||
<action>Read fully and follow `{installed_path}/discover-inputs.md` to load all input files</action>
|
||||
<action>Load {project_context} for coding standards (if exists)</action>
|
||||
</step>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ This is a COMPETITION to create the **ULTIMATE story context** that makes LLM de
|
|||
|
||||
### **When Running from Create-Story Workflow:**
|
||||
|
||||
- The `{project-root}/_bmad/core/tasks/workflow.xml` framework will automatically:
|
||||
- The workflow framework will automatically:
|
||||
- Load this checklist file
|
||||
- Load the newly created story file (`{story_file_path}`)
|
||||
- Load workflow variables from `{installed_path}/workflow.md`
|
||||
|
|
@ -51,7 +51,7 @@ This is a COMPETITION to create the **ULTIMATE story context** that makes LLM de
|
|||
- **Story file**: The story file to review and improve
|
||||
- **Workflow variables**: From workflow.md (implementation_artifacts, epics_file, etc.)
|
||||
- **Source documents**: Epics, architecture, etc. (discovered or provided)
|
||||
- **Validation framework**: `validate-workflow.xml` (handles checklist execution)
|
||||
- **Validation framework**: The workflow's checklist execution system
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -63,10 +63,9 @@ You will systematically re-do the entire story creation process, but with a crit
|
|||
|
||||
1. **Load the workflow configuration**: `{installed_path}/workflow.md` for variable inclusion
|
||||
2. **Load the story file**: `{story_file_path}` (provided by user or discovered)
|
||||
3. **Load validation framework**: `{project-root}/_bmad/core/tasks/workflow.xml`
|
||||
4. **Extract metadata**: epic_num, story_num, story_key, story_title from story file
|
||||
5. **Resolve all workflow variables**: implementation_artifacts, epics_file, architecture_file, etc.
|
||||
6. **Understand current status**: What story implementation guidance is currently provided?
|
||||
3. **Extract metadata**: epic_num, story_num, story_key, story_title from story file
|
||||
4. **Resolve all workflow variables**: implementation_artifacts, epics_file, architecture_file, etc.
|
||||
5. **Understand current status**: What story implementation guidance is currently provided?
|
||||
|
||||
**Note:** If running in fresh context, user should provide the story file path being reviewed. If running from create-story workflow, the validation framework will automatically discover the checklist and story file.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
# Discover Inputs Protocol
|
||||
|
||||
**Objective:** Intelligently load project files (whole or sharded) based on the workflow's Input Files configuration.
|
||||
|
||||
**Prerequisite:** Only execute this protocol if the workflow defines an Input Files section. If no input file patterns are configured, skip this entirely.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Parse Input File Patterns
|
||||
|
||||
- Read the Input Files table from the workflow configuration.
|
||||
- For each input group (prd, architecture, epics, ux, etc.), note the **load strategy** if specified.
|
||||
|
||||
## Step 2: Load Files Using Smart Strategies
|
||||
|
||||
For each pattern in the Input Files table, work through the following substeps in order:
|
||||
|
||||
### 2a: Try Sharded Documents First
|
||||
|
||||
If a sharded pattern exists for this input, determine the load strategy (defaults to **FULL_LOAD** if not specified), then apply the matching strategy:
|
||||
|
||||
#### FULL_LOAD Strategy
|
||||
|
||||
Load ALL files in the sharded directory. Use this for PRD, Architecture, UX, brownfield docs, or whenever the full picture is needed.
|
||||
|
||||
1. Use the glob pattern to find ALL `.md` files (e.g., `{planning_artifacts}/*architecture*/*.md`).
|
||||
2. Load EVERY matching file completely.
|
||||
3. Concatenate content in logical order: `index.md` first if it exists, then alphabetical.
|
||||
4. Store the combined result in a variable named `{pattern_name_content}` (e.g., `{architecture_content}`).
|
||||
|
||||
#### SELECTIVE_LOAD Strategy
|
||||
|
||||
Load a specific shard using a template variable. Example: used for epics with `{{epic_num}}`.
|
||||
|
||||
1. Check for template variables in the sharded pattern (e.g., `{{epic_num}}`).
|
||||
2. If the variable is undefined, ask the user for the value OR infer it from context.
|
||||
3. Resolve the template to a specific file path.
|
||||
4. Load that specific file.
|
||||
5. Store in variable: `{pattern_name_content}`.
|
||||
|
||||
#### INDEX_GUIDED Strategy
|
||||
|
||||
Load index.md, analyze the structure and description of each doc in the index, then intelligently load relevant docs.
|
||||
|
||||
**DO NOT BE LAZY** -- use best judgment to load documents that might have relevant information, even if there is only a 5% chance of relevance.
|
||||
|
||||
1. Load `index.md` from the sharded directory.
|
||||
2. Parse the table of contents, links, and section headers.
|
||||
3. Analyze the workflow's purpose and objective.
|
||||
4. Identify which linked/referenced documents are likely relevant.
|
||||
- *Example:* If the workflow is about authentication and the index shows "Auth Overview", "Payment Setup", "Deployment" -- load the auth docs, consider deployment docs, skip payment.
|
||||
5. Load all identified relevant documents.
|
||||
6. Store combined content in variable: `{pattern_name_content}`.
|
||||
|
||||
**When in doubt, LOAD IT** -- context is valuable, and being thorough is better than missing critical info.
|
||||
|
||||
---
|
||||
|
||||
After applying the matching strategy, mark the pattern as **RESOLVED** and move to the next pattern.
|
||||
|
||||
### 2b: Try Whole Document if No Sharded Found
|
||||
|
||||
If no sharded matches were found OR no sharded pattern exists for this input:
|
||||
|
||||
1. Attempt a glob match on the "whole" pattern (e.g., `{planning_artifacts}/*prd*.md`).
|
||||
2. If matches are found, load ALL matching files completely (no offset/limit).
|
||||
3. Store content in variable: `{pattern_name_content}` (e.g., `{prd_content}`).
|
||||
4. Mark pattern as **RESOLVED** and move to the next pattern.
|
||||
|
||||
### 2c: Handle Not Found
|
||||
|
||||
If no matches were found for either sharded or whole patterns:
|
||||
|
||||
1. Set `{pattern_name_content}` to empty string.
|
||||
2. Note in session: "No {pattern_name} files found" -- this is not an error, just unavailable. Offer the user a chance to provide the file.
|
||||
|
||||
## Step 3: Report Discovery Results
|
||||
|
||||
List all loaded content variables with file counts. Example:
|
||||
|
||||
```
|
||||
OK Loaded {prd_content} from 5 sharded files: prd/index.md, prd/requirements.md, ...
|
||||
OK Loaded {architecture_content} from 1 file: Architecture.md
|
||||
OK Loaded {epics_content} from selective load: epics/epic-3.md
|
||||
-- No ux_design files found
|
||||
```
|
||||
|
||||
This gives the workflow transparency into what context is available.
|
||||
|
|
@ -220,8 +220,7 @@ Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
|
|||
<critical>🔬 EXHAUSTIVE ARTIFACT ANALYSIS - This is where you prevent future developer fuckups!</critical>
|
||||
|
||||
<!-- Load all available content through discovery protocol -->
|
||||
<invoke-protocol
|
||||
name="discover_inputs" />
|
||||
<action>Read fully and follow `{installed_path}/discover-inputs.md` to load all input files</action>
|
||||
<note>Available content: {epics_content}, {prd_content}, {architecture_content}, {ux_content},
|
||||
{project_context}</note>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,412 +0,0 @@
|
|||
<workflow>
|
||||
<critical>The workflow execution engine is governed by: {project-root}/_bmad/core/tasks/workflow.xml</critical>
|
||||
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
|
||||
<critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical>
|
||||
<critical>Generate all documents in {document_output_language}</critical>
|
||||
<critical>Only modify the story file in these areas: Tasks/Subtasks checkboxes, Dev Agent Record (Debug Log, Completion Notes), File List,
|
||||
Change Log, and Status</critical>
|
||||
<critical>Execute ALL steps in exact order; do NOT skip steps</critical>
|
||||
<critical>Absolutely DO NOT stop because of "milestones", "significant progress", or "session boundaries". Continue in a single execution
|
||||
until the story is COMPLETE (all ACs satisfied and all tasks/subtasks checked) UNLESS a HALT condition is triggered or the USER gives
|
||||
other instruction.</critical>
|
||||
<critical>Do NOT schedule a "next session" or request review pauses unless a HALT condition applies. Only Step 6 decides completion.</critical>
|
||||
<critical>User skill level ({user_skill_level}) affects conversation style ONLY, not code updates.</critical>
|
||||
|
||||
<step n="1" goal="Find next ready story and load it" tag="sprint-status">
|
||||
<check if="{{story_path}} is provided">
|
||||
<action>Use {{story_path}} directly</action>
|
||||
<action>Read COMPLETE story file</action>
|
||||
<action>Extract story_key from filename or metadata</action>
|
||||
<goto anchor="task_check" />
|
||||
</check>
|
||||
|
||||
<!-- Sprint-based story discovery -->
|
||||
<check if="{{sprint_status}} file exists">
|
||||
<critical>MUST read COMPLETE sprint-status.yaml file from start to end to preserve order</critical>
|
||||
<action>Load the FULL file: {{sprint_status}}</action>
|
||||
<action>Read ALL lines from beginning to end - do not skip any content</action>
|
||||
<action>Parse the development_status section completely to understand story order</action>
|
||||
|
||||
<action>Find the FIRST story (by reading in order from top to bottom) where:
|
||||
- Key matches pattern: number-number-name (e.g., "1-2-user-auth")
|
||||
- NOT an epic key (epic-X) or retrospective (epic-X-retrospective)
|
||||
- Status value equals "ready-for-dev"
|
||||
</action>
|
||||
|
||||
<check if="no ready-for-dev or in-progress story found">
|
||||
<output>📋 No ready-for-dev stories found in sprint-status.yaml
|
||||
|
||||
**Current Sprint Status:** {{sprint_status_summary}}
|
||||
|
||||
**What would you like to do?**
|
||||
1. Run `create-story` to create next story from epics with comprehensive context
|
||||
2. Run `*validate-create-story` to improve existing stories before development (recommended quality check)
|
||||
3. Specify a particular story file to develop (provide full path)
|
||||
4. Check {{sprint_status}} file to see current sprint status
|
||||
|
||||
💡 **Tip:** Stories in `ready-for-dev` may not have been validated. Consider running `validate-create-story` first for a quality
|
||||
check.
|
||||
</output>
|
||||
<ask>Choose option [1], [2], [3], or [4], or specify story file path:</ask>
|
||||
|
||||
<check if="user chooses '1'">
|
||||
<action>HALT - Run create-story to create next story</action>
|
||||
</check>
|
||||
|
||||
<check if="user chooses '2'">
|
||||
<action>HALT - Run validate-create-story to improve existing stories</action>
|
||||
</check>
|
||||
|
||||
<check if="user chooses '3'">
|
||||
<ask>Provide the story file path to develop:</ask>
|
||||
<action>Store user-provided story path as {{story_path}}</action>
|
||||
<goto anchor="task_check" />
|
||||
</check>
|
||||
|
||||
<check if="user chooses '4'">
|
||||
<output>Loading {{sprint_status}} for detailed status review...</output>
|
||||
<action>Display detailed sprint status analysis</action>
|
||||
<action>HALT - User can review sprint status and provide story path</action>
|
||||
</check>
|
||||
|
||||
<check if="user provides story file path">
|
||||
<action>Store user-provided story path as {{story_path}}</action>
|
||||
<goto anchor="task_check" />
|
||||
</check>
|
||||
</check>
|
||||
</check>
|
||||
|
||||
<!-- Non-sprint story discovery -->
|
||||
<check if="{{sprint_status}} file does NOT exist">
|
||||
<action>Search {implementation_artifacts} for stories directly</action>
|
||||
<action>Find stories with "ready-for-dev" status in files</action>
|
||||
<action>Look for story files matching pattern: *-*-*.md</action>
|
||||
<action>Read each candidate story file to check Status section</action>
|
||||
|
||||
<check if="no ready-for-dev stories found in story files">
|
||||
<output>📋 No ready-for-dev stories found
|
||||
|
||||
**Available Options:**
|
||||
1. Run `create-story` to create next story from epics with comprehensive context
|
||||
2. Run `*validate-create-story` to improve existing stories
|
||||
3. Specify which story to develop
|
||||
</output>
|
||||
<ask>What would you like to do? Choose option [1], [2], or [3]:</ask>
|
||||
|
||||
<check if="user chooses '1'">
|
||||
<action>HALT - Run create-story to create next story</action>
|
||||
</check>
|
||||
|
||||
<check if="user chooses '2'">
|
||||
<action>HALT - Run validate-create-story to improve existing stories</action>
|
||||
</check>
|
||||
|
||||
<check if="user chooses '3'">
|
||||
<ask>It's unclear what story you want developed. Please provide the full path to the story file:</ask>
|
||||
<action>Store user-provided story path as {{story_path}}</action>
|
||||
<action>Continue with provided story file</action>
|
||||
</check>
|
||||
</check>
|
||||
|
||||
<check if="ready-for-dev story found in files">
|
||||
<action>Use discovered story file and extract story_key</action>
|
||||
</check>
|
||||
</check>
|
||||
|
||||
<action>Store the found story_key (e.g., "1-2-user-authentication") for later status updates</action>
|
||||
<action>Find matching story file in {implementation_artifacts} using story_key pattern: {{story_key}}.md</action>
|
||||
<action>Read COMPLETE story file from discovered path</action>
|
||||
|
||||
<anchor id="task_check" />
|
||||
|
||||
<action>Parse sections: Story, Acceptance Criteria, Tasks/Subtasks, Dev Notes, Dev Agent Record, File List, Change Log, Status</action>
|
||||
|
||||
<action>Load comprehensive context from story file's Dev Notes section</action>
|
||||
<action>Extract developer guidance from Dev Notes: architecture requirements, previous learnings, technical specifications</action>
|
||||
<action>Use enhanced story context to inform implementation decisions and approaches</action>
|
||||
|
||||
<action>Identify first incomplete task (unchecked [ ]) in Tasks/Subtasks</action>
|
||||
|
||||
<action if="no incomplete tasks">
|
||||
<goto step="6">Completion sequence</goto>
|
||||
</action>
|
||||
<action if="story file inaccessible">HALT: "Cannot develop story without access to story file"</action>
|
||||
<action if="incomplete task or subtask requirements ambiguous">ASK user to clarify or HALT</action>
|
||||
</step>
|
||||
|
||||
<step n="2" goal="Load project context and story information">
|
||||
<critical>Load all available context to inform implementation</critical>
|
||||
|
||||
<action>Load {project_context} for coding standards and project-wide patterns (if exists)</action>
|
||||
<action>Parse sections: Story, Acceptance Criteria, Tasks/Subtasks, Dev Notes, Dev Agent Record, File List, Change Log, Status</action>
|
||||
<action>Load comprehensive context from story file's Dev Notes section</action>
|
||||
<action>Extract developer guidance from Dev Notes: architecture requirements, previous learnings, technical specifications</action>
|
||||
<action>Use enhanced story context to inform implementation decisions and approaches</action>
|
||||
<output>✅ **Context Loaded**
|
||||
Story and project context available for implementation
|
||||
</output>
|
||||
</step>
|
||||
|
||||
<step n="3" goal="Detect review continuation and extract review context">
|
||||
<critical>Determine if this is a fresh start or continuation after code review</critical>
|
||||
|
||||
<action>Check if "Senior Developer Review (AI)" section exists in the story file</action>
|
||||
<action>Check if "Review Follow-ups (AI)" subsection exists under Tasks/Subtasks</action>
|
||||
|
||||
<check if="Senior Developer Review section exists">
|
||||
<action>Set review_continuation = true</action>
|
||||
<action>Extract from "Senior Developer Review (AI)" section:
|
||||
- Review outcome (Approve/Changes Requested/Blocked)
|
||||
- Review date
|
||||
- Total action items with checkboxes (count checked vs unchecked)
|
||||
- Severity breakdown (High/Med/Low counts)
|
||||
</action>
|
||||
<action>Count unchecked [ ] review follow-up tasks in "Review Follow-ups (AI)" subsection</action>
|
||||
<action>Store list of unchecked review items as {{pending_review_items}}</action>
|
||||
|
||||
<output>⏯️ **Resuming Story After Code Review** ({{review_date}})
|
||||
|
||||
**Review Outcome:** {{review_outcome}}
|
||||
**Action Items:** {{unchecked_review_count}} remaining to address
|
||||
**Priorities:** {{high_count}} High, {{med_count}} Medium, {{low_count}} Low
|
||||
|
||||
**Strategy:** Will prioritize review follow-up tasks (marked [AI-Review]) before continuing with regular tasks.
|
||||
</output>
|
||||
</check>
|
||||
|
||||
<check if="Senior Developer Review section does NOT exist">
|
||||
<action>Set review_continuation = false</action>
|
||||
<action>Set {{pending_review_items}} = empty</action>
|
||||
|
||||
<output>🚀 **Starting Fresh Implementation**
|
||||
|
||||
Story: {{story_key}}
|
||||
Story Status: {{current_status}}
|
||||
First incomplete task: {{first_task_description}}
|
||||
</output>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="4" goal="Mark story in-progress" tag="sprint-status">
|
||||
<check if="{{sprint_status}} file exists">
|
||||
<action>Load the FULL file: {{sprint_status}}</action>
|
||||
<action>Read all development_status entries to find {{story_key}}</action>
|
||||
<action>Get current status value for development_status[{{story_key}}]</action>
|
||||
|
||||
<check if="current status == 'ready-for-dev' OR review_continuation == true">
|
||||
<action>Update the story in the sprint status report to = "in-progress"</action>
|
||||
<action>Update last_updated field to current date</action>
|
||||
<output>🚀 Starting work on story {{story_key}}
|
||||
Status updated: ready-for-dev → in-progress
|
||||
</output>
|
||||
</check>
|
||||
|
||||
<check if="current status == 'in-progress'">
|
||||
<output>⏯️ Resuming work on story {{story_key}}
|
||||
Story is already marked in-progress
|
||||
</output>
|
||||
</check>
|
||||
|
||||
<check if="current status is neither ready-for-dev nor in-progress">
|
||||
<output>⚠️ Unexpected story status: {{current_status}}
|
||||
Expected ready-for-dev or in-progress. Continuing anyway...
|
||||
</output>
|
||||
</check>
|
||||
|
||||
<action>Store {{current_sprint_status}} for later use</action>
|
||||
</check>
|
||||
|
||||
<check if="{{sprint_status}} file does NOT exist">
|
||||
<output>ℹ️ No sprint status file exists - story progress will be tracked in story file only</output>
|
||||
<action>Set {{current_sprint_status}} = "no-sprint-tracking"</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
<step n="5" goal="Implement task following red-green-refactor cycle">
|
||||
<critical>FOLLOW THE STORY FILE TASKS/SUBTASKS SEQUENCE EXACTLY AS WRITTEN - NO DEVIATION</critical>
|
||||
|
||||
<action>Review the current task/subtask from the story file - this is your authoritative implementation guide</action>
|
||||
<action>Plan implementation following red-green-refactor cycle</action>
|
||||
|
||||
<!-- RED PHASE -->
|
||||
<action>Write FAILING tests first for the task/subtask functionality</action>
|
||||
<action>Confirm tests fail before implementation - this validates test correctness</action>
|
||||
|
||||
<!-- GREEN PHASE -->
|
||||
<action>Implement MINIMAL code to make tests pass</action>
|
||||
<action>Run tests to confirm they now pass</action>
|
||||
<action>Handle error conditions and edge cases as specified in task/subtask</action>
|
||||
|
||||
<!-- REFACTOR PHASE -->
|
||||
<action>Improve code structure while keeping tests green</action>
|
||||
<action>Ensure code follows architecture patterns and coding standards from Dev Notes</action>
|
||||
|
||||
<action>Document technical approach and decisions in Dev Agent Record → Implementation Plan</action>
|
||||
|
||||
<action if="new dependencies required beyond story specifications">HALT: "Additional dependencies need user approval"</action>
|
||||
<action if="3 consecutive implementation failures occur">HALT and request guidance</action>
|
||||
<action if="required configuration is missing">HALT: "Cannot proceed without necessary configuration files"</action>
|
||||
|
||||
<critical>NEVER implement anything not mapped to a specific task/subtask in the story file</critical>
|
||||
<critical>NEVER proceed to next task until current task/subtask is complete AND tests pass</critical>
|
||||
<critical>Execute continuously without pausing until all tasks/subtasks are complete or explicit HALT condition</critical>
|
||||
<critical>Do NOT propose to pause for review until Step 9 completion gates are satisfied</critical>
|
||||
</step>
|
||||
|
||||
<step n="6" goal="Author comprehensive tests">
|
||||
<action>Create unit tests for business logic and core functionality introduced/changed by the task</action>
|
||||
<action>Add integration tests for component interactions specified in story requirements</action>
|
||||
<action>Include end-to-end tests for critical user flows when story requirements demand them</action>
|
||||
<action>Cover edge cases and error handling scenarios identified in story Dev Notes</action>
|
||||
</step>
|
||||
|
||||
<step n="7" goal="Run validations and tests">
|
||||
<action>Determine how to run tests for this repo (infer test framework from project structure)</action>
|
||||
<action>Run all existing tests to ensure no regressions</action>
|
||||
<action>Run the new tests to verify implementation correctness</action>
|
||||
<action>Run linting and code quality checks if configured in project</action>
|
||||
<action>Validate implementation meets ALL story acceptance criteria; enforce quantitative thresholds explicitly</action>
|
||||
<action if="regression tests fail">STOP and fix before continuing - identify breaking changes immediately</action>
|
||||
<action if="new tests fail">STOP and fix before continuing - ensure implementation correctness</action>
|
||||
</step>
|
||||
|
||||
<step n="8" goal="Validate and mark task complete ONLY when fully done">
|
||||
<critical>NEVER mark a task complete unless ALL conditions are met - NO LYING OR CHEATING</critical>
|
||||
|
||||
<!-- VALIDATION GATES -->
|
||||
<action>Verify ALL tests for this task/subtask ACTUALLY EXIST and PASS 100%</action>
|
||||
<action>Confirm implementation matches EXACTLY what the task/subtask specifies - no extra features</action>
|
||||
<action>Validate that ALL acceptance criteria related to this task are satisfied</action>
|
||||
<action>Run full test suite to ensure NO regressions introduced</action>
|
||||
|
||||
<!-- REVIEW FOLLOW-UP HANDLING -->
|
||||
<check if="task is review follow-up (has [AI-Review] prefix)">
|
||||
<action>Extract review item details (severity, description, related AC/file)</action>
|
||||
<action>Add to resolution tracking list: {{resolved_review_items}}</action>
|
||||
|
||||
<!-- Mark task in Review Follow-ups section -->
|
||||
<action>Mark task checkbox [x] in "Tasks/Subtasks → Review Follow-ups (AI)" section</action>
|
||||
|
||||
<!-- CRITICAL: Also mark corresponding action item in review section -->
|
||||
<action>Find matching action item in "Senior Developer Review (AI) → Action Items" section by matching description</action>
|
||||
<action>Mark that action item checkbox [x] as resolved</action>
|
||||
|
||||
<action>Add to Dev Agent Record → Completion Notes: "✅ Resolved review finding [{{severity}}]: {{description}}"</action>
|
||||
</check>
|
||||
|
||||
<!-- ONLY MARK COMPLETE IF ALL VALIDATION PASS -->
|
||||
<check if="ALL validation gates pass AND tests ACTUALLY exist and pass">
|
||||
<action>ONLY THEN mark the task (and subtasks) checkbox with [x]</action>
|
||||
<action>Update File List section with ALL new, modified, or deleted files (paths relative to repo root)</action>
|
||||
<action>Add completion notes to Dev Agent Record summarizing what was ACTUALLY implemented and tested</action>
|
||||
</check>
|
||||
|
||||
<check if="ANY validation fails">
|
||||
<action>DO NOT mark task complete - fix issues first</action>
|
||||
<action>HALT if unable to fix validation failures</action>
|
||||
</check>
|
||||
|
||||
<check if="review_continuation == true and {{resolved_review_items}} is not empty">
|
||||
<action>Count total resolved review items in this session</action>
|
||||
<action>Add Change Log entry: "Addressed code review findings - {{resolved_count}} items resolved (Date: {{date}})"</action>
|
||||
</check>
|
||||
|
||||
<action>Save the story file</action>
|
||||
<action>Determine if more incomplete tasks remain</action>
|
||||
<action if="more tasks remain">
|
||||
<goto step="5">Next task</goto>
|
||||
</action>
|
||||
<action if="no tasks remain">
|
||||
<goto step="9">Completion</goto>
|
||||
</action>
|
||||
</step>
|
||||
|
||||
<step n="9" goal="Story completion and mark for review" tag="sprint-status">
|
||||
<action>Verify ALL tasks and subtasks are marked [x] (re-scan the story document now)</action>
|
||||
<action>Run the full regression suite (do not skip)</action>
|
||||
<action>Confirm File List includes every changed file</action>
|
||||
<action>Execute enhanced definition-of-done validation</action>
|
||||
<action>Update the story Status to: "review"</action>
|
||||
|
||||
<!-- Enhanced Definition of Done Validation -->
|
||||
<action>Validate definition-of-done checklist with essential requirements:
|
||||
- All tasks/subtasks marked complete with [x]
|
||||
- Implementation satisfies every Acceptance Criterion
|
||||
- Unit tests for core functionality added/updated
|
||||
- Integration tests for component interactions added when required
|
||||
- End-to-end tests for critical flows added when story demands them
|
||||
- All tests pass (no regressions, new tests successful)
|
||||
- Code quality checks pass (linting, static analysis if configured)
|
||||
- File List includes every new/modified/deleted file (relative paths)
|
||||
- Dev Agent Record contains implementation notes
|
||||
- Change Log includes summary of changes
|
||||
- Only permitted story sections were modified
|
||||
</action>
|
||||
|
||||
<!-- Mark story ready for review - sprint status conditional -->
|
||||
<check if="{sprint_status} file exists AND {{current_sprint_status}} != 'no-sprint-tracking'">
|
||||
<action>Load the FULL file: {sprint_status}</action>
|
||||
<action>Find development_status key matching {{story_key}}</action>
|
||||
<action>Verify current status is "in-progress" (expected previous state)</action>
|
||||
<action>Update development_status[{{story_key}}] = "review"</action>
|
||||
<action>Update last_updated field to current date</action>
|
||||
<action>Save file, preserving ALL comments and structure including STATUS DEFINITIONS</action>
|
||||
<output>✅ Story status updated to "review" in sprint-status.yaml</output>
|
||||
</check>
|
||||
|
||||
<check if="{sprint_status} file does NOT exist OR {{current_sprint_status}} == 'no-sprint-tracking'">
|
||||
<output>ℹ️ Story status updated to "review" in story file (no sprint tracking configured)</output>
|
||||
</check>
|
||||
|
||||
<check if="story key not found in sprint status">
|
||||
<output>⚠️ Story file updated, but sprint-status update failed: {{story_key}} not found
|
||||
|
||||
Story status is set to "review" in file, but sprint-status.yaml may be out of sync.
|
||||
</output>
|
||||
</check>
|
||||
|
||||
<!-- Final validation gates -->
|
||||
<action if="any task is incomplete">HALT - Complete remaining tasks before marking ready for review</action>
|
||||
<action if="regression failures exist">HALT - Fix regression issues before completing</action>
|
||||
<action if="File List is incomplete">HALT - Update File List with all changed files</action>
|
||||
<action if="definition-of-done validation fails">HALT - Address DoD failures before completing</action>
|
||||
</step>
|
||||
|
||||
<step n="10" goal="Completion communication and user support">
|
||||
<action>Execute the enhanced definition-of-done checklist using the validation framework</action>
|
||||
<action>Prepare a concise summary in Dev Agent Record → Completion Notes</action>
|
||||
|
||||
<action>Communicate to {user_name} that story implementation is complete and ready for review</action>
|
||||
<action>Summarize key accomplishments: story ID, story key, title, key changes made, tests added, files modified</action>
|
||||
<action>Provide the story file path and current status (now "review")</action>
|
||||
|
||||
<action>Based on {user_skill_level}, ask if user needs any explanations about:
|
||||
- What was implemented and how it works
|
||||
- Why certain technical decisions were made
|
||||
- How to test or verify the changes
|
||||
- Any patterns, libraries, or approaches used
|
||||
- Anything else they'd like clarified
|
||||
</action>
|
||||
|
||||
<check if="user asks for explanations">
|
||||
<action>Provide clear, contextual explanations tailored to {user_skill_level}</action>
|
||||
<action>Use examples and references to specific code when helpful</action>
|
||||
</check>
|
||||
|
||||
<action>Once explanations are complete (or user indicates no questions), suggest logical next steps</action>
|
||||
<action>Recommended next steps (flexible based on project setup):
|
||||
- Review the implemented story and test the changes
|
||||
- Verify all acceptance criteria are met
|
||||
- Ensure deployment readiness if applicable
|
||||
- Run `code-review` workflow for peer review
|
||||
- Optional: If Test Architect module installed, run `/bmad:tea:automate` to expand guardrail tests
|
||||
</action>
|
||||
|
||||
<output>💡 **Tip:** For best results, run `code-review` using a **different** LLM than the one that implemented this story.</output>
|
||||
<check if="{sprint_status} file exists">
|
||||
<action>Suggest checking {sprint_status} to see project progress</action>
|
||||
</check>
|
||||
<action>Remain flexible - allow user to choose their own path or ask for other assistance</action>
|
||||
</step>
|
||||
|
||||
</workflow>
|
||||
|
|
@ -27,8 +27,3 @@ shard-doc.xml:
|
|||
canonicalId: bmad-shard-doc
|
||||
type: task
|
||||
description: "Splits large markdown documents into smaller, organized files based on sections"
|
||||
|
||||
workflow.xml:
|
||||
canonicalId: bmad-workflow
|
||||
type: task
|
||||
description: "Execute given workflow by loading its configuration and following instructions"
|
||||
|
|
|
|||
|
|
@ -1,235 +0,0 @@
|
|||
<task id="_bmad/core/tasks/workflow.xml" name="Execute Workflow" internal="true">
|
||||
<objective>Execute given workflow by loading its configuration, following instructions, and producing output</objective>
|
||||
|
||||
<llm critical="true">
|
||||
<mandate>Always read COMPLETE files - NEVER use offset/limit when reading any workflow related files</mandate>
|
||||
<mandate>Instructions are MANDATORY - either as file path, steps or embedded list in YAML, XML or markdown</mandate>
|
||||
<mandate>Execute ALL steps in instructions IN EXACT ORDER</mandate>
|
||||
<mandate>Save to template output file after EVERY "template-output" tag</mandate>
|
||||
<mandate>NEVER skip a step - YOU are responsible for every steps execution without fail or excuse</mandate>
|
||||
</llm>
|
||||
|
||||
<WORKFLOW-RULES critical="true">
|
||||
<rule n="1">Steps execute in exact numerical order (1, 2, 3...)</rule>
|
||||
<rule n="2">Optional steps: Ask user unless #yolo mode active</rule>
|
||||
<rule n="3">Template-output tags: Save content, discuss with the user the section completed, and NEVER proceed until the users indicates
|
||||
to proceed (unless YOLO mode has been activated)</rule>
|
||||
</WORKFLOW-RULES>
|
||||
|
||||
<flow>
|
||||
<step n="1" title="Load and Initialize Workflow">
|
||||
<substep n="1a" title="Load Configuration and Resolve Variables">
|
||||
<action>Read workflow.yaml from provided path</action>
|
||||
<mandate>Load config_source (REQUIRED for all modules)</mandate>
|
||||
<phase n="1">Load external config from config_source path</phase>
|
||||
<phase n="2">Resolve all {config_source}: references with values from config</phase>
|
||||
<phase n="3">Resolve system variables (date:system-generated) and paths ({project-root}, {installed_path})</phase>
|
||||
<phase n="4">Ask user for input of any variables that are still unknown</phase>
|
||||
</substep>
|
||||
|
||||
<substep n="1b" title="Load Required Components">
|
||||
<mandate>Instructions: Read COMPLETE file from path OR embedded list (REQUIRED)</mandate>
|
||||
<check>If template path → Read COMPLETE template file</check>
|
||||
<check>If validation path → Note path for later loading when needed</check>
|
||||
<check>If template: false → Mark as action-workflow (else template-workflow)</check>
|
||||
<note>Data files (csv, json) → Store paths only, load on-demand when instructions reference them</note>
|
||||
</substep>
|
||||
|
||||
<substep n="1c" title="Initialize Output" if="template-workflow">
|
||||
<action>Resolve default_output_file path with all variables and {{date}}</action>
|
||||
<action>Create output directory if doesn't exist</action>
|
||||
<action>If template-workflow → Write template to output file with placeholders</action>
|
||||
<action>If action-workflow → Skip file creation</action>
|
||||
</substep>
|
||||
</step>
|
||||
|
||||
<step n="2" title="Process Each Instruction Step in Order">
|
||||
<iterate>For each step in instructions:</iterate>
|
||||
|
||||
<substep n="2a" title="Handle Step Attributes">
|
||||
<check>If optional="true" and NOT #yolo → Ask user to include</check>
|
||||
<check>If if="condition" → Evaluate condition</check>
|
||||
<check>If for-each="item" → Repeat step for each item</check>
|
||||
<check>If repeat="n" → Repeat step n times</check>
|
||||
</substep>
|
||||
|
||||
<substep n="2b" title="Execute Step Content">
|
||||
<action>Process step instructions (markdown or XML tags)</action>
|
||||
<action>Replace {{variables}} with values (ask user if unknown)</action>
|
||||
<execute-tags>
|
||||
<tag>action xml tag → Perform the action</tag>
|
||||
<tag>check if="condition" xml tag → Conditional block wrapping actions (requires closing </check>)</tag>
|
||||
<tag>ask xml tag → Prompt user and WAIT for response</tag>
|
||||
<tag>invoke-workflow xml tag → Execute another workflow with given inputs and the workflow.xml runner</tag>
|
||||
<tag>invoke-task xml tag → Execute specified task</tag>
|
||||
<tag>invoke-protocol name="protocol_name" xml tag → Execute reusable protocol from protocols section</tag>
|
||||
<tag>goto step="x" → Jump to specified step</tag>
|
||||
</execute-tags>
|
||||
</substep>
|
||||
|
||||
<substep n="2c" title="Handle template-output Tags">
|
||||
<if tag="template-output">
|
||||
<mandate>Generate content for this section</mandate>
|
||||
<mandate>Save to file (Write first time, Edit subsequent)</mandate>
|
||||
<action>Display generated content</action>
|
||||
<ask> [a] Advanced Elicitation, [c] Continue, [p] Party-Mode, [y] YOLO the rest of this document only. WAIT for response. <if
|
||||
response="a">
|
||||
<action>Start the advanced elicitation workflow {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md</action>
|
||||
</if>
|
||||
<if
|
||||
response="c">
|
||||
<action>Continue to next step</action>
|
||||
</if>
|
||||
<if response="p">
|
||||
<action>Start the party-mode workflow {project-root}/_bmad/core/workflows/party-mode/workflow.md</action>
|
||||
</if>
|
||||
<if
|
||||
response="y">
|
||||
<action>Enter #yolo mode for the rest of the workflow</action>
|
||||
</if>
|
||||
</ask>
|
||||
</if>
|
||||
</substep>
|
||||
|
||||
<substep n="2d" title="Step Completion">
|
||||
<check>If no special tags and NOT #yolo:</check>
|
||||
<ask>Continue to next step? (y/n/edit)</ask>
|
||||
</substep>
|
||||
</step>
|
||||
|
||||
<step n="3" title="Completion">
|
||||
<check>Confirm document saved to output path</check>
|
||||
<action>Report workflow completion</action>
|
||||
</step>
|
||||
</flow>
|
||||
|
||||
<execution-modes>
|
||||
<mode name="normal">Full user interaction and confirmation of EVERY step at EVERY template output - NO EXCEPTIONS except yolo MODE</mode>
|
||||
<mode name="yolo">Skip all confirmations and elicitation, minimize prompts and try to produce all of the workflow automatically by
|
||||
simulating the remaining discussions with an simulated expert user</mode>
|
||||
</execution-modes>
|
||||
|
||||
<supported-tags desc="Instructions can use these tags">
|
||||
<structural>
|
||||
<tag>step n="X" goal="..." - Define step with number and goal</tag>
|
||||
<tag>optional="true" - Step can be skipped</tag>
|
||||
<tag>if="condition" - Conditional execution</tag>
|
||||
<tag>for-each="collection" - Iterate over items</tag>
|
||||
<tag>repeat="n" - Repeat n times</tag>
|
||||
</structural>
|
||||
<execution>
|
||||
<tag>action - Required action to perform</tag>
|
||||
<tag>action if="condition" - Single conditional action (inline, no closing tag needed)</tag>
|
||||
<tag>check if="condition">...</check> - Conditional block wrapping multiple items (closing tag required)</tag>
|
||||
<tag>ask - Get user input (ALWAYS wait for response before continuing)</tag>
|
||||
<tag>goto - Jump to another step</tag>
|
||||
<tag>invoke-workflow - Call another workflow</tag>
|
||||
<tag>invoke-task - Call a task</tag>
|
||||
<tag>invoke-protocol - Execute a reusable protocol (e.g., discover_inputs)</tag>
|
||||
</execution>
|
||||
<output>
|
||||
<tag>template-output - Save content checkpoint</tag>
|
||||
<tag>critical - Cannot be skipped</tag>
|
||||
<tag>example - Show example output</tag>
|
||||
</output>
|
||||
</supported-tags>
|
||||
|
||||
<protocols desc="Reusable workflow protocols that can be invoked via invoke-protocol tag">
|
||||
<protocol name="discover_inputs" desc="Smart file discovery and loading based on input_file_patterns">
|
||||
<objective>Intelligently load project files (whole or sharded) based on workflow's input_file_patterns configuration</objective>
|
||||
|
||||
<critical>Only execute if workflow.yaml contains input_file_patterns section</critical>
|
||||
|
||||
<flow>
|
||||
<step n="1" title="Parse Input File Patterns">
|
||||
<action>Read input_file_patterns from loaded workflow.yaml</action>
|
||||
<action>For each pattern group (prd, architecture, epics, etc.), note the load_strategy if present</action>
|
||||
</step>
|
||||
|
||||
<step n="2" title="Load Files Using Smart Strategies">
|
||||
<iterate>For each pattern in input_file_patterns:</iterate>
|
||||
|
||||
<substep n="2a" title="Try Sharded Documents First">
|
||||
<check if="sharded pattern exists">
|
||||
<action>Determine load_strategy from pattern config (defaults to FULL_LOAD if not specified)</action>
|
||||
|
||||
<strategy name="FULL_LOAD">
|
||||
<desc>Load ALL files in sharded directory - used for PRD, Architecture, UX, brownfield docs</desc>
|
||||
<action>Use glob pattern to find ALL .md files (e.g., "{output_folder}/*architecture*/*.md")</action>
|
||||
<action>Load EVERY matching file completely</action>
|
||||
<action>Concatenate content in logical order (index.md first if exists, then alphabetical)</action>
|
||||
<action>Store in variable: {pattern_name_content}</action>
|
||||
</strategy>
|
||||
|
||||
<strategy name="SELECTIVE_LOAD">
|
||||
<desc>Load specific shard using template variable - example: used for epics with {{epic_num}}</desc>
|
||||
<action>Check for template variables in sharded_single pattern (e.g., {{epic_num}})</action>
|
||||
<action>If variable undefined, ask user for value OR infer from context</action>
|
||||
<action>Resolve template to specific file path</action>
|
||||
<action>Load that specific file</action>
|
||||
<action>Store in variable: {pattern_name_content}</action>
|
||||
</strategy>
|
||||
|
||||
<strategy name="INDEX_GUIDED">
|
||||
<desc>Load index.md, analyze structure and description of each doc in the index, then intelligently load relevant docs</desc>
|
||||
<mandate>DO NOT BE LAZY - use best judgment to load documents that might have relevant information, even if only a 5% chance</mandate>
|
||||
<action>Load index.md from sharded directory</action>
|
||||
<action>Parse table of contents, links, section headers</action>
|
||||
<action>Analyze workflow's purpose and objective</action>
|
||||
<action>Identify which linked/referenced documents are likely relevant</action>
|
||||
<example>If workflow is about authentication and index shows "Auth Overview", "Payment Setup", "Deployment" → Load auth
|
||||
docs, consider deployment docs, skip payment</example>
|
||||
<action>Load all identified relevant documents</action>
|
||||
<action>Store combined content in variable: {pattern_name_content}</action>
|
||||
<note>When in doubt, LOAD IT - context is valuable, being thorough is better than missing critical info</note>
|
||||
</strategy>
|
||||
<action>Mark pattern as RESOLVED, skip to next pattern</action>
|
||||
</check>
|
||||
</substep>
|
||||
|
||||
<substep n="2b" title="Try Whole Document if No Sharded Found">
|
||||
<check if="no sharded matches found OR no sharded pattern exists">
|
||||
<action>Attempt glob match on 'whole' pattern (e.g., "{output_folder}/*prd*.md")</action>
|
||||
<check if="matches found">
|
||||
<action>Load ALL matching files completely (no offset/limit)</action>
|
||||
<action>Store content in variable: {pattern_name_content} (e.g., {prd_content})</action>
|
||||
<action>Mark pattern as RESOLVED, skip to next pattern</action>
|
||||
</check>
|
||||
</check>
|
||||
</substep>
|
||||
|
||||
<substep n="2c" title="Handle Not Found">
|
||||
<check if="no matches for sharded OR whole">
|
||||
<action>Set {pattern_name_content} to empty string</action>
|
||||
<action>Note in session: "No {pattern_name} files found" (not an error, just unavailable, offer use change to provide)</action>
|
||||
</check>
|
||||
</substep>
|
||||
</step>
|
||||
|
||||
<step n="3" title="Report Discovery Results">
|
||||
<action>List all loaded content variables with file counts</action>
|
||||
<example>
|
||||
✓ Loaded {prd_content} from 5 sharded files: prd/index.md, prd/requirements.md, ...
|
||||
✓ Loaded {architecture_content} from 1 file: Architecture.md
|
||||
✓ Loaded {epics_content} from selective load: epics/epic-3.md
|
||||
○ No ux_design files found
|
||||
</example>
|
||||
<note>This gives workflow transparency into what context is available</note>
|
||||
</step>
|
||||
</flow>
|
||||
|
||||
</protocol>
|
||||
</protocols>
|
||||
|
||||
<llm final="true">
|
||||
<critical-rules>
|
||||
• This is the complete workflow execution engine
|
||||
• You MUST Follow instructions exactly as written
|
||||
• The workflow execution engine is governed by: {project-root}/_bmad/core/tasks/workflow.xml
|
||||
• You MUST have already loaded and processed: {installed_path}/workflow.yaml
|
||||
• This workflow uses INTENT-DRIVEN PLANNING - adapt organically to product type and context
|
||||
• YOU ARE FACILITATING A CONVERSATION With a user to produce a final document step by step. The whole process is meant to be
|
||||
collaborative helping the user flesh out their ideas. Do not rush or optimize and skip any section.
|
||||
</critical-rules>
|
||||
</llm>
|
||||
</task>
|
||||
|
|
@ -11,4 +11,4 @@
|
|||
<step n="{HELP_STEP}">Let {user_name} know they can type command `/bmad-help` at any time to get advice on what to do next, and that they can combine that with what they need help with <example>`/bmad-help where should I start with an idea I have that does XYZ`</example></step>
|
||||
<step n="{HALT_STEP}">STOP and WAIT for user input - do NOT execute menu items automatically - accept number or cmd trigger or fuzzy command match</step>
|
||||
<step n="{INPUT_STEP}">On user input: Number → process menu item[n] | Text → case-insensitive substring match | Multiple matches → ask user to clarify | No match → show "Not recognized"</step>
|
||||
<step n="{EXECUTE_STEP}">When processing a menu item: Check menu-handlers section below - extract any attributes from the selected menu item (workflow, exec, tmpl, data, action, validate-workflow) and follow the corresponding handler instructions</step>
|
||||
<step n="{EXECUTE_STEP}">When processing a menu item: Check menu-handlers section below - extract any attributes from the selected menu item (exec, tmpl, data, action, multi) and follow the corresponding handler instructions</step>
|
||||
|
|
@ -4,10 +4,9 @@
|
|||
2. Parse all nested handlers within the multi item
|
||||
3. For each nested handler:
|
||||
- Use the 'match' attribute for fuzzy matching user input (or Exact Match of character code in brackets [])
|
||||
- Process based on handler attributes (exec, workflow, action)
|
||||
- Process based on handler attributes (exec, action)
|
||||
4. When user input matches a handler's 'match' pattern:
|
||||
- For exec="path/to/file.md": follow the `handler type="exec"` instructions
|
||||
- For workflow="path/to/workflow.yaml": follow the `handler type="workflow"` instructions
|
||||
- For action="...": Perform the specified action directly
|
||||
5. Support both exact matches and fuzzy matching based on the match attribute
|
||||
6. If no handler matches, prompt user to choose from available options
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<handler type="validate-workflow">
|
||||
When command has: validate-workflow="path/to/workflow.yaml"
|
||||
1. You MUST LOAD the file at: {project-root}/_bmad/core/tasks/validate-workflow.xml
|
||||
2. READ its entire contents and EXECUTE all instructions in that file
|
||||
3. Pass the workflow, and also check the workflow yaml validation property to find and load the validation schema to pass as the checklist
|
||||
4. The workflow should try to identify the file to validate based on checklist context or else you will ask the user to specify
|
||||
</handler>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<handler type="workflow">
|
||||
When menu item has: workflow="path/to/workflow.yaml":
|
||||
|
||||
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
|
||||
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>
|
||||
|
|
@ -19,7 +19,7 @@ agent:
|
|||
menu:
|
||||
- trigger: workflow-test
|
||||
description: Test workflow command
|
||||
workflow: path/to/workflow
|
||||
exec: path/to/workflow
|
||||
- trigger: validate-test
|
||||
description: Test validate-workflow command
|
||||
validate-workflow: path/to/validation
|
||||
|
|
|
|||
|
|
@ -19,6 +19,5 @@ agent:
|
|||
menu:
|
||||
- trigger: multi-command
|
||||
description: Menu item with multiple command targets
|
||||
workflow: path/to/workflow
|
||||
exec: npm test
|
||||
exec: path/to/workflow
|
||||
action: perform_action
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ agent:
|
|||
action: display_help
|
||||
- trigger: start-workflow
|
||||
description: Start a workflow
|
||||
workflow: path/to/workflow
|
||||
exec: path/to/workflow
|
||||
- trigger: execute
|
||||
description: Execute command
|
||||
exec: npm test
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs,
|
||||
bmm,anytime,Document Project,DP,,_bmad/bmm/workflows/document-project/workflow.yaml,bmad-bmm-document-project,false,analyst,Create Mode,"Analyze project",project-knowledge,*,
|
||||
bmm,anytime,Document Project,DP,,_bmad/bmm/workflows/document-project/workflow.md,bmad-bmm-document-project,false,analyst,Create Mode,"Analyze project",project-knowledge,*,
|
||||
bmm,1-analysis,Brainstorm Project,BP,10,_bmad/core/workflows/brainstorming/workflow.md,bmad-brainstorming,false,analyst,data=template.md,"Brainstorming",planning_artifacts,"session",
|
||||
|
|
|
|||
|
|
|
@ -58,7 +58,7 @@ test('bmm-style.csv: extracts workflow-file refs with trailing commas', () => {
|
|||
const { fullPath, content } = loadFixture('valid/bmm-style.csv');
|
||||
const refs = extractCsvRefs(fullPath, content);
|
||||
assert(refs.length === 2, `Expected 2 refs, got ${refs.length}`);
|
||||
assert(refs[0].raw === '_bmad/bmm/workflows/document-project/workflow.yaml', `Wrong raw[0]: ${refs[0].raw}`);
|
||||
assert(refs[0].raw === '_bmad/bmm/workflows/document-project/workflow.md', `Wrong raw[0]: ${refs[0].raw}`);
|
||||
assert(refs[1].raw === '_bmad/core/workflows/brainstorming/workflow.md', `Wrong raw[1]: ${refs[1].raw}`);
|
||||
assert(refs[0].type === 'project-root', `Wrong type: ${refs[0].type}`);
|
||||
assert(refs[0].line === 2, `Wrong line for row 0: ${refs[0].line}`);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* Workflow Path Regex Tests
|
||||
*
|
||||
* Tests that the source and install workflow path regexes in ModuleManager
|
||||
* extract the correct capture groups (module name and workflow sub-path).
|
||||
*
|
||||
* Usage: node test/test-workflow-path-regex.js
|
||||
*/
|
||||
|
||||
// ANSI colors
|
||||
const colors = {
|
||||
reset: '\u001B[0m',
|
||||
green: '\u001B[32m',
|
||||
red: '\u001B[31m',
|
||||
cyan: '\u001B[36m',
|
||||
dim: '\u001B[2m',
|
||||
};
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function assert(condition, testName, errorMessage = '') {
|
||||
if (condition) {
|
||||
console.log(`${colors.green}✓${colors.reset} ${testName}`);
|
||||
passed++;
|
||||
} else {
|
||||
console.log(`${colors.red}✗${colors.reset} ${testName}`);
|
||||
if (errorMessage) {
|
||||
console.log(` ${colors.dim}${errorMessage}${colors.reset}`);
|
||||
}
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// These regexes are extracted from ModuleManager.vendorWorkflowDependencies()
|
||||
// in tools/cli/installers/lib/modules/manager.js
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Source regex (line ~1081) — uses non-capturing group for _bmad
|
||||
const SOURCE_REGEX = /\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/;
|
||||
|
||||
// Install regex (line ~1091) — uses non-capturing group for _bmad,
|
||||
// consistent with source regex
|
||||
const INSTALL_REGEX = /\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test data
|
||||
// ---------------------------------------------------------------------------
|
||||
const sourcePath = '{project-root}/_bmad/bmm/workflows/4-implementation/create-story/workflow.md';
|
||||
const installPath = '{project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.md';
|
||||
|
||||
console.log(`\n${colors.cyan}Workflow Path Regex Tests${colors.reset}\n`);
|
||||
|
||||
// --- Source regex tests (these should pass — source regex is correct) ---
|
||||
|
||||
const sourceMatch = sourcePath.match(SOURCE_REGEX);
|
||||
|
||||
assert(sourceMatch !== null, 'Source regex matches source path');
|
||||
assert(
|
||||
sourceMatch && sourceMatch[1] === 'bmm',
|
||||
'Source regex group [1] is the module name',
|
||||
`Expected "bmm", got "${sourceMatch && sourceMatch[1]}"`,
|
||||
);
|
||||
assert(
|
||||
sourceMatch && sourceMatch[2] === '4-implementation/create-story/workflow.md',
|
||||
'Source regex group [2] is the workflow sub-path',
|
||||
`Expected "4-implementation/create-story/workflow.md", got "${sourceMatch && sourceMatch[2]}"`,
|
||||
);
|
||||
|
||||
// --- Install regex tests (group [2] returns module name, not sub-path) ---
|
||||
|
||||
const installMatch = installPath.match(INSTALL_REGEX);
|
||||
|
||||
assert(installMatch !== null, 'Install regex matches install path');
|
||||
|
||||
// This is the critical test: installMatch[2] should be the workflow sub-path,
|
||||
// because the code uses it as `installWorkflowSubPath`.
|
||||
// With the bug, installMatch[2] is "bmgd" (module name) instead of the sub-path.
|
||||
assert(
|
||||
installMatch && installMatch[2] === '4-production/create-story/workflow.md',
|
||||
'Install regex group [2] is the workflow sub-path (used as installWorkflowSubPath)',
|
||||
`Expected "4-production/create-story/workflow.md", got "${installMatch && installMatch[2]}"`,
|
||||
);
|
||||
|
||||
// --- Summary ---
|
||||
console.log(`\n${passed} passed, ${failed} failed\n`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
|
|
@ -148,7 +148,7 @@ class ManifestGenerator {
|
|||
/**
|
||||
* Recursively walk a module directory tree, collecting skill directories.
|
||||
* A skill directory is one that contains both a bmad-skill-manifest.yaml with
|
||||
* type: skill AND a workflow.md (or workflow.yaml) file.
|
||||
* type: skill AND a workflow.md file.
|
||||
* Populates this.skills[] and this.skillClaimedDirs (Set of absolute paths).
|
||||
*/
|
||||
async collectSkills() {
|
||||
|
|
@ -172,37 +172,23 @@ class ManifestGenerator {
|
|||
// Check this directory for skill manifest + workflow file
|
||||
const manifest = await this.loadSkillManifest(dir);
|
||||
|
||||
// Try both workflow.md and workflow.yaml
|
||||
const workflowFilenames = ['workflow.md', 'workflow.yaml'];
|
||||
for (const workflowFile of workflowFilenames) {
|
||||
const workflowFile = 'workflow.md';
|
||||
const workflowPath = path.join(dir, workflowFile);
|
||||
if (!(await fs.pathExists(workflowPath))) continue;
|
||||
|
||||
if (await fs.pathExists(workflowPath)) {
|
||||
const artifactType = this.getArtifactType(manifest, workflowFile);
|
||||
if (artifactType !== 'skill') continue;
|
||||
|
||||
if (artifactType === 'skill') {
|
||||
// Read and parse the workflow file
|
||||
try {
|
||||
const rawContent = await fs.readFile(workflowPath, 'utf8');
|
||||
const content = rawContent.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
|
||||
|
||||
let workflow;
|
||||
if (workflowFile === 'workflow.yaml') {
|
||||
workflow = yaml.parse(content);
|
||||
} else {
|
||||
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
||||
if (!frontmatterMatch) {
|
||||
if (debug) console.log(`[DEBUG] collectSkills: skipped (no frontmatter): ${workflowPath}`);
|
||||
continue;
|
||||
}
|
||||
workflow = yaml.parse(frontmatterMatch[1]);
|
||||
}
|
||||
if (frontmatterMatch) {
|
||||
const workflow = yaml.parse(frontmatterMatch[1]);
|
||||
|
||||
if (!workflow || !workflow.name || !workflow.description) {
|
||||
if (debug) console.log(`[DEBUG] collectSkills: skipped (missing name/description): ${workflowPath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Build path relative from module root
|
||||
const relativePath = path.relative(modulePath, dir).split(path.sep).join('/');
|
||||
const installPath = relativePath
|
||||
|
|
@ -239,11 +225,15 @@ class ManifestGenerator {
|
|||
if (debug) {
|
||||
console.log(`[DEBUG] collectSkills: claimed skill "${workflow.name}" as ${canonicalId} at ${dir}`);
|
||||
}
|
||||
break; // Successfully claimed — skip remaining workflow filenames
|
||||
}
|
||||
} else {
|
||||
if (debug) console.log(`[DEBUG] collectSkills: skipped (no frontmatter): ${workflowPath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (debug) console.log(`[DEBUG] collectSkills: failed to parse ${workflowPath}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Warn if manifest says type:skill but no workflow file found
|
||||
if (manifest && !this.skillClaimedDirs.has(dir)) {
|
||||
|
|
@ -260,11 +250,11 @@ class ManifestGenerator {
|
|||
}
|
||||
}
|
||||
if (hasSkillType && debug) {
|
||||
const hasWorkflow = workflowFilenames.some((f) => entries.some((e) => e.name === f));
|
||||
const hasWorkflow = entries.some((e) => e.name === workflowFile);
|
||||
if (hasWorkflow) {
|
||||
console.log(`[DEBUG] collectSkills: dir has type:skill manifest but workflow file failed to parse: ${dir}`);
|
||||
} else {
|
||||
console.log(`[DEBUG] collectSkills: dir has type:skill manifest but no workflow.md/workflow.yaml: ${dir}`);
|
||||
console.log(`[DEBUG] collectSkills: dir has type:skill manifest but no workflow.md: ${dir}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -308,7 +298,7 @@ class ManifestGenerator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Recursively find and parse workflow.yaml and workflow.md files
|
||||
* Recursively find and parse workflow.md files
|
||||
*/
|
||||
async getWorkflowsFromPath(basePath, moduleName, subDir = 'workflows') {
|
||||
const workflows = [];
|
||||
|
|
@ -326,7 +316,7 @@ class ManifestGenerator {
|
|||
return workflows;
|
||||
}
|
||||
|
||||
// Recursively find workflow.yaml files
|
||||
// Recursively find workflow.md files
|
||||
const findWorkflows = async (dir, relativePath = '') => {
|
||||
// Skip directories already claimed as skills
|
||||
if (this.skillClaimedDirs && this.skillClaimedDirs.has(dir)) return;
|
||||
|
|
@ -344,11 +334,7 @@ class ManifestGenerator {
|
|||
// Recurse into subdirectories
|
||||
const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
||||
await findWorkflows(fullPath, newRelativePath);
|
||||
} else if (
|
||||
entry.name === 'workflow.yaml' ||
|
||||
entry.name === 'workflow.md' ||
|
||||
(entry.name.startsWith('workflow-') && entry.name.endsWith('.md'))
|
||||
) {
|
||||
} else if (entry.name === 'workflow.md' || (entry.name.startsWith('workflow-') && entry.name.endsWith('.md'))) {
|
||||
// Parse workflow file (both YAML and MD formats)
|
||||
if (debug) {
|
||||
console.log(`[DEBUG] Found workflow file: ${fullPath}`);
|
||||
|
|
@ -358,11 +344,6 @@ class ManifestGenerator {
|
|||
const rawContent = await fs.readFile(fullPath, 'utf8');
|
||||
const content = rawContent.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
|
||||
|
||||
let workflow;
|
||||
if (entry.name === 'workflow.yaml') {
|
||||
// Parse YAML workflow
|
||||
workflow = yaml.parse(content);
|
||||
} else {
|
||||
// Parse MD workflow with YAML frontmatter
|
||||
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
||||
if (!frontmatterMatch) {
|
||||
|
|
@ -371,8 +352,7 @@ class ManifestGenerator {
|
|||
}
|
||||
continue; // Skip MD files without frontmatter
|
||||
}
|
||||
workflow = yaml.parse(frontmatterMatch[1]);
|
||||
}
|
||||
const workflow = yaml.parse(frontmatterMatch[1]);
|
||||
|
||||
if (debug) {
|
||||
console.log(`[DEBUG] Parsed: name="${workflow.name}", description=${workflow.description ? 'OK' : 'MISSING'}`);
|
||||
|
|
@ -1343,7 +1323,7 @@ class ManifestGenerator {
|
|||
// Check for manifest in this directory
|
||||
const manifest = await this.loadSkillManifest(dir);
|
||||
if (manifest) {
|
||||
const type = this.getArtifactType(manifest, 'workflow.md') || this.getArtifactType(manifest, 'workflow.yaml');
|
||||
const type = this.getArtifactType(manifest, 'workflow.md');
|
||||
if (type === 'skill') return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ class BaseIdeSetup {
|
|||
// Get core workflows
|
||||
const coreWorkflowsPath = path.join(bmadDir, 'core', 'workflows');
|
||||
if (await fs.pathExists(coreWorkflowsPath)) {
|
||||
const coreWorkflows = await this.findWorkflowYamlFiles(coreWorkflowsPath);
|
||||
const coreWorkflows = await this.findWorkflowFiles(coreWorkflowsPath);
|
||||
workflows.push(
|
||||
...coreWorkflows.map((w) => ({
|
||||
...w,
|
||||
|
|
@ -304,7 +304,7 @@ class BaseIdeSetup {
|
|||
if (entry.isDirectory() && entry.name !== 'core' && entry.name !== '_config' && entry.name !== 'agents') {
|
||||
const moduleWorkflowsPath = path.join(bmadDir, entry.name, 'workflows');
|
||||
if (await fs.pathExists(moduleWorkflowsPath)) {
|
||||
const moduleWorkflows = await this.findWorkflowYamlFiles(moduleWorkflowsPath);
|
||||
const moduleWorkflows = await this.findWorkflowFiles(moduleWorkflowsPath);
|
||||
workflows.push(
|
||||
...moduleWorkflows.map((w) => ({
|
||||
...w,
|
||||
|
|
@ -324,11 +324,13 @@ class BaseIdeSetup {
|
|||
}
|
||||
|
||||
/**
|
||||
* Recursively find workflow.yaml files
|
||||
* Recursively find workflow.md files
|
||||
* @param {string} dir - Directory to search
|
||||
* @param {string} [rootDir] - Original root directory (used internally for recursion)
|
||||
* @returns {Array} List of workflow file info objects
|
||||
*/
|
||||
async findWorkflowYamlFiles(dir) {
|
||||
async findWorkflowFiles(dir, rootDir = null) {
|
||||
rootDir = rootDir || dir;
|
||||
const workflows = [];
|
||||
|
||||
if (!(await fs.pathExists(dir))) {
|
||||
|
|
@ -342,14 +344,17 @@ class BaseIdeSetup {
|
|||
|
||||
if (entry.isDirectory()) {
|
||||
// Recursively search subdirectories
|
||||
const subWorkflows = await this.findWorkflowYamlFiles(fullPath);
|
||||
const subWorkflows = await this.findWorkflowFiles(fullPath, rootDir);
|
||||
workflows.push(...subWorkflows);
|
||||
} else if (entry.isFile() && entry.name === 'workflow.yaml') {
|
||||
// Read workflow.yaml to get name and standalone property
|
||||
} else if (entry.isFile() && entry.name === 'workflow.md') {
|
||||
// Read workflow.md frontmatter to get name and standalone property
|
||||
try {
|
||||
const yaml = require('yaml');
|
||||
const content = await fs.readFile(fullPath, 'utf8');
|
||||
const workflowData = yaml.parse(content);
|
||||
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||
if (!frontmatterMatch) continue;
|
||||
|
||||
const workflowData = yaml.parse(frontmatterMatch[1]);
|
||||
|
||||
if (workflowData && workflowData.name) {
|
||||
// Workflows are standalone by default unless explicitly false
|
||||
|
|
@ -357,7 +362,7 @@ class BaseIdeSetup {
|
|||
workflows.push({
|
||||
name: workflowData.name,
|
||||
path: fullPath,
|
||||
relativePath: path.relative(dir, fullPath),
|
||||
relativePath: path.relative(rootDir, fullPath),
|
||||
filename: entry.name,
|
||||
description: workflowData.description || '',
|
||||
standalone: standalone,
|
||||
|
|
@ -376,9 +381,11 @@ class BaseIdeSetup {
|
|||
* Scan a directory for files with specific extension(s)
|
||||
* @param {string} dir - Directory to scan
|
||||
* @param {string|Array<string>} ext - File extension(s) to match (e.g., '.md' or ['.md', '.xml'])
|
||||
* @param {string} [rootDir] - Original root directory (used internally for recursion)
|
||||
* @returns {Array} List of file info objects
|
||||
*/
|
||||
async scanDirectory(dir, ext) {
|
||||
async scanDirectory(dir, ext, rootDir = null) {
|
||||
rootDir = rootDir || dir;
|
||||
const files = [];
|
||||
|
||||
if (!(await fs.pathExists(dir))) {
|
||||
|
|
@ -395,7 +402,7 @@ class BaseIdeSetup {
|
|||
|
||||
if (entry.isDirectory()) {
|
||||
// Recursively scan subdirectories
|
||||
const subFiles = await this.scanDirectory(fullPath, ext);
|
||||
const subFiles = await this.scanDirectory(fullPath, ext, rootDir);
|
||||
files.push(...subFiles);
|
||||
} else if (entry.isFile()) {
|
||||
// Check if file matches any of the extensions
|
||||
|
|
@ -404,7 +411,7 @@ class BaseIdeSetup {
|
|||
files.push({
|
||||
name: path.basename(entry.name, matchedExt),
|
||||
path: fullPath,
|
||||
relativePath: path.relative(dir, fullPath),
|
||||
relativePath: path.relative(rootDir, fullPath),
|
||||
filename: entry.name,
|
||||
});
|
||||
}
|
||||
|
|
@ -418,9 +425,11 @@ class BaseIdeSetup {
|
|||
* Scan a directory for files with specific extension(s) and check standalone attribute
|
||||
* @param {string} dir - Directory to scan
|
||||
* @param {string|Array<string>} ext - File extension(s) to match (e.g., '.md' or ['.md', '.xml'])
|
||||
* @param {string} [rootDir] - Original root directory (used internally for recursion)
|
||||
* @returns {Array} List of file info objects with standalone property
|
||||
*/
|
||||
async scanDirectoryWithStandalone(dir, ext) {
|
||||
async scanDirectoryWithStandalone(dir, ext, rootDir = null) {
|
||||
rootDir = rootDir || dir;
|
||||
const files = [];
|
||||
|
||||
if (!(await fs.pathExists(dir))) {
|
||||
|
|
@ -437,7 +446,7 @@ class BaseIdeSetup {
|
|||
|
||||
if (entry.isDirectory()) {
|
||||
// Recursively scan subdirectories
|
||||
const subFiles = await this.scanDirectoryWithStandalone(fullPath, ext);
|
||||
const subFiles = await this.scanDirectoryWithStandalone(fullPath, ext, rootDir);
|
||||
files.push(...subFiles);
|
||||
} else if (entry.isFile()) {
|
||||
// Check if file matches any of the extensions
|
||||
|
|
@ -481,7 +490,7 @@ class BaseIdeSetup {
|
|||
files.push({
|
||||
name: path.basename(entry.name, matchedExt),
|
||||
path: fullPath,
|
||||
relativePath: path.relative(dir, fullPath),
|
||||
relativePath: path.relative(rootDir, fullPath),
|
||||
filename: entry.name,
|
||||
standalone: standalone,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -232,16 +232,8 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|||
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
// Use different template based on workflow type (YAML vs MD)
|
||||
// Default to 'default' template type, but allow override via config
|
||||
const workflowTemplateType = artifact.isYamlWorkflow
|
||||
? config.yaml_workflow_template || `${templateType}-workflow-yaml`
|
||||
: config.md_workflow_template || `${templateType}-workflow`;
|
||||
|
||||
// Fall back to default templates if specific ones don't exist
|
||||
const finalTemplateType = artifact.isYamlWorkflow ? 'default-workflow-yaml' : 'default-workflow';
|
||||
// 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 workflowTemplateType = config.md_workflow_template || `${templateType}-workflow`;
|
||||
const { content: template, extension } = await this.loadTemplate(workflowTemplateType, '', config, 'default-workflow');
|
||||
const content = this.renderTemplate(template, artifact);
|
||||
const filename = this.generateFilename(artifact, 'workflow', extension);
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ function toDashPath(relativePath) {
|
|||
}
|
||||
|
||||
// Strip common file extensions to avoid double extensions in generated filenames
|
||||
// e.g., 'create-story.xml' → 'create-story', 'workflow.yaml' → 'workflow'
|
||||
// e.g., 'create-story.xml' → 'create-story', 'workflow.md' → 'workflow'
|
||||
const withoutExt = relativePath.replace(/\.(md|yaml|yml|json|xml|toml)$/i, '');
|
||||
const parts = withoutExt.split(/[/\\]/);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,58 +1,16 @@
|
|||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const csv = require('csv-parse/sync');
|
||||
const prompts = require('../../../../lib/prompts');
|
||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
|
||||
const { BMAD_FOLDER_NAME } = require('./path-utils');
|
||||
|
||||
/**
|
||||
* Generates command files for each workflow in the manifest
|
||||
*/
|
||||
class WorkflowCommandGenerator {
|
||||
constructor(bmadFolderName = BMAD_FOLDER_NAME) {
|
||||
this.templatePath = path.join(__dirname, '../templates/workflow-command-template.md');
|
||||
this.bmadFolderName = bmadFolderName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate workflow commands from the manifest CSV
|
||||
* @param {string} projectDir - Project directory
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
*/
|
||||
async generateWorkflowCommands(projectDir, bmadDir) {
|
||||
const workflows = await this.loadWorkflowManifest(bmadDir);
|
||||
|
||||
if (!workflows) {
|
||||
await prompts.log.warn('Workflow manifest not found. Skipping command generation.');
|
||||
return { generated: 0 };
|
||||
}
|
||||
|
||||
// ALL workflows now generate commands - no standalone filtering
|
||||
const allWorkflows = workflows;
|
||||
|
||||
// Base commands directory
|
||||
const baseCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
|
||||
|
||||
let generatedCount = 0;
|
||||
|
||||
// Generate a command file for each workflow, organized by module
|
||||
for (const workflow of allWorkflows) {
|
||||
const moduleWorkflowsDir = path.join(baseCommandsDir, workflow.module, 'workflows');
|
||||
await fs.ensureDir(moduleWorkflowsDir);
|
||||
|
||||
const commandContent = await this.generateCommandContent(workflow, bmadDir);
|
||||
const commandPath = path.join(moduleWorkflowsDir, `${workflow.name}.md`);
|
||||
|
||||
await fs.writeFile(commandPath, commandContent);
|
||||
generatedCount++;
|
||||
}
|
||||
|
||||
// Also create a workflow launcher README in each module
|
||||
const groupedWorkflows = this.groupWorkflowsByModule(allWorkflows);
|
||||
await this.createModuleWorkflowLaunchers(baseCommandsDir, groupedWorkflows);
|
||||
|
||||
return { generated: generatedCount };
|
||||
}
|
||||
|
||||
async collectWorkflowArtifacts(bmadDir) {
|
||||
const workflows = await this.loadWorkflowManifest(bmadDir);
|
||||
|
||||
|
|
@ -66,8 +24,7 @@ class WorkflowCommandGenerator {
|
|||
const artifacts = [];
|
||||
|
||||
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)
|
||||
// Calculate the relative workflow path (e.g., bmm/workflows/4-implementation/sprint-planning/workflow.md)
|
||||
let workflowRelPath = workflow.path || '';
|
||||
// Normalize path separators for cross-platform compatibility
|
||||
workflowRelPath = workflowRelPath.replaceAll('\\', '/');
|
||||
|
|
@ -85,18 +42,14 @@ class WorkflowCommandGenerator {
|
|||
workflowRelPath = `${match[1]}/${match[2]}`;
|
||||
}
|
||||
}
|
||||
// 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
|
||||
name: workflow.name,
|
||||
description: workflow.description || `${workflow.name} workflow`,
|
||||
module: workflow.module,
|
||||
canonicalId: workflow.canonicalId || '',
|
||||
relativePath: path.join(workflow.module, 'workflows', `${workflow.name}.md`),
|
||||
workflowPath: workflowRelPath, // Relative path to actual workflow file
|
||||
content: commandContent,
|
||||
sourcePath: workflow.path,
|
||||
});
|
||||
}
|
||||
|
|
@ -121,46 +74,6 @@ class WorkflowCommandGenerator {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate command content for a workflow
|
||||
*/
|
||||
async generateCommandContent(workflow, bmadDir) {
|
||||
// Determine template based on workflow file type
|
||||
const isMarkdownWorkflow = workflow.path.endsWith('workflow.md');
|
||||
const templateName = isMarkdownWorkflow ? 'workflow-commander.md' : 'workflow-command-template.md';
|
||||
const templatePath = path.join(path.dirname(this.templatePath), templateName);
|
||||
|
||||
// Load the appropriate template
|
||||
const template = await fs.readFile(templatePath, 'utf8');
|
||||
|
||||
// Convert source path to installed path
|
||||
// From: /Users/.../src/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('_bmad', this.bmadFolderName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workflow launcher files for each module
|
||||
*/
|
||||
|
|
@ -218,10 +131,9 @@ class WorkflowCommandGenerator {
|
|||
## Execution
|
||||
|
||||
When running any workflow:
|
||||
1. LOAD {project-root}/${this.bmadFolderName}/core/tasks/workflow.xml
|
||||
2. Pass the workflow path as 'workflow-config' parameter
|
||||
3. Follow workflow.xml instructions EXACTLY
|
||||
4. Save outputs after EACH section
|
||||
1. LOAD the workflow.md file at the path shown above
|
||||
2. READ its entire contents and follow its directions exactly
|
||||
3. Save outputs after EACH section
|
||||
|
||||
## Modes
|
||||
- Normal: Full interaction
|
||||
|
|
@ -262,58 +174,6 @@ When running any workflow:
|
|||
skip_empty_lines: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Write workflow command artifacts using underscore format (Windows-compatible)
|
||||
* Creates flat files like: bmad_bmm_correct-course.md
|
||||
*
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @param {Array} artifacts - Workflow artifacts
|
||||
* @returns {number} Count of commands written
|
||||
*/
|
||||
async writeColonArtifacts(baseCommandsDir, artifacts) {
|
||||
let writtenCount = 0;
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
// Convert relativePath to underscore format: bmm/workflows/correct-course.md → bmad_bmm_correct-course.md
|
||||
const flatName = toColonPath(artifact.relativePath);
|
||||
const commandPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(commandPath));
|
||||
await fs.writeFile(commandPath, artifact.content);
|
||||
writtenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return writtenCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write workflow command artifacts using dash format (NEW STANDARD)
|
||||
* Creates flat files like: bmad-bmm-correct-course.md
|
||||
*
|
||||
* Note: Workflows do NOT have bmad-agent- prefix - only agents do.
|
||||
*
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @param {Array} artifacts - Workflow artifacts
|
||||
* @returns {number} Count of commands written
|
||||
*/
|
||||
async writeDashArtifacts(baseCommandsDir, artifacts) {
|
||||
let writtenCount = 0;
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
// Convert relativePath to dash format: bmm/workflows/correct-course.md → bmad-bmm-correct-course.md
|
||||
const flatName = toDashPath(artifact.relativePath);
|
||||
const commandPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(commandPath));
|
||||
await fs.writeFile(commandPath, artifact.content);
|
||||
writtenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return writtenCount;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { WorkflowCommandGenerator };
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
---
|
||||
name: '{{name}}'
|
||||
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
|
||||
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>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
---
|
||||
inclusion: manual
|
||||
---
|
||||
|
||||
# {{name}}
|
||||
|
||||
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 #[[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
|
||||
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>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
---
|
||||
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}/_bmad/core/tasks/workflow.xml
|
||||
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config {project-root}/{{workflow_path}}
|
||||
3. Pass the yaml path {{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>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
description: '{{description}}'
|
||||
---
|
||||
|
||||
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL {project-root}/{{workflow_path}}, READ its entire contents and follow its directions exactly!
|
||||
|
|
@ -762,14 +762,8 @@ class ModuleManager {
|
|||
}
|
||||
}
|
||||
|
||||
// Check if this is a workflow.yaml file
|
||||
if (file.endsWith('workflow.yaml')) {
|
||||
await fs.ensureDir(path.dirname(targetFile));
|
||||
await this.copyWorkflowYamlStripped(sourceFile, targetFile);
|
||||
} else {
|
||||
// Copy the file with placeholder replacement
|
||||
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile);
|
||||
}
|
||||
|
||||
// Track the file if callback provided
|
||||
if (fileTrackingCallback) {
|
||||
|
|
@ -778,92 +772,6 @@ class ModuleManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy workflow.yaml file with web_bundle section stripped
|
||||
* Preserves comments, formatting, and line breaks
|
||||
* @param {string} sourceFile - Source workflow.yaml file path
|
||||
* @param {string} targetFile - Target workflow.yaml file path
|
||||
*/
|
||||
async copyWorkflowYamlStripped(sourceFile, targetFile) {
|
||||
// Read the source YAML file
|
||||
let yamlContent = await fs.readFile(sourceFile, 'utf8');
|
||||
|
||||
// IMPORTANT: Replace escape sequence and placeholder BEFORE parsing YAML
|
||||
// Otherwise parsing will fail on the placeholder
|
||||
yamlContent = yamlContent.replaceAll('_bmad', this.bmadFolderName);
|
||||
|
||||
try {
|
||||
// First check if web_bundle exists by parsing
|
||||
const workflowConfig = yaml.parse(yamlContent);
|
||||
|
||||
if (workflowConfig.web_bundle === undefined) {
|
||||
// No web_bundle section, just write (placeholders already replaced above)
|
||||
await fs.writeFile(targetFile, yamlContent, 'utf8');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the line that starts web_bundle
|
||||
const lines = yamlContent.split('\n');
|
||||
let startIdx = -1;
|
||||
let endIdx = -1;
|
||||
let baseIndent = 0;
|
||||
|
||||
// Find the start of web_bundle section
|
||||
for (const [i, line] of lines.entries()) {
|
||||
const match = line.match(/^(\s*)web_bundle:/);
|
||||
if (match) {
|
||||
startIdx = i;
|
||||
baseIndent = match[1].length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startIdx === -1) {
|
||||
// web_bundle not found in text (shouldn't happen), copy as-is
|
||||
await fs.writeFile(targetFile, yamlContent, 'utf8');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the end of web_bundle section
|
||||
// It ends when we find a line with same or less indentation that's not empty/comment
|
||||
endIdx = startIdx;
|
||||
for (let i = startIdx + 1; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (line.trim() === '' || line.trim().startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check indentation
|
||||
const indent = line.match(/^(\s*)/)[1].length;
|
||||
if (indent <= baseIndent) {
|
||||
// Found next section at same or lower indentation
|
||||
endIdx = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find an end, it goes to end of file
|
||||
if (endIdx === startIdx) {
|
||||
endIdx = lines.length - 1;
|
||||
}
|
||||
|
||||
// Remove the web_bundle section (including the line before if it's just a blank line)
|
||||
const newLines = [...lines.slice(0, startIdx), ...lines.slice(endIdx + 1)];
|
||||
|
||||
// Clean up any double blank lines that might result
|
||||
const strippedYaml = newLines.join('\n').replaceAll(/\n\n\n+/g, '\n\n');
|
||||
|
||||
// Placeholders already replaced at the beginning of this function
|
||||
await fs.writeFile(targetFile, strippedYaml, 'utf8');
|
||||
} catch {
|
||||
// If anything fails, just copy the file as-is
|
||||
await prompts.log.warn(` Could not process ${path.basename(sourceFile)}, copying as-is`);
|
||||
await fs.copy(sourceFile, targetFile, { overwrite: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile .agent.yaml files to .md format in modules
|
||||
* @param {string} sourcePath - Source module path
|
||||
|
|
@ -1165,13 +1073,11 @@ class ModuleManager {
|
|||
await prompts.log.message(` Processing: ${agentFile}`);
|
||||
|
||||
for (const item of workflowInstallItems) {
|
||||
const sourceWorkflowPath = item.workflow; // Where to copy FROM
|
||||
const sourceWorkflowPath = item.exec; // Where to copy FROM
|
||||
const installWorkflowPath = item['workflow-install']; // Where to copy TO
|
||||
|
||||
// Parse SOURCE workflow path
|
||||
// Handle both _bmad placeholder and hardcoded 'bmad'
|
||||
// Example: {project-root}/_bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
|
||||
// Or: {project-root}/bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
|
||||
// Example: {project-root}/_bmad/bmm/workflows/4-implementation/create-story/workflow.md
|
||||
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/);
|
||||
if (!sourceMatch) {
|
||||
await prompts.log.warn(` Could not parse workflow path: ${sourceWorkflowPath}`);
|
||||
|
|
@ -1181,9 +1087,8 @@ class ModuleManager {
|
|||
const [, sourceModule, sourceWorkflowSubPath] = sourceMatch;
|
||||
|
||||
// Parse INSTALL workflow path
|
||||
// Handle_bmad
|
||||
// Example: {project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.yaml
|
||||
const installMatch = installWorkflowPath.match(/\{project-root\}\/(_bmad)\/([^/]+)\/workflows\/(.+)/);
|
||||
// Example: {project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.md
|
||||
const installMatch = installWorkflowPath.match(/\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/);
|
||||
if (!installMatch) {
|
||||
await prompts.log.warn(` Could not parse workflow-install path: ${installWorkflowPath}`);
|
||||
continue;
|
||||
|
|
@ -1192,9 +1097,9 @@ class ModuleManager {
|
|||
const installWorkflowSubPath = installMatch[2];
|
||||
|
||||
const sourceModulePath = getModulePath(sourceModule);
|
||||
const actualSourceWorkflowPath = path.join(sourceModulePath, 'workflows', sourceWorkflowSubPath.replace(/\/workflow\.yaml$/, ''));
|
||||
const actualSourceWorkflowPath = path.join(sourceModulePath, 'workflows', sourceWorkflowSubPath.replace(/\/workflow\.md$/, ''));
|
||||
|
||||
const actualDestWorkflowPath = path.join(targetPath, 'workflows', installWorkflowSubPath.replace(/\/workflow\.yaml$/, ''));
|
||||
const actualDestWorkflowPath = path.join(targetPath, 'workflows', installWorkflowSubPath.replace(/\/workflow\.md$/, ''));
|
||||
|
||||
// Check if source workflow exists
|
||||
if (!(await fs.pathExists(actualSourceWorkflowPath))) {
|
||||
|
|
@ -1204,18 +1109,12 @@ class ModuleManager {
|
|||
|
||||
// Copy the entire workflow folder
|
||||
await prompts.log.message(
|
||||
` Vendoring: ${sourceModule}/workflows/${sourceWorkflowSubPath.replace(/\/workflow\.yaml$/, '')} → ${moduleName}/workflows/${installWorkflowSubPath.replace(/\/workflow\.yaml$/, '')}`,
|
||||
` Vendoring: ${sourceModule}/workflows/${sourceWorkflowSubPath.replace(/\/workflow\.md$/, '')} → ${moduleName}/workflows/${installWorkflowSubPath.replace(/\/workflow\.md$/, '')}`,
|
||||
);
|
||||
|
||||
await fs.ensureDir(path.dirname(actualDestWorkflowPath));
|
||||
// Copy the workflow directory recursively with placeholder replacement
|
||||
await this.copyDirectoryWithPlaceholderReplacement(actualSourceWorkflowPath, actualDestWorkflowPath);
|
||||
|
||||
// Update the workflow.yaml config_source reference
|
||||
const workflowYamlPath = path.join(actualDestWorkflowPath, 'workflow.yaml');
|
||||
if (await fs.pathExists(workflowYamlPath)) {
|
||||
await this.updateWorkflowConfigSource(workflowYamlPath, moduleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1224,28 +1123,6 @@ class ModuleManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update workflow.yaml config_source to point to new module
|
||||
* @param {string} workflowYamlPath - Path to workflow.yaml file
|
||||
* @param {string} newModuleName - New module name to reference
|
||||
*/
|
||||
async updateWorkflowConfigSource(workflowYamlPath, newModuleName) {
|
||||
let yamlContent = await fs.readFile(workflowYamlPath, 'utf8');
|
||||
|
||||
// Replace config_source: "{project-root}/_bmad/OLD_MODULE/config.yaml"
|
||||
// with config_source: "{project-root}/_bmad/NEW_MODULE/config.yaml"
|
||||
// Note: At this point _bmad has already been replaced with actual folder name
|
||||
const configSourcePattern = /config_source:\s*["']?\{project-root\}\/[^/]+\/[^/]+\/config\.yaml["']?/g;
|
||||
const newConfigSource = `config_source: "{project-root}/${this.bmadFolderName}/${newModuleName}/config.yaml"`;
|
||||
|
||||
const updatedYaml = yamlContent.replaceAll(configSourcePattern, newConfigSource);
|
||||
|
||||
if (updatedYaml !== yamlContent) {
|
||||
await fs.writeFile(workflowYamlPath, updatedYaml, 'utf8');
|
||||
await prompts.log.message(` Updated config_source to: ${this.bmadFolderName}/${newModuleName}/config.yaml`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create directories declared in module.yaml's `directories` key
|
||||
* This replaces the security-risky module installer pattern with declarative config
|
||||
|
|
|
|||
|
|
@ -39,16 +39,10 @@ class AgentAnalyzer {
|
|||
if (Array.isArray(execArray)) {
|
||||
for (const exec of execArray) {
|
||||
if (exec.route) {
|
||||
// Check if route is a workflow or exec
|
||||
if (exec.route.endsWith('.yaml') || exec.route.endsWith('.yml')) {
|
||||
profile.usedAttributes.add('workflow');
|
||||
} else {
|
||||
profile.usedAttributes.add('exec');
|
||||
}
|
||||
}
|
||||
if (exec.workflow) profile.usedAttributes.add('workflow');
|
||||
if (exec.action) profile.usedAttributes.add('action');
|
||||
if (exec.type && ['exec', 'action', 'workflow'].includes(exec.type)) {
|
||||
if (exec.type && ['exec', 'action'].includes(exec.type)) {
|
||||
profile.usedAttributes.add(exec.type);
|
||||
}
|
||||
}
|
||||
|
|
@ -57,12 +51,6 @@ class AgentAnalyzer {
|
|||
}
|
||||
} else {
|
||||
// Check for each possible attribute in legacy items
|
||||
if (item.workflow) {
|
||||
profile.usedAttributes.add('workflow');
|
||||
}
|
||||
if (item['validate-workflow']) {
|
||||
profile.usedAttributes.add('validate-workflow');
|
||||
}
|
||||
if (item.exec) {
|
||||
profile.usedAttributes.add('exec');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,6 @@ function buildMenuXml(menuItems) {
|
|||
const attrs = [`cmd="${trigger}"`];
|
||||
|
||||
// Add handler attributes
|
||||
if (item.workflow) attrs.push(`workflow="${item.workflow}"`);
|
||||
if (item.exec) attrs.push(`exec="${item.exec}"`);
|
||||
if (item.tmpl) attrs.push(`tmpl="${item.tmpl}"`);
|
||||
if (item.data) attrs.push(`data="${item.data}"`);
|
||||
|
|
@ -187,8 +186,6 @@ function buildNestedHandlers(triggers) {
|
|||
|
||||
// Add handler attributes based on exec data
|
||||
if (execData.route) attrs.push(`exec="${execData.route}"`);
|
||||
if (execData.workflow) attrs.push(`workflow="${execData.workflow}"`);
|
||||
if (execData['validate-workflow']) attrs.push(`validate-workflow="${execData['validate-workflow']}"`);
|
||||
if (execData.action) attrs.push(`action="${execData.action}"`);
|
||||
if (execData.data) attrs.push(`data="${execData.data}"`);
|
||||
if (execData.tmpl) attrs.push(`tmpl="${execData.tmpl}"`);
|
||||
|
|
@ -212,7 +209,6 @@ function processExecArray(execArray) {
|
|||
const result = {
|
||||
description: '',
|
||||
route: null,
|
||||
workflow: null,
|
||||
data: null,
|
||||
action: null,
|
||||
type: null,
|
||||
|
|
@ -229,13 +225,8 @@ function processExecArray(execArray) {
|
|||
}
|
||||
|
||||
if (exec.route) {
|
||||
// Determine if it's a workflow or exec based on file extension or context
|
||||
if (exec.route.endsWith('.yaml') || exec.route.endsWith('.yml')) {
|
||||
result.workflow = exec.route;
|
||||
} else {
|
||||
result.route = exec.route;
|
||||
}
|
||||
}
|
||||
|
||||
if (exec.data !== null && exec.data !== undefined) {
|
||||
result.data = exec.data;
|
||||
|
|
|
|||
|
|
@ -367,15 +367,6 @@ class YamlXmlBuilder {
|
|||
const attrs = [`cmd="${trigger}"`];
|
||||
|
||||
// Add handler attributes
|
||||
// If workflow-install exists, use its value for workflow attribute (vendoring)
|
||||
// workflow-install is build-time metadata - tells installer where to copy workflows
|
||||
// The final XML should only have workflow pointing to the install location
|
||||
if (item['workflow-install']) {
|
||||
attrs.push(`workflow="${item['workflow-install']}"`);
|
||||
} else if (item.workflow) {
|
||||
attrs.push(`workflow="${item.workflow}"`);
|
||||
}
|
||||
|
||||
if (item['validate-workflow']) attrs.push(`validate-workflow="${item['validate-workflow']}"`);
|
||||
if (item.exec) attrs.push(`exec="${item.exec}"`);
|
||||
if (item.tmpl) attrs.push(`tmpl="${item.tmpl}"`);
|
||||
|
|
@ -417,8 +408,6 @@ class YamlXmlBuilder {
|
|||
|
||||
// Add handler attributes based on exec data
|
||||
if (execData.route) attrs.push(`exec="${execData.route}"`);
|
||||
if (execData.workflow) attrs.push(`workflow="${execData.workflow}"`);
|
||||
if (execData['validate-workflow']) attrs.push(`validate-workflow="${execData['validate-workflow']}"`);
|
||||
if (execData.action) attrs.push(`action="${execData.action}"`);
|
||||
if (execData.data) attrs.push(`data="${execData.data}"`);
|
||||
if (execData.tmpl) attrs.push(`tmpl="${execData.tmpl}"`);
|
||||
|
|
@ -442,7 +431,6 @@ class YamlXmlBuilder {
|
|||
const result = {
|
||||
description: '',
|
||||
route: null,
|
||||
workflow: null,
|
||||
data: null,
|
||||
action: null,
|
||||
type: null,
|
||||
|
|
@ -459,13 +447,8 @@ class YamlXmlBuilder {
|
|||
}
|
||||
|
||||
if (exec.route) {
|
||||
// Determine if it's a workflow or exec based on file extension or context
|
||||
if (exec.route.endsWith('.yaml') || exec.route.endsWith('.yml')) {
|
||||
result.workflow = exec.route;
|
||||
} else {
|
||||
result.route = exec.route;
|
||||
}
|
||||
}
|
||||
|
||||
if (exec.data !== null && exec.data !== undefined) {
|
||||
result.data = exec.data;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
const assert = require('node:assert');
|
||||
const { z } = require('zod');
|
||||
|
||||
const COMMAND_TARGET_KEYS = ['workflow', 'validate-workflow', 'exec', 'action', 'tmpl', 'data'];
|
||||
const COMMAND_TARGET_KEYS = ['validate-workflow', 'exec', 'action', 'tmpl', 'data'];
|
||||
const TRIGGER_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
||||
const COMPOUND_TRIGGER_PATTERN = /^([A-Z]{1,3}) or fuzzy match on ([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
||||
|
||||
|
|
@ -273,8 +273,6 @@ function buildMenuItemSchema() {
|
|||
.object({
|
||||
trigger: createNonEmptyString('agent.menu[].trigger'),
|
||||
description: createNonEmptyString('agent.menu[].description'),
|
||||
workflow: createNonEmptyString('agent.menu[].workflow').optional(),
|
||||
'workflow-install': createNonEmptyString('agent.menu[].workflow-install').optional(),
|
||||
'validate-workflow': createNonEmptyString('agent.menu[].validate-workflow').optional(),
|
||||
exec: createNonEmptyString('agent.menu[].exec').optional(),
|
||||
action: createNonEmptyString('agent.menu[].action').optional(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue