fix: address coderabbit workflow migration follow-ups

This commit is contained in:
Dicky Moore 2026-02-08 22:16:06 +00:00
parent dde139a560
commit 0a3f48f13f
7 changed files with 165 additions and 94 deletions

View File

@ -78,15 +78,15 @@ Work through phases 1-3. **Use fresh chats for each workflow.**
### Phase 1: Analysis (Optional) ### Phase 1: Analysis (Optional)
All workflows in this phase are optional: All workflows in this phase are optional:
- **brainstorming** — Guided ideation - **brainstorming** (`bmad-bmm-brainstorming`) — Guided ideation
- **research** — Market and technical research - **research** (`bmad-bmm-research`) — Market and technical research
- **create-product-brief** — Recommended foundation document - **create-product-brief** (`bmad-bmm-create-product-brief`) — Recommended foundation document
### Phase 2: Planning (Required) ### Phase 2: Planning (Required)
**For BMad Method and Enterprise tracks:** **For BMad Method and Enterprise tracks:**
1. Load the **PM agent** in a new chat 1. Load the **PM agent** in a new chat
2. Run the `prd` workflow 2. Run the `create-prd` workflow (`bmad-bmm-create-prd`)
3. Output: `PRD.md` 3. Output: `PRD.md`
**For Quick Flow track:** **For Quick Flow track:**
@ -100,7 +100,7 @@ If your project has a user interface, load the **UX-Designer agent** and run the
**Create Architecture** **Create Architecture**
1. Load the **Architect agent** in a new chat 1. Load the **Architect agent** in a new chat
2. Run `create-architecture` 2. Run `create-architecture` (`bmad-bmm-create-architecture`)
3. Output: Architecture document with technical decisions 3. Output: Architecture document with technical decisions
**Create Epics and Stories** **Create Epics and Stories**
@ -110,12 +110,12 @@ Epics and stories are now created *after* architecture. This produces better qua
::: :::
1. Load the **PM agent** in a new chat 1. Load the **PM agent** in a new chat
2. Run `create-epics-and-stories` 2. Run `create-epics-and-stories` (`bmad-bmm-create-epics-and-stories`)
3. The workflow uses both PRD and Architecture to create technically-informed stories 3. The workflow uses both PRD and Architecture to create technically-informed stories
**Implementation Readiness Check** *(Highly Recommended)* **Implementation Readiness Check** *(Highly Recommended)*
1. Load the **Architect agent** in a new chat 1. Load the **Architect agent** in a new chat
2. Run `check-implementation-readiness` 2. Run `check-implementation-readiness` (`bmad-bmm-check-implementation-readiness`)
3. Validates cohesion across all planning documents 3. Validates cohesion across all planning documents
## Step 2: Build Your Project ## Step 2: Build Your Project
@ -124,7 +124,7 @@ Once planning is complete, move to implementation. **Each workflow should run in
### Initialize Sprint Planning ### Initialize Sprint Planning
Load the **SM agent** and run `sprint-planning`. This creates `sprint-status.yaml` to track all epics and stories. Load the **SM agent** and run `sprint-planning` (`bmad-bmm-sprint-planning`). This creates `sprint-status.yaml` to track all epics and stories.
### The Build Cycle ### The Build Cycle
@ -132,11 +132,11 @@ For each story, repeat this cycle with fresh chats:
| Step | Agent | Workflow | Purpose | | Step | Agent | Workflow | Purpose |
| ---- | ----- | -------------- | ---------------------------------- | | ---- | ----- | -------------- | ---------------------------------- |
| 1 | SM | `create-story` | Create story file from epic | | 1 | SM | `create-story` (`bmad-bmm-create-story`) | Create story file from epic |
| 2 | DEV | `dev-story` | Implement the story | | 2 | DEV | `dev-story` (`bmad-bmm-dev-story`) | Implement the story |
| 3 | DEV | `code-review` | Quality validation *(recommended)* | | 3 | DEV | `code-review` (`bmad-bmm-code-review`) | Quality validation *(recommended)* |
After completing all stories in an epic, load the **SM agent** and run `retrospective`. After completing all stories in an epic, load the **SM agent** and run `retrospective` (`bmad-bmm-retrospective`).
## What You've Accomplished ## What You've Accomplished
@ -162,17 +162,17 @@ your-project/
## Quick Reference ## Quick Reference
| Workflow | Agent | Purpose | | Workflow | Slash Command | Agent | Purpose |
| -------------------------------- | --------- | ------------------------------------ | | -------------------------------- | ------------------------------------- | --------- | ------------------------------------ |
| `help` | Any | Get guidance on what to do next | | `help` | `bmad-help` | Any | Get guidance on what to do next |
| `prd` | PM | Create Product Requirements Document | | `create-prd` | `bmad-bmm-create-prd` | PM | Create Product Requirements Document |
| `create-architecture` | Architect | Create architecture document | | `create-architecture` | `bmad-bmm-create-architecture` | Architect | Create architecture document |
| `create-epics-and-stories` | PM | Break down PRD into epics | | `create-epics-and-stories` | `bmad-bmm-create-epics-and-stories` | PM | Break down PRD into epics |
| `check-implementation-readiness` | Architect | Validate planning cohesion | | `check-implementation-readiness` | `bmad-bmm-check-implementation-readiness` | Architect | Validate planning cohesion |
| `sprint-planning` | SM | Initialize sprint tracking | | `sprint-planning` | `bmad-bmm-sprint-planning` | SM | Initialize sprint tracking |
| `create-story` | SM | Create a story file | | `create-story` | `bmad-bmm-create-story` | SM | Create a story file |
| `dev-story` | DEV | Implement a story | | `dev-story` | `bmad-bmm-dev-story` | DEV | Implement a story |
| `code-review` | DEV | Review implemented code | | `code-review` | `bmad-bmm-code-review` | DEV | Review implemented code |
## Common Questions ## Common Questions
@ -183,7 +183,7 @@ Only for BMad Method and Enterprise tracks. Quick Flow skips from tech-spec to i
Yes. The SM agent has a `correct-course` workflow for handling scope changes. Yes. The SM agent has a `correct-course` workflow for handling scope changes.
**What if I want to brainstorm first?** **What if I want to brainstorm first?**
Load the Analyst agent and run `brainstorming` before starting your PRD. Load the Analyst agent and run `brainstorming` (`bmad-bmm-brainstorming`) before starting your PRD.
**Do I need to follow a strict order?** **Do I need to follow a strict order?**
Not strictly. Once you learn the flow, you can run workflows directly using the Quick Reference above. Not strictly. Once you learn the flow, you can run workflows directly using the Quick Reference above.
@ -192,7 +192,7 @@ Not strictly. Once you learn the flow, you can run workflows directly using the
- **During workflows** — Agents guide you with questions and explanations - **During workflows** — Agents guide you with questions and explanations
- **Community** — [Discord](https://discord.gg/gk8jAdXWmj) (#bmad-method-help, #report-bugs-and-issues) - **Community** — [Discord](https://discord.gg/gk8jAdXWmj) (#bmad-method-help, #report-bugs-and-issues)
- **Stuck?** — Run `help` to see what to do next - **Stuck?** — Run `help` (`bmad-help` on most platforms) to see what to do next
## Key Takeaways ## Key Takeaways

View File

@ -59,7 +59,7 @@ input_file_patterns:
<check if="{{story_path}} is provided by user or user provided the epic and story number such as 2-4 or 1.6 or epic 1 story 5"> <check if="{{story_path}} is provided by user or user provided the epic and story number such as 2-4 or 1.6 or epic 1 story 5">
<action>Parse user-provided story path: extract epic_num, story_num, story_title from format like "1-2-user-auth"</action> <action>Parse user-provided story path: extract epic_num, story_num, story_title from format like "1-2-user-auth"</action>
<action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action> <action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action>
<action>GOTO step 2a</action> <action>GOTO step 2</action>
</check> </check>
<action>Check if {{sprint_status}} file exists for auto discover</action> <action>Check if {{sprint_status}} file exists for auto discover</action>
@ -85,12 +85,12 @@ input_file_patterns:
<check if="user provides epic-story number"> <check if="user provides epic-story number">
<action>Parse user input: extract epic_num, story_num, story_title</action> <action>Parse user input: extract epic_num, story_num, story_title</action>
<action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action> <action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action>
<action>GOTO step 2a</action> <action>GOTO step 2</action>
</check> </check>
<check if="user provides story docs path"> <check if="user provides story docs path">
<action>Use user-provided path for story documents</action> <action>Use user-provided path for story documents</action>
<action>GOTO step 2a</action> <action>GOTO step 2</action>
</check> </check>
</check> </check>
@ -152,7 +152,7 @@ input_file_patterns:
<output>📊 Epic {{epic_num}} status updated to in-progress</output> <output>📊 Epic {{epic_num}} status updated to in-progress</output>
</check> </check>
<action>GOTO step 2a</action> <action>GOTO step 2</action>
</check> </check>
</step> </step>
@ -174,6 +174,7 @@ input_file_patterns:
(As a, I want, so that) - Detailed acceptance criteria (already BDD formatted) - Technical requirements specific to this story - (As a, I want, so that) - Detailed acceptance criteria (already BDD formatted) - Technical requirements specific to this story -
Business context and value - Success criteria <!-- Previous story analysis for context continuity --> Business context and value - Success criteria <!-- Previous story analysis for context continuity -->
<check if="story_num > 1"> <check if="story_num > 1">
<action>Set {{previous_story_num}} = {{story_num}} - 1</action>
<action>Load previous story file: {{story_dir}}/{{epic_num}}-{{previous_story_num}}-*.md</action> **PREVIOUS STORY INTELLIGENCE:** - <action>Load previous story file: {{story_dir}}/{{epic_num}}-{{previous_story_num}}-*.md</action> **PREVIOUS STORY INTELLIGENCE:** -
Dev notes and learnings from previous story - Review feedback and corrections needed - Files that were created/modified and their Dev notes and learnings from previous story - Review feedback and corrections needed - Files that were created/modified and their
patterns - Testing approaches that worked/didn't work - Problems encountered and solutions found - Code patterns established <action>Extract patterns - Testing approaches that worked/didn't work - Problems encountered and solutions found - Code patterns established <action>Extract

View File

@ -18,6 +18,13 @@
- Set suggestion = "" - Set suggestion = ""
- Set next_workflow = "" - Set next_workflow = ""
- Set next_agent = "" - Set next_agent = ""
- Set status_file_path = ""
- Set field_type = ""
- Set workflow_mode = ""
- Set scan_level = ""
- Set subworkflow_success = false
- Set status_update_success = false
- Set cached_project_types = ""
</action> </action>
<action>Attempt to load workflow status directly from `{output_folder}/bmm-workflow-status.yaml`: <action>Attempt to load workflow status directly from `{output_folder}/bmm-workflow-status.yaml`:
@ -29,10 +36,18 @@
- Extract field_type, warning, suggestion, next_workflow, next_agent if present - Extract field_type, warning, suggestion, next_workflow, next_agent if present
- If file is missing, unreadable, or malformed: - If file is missing, unreadable, or malformed:
- Keep defaults and continue in standalone mode - Keep defaults and continue in standalone mode
- Set status_load_error_reason from the caught file/parse error (e.g., missing file, permission denied, YAML parse error)
- Set warning = "Unable to load workflow status from {output_folder}/bmm-workflow-status.yaml: {{status_load_error_reason}}"
- Output warning and continue in standalone mode
</action> </action>
<check if="status_exists == false"> <check if="status_exists == false">
<output>{{suggestion}}</output> <check if="suggestion != ''">
<output>{{suggestion}}</output>
</check>
<check if="warning != ''">
<output>{{warning}}</output>
</check>
<output>Note: Documentation workflow can run standalone. Continuing without progress tracking.</output> <output>Note: Documentation workflow can run standalone. Continuing without progress tracking.</output>
<action>Set standalone_mode = true</action> <action>Set standalone_mode = true</action>
<action>Set status_file_found = false</action> <action>Set status_file_found = false</action>
@ -62,7 +77,9 @@
<output>Note: This may be auto-invoked by prd for brownfield documentation.</output> <output>Note: This may be auto-invoked by prd for brownfield documentation.</output>
<ask>Continue with documentation? (y/n)</ask> <ask>Continue with documentation? (y/n)</ask>
<check if="n"> <check if="n">
<output>{{suggestion}}</output> <check if="suggestion != ''">
<output>{{suggestion}}</output>
</check>
<action>Exit workflow</action> <action>Exit workflow</action>
</check> </check>
</check> </check>
@ -78,14 +95,33 @@
<check if="project-scan-report.json exists"> <check if="project-scan-report.json exists">
<action>Read state file and extract: timestamps, mode, scan_level, current_step, completed_steps, project_classification</action> <action>Read state file and extract: timestamps, mode, scan_level, current_step, completed_steps, project_classification</action>
<action>Validate last_updated from state file:
- If last_updated is missing or invalid, set state_age_hours = 999 and mark state as stale
- Otherwise parse timestamp and continue
</action>
<action>Extract cached project_type_id(s) from state file if present</action> <action>Extract cached project_type_id(s) from state file if present</action>
<action>Calculate age of state file (current time - last_updated)</action> <action>Calculate age of state file (current time - last_updated)</action>
<check if="state file age >= 24 hours"> <check if="state file age >= 24 hours">
<action>Display: "Found old state file (>24 hours). Starting fresh scan."</action> <action>Display: "Found old state file (>24 hours). Starting fresh scan."</action>
<action>Create archive directory: {output_folder}/.archive/</action> <action>Attempt to create archive directory: {output_folder}/.archive/</action>
<action>Archive old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action> <check if="archive directory creation failed">
<output>Failed to create archive directory at {output_folder}/.archive/. Keeping existing state and exiting to avoid data loss.</output>
<action>Set resume_mode = true</action>
<action>Exit workflow</action>
</check>
<action>Attempt to archive old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action>
<check if="archive move failed">
<output>Failed to archive old state file. Keeping existing state and exiting to avoid data loss.</output>
<action>Set resume_mode = true</action>
<action>Exit workflow</action>
</check>
<action>Set resume_mode = false</action> <action>Set resume_mode = false</action>
<action>Set workflow_mode = ""</action>
<action>Set scan_level = ""</action>
<action>Set cached_project_types = ""</action>
<action>Set current_step = ""</action>
<action>Set subworkflow_success = false</action>
<action>Continue to Step 3</action> <action>Continue to Step 3</action>
</check> </check>
@ -112,15 +148,25 @@ Your choice [1/2/3]:
<check if="user selects 1"> <check if="user selects 1">
<action>Set resume_mode = true</action> <action>Set resume_mode = true</action>
<action>Set workflow_mode = {{mode}}</action> <action>Validate persisted mode before assigning workflow_mode:
- If mode is one of [deep_dive, initial_scan, full_rescan], set workflow_mode = {{mode}}
- Otherwise set workflow_mode = "full_rescan", set resume_mode = false, and continue as fresh scan
</action>
<action>Set subworkflow_success = false</action> <action>Set subworkflow_success = false</action>
<action>Load findings summaries from state file</action> <action>Load findings summaries from state file</action>
<action>Load cached project_type_id(s) from state file</action> <action>Load cached project_type_id(s) from state file</action>
<critical>CONDITIONAL CSV LOADING FOR RESUME:</critical> <critical>CONDITIONAL CSV LOADING FOR RESUME:</critical>
<check if="cached_project_types == ''">
<output>No cached project types found. Falling back to full CSV load.</output>
<action>Load project-types.csv and architecture_registry.csv</action>
<action>Load documentation_requirements_csv for active project classification</action>
</check>
<check if="cached_project_types != ''">
<action>For each cached project_type_id, load ONLY the corresponding row from: {documentation_requirements_csv}</action> <action>For each cached project_type_id, load ONLY the corresponding row from: {documentation_requirements_csv}</action>
<action>Skip loading project-types.csv and architecture_registry.csv (not needed on resume)</action> <action>Skip loading project-types.csv and architecture_registry.csv (not needed on resume)</action>
<action>Store loaded doc requirements for use in remaining steps</action> <action>Store loaded doc requirements for use in remaining steps</action>
</check>
<action>Display: "Resuming {{workflow_mode}} from {{current_step}} with cached project type(s): {{cached_project_types}}"</action> <action>Display: "Resuming {{workflow_mode}} from {{current_step}} with cached project type(s): {{cached_project_types}}"</action>
@ -152,9 +198,21 @@ Your choice [1/2/3]:
</check> </check>
<check if="user selects 2"> <check if="user selects 2">
<action>Create archive directory: {output_folder}/.archive/</action> <action>Attempt to create archive directory: {output_folder}/.archive/</action>
<action>Move old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action> <check if="archive directory creation failed">
<output>Failed to create archive directory. Keeping existing state and exiting to avoid data loss.</output>
<action>Set resume_mode = true</action>
<action>Exit workflow</action>
</check>
<action>Attempt to move old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action>
<check if="archive move failed">
<output>Failed to archive old state file. Keeping existing state and exiting to avoid data loss.</output>
<action>Set resume_mode = true</action>
<action>Exit workflow</action>
</check>
<action>Set resume_mode = false</action> <action>Set resume_mode = false</action>
<action>Reset workflow_mode, scan_level, cached_project_types, current_step to defaults</action>
<action>Set subworkflow_success = false</action>
<action>Continue to Step 3</action> <action>Continue to Step 3</action>
</check> </check>
@ -198,6 +256,7 @@ Your choice [1/2/3]:
<check if="user selects 1"> <check if="user selects 1">
<action>Set workflow_mode = "full_rescan"</action> <action>Set workflow_mode = "full_rescan"</action>
<action>Set scan_level = "standard"</action>
<action>Display: "Starting full project rescan..."</action> <action>Display: "Starting full project rescan..."</action>
<action>Read fully and follow: {installed_path}/workflows/full-scan-instructions.md</action> <action>Read fully and follow: {installed_path}/workflows/full-scan-instructions.md</action>
<action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action> <action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action>
@ -234,6 +293,7 @@ Your choice [1/2/3]:
<check if="index.md does not exist"> <check if="index.md does not exist">
<action>Set workflow_mode = "initial_scan"</action> <action>Set workflow_mode = "initial_scan"</action>
<action>Set scan_level = "initial"</action>
<action>Display: "No existing documentation found. Starting initial project scan..."</action> <action>Display: "No existing documentation found. Starting initial project scan..."</action>
<action>Read fully and follow: {installed_path}/workflows/full-scan-instructions.md</action> <action>Read fully and follow: {installed_path}/workflows/full-scan-instructions.md</action>
<action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action> <action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action>
@ -282,8 +342,13 @@ Your choice [1/2/3]:
<output>**Status Updated:** Progress tracking updated. <output>**Status Updated:** Progress tracking updated.
**Next Steps:** **Next Steps:**
- **Next required:** {{next_workflow}} ({{next_agent}} agent)
- Run `bmad-help` if you need recommended next workflows.</output> - Run `bmad-help` if you need recommended next workflows.</output>
<check if="next_workflow != '' AND next_agent != ''">
<output>- **Next required:** {{next_workflow}} ({{next_agent}} agent)</output>
</check>
<check if="next_workflow == '' OR next_agent == ''">
<output>- **Next required:** not specified</output>
</check>
</check> </check>
<check if="status_file_found == false OR status_update_success != true"> <check if="status_file_found == false OR status_update_success != true">

View File

@ -31,7 +31,7 @@ Execute a validation checklist against a target file and report findings clearly
- Path-like tokens in checklist items - Path-like tokens in checklist items
- First matching path from glob patterns supplied by checklist/input - First matching path from glob patterns supplied by checklist/input
- Normalize all candidate paths relative to repo root and resolve `.`/`..`. - Normalize all candidate paths relative to repo root and resolve `.`/`..`.
- Validate candidate existence and expected file type (`.yaml`, `.yml`, `.json`, or checklist-defined extension). - Validate candidate existence and expected file type (`.md`, `.yaml`, `.yml`, `.json`, or checklist-defined extension).
- If multiple valid candidates remain, prefer explicit key fields over inferred tokens. - If multiple valid candidates remain, prefer explicit key fields over inferred tokens.
- If no valid candidate is found, prompt user with schema example: - If no valid candidate is found, prompt user with schema example:
- `Please provide the exact file path (relative to repo root), e.g. ./workflows/ci.yml` - `Please provide the exact file path (relative to repo root), e.g. ./workflows/ci.yml`
@ -50,11 +50,17 @@ Execute a validation checklist against a target file and report findings clearly
- If checklist requires edits/auto-fixes, follow safe-edit protocol: - If checklist requires edits/auto-fixes, follow safe-edit protocol:
- Ask for confirmation before editing. - Ask for confirmation before editing.
- Create backup snapshot of target file before changes. - Create backup snapshot of target file before changes.
- Use deterministic backup location: `{project-root}/.bmad-tmp/validate-workflow/`.
- Name backup as `{target-file-name}.{timestamp}.bak` and diff as `{target-file-name}.{timestamp}.diff`.
- If temp backup directory cannot be created, fall back to adjacent backup file `{target-file}.bak`.
- Generate reversible diff preview and show it to user. - Generate reversible diff preview and show it to user.
- Apply edits only after user approval. - Apply edits only after user approval.
- Run syntax/validation checks against edited file. - Run syntax/validation checks against edited file.
- If validation fails or user cancels, rollback from backup and report rollback status. - If validation fails or user cancels, rollback from backup and report rollback status.
- Record backup/diff locations in task output. - Record full backup and diff paths in task output.
- Support `retain_artifacts` flag (default `false`) to keep backup/diff artifacts when requested.
6. **Finalize** 6. **Finalize**
- Confirm completion and provide the final validation summary. - Confirm completion and provide the final validation summary.
- If edits succeeded and `retain_artifacts` is `false`, delete backup/diff artifacts and report cleanup status.
- If edits failed or rollback occurred, preserve backup/diff artifacts and report rollback path explicitly.

View File

@ -114,6 +114,12 @@ async function runTests() {
console.log(`========================================${colors.reset}\n`); console.log(`========================================${colors.reset}\n`);
const projectRoot = path.join(__dirname, '..'); const projectRoot = path.join(__dirname, '..');
const tmpRoots = [];
const trackTmp = async (prefix) => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
tmpRoots.push(dir);
return dir;
};
// ============================================================ // ============================================================
// Test 1: YAML → XML Agent Compilation (In-Memory) // Test 1: YAML → XML Agent Compilation (In-Memory)
@ -432,7 +438,7 @@ async function runTests() {
console.log(`${colors.yellow}Test Suite 11: Gemini Template Extension Guard${colors.reset}\n`); console.log(`${colors.yellow}Test Suite 11: Gemini Template Extension Guard${colors.reset}\n`);
try { try {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-gemini-install-')); const tmpRoot = await trackTmp('bmad-gemini-install-');
const projectDir = path.join(tmpRoot, 'project'); const projectDir = path.join(tmpRoot, 'project');
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME); const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
await fs.ensureDir(projectDir); await fs.ensureDir(projectDir);
@ -470,7 +476,7 @@ async function runTests() {
console.log(`${colors.yellow}Test Suite 12: Manifest Stale Entry Cleanup Guard${colors.reset}\n`); console.log(`${colors.yellow}Test Suite 12: Manifest Stale Entry Cleanup Guard${colors.reset}\n`);
try { try {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-manifest-clean-')); const tmpRoot = await trackTmp('bmad-manifest-clean-');
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME); const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
await fs.copy(path.join(projectRoot, 'src', 'core'), path.join(bmadDir, 'core')); await fs.copy(path.join(projectRoot, 'src', 'core'), path.join(bmadDir, 'core'));
await fs.copy(path.join(projectRoot, 'src', 'bmm'), path.join(bmadDir, 'bmm')); await fs.copy(path.join(projectRoot, 'src', 'bmm'), path.join(bmadDir, 'bmm'));
@ -505,7 +511,7 @@ async function runTests() {
console.log(`${colors.yellow}Test Suite 13: Internal Task Exposure Guard${colors.reset}\n`); console.log(`${colors.yellow}Test Suite 13: Internal Task Exposure Guard${colors.reset}\n`);
try { try {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-task-filter-')); const tmpRoot = await trackTmp('bmad-task-filter-');
const projectDir = path.join(tmpRoot, 'project'); const projectDir = path.join(tmpRoot, 'project');
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME); const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
const commandsDir = path.join(tmpRoot, 'commands'); const commandsDir = path.join(tmpRoot, 'commands');
@ -592,7 +598,7 @@ web_bundle:
console.log(`${colors.yellow}Test Suite 16: Task/Tool Standalone + CRLF Guard${colors.reset}\n`); console.log(`${colors.yellow}Test Suite 16: Task/Tool Standalone + CRLF Guard${colors.reset}\n`);
try { try {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-standalone-crlf-')); const tmpRoot = await trackTmp('bmad-standalone-crlf-');
const coreTasksDir = path.join(tmpRoot, '_bmad', 'core', 'tasks'); const coreTasksDir = path.join(tmpRoot, '_bmad', 'core', 'tasks');
const coreToolsDir = path.join(tmpRoot, '_bmad', 'core', 'tools'); const coreToolsDir = path.join(tmpRoot, '_bmad', 'core', 'tools');
await fs.ensureDir(coreTasksDir); await fs.ensureDir(coreTasksDir);
@ -717,7 +723,7 @@ internal: true
console.log(`${colors.yellow}Test Suite 18: Codex Task Visibility Guard${colors.reset}\n`); console.log(`${colors.yellow}Test Suite 18: Codex Task Visibility Guard${colors.reset}\n`);
try { try {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-codex-visibility-')); const tmpRoot = await trackTmp('bmad-codex-visibility-');
const projectDir = path.join(tmpRoot, 'project'); const projectDir = path.join(tmpRoot, 'project');
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME); const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
await fs.ensureDir(projectDir); await fs.ensureDir(projectDir);
@ -751,7 +757,7 @@ internal: true
console.log(`${colors.yellow}Test Suite 19: Empty Artifact Target Guard${colors.reset}\n`); console.log(`${colors.yellow}Test Suite 19: Empty Artifact Target Guard${colors.reset}\n`);
try { try {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-empty-target-')); const tmpRoot = await trackTmp('bmad-empty-target-');
const projectDir = path.join(tmpRoot, 'project'); const projectDir = path.join(tmpRoot, 'project');
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME); const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
await fs.ensureDir(projectDir); await fs.ensureDir(projectDir);
@ -856,6 +862,10 @@ internal: true
console.log(''); console.log('');
for (const tmpRoot of tmpRoots) {
await fs.remove(tmpRoot).catch(() => {});
}
// ============================================================ // ============================================================
// Summary // Summary
// ============================================================ // ============================================================

View File

@ -75,10 +75,10 @@ class WorkflowCommandGenerator {
if (workflowRelPath.includes('_bmad/')) { if (workflowRelPath.includes('_bmad/')) {
const parts = workflowRelPath.split(/_bmad\//); const parts = workflowRelPath.split(/_bmad\//);
if (parts.length > 1) { if (parts.length > 1) {
workflowRelPath = parts.slice(1).join('/'); workflowRelPath = parts.at(-1);
} }
} else if (workflowRelPath.includes('/src/')) { } else if (workflowRelPath.includes('/src/') || workflowRelPath.startsWith('src/')) {
const match = workflowRelPath.match(/\/src\/([^/]+)\/(.+)/); const match = workflowRelPath.match(/(?:^|\/)src\/([^/]+)\/(.+)/);
if (match) { if (match) {
workflowRelPath = `${match[1]}/${match[2]}`; workflowRelPath = `${match[1]}/${match[2]}`;
} }
@ -119,30 +119,9 @@ class WorkflowCommandGenerator {
* Generate command content for a workflow * Generate command content for a workflow
*/ */
async generateCommandContent(workflow, bmadDir) { async generateCommandContent(workflow, bmadDir) {
// Determine template based on workflow file type // Load the workflow command template
const templatePath = path.join(path.dirname(this.templatePath), 'workflow-commander.md'); const template = await fs.readFile(this.templatePath, 'utf8');
const workflowPath = this.mapSourcePathToInstalled(workflow.path);
// Load the appropriate template
const template = await fs.readFile(templatePath, 'utf8');
// Convert source path to installed path
// From: /Users/.../src/bmm/workflows/.../workflow.md
// To: {project-root}/_bmad/bmm/workflows/.../workflow.md
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 // Replace template variables
return template return template
@ -212,14 +191,15 @@ class WorkflowCommandGenerator {
When running any workflow: When running any workflow:
1. Resolve loader paths: 1. Resolve loader paths:
- Primary: {project-root}/${this.bmadFolderName}/core/tasks/workflow.md - Primary: {project-root}/${this.bmadFolderName}/core/tasks/workflow.md
- Fallback: {project-root}/src/core/tasks/workflow.md - Optional dev fallback: {project-root}/src/core/tasks/workflow.md (only if it exists and is readable)
2. Check the primary path exists and is readable before loading 2. Check the primary path exists and is readable before loading
3. If primary is missing/unreadable, log a warning with the path and error, then try fallback 3. If primary is missing/unreadable, log a warning with the primary path and error
4. If fallback is also missing/unreadable, log an error with both attempted paths and stop 4. Only if the dev fallback exists and is readable, try the fallback path; otherwise skip it
5. LOAD the resolved workflow loader file 5. If no readable loader is found, log an error with all attempted readable paths and stop
6. Pass the workflow path as 'workflow-config' parameter 6. LOAD the resolved workflow loader file
7. Follow workflow.md instructions EXACTLY 7. Pass the workflow path as 'workflow-config' parameter
8. Save outputs after EACH section 8. Follow workflow.md instructions EXACTLY
9. Save outputs after EACH section
## Modes ## Modes
- Normal: Full interaction - Normal: Full interaction
@ -230,21 +210,33 @@ When running any workflow:
} }
transformWorkflowPath(workflowPath) { transformWorkflowPath(workflowPath) {
let transformed = workflowPath; return this.mapSourcePathToInstalled(workflowPath, true);
}
if (workflowPath.includes('/src/bmm/')) { mapSourcePathToInstalled(sourcePath, includeProjectRootPrefix = false) {
const match = workflowPath.match(/\/src\/bmm\/(.+)/); if (!sourcePath) {
if (match) { return sourcePath;
transformed = `{project-root}/${this.bmadFolderName}/bmm/${match[1]}`;
}
} else if (workflowPath.includes('/src/core/')) {
const match = workflowPath.match(/\/src\/core\/(.+)/);
if (match) {
transformed = `{project-root}/${this.bmadFolderName}/core/${match[1]}`;
}
} }
return transformed; const normalized = sourcePath.replaceAll('\\', '/');
const srcMatch = normalized.match(/(?:^|\/)src\/([^/]+)\/(.+)/);
if (srcMatch) {
const mapped = `${this.bmadFolderName}/${srcMatch[1]}/${srcMatch[2]}`;
return includeProjectRootPrefix ? `{project-root}/${mapped}` : mapped;
}
if (normalized.includes('_bmad/')) {
const parts = normalized.split(/_bmad\//);
const relative = parts.at(-1);
const mapped = `${this.bmadFolderName}/${relative}`;
return includeProjectRootPrefix ? `{project-root}/${mapped}` : mapped;
}
if (normalized.startsWith(`${this.bmadFolderName}/`)) {
return includeProjectRootPrefix ? `{project-root}/${normalized}` : normalized;
}
return sourcePath;
} }
async loadWorkflowManifest(bmadDir) { async loadWorkflowManifest(bmadDir) {

View File

@ -813,7 +813,6 @@ class ModuleManager {
const newline = frontmatterMatch[0].includes('\r\n') ? '\r\n' : '\n'; const newline = frontmatterMatch[0].includes('\r\n') ? '\r\n' : '\n';
try { try {
const yaml = require('yaml');
const parsed = yaml.parse(frontmatterMatch[1]); const parsed = yaml.parse(frontmatterMatch[1]);
if (!parsed || typeof parsed !== 'object' || !Object.prototype.hasOwnProperty.call(parsed, 'web_bundle')) { if (!parsed || typeof parsed !== 'object' || !Object.prototype.hasOwnProperty.call(parsed, 'web_bundle')) {
@ -897,7 +896,6 @@ class ModuleManager {
let manifestData = {}; let manifestData = {};
if (await fs.pathExists(manifestPath)) { if (await fs.pathExists(manifestPath)) {
const manifestContent = await fs.readFile(manifestPath, 'utf8'); const manifestContent = await fs.readFile(manifestPath, 'utf8');
const yaml = require('yaml');
manifestData = yaml.parse(manifestContent); manifestData = yaml.parse(manifestContent);
} }
if (!manifestData.agentCustomizations) { if (!manifestData.agentCustomizations) {
@ -906,7 +904,6 @@ class ModuleManager {
manifestData.agentCustomizations[path.relative(bmadDir, customizePath)] = originalHash; manifestData.agentCustomizations[path.relative(bmadDir, customizePath)] = originalHash;
// Write back to manifest // Write back to manifest
const yaml = require('yaml');
// Clean the manifest data to remove any non-serializable values // Clean the manifest data to remove any non-serializable values
const cleanManifestData = structuredClone(manifestData); const cleanManifestData = structuredClone(manifestData);