diff --git a/.augment/code_review_guidelines.yaml b/.augment/code_review_guidelines.yaml index 02e4f2b95..d2b33ef4d 100644 --- a/.augment/code_review_guidelines.yaml +++ b/.augment/code_review_guidelines.yaml @@ -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 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 for required actions, for user input (must HALT), for jumps, for conditionals" - severity: "medium" - - - id: "xml_ask_must_halt" - description: " 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" diff --git a/docs/reference/commands.md b/docs/reference/commands.md index c61b236a7..0de99157c 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -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 | | --- | --- | diff --git a/src/bmm/agents/analyst.agent.yaml b/src/bmm/agents/analyst.agent.yaml index 7814ee609..4767dadfa 100644 --- a/src/bmm/agents/analyst.agent.yaml +++ b/src/bmm/agents/analyst.agent.yaml @@ -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" diff --git a/src/bmm/agents/dev.agent.yaml b/src/bmm/agents/dev.agent.yaml index 6de3d2c97..6818dc968 100644 --- a/src/bmm/agents/dev.agent.yaml +++ b/src/bmm/agents/dev.agent.yaml @@ -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" diff --git a/src/bmm/agents/pm.agent.yaml b/src/bmm/agents/pm.agent.yaml index 267797053..bf104246a 100644 --- a/src/bmm/agents/pm.agent.yaml +++ b/src/bmm/agents/pm.agent.yaml @@ -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" diff --git a/src/bmm/agents/qa.agent.yaml b/src/bmm/agents/qa.agent.yaml index 71ff83930..65b0711b2 100644 --- a/src/bmm/agents/qa.agent.yaml +++ b/src/bmm/agents/qa.agent.yaml @@ -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: diff --git a/src/bmm/agents/quick-flow-solo-dev.agent.yaml b/src/bmm/agents/quick-flow-solo-dev.agent.yaml index 72f861e8e..acaab975b 100644 --- a/src/bmm/agents/quick-flow-solo-dev.agent.yaml +++ b/src/bmm/agents/quick-flow-solo-dev.agent.yaml @@ -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" diff --git a/src/bmm/agents/sm.agent.yaml b/src/bmm/agents/sm.agent.yaml index 11716df24..19506fae1 100644 --- a/src/bmm/agents/sm.agent.yaml +++ b/src/bmm/agents/sm.agent.yaml @@ -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" diff --git a/src/bmm/agents/tech-writer/tech-writer.agent.yaml b/src/bmm/agents/tech-writer/tech-writer.agent.yaml index da3fe9c91..25f0aca06 100644 --- a/src/bmm/agents/tech-writer/tech-writer.agent.yaml +++ b/src/bmm/agents/tech-writer/tech-writer.agent.yaml @@ -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 diff --git a/src/bmm/workflows/4-implementation/code-review/discover-inputs.md b/src/bmm/workflows/4-implementation/code-review/discover-inputs.md new file mode 100644 index 000000000..2c313db3d --- /dev/null +++ b/src/bmm/workflows/4-implementation/code-review/discover-inputs.md @@ -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. diff --git a/src/bmm/workflows/4-implementation/code-review/workflow.md b/src/bmm/workflows/4-implementation/code-review/workflow.md index fbfdf4cb2..f4dd8188b 100644 --- a/src/bmm/workflows/4-implementation/code-review/workflow.md +++ b/src/bmm/workflows/4-implementation/code-review/workflow.md @@ -81,7 +81,7 @@ Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: - Missing documentation of what was actually changed - + Read fully and follow `{installed_path}/discover-inputs.md` to load all input files Load {project_context} for coding standards (if exists) diff --git a/src/bmm/workflows/4-implementation/create-story/checklist.md b/src/bmm/workflows/4-implementation/create-story/checklist.md index 7b8550e2d..06ad346ba 100644 --- a/src/bmm/workflows/4-implementation/create-story/checklist.md +++ b/src/bmm/workflows/4-implementation/create-story/checklist.md @@ -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. diff --git a/src/bmm/workflows/4-implementation/create-story/discover-inputs.md b/src/bmm/workflows/4-implementation/create-story/discover-inputs.md new file mode 100644 index 000000000..2c313db3d --- /dev/null +++ b/src/bmm/workflows/4-implementation/create-story/discover-inputs.md @@ -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. diff --git a/src/bmm/workflows/4-implementation/create-story/workflow.md b/src/bmm/workflows/4-implementation/create-story/workflow.md index 55556adb6..bd99a448f 100644 --- a/src/bmm/workflows/4-implementation/create-story/workflow.md +++ b/src/bmm/workflows/4-implementation/create-story/workflow.md @@ -220,8 +220,7 @@ Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: đŸ”Ŧ EXHAUSTIVE ARTIFACT ANALYSIS - This is where you prevent future developer fuckups! - + Read fully and follow `{installed_path}/discover-inputs.md` to load all input files Available content: {epics_content}, {prd_content}, {architecture_content}, {ux_content}, {project_context} diff --git a/src/bmm/workflows/4-implementation/dev-story/instructions.xml b/src/bmm/workflows/4-implementation/dev-story/instructions.xml deleted file mode 100644 index c39c35f2e..000000000 --- a/src/bmm/workflows/4-implementation/dev-story/instructions.xml +++ /dev/null @@ -1,412 +0,0 @@ - - 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 - Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level} - Generate all documents in {document_output_language} - Only modify the story file in these areas: Tasks/Subtasks checkboxes, Dev Agent Record (Debug Log, Completion Notes), File List, - Change Log, and Status - Execute ALL steps in exact order; do NOT skip steps - 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. - Do NOT schedule a "next session" or request review pauses unless a HALT condition applies. Only Step 6 decides completion. - User skill level ({user_skill_level}) affects conversation style ONLY, not code updates. - - - - Use {{story_path}} directly - Read COMPLETE story file - Extract story_key from filename or metadata - - - - - - MUST read COMPLETE sprint-status.yaml file from start to end to preserve order - Load the FULL file: {{sprint_status}} - Read ALL lines from beginning to end - do not skip any content - Parse the development_status section completely to understand story order - - 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" - - - - 📋 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. - - Choose option [1], [2], [3], or [4], or specify story file path: - - - HALT - Run create-story to create next story - - - - HALT - Run validate-create-story to improve existing stories - - - - Provide the story file path to develop: - Store user-provided story path as {{story_path}} - - - - - Loading {{sprint_status}} for detailed status review... - Display detailed sprint status analysis - HALT - User can review sprint status and provide story path - - - - Store user-provided story path as {{story_path}} - - - - - - - - Search {implementation_artifacts} for stories directly - Find stories with "ready-for-dev" status in files - Look for story files matching pattern: *-*-*.md - Read each candidate story file to check Status section - - - 📋 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 - - What would you like to do? Choose option [1], [2], or [3]: - - - HALT - Run create-story to create next story - - - - HALT - Run validate-create-story to improve existing stories - - - - It's unclear what story you want developed. Please provide the full path to the story file: - Store user-provided story path as {{story_path}} - Continue with provided story file - - - - - Use discovered story file and extract story_key - - - - Store the found story_key (e.g., "1-2-user-authentication") for later status updates - Find matching story file in {implementation_artifacts} using story_key pattern: {{story_key}}.md - Read COMPLETE story file from discovered path - - - - Parse sections: Story, Acceptance Criteria, Tasks/Subtasks, Dev Notes, Dev Agent Record, File List, Change Log, Status - - Load comprehensive context from story file's Dev Notes section - Extract developer guidance from Dev Notes: architecture requirements, previous learnings, technical specifications - Use enhanced story context to inform implementation decisions and approaches - - Identify first incomplete task (unchecked [ ]) in Tasks/Subtasks - - - Completion sequence - - HALT: "Cannot develop story without access to story file" - ASK user to clarify or HALT - - - - Load all available context to inform implementation - - Load {project_context} for coding standards and project-wide patterns (if exists) - Parse sections: Story, Acceptance Criteria, Tasks/Subtasks, Dev Notes, Dev Agent Record, File List, Change Log, Status - Load comprehensive context from story file's Dev Notes section - Extract developer guidance from Dev Notes: architecture requirements, previous learnings, technical specifications - Use enhanced story context to inform implementation decisions and approaches - ✅ **Context Loaded** - Story and project context available for implementation - - - - - Determine if this is a fresh start or continuation after code review - - Check if "Senior Developer Review (AI)" section exists in the story file - Check if "Review Follow-ups (AI)" subsection exists under Tasks/Subtasks - - - Set review_continuation = true - 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) - - Count unchecked [ ] review follow-up tasks in "Review Follow-ups (AI)" subsection - Store list of unchecked review items as {{pending_review_items}} - - â¯ī¸ **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. - - - - - Set review_continuation = false - Set {{pending_review_items}} = empty - - 🚀 **Starting Fresh Implementation** - - Story: {{story_key}} - Story Status: {{current_status}} - First incomplete task: {{first_task_description}} - - - - - - - Load the FULL file: {{sprint_status}} - Read all development_status entries to find {{story_key}} - Get current status value for development_status[{{story_key}}] - - - Update the story in the sprint status report to = "in-progress" - Update last_updated field to current date - 🚀 Starting work on story {{story_key}} - Status updated: ready-for-dev → in-progress - - - - - â¯ī¸ Resuming work on story {{story_key}} - Story is already marked in-progress - - - - - âš ī¸ Unexpected story status: {{current_status}} - Expected ready-for-dev or in-progress. Continuing anyway... - - - - Store {{current_sprint_status}} for later use - - - - â„šī¸ No sprint status file exists - story progress will be tracked in story file only - Set {{current_sprint_status}} = "no-sprint-tracking" - - - - - FOLLOW THE STORY FILE TASKS/SUBTASKS SEQUENCE EXACTLY AS WRITTEN - NO DEVIATION - - Review the current task/subtask from the story file - this is your authoritative implementation guide - Plan implementation following red-green-refactor cycle - - - Write FAILING tests first for the task/subtask functionality - Confirm tests fail before implementation - this validates test correctness - - - Implement MINIMAL code to make tests pass - Run tests to confirm they now pass - Handle error conditions and edge cases as specified in task/subtask - - - Improve code structure while keeping tests green - Ensure code follows architecture patterns and coding standards from Dev Notes - - Document technical approach and decisions in Dev Agent Record → Implementation Plan - - HALT: "Additional dependencies need user approval" - HALT and request guidance - HALT: "Cannot proceed without necessary configuration files" - - NEVER implement anything not mapped to a specific task/subtask in the story file - NEVER proceed to next task until current task/subtask is complete AND tests pass - Execute continuously without pausing until all tasks/subtasks are complete or explicit HALT condition - Do NOT propose to pause for review until Step 9 completion gates are satisfied - - - - Create unit tests for business logic and core functionality introduced/changed by the task - Add integration tests for component interactions specified in story requirements - Include end-to-end tests for critical user flows when story requirements demand them - Cover edge cases and error handling scenarios identified in story Dev Notes - - - - Determine how to run tests for this repo (infer test framework from project structure) - Run all existing tests to ensure no regressions - Run the new tests to verify implementation correctness - Run linting and code quality checks if configured in project - Validate implementation meets ALL story acceptance criteria; enforce quantitative thresholds explicitly - STOP and fix before continuing - identify breaking changes immediately - STOP and fix before continuing - ensure implementation correctness - - - - NEVER mark a task complete unless ALL conditions are met - NO LYING OR CHEATING - - - Verify ALL tests for this task/subtask ACTUALLY EXIST and PASS 100% - Confirm implementation matches EXACTLY what the task/subtask specifies - no extra features - Validate that ALL acceptance criteria related to this task are satisfied - Run full test suite to ensure NO regressions introduced - - - - Extract review item details (severity, description, related AC/file) - Add to resolution tracking list: {{resolved_review_items}} - - - Mark task checkbox [x] in "Tasks/Subtasks → Review Follow-ups (AI)" section - - - Find matching action item in "Senior Developer Review (AI) → Action Items" section by matching description - Mark that action item checkbox [x] as resolved - - Add to Dev Agent Record → Completion Notes: "✅ Resolved review finding [{{severity}}]: {{description}}" - - - - - ONLY THEN mark the task (and subtasks) checkbox with [x] - Update File List section with ALL new, modified, or deleted files (paths relative to repo root) - Add completion notes to Dev Agent Record summarizing what was ACTUALLY implemented and tested - - - - DO NOT mark task complete - fix issues first - HALT if unable to fix validation failures - - - - Count total resolved review items in this session - Add Change Log entry: "Addressed code review findings - {{resolved_count}} items resolved (Date: {{date}})" - - - Save the story file - Determine if more incomplete tasks remain - - Next task - - - Completion - - - - - Verify ALL tasks and subtasks are marked [x] (re-scan the story document now) - Run the full regression suite (do not skip) - Confirm File List includes every changed file - Execute enhanced definition-of-done validation - Update the story Status to: "review" - - - 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 - - - - - Load the FULL file: {sprint_status} - Find development_status key matching {{story_key}} - Verify current status is "in-progress" (expected previous state) - Update development_status[{{story_key}}] = "review" - Update last_updated field to current date - Save file, preserving ALL comments and structure including STATUS DEFINITIONS - ✅ Story status updated to "review" in sprint-status.yaml - - - - â„šī¸ Story status updated to "review" in story file (no sprint tracking configured) - - - - âš ī¸ 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. - - - - - HALT - Complete remaining tasks before marking ready for review - HALT - Fix regression issues before completing - HALT - Update File List with all changed files - HALT - Address DoD failures before completing - - - - Execute the enhanced definition-of-done checklist using the validation framework - Prepare a concise summary in Dev Agent Record → Completion Notes - - Communicate to {user_name} that story implementation is complete and ready for review - Summarize key accomplishments: story ID, story key, title, key changes made, tests added, files modified - Provide the story file path and current status (now "review") - - 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 - - - - Provide clear, contextual explanations tailored to {user_skill_level} - Use examples and references to specific code when helpful - - - Once explanations are complete (or user indicates no questions), suggest logical next steps - 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 - - - 💡 **Tip:** For best results, run `code-review` using a **different** LLM than the one that implemented this story. - - Suggest checking {sprint_status} to see project progress - - Remain flexible - allow user to choose their own path or ask for other assistance - - - diff --git a/src/core/tasks/bmad-skill-manifest.yaml b/src/core/tasks/bmad-skill-manifest.yaml index cfe67caa5..ea8fdbcf1 100644 --- a/src/core/tasks/bmad-skill-manifest.yaml +++ b/src/core/tasks/bmad-skill-manifest.yaml @@ -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" diff --git a/src/core/tasks/workflow.xml b/src/core/tasks/workflow.xml deleted file mode 100644 index 351872ac8..000000000 --- a/src/core/tasks/workflow.xml +++ /dev/null @@ -1,235 +0,0 @@ - - Execute given workflow by loading its configuration, following instructions, and producing output - - - Always read COMPLETE files - NEVER use offset/limit when reading any workflow related files - Instructions are MANDATORY - either as file path, steps or embedded list in YAML, XML or markdown - Execute ALL steps in instructions IN EXACT ORDER - Save to template output file after EVERY "template-output" tag - NEVER skip a step - YOU are responsible for every steps execution without fail or excuse - - - - Steps execute in exact numerical order (1, 2, 3...) - Optional steps: Ask user unless #yolo mode active - 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) - - - - - - Read workflow.yaml from provided path - Load config_source (REQUIRED for all modules) - Load external config from config_source path - Resolve all {config_source}: references with values from config - Resolve system variables (date:system-generated) and paths ({project-root}, {installed_path}) - Ask user for input of any variables that are still unknown - - - - Instructions: Read COMPLETE file from path OR embedded list (REQUIRED) - If template path → Read COMPLETE template file - If validation path → Note path for later loading when needed - If template: false → Mark as action-workflow (else template-workflow) - Data files (csv, json) → Store paths only, load on-demand when instructions reference them - - - - Resolve default_output_file path with all variables and {{date}} - Create output directory if doesn't exist - If template-workflow → Write template to output file with placeholders - If action-workflow → Skip file creation - - - - - For each step in instructions: - - - If optional="true" and NOT #yolo → Ask user to include - If if="condition" → Evaluate condition - If for-each="item" → Repeat step for each item - If repeat="n" → Repeat step n times - - - - Process step instructions (markdown or XML tags) - Replace {{variables}} with values (ask user if unknown) - - action xml tag → Perform the action - check if="condition" xml tag → Conditional block wrapping actions (requires closing </check>) - ask xml tag → Prompt user and WAIT for response - invoke-workflow xml tag → Execute another workflow with given inputs and the workflow.xml runner - invoke-task xml tag → Execute specified task - invoke-protocol name="protocol_name" xml tag → Execute reusable protocol from protocols section - goto step="x" → Jump to specified step - - - - - - Generate content for this section - Save to file (Write first time, Edit subsequent) - Display generated content - [a] Advanced Elicitation, [c] Continue, [p] Party-Mode, [y] YOLO the rest of this document only. WAIT for response. - Start the advanced elicitation workflow {project-root}/_bmad/core/workflows/advanced-elicitation/workflow.md - - - Continue to next step - - - Start the party-mode workflow {project-root}/_bmad/core/workflows/party-mode/workflow.md - - - Enter #yolo mode for the rest of the workflow - - - - - - - If no special tags and NOT #yolo: - Continue to next step? (y/n/edit) - - - - - Confirm document saved to output path - Report workflow completion - - - - - Full user interaction and confirmation of EVERY step at EVERY template output - NO EXCEPTIONS except yolo MODE - 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 - - - - - step n="X" goal="..." - Define step with number and goal - optional="true" - Step can be skipped - if="condition" - Conditional execution - for-each="collection" - Iterate over items - repeat="n" - Repeat n times - - - action - Required action to perform - action if="condition" - Single conditional action (inline, no closing tag needed) - check if="condition">...</check> - Conditional block wrapping multiple items (closing tag required) - ask - Get user input (ALWAYS wait for response before continuing) - goto - Jump to another step - invoke-workflow - Call another workflow - invoke-task - Call a task - invoke-protocol - Execute a reusable protocol (e.g., discover_inputs) - - - template-output - Save content checkpoint - critical - Cannot be skipped - example - Show example output - - - - - - Intelligently load project files (whole or sharded) based on workflow's input_file_patterns configuration - - Only execute if workflow.yaml contains input_file_patterns section - - - - Read input_file_patterns from loaded workflow.yaml - For each pattern group (prd, architecture, epics, etc.), note the load_strategy if present - - - - For each pattern in input_file_patterns: - - - - Determine load_strategy from pattern config (defaults to FULL_LOAD if not specified) - - - Load ALL files in sharded directory - used for PRD, Architecture, UX, brownfield docs - Use glob pattern to find ALL .md files (e.g., "{output_folder}/*architecture*/*.md") - Load EVERY matching file completely - Concatenate content in logical order (index.md first if exists, then alphabetical) - Store in variable: {pattern_name_content} - - - - Load specific shard using template variable - example: used for epics with {{epic_num}} - Check for template variables in sharded_single pattern (e.g., {{epic_num}}) - If variable undefined, ask user for value OR infer from context - Resolve template to specific file path - Load that specific file - Store in variable: {pattern_name_content} - - - - Load index.md, analyze 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 only a 5% chance - Load index.md from sharded directory - Parse table of contents, links, section headers - Analyze workflow's purpose and objective - Identify which linked/referenced documents are likely relevant - If workflow is about authentication and index shows "Auth Overview", "Payment Setup", "Deployment" → Load auth - docs, consider deployment docs, skip payment - Load all identified relevant documents - Store combined content in variable: {pattern_name_content} - When in doubt, LOAD IT - context is valuable, being thorough is better than missing critical info - - Mark pattern as RESOLVED, skip to next pattern - - - - - - Attempt glob match on 'whole' pattern (e.g., "{output_folder}/*prd*.md") - - Load ALL matching files completely (no offset/limit) - Store content in variable: {pattern_name_content} (e.g., {prd_content}) - Mark pattern as RESOLVED, skip to next pattern - - - - - - - Set {pattern_name_content} to empty string - Note in session: "No {pattern_name} files found" (not an error, just unavailable, offer use change to provide) - - - - - - List all loaded content variables with file counts - - ✓ 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 - - This gives workflow transparency into what context is available - - - - - - - - - â€ĸ 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. - - - \ No newline at end of file diff --git a/src/utility/agent-components/activation-steps.txt b/src/utility/agent-components/activation-steps.txt index 9ead0e01c..abcc0e6fa 100644 --- a/src/utility/agent-components/activation-steps.txt +++ b/src/utility/agent-components/activation-steps.txt @@ -11,4 +11,4 @@ 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 `/bmad-help where should I start with an idea I have that does XYZ` STOP and WAIT for user input - do NOT execute menu items automatically - accept number or cmd trigger or fuzzy command match On user input: Number → process menu item[n] | Text → case-insensitive substring match | Multiple matches → ask user to clarify | No match → show "Not recognized" - 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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/src/utility/agent-components/handler-multi.txt b/src/utility/agent-components/handler-multi.txt index f33a73fe5..e05be2390 100644 --- a/src/utility/agent-components/handler-multi.txt +++ b/src/utility/agent-components/handler-multi.txt @@ -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 diff --git a/src/utility/agent-components/handler-validate-workflow.txt b/src/utility/agent-components/handler-validate-workflow.txt deleted file mode 100644 index aca040550..000000000 --- a/src/utility/agent-components/handler-validate-workflow.txt +++ /dev/null @@ -1,7 +0,0 @@ - - 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 - \ No newline at end of file diff --git a/src/utility/agent-components/handler-workflow.txt b/src/utility/agent-components/handler-workflow.txt deleted file mode 100644 index 1be1dcbe5..000000000 --- a/src/utility/agent-components/handler-workflow.txt +++ /dev/null @@ -1,10 +0,0 @@ - - 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 - \ No newline at end of file diff --git a/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml b/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml index 959085cb7..22ae9886d 100644 --- a/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +++ b/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml @@ -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 diff --git a/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml b/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml index 945722b5b..9133b02de 100644 --- a/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +++ b/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml @@ -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 diff --git a/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml b/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml index c8a23a9d5..c95025025 100644 --- a/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +++ b/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml @@ -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 diff --git a/test/fixtures/file-refs-csv/valid/bmm-style.csv b/test/fixtures/file-refs-csv/valid/bmm-style.csv index ab870ab01..c803064ba 100644 --- a/test/fixtures/file-refs-csv/valid/bmm-style.csv +++ b/test/fixtures/file-refs-csv/valid/bmm-style.csv @@ -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", diff --git a/test/test-file-refs-csv.js b/test/test-file-refs-csv.js index d068bd75d..c6a8e4623 100644 --- a/test/test-file-refs-csv.js +++ b/test/test-file-refs-csv.js @@ -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}`); diff --git a/test/test-workflow-path-regex.js b/test/test-workflow-path-regex.js new file mode 100644 index 000000000..488d69b76 --- /dev/null +++ b/test/test-workflow-path-regex.js @@ -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); diff --git a/tools/cli/installers/lib/core/manifest-generator.js b/tools/cli/installers/lib/core/manifest-generator.js index 8562672b5..b02ccde9e 100644 --- a/tools/cli/installers/lib/core/manifest-generator.js +++ b/tools/cli/installers/lib/core/manifest-generator.js @@ -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,76 +172,66 @@ 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 workflowPath = path.join(dir, workflowFile); - if (!(await fs.pathExists(workflowPath))) continue; - + const workflowFile = 'workflow.md'; + const workflowPath = path.join(dir, workflowFile); + 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'); - // 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 (frontmatterMatch) { + const workflow = yaml.parse(frontmatterMatch[1]); + + if (!workflow || !workflow.name || !workflow.description) { + if (debug) console.log(`[DEBUG] collectSkills: skipped (missing name/description): ${workflowPath}`); + } else { + // Build path relative from module root + const relativePath = path.relative(modulePath, dir).split(path.sep).join('/'); + const installPath = relativePath + ? `${this.bmadFolderName}/${moduleName}/${relativePath}/${workflowFile}` + : `${this.bmadFolderName}/${moduleName}/${workflowFile}`; + + // Skills derive canonicalId from directory name — never from manifest + if (manifest && manifest.__single && manifest.__single.canonicalId) { + console.warn( + `Warning: Skill manifest at ${dir}/bmad-skill-manifest.yaml contains canonicalId — this field is ignored for skills (directory name is the canonical ID)`, + ); + } + const canonicalId = path.basename(dir); + + this.skills.push({ + name: workflow.name, + description: this.cleanForCSV(workflow.description), + module: moduleName, + path: installPath, + canonicalId, + install_to_bmad: this.getInstallToBmad(manifest, workflowFile), + }); + + // Add to files list + this.files.push({ + type: 'skill', + name: workflow.name, + module: moduleName, + path: installPath, + }); + + this.skillClaimedDirs.add(dir); + + if (debug) { + console.log(`[DEBUG] collectSkills: claimed skill "${workflow.name}" as ${canonicalId} at ${dir}`); + } + } + } else { if (debug) console.log(`[DEBUG] collectSkills: skipped (no frontmatter): ${workflowPath}`); - continue; } - workflow = yaml.parse(frontmatterMatch[1]); + } catch (error) { + if (debug) console.log(`[DEBUG] collectSkills: failed to parse ${workflowPath}: ${error.message}`); } - - if (!workflow || !workflow.name || !workflow.description) { - if (debug) console.log(`[DEBUG] collectSkills: skipped (missing name/description): ${workflowPath}`); - continue; - } - - // Build path relative from module root - const relativePath = path.relative(modulePath, dir).split(path.sep).join('/'); - const installPath = relativePath - ? `${this.bmadFolderName}/${moduleName}/${relativePath}/${workflowFile}` - : `${this.bmadFolderName}/${moduleName}/${workflowFile}`; - - // Skills derive canonicalId from directory name — never from manifest - if (manifest && manifest.__single && manifest.__single.canonicalId) { - console.warn( - `Warning: Skill manifest at ${dir}/bmad-skill-manifest.yaml contains canonicalId — this field is ignored for skills (directory name is the canonical ID)`, - ); - } - const canonicalId = path.basename(dir); - - this.skills.push({ - name: workflow.name, - description: this.cleanForCSV(workflow.description), - module: moduleName, - path: installPath, - canonicalId, - install_to_bmad: this.getInstallToBmad(manifest, workflowFile), - }); - - // Add to files list - this.files.push({ - type: 'skill', - name: workflow.name, - module: moduleName, - path: installPath, - }); - - this.skillClaimedDirs.add(dir); - - if (debug) { - console.log(`[DEBUG] collectSkills: claimed skill "${workflow.name}" as ${canonicalId} at ${dir}`); - } - break; // Successfully claimed — skip remaining workflow filenames - } catch (error) { - if (debug) console.log(`[DEBUG] collectSkills: failed to parse ${workflowPath}: ${error.message}`); } } @@ -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,21 +344,15 @@ 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) { - if (debug) { - console.log(`[DEBUG] Skipped (no frontmatter): ${fullPath}`); - } - continue; // Skip MD files without frontmatter + // Parse MD workflow with YAML frontmatter + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + if (!frontmatterMatch) { + if (debug) { + console.log(`[DEBUG] Skipped (no frontmatter): ${fullPath}`); } - workflow = yaml.parse(frontmatterMatch[1]); + continue; // Skip MD files without frontmatter } + 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; } diff --git a/tools/cli/installers/lib/ide/_base-ide.js b/tools/cli/installers/lib/ide/_base-ide.js index 9bfbdcf30..a09222868 100644 --- a/tools/cli/installers/lib/ide/_base-ide.js +++ b/tools/cli/installers/lib/ide/_base-ide.js @@ -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} 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} 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, }); diff --git a/tools/cli/installers/lib/ide/_config-driven.js b/tools/cli/installers/lib/ide/_config-driven.js index e72b61481..610913dd7 100644 --- a/tools/cli/installers/lib/ide/_config-driven.js +++ b/tools/cli/installers/lib/ide/_config-driven.js @@ -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); diff --git a/tools/cli/installers/lib/ide/shared/path-utils.js b/tools/cli/installers/lib/ide/shared/path-utils.js index 45efd2ec1..4ab033f17 100644 --- a/tools/cli/installers/lib/ide/shared/path-utils.js +++ b/tools/cli/installers/lib/ide/shared/path-utils.js @@ -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(/[/\\]/); diff --git a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js index 793252bac..ed8c3e508 100644 --- a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js @@ -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 }; diff --git a/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md b/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md deleted file mode 100644 index 4a8da26e7..000000000 --- a/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md +++ /dev/null @@ -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: - - -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 - diff --git a/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md b/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md deleted file mode 100644 index 4ee4e0824..000000000 --- a/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md +++ /dev/null @@ -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: - - -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 - diff --git a/tools/cli/installers/lib/ide/templates/workflow-command-template.md b/tools/cli/installers/lib/ide/templates/workflow-command-template.md deleted file mode 100644 index 328983c9e..000000000 --- a/tools/cli/installers/lib/ide/templates/workflow-command-template.md +++ /dev/null @@ -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: - - -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 - diff --git a/tools/cli/installers/lib/ide/templates/workflow-commander.md b/tools/cli/installers/lib/ide/templates/workflow-commander.md deleted file mode 100644 index 66eee15d1..000000000 --- a/tools/cli/installers/lib/ide/templates/workflow-commander.md +++ /dev/null @@ -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! diff --git a/tools/cli/installers/lib/modules/manager.js b/tools/cli/installers/lib/modules/manager.js index f162593b7..7ac85678b 100644 --- a/tools/cli/installers/lib/modules/manager.js +++ b/tools/cli/installers/lib/modules/manager.js @@ -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); - } + // 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 diff --git a/tools/cli/lib/agent-analyzer.js b/tools/cli/lib/agent-analyzer.js index ae834a098..a62bdd7cf 100644 --- a/tools/cli/lib/agent-analyzer.js +++ b/tools/cli/lib/agent-analyzer.js @@ -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'); - } + 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'); } diff --git a/tools/cli/lib/agent/compiler.js b/tools/cli/lib/agent/compiler.js index f9f71baab..cc91f9bd6 100644 --- a/tools/cli/lib/agent/compiler.js +++ b/tools/cli/lib/agent/compiler.js @@ -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,12 +225,7 @@ 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; - } + result.route = exec.route; } if (exec.data !== null && exec.data !== undefined) { diff --git a/tools/cli/lib/yaml-xml-builder.js b/tools/cli/lib/yaml-xml-builder.js index ac140814f..f4f8e2f5a 100644 --- a/tools/cli/lib/yaml-xml-builder.js +++ b/tools/cli/lib/yaml-xml-builder.js @@ -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,12 +447,7 @@ 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; - } + result.route = exec.route; } if (exec.data !== null && exec.data !== undefined) { diff --git a/tools/schema/agent.js b/tools/schema/agent.js index 93ced7c6e..dfec1322f 100644 --- a/tools/schema/agent.js +++ b/tools/schema/agent.js @@ -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(),