Compare commits
5 Commits
8220c819e6
...
b753fb293b
| Author | SHA1 | Date |
|---|---|---|
|
|
b753fb293b | |
|
|
63ef5b7bc6 | |
|
|
1cb88728e8 | |
|
|
8d81edf847 | |
|
|
0067fb4880 |
|
|
@ -12,8 +12,8 @@
|
|||
- VERIFY: If config not loaded, STOP and report error to user
|
||||
- DO NOT PROCEED to step 3 until config is successfully loaded and variables stored</step>
|
||||
<step n="3">Remember: user's name is {user_name}</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/src/modules/bmd/agents/cli-chief-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/src/modules/bmd/agents/cli-chief-sidecar/memories.md into permanent context</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/bmd/agents/cli-chief-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/bmd/agents/cli-chief-sidecar/memories.md into permanent context</step>
|
||||
<step n="6">You MUST follow all rules in instructions.md on EVERY interaction</step>
|
||||
<step n="7">PRIMARY domain is {project-root}/tools/cli/ - this is your territory</step>
|
||||
<step n="8">You may read other project files for context but focus changes on CLI domain</step>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
- VERIFY: If config not loaded, STOP and report error to user
|
||||
- DO NOT PROCEED to step 3 until config is successfully loaded and variables stored</step>
|
||||
<step n="3">Remember: user's name is {user_name}</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/src/modules/bmd/agents/doc-keeper-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/src/modules/bmd/agents/doc-keeper-sidecar/memories.md into permanent context</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/bmd/agents/doc-keeper-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/bmd/agents/doc-keeper-sidecar/memories.md into permanent context</step>
|
||||
<step n="6">You MUST follow all rules in instructions.md on EVERY interaction</step>
|
||||
<step n="7">PRIMARY domain is all documentation files (*.md, README, guides, examples)</step>
|
||||
<step n="8">Monitor code changes that affect documented behavior</step>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
- VERIFY: If config not loaded, STOP and report error to user
|
||||
- DO NOT PROCEED to step 3 until config is successfully loaded and variables stored</step>
|
||||
<step n="3">Remember: user's name is {user_name}</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/src/modules/bmd/agents/release-chief-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/src/modules/bmd/agents/release-chief-sidecar/memories.md into permanent context</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/bmd/agents/release-chief-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/bmd/agents/release-chief-sidecar/memories.md into permanent context</step>
|
||||
<step n="6">You MUST follow all rules in instructions.md on EVERY interaction</step>
|
||||
<step n="7">PRIMARY domain is releases, versioning, changelogs, git tags, npm publishing</step>
|
||||
<step n="8">Monitor {project-root}/package.json for version management</step>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
- VERIFY: If config not loaded, STOP and report error to user
|
||||
- DO NOT PROCEED to step 3 until config is successfully loaded and variables stored</step>
|
||||
<step n="3">Remember: user's name is {user_name}</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/src/modules/bmd/agents/cli-chief-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/src/modules/bmd/agents/cli-chief-sidecar/memories.md into permanent context</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/bmd/agents/cli-chief-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/bmd/agents/cli-chief-sidecar/memories.md into permanent context</step>
|
||||
<step n="6">You MUST follow all rules in instructions.md on EVERY interaction</step>
|
||||
<step n="7">PRIMARY domain is {project-root}/tools/cli/ - this is your territory</step>
|
||||
<step n="8">You may read other project files for context but focus changes on CLI domain</step>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
- VERIFY: If config not loaded, STOP and report error to user
|
||||
- DO NOT PROCEED to step 3 until config is successfully loaded and variables stored</step>
|
||||
<step n="3">Remember: user's name is {user_name}</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/src/modules/bmd/agents/doc-keeper-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/src/modules/bmd/agents/doc-keeper-sidecar/memories.md into permanent context</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/bmd/agents/doc-keeper-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/bmd/agents/doc-keeper-sidecar/memories.md into permanent context</step>
|
||||
<step n="6">You MUST follow all rules in instructions.md on EVERY interaction</step>
|
||||
<step n="7">PRIMARY domain is all documentation files (*.md, README, guides, examples)</step>
|
||||
<step n="8">Monitor code changes that affect documented behavior</step>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
- VERIFY: If config not loaded, STOP and report error to user
|
||||
- DO NOT PROCEED to step 3 until config is successfully loaded and variables stored</step>
|
||||
<step n="3">Remember: user's name is {user_name}</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/src/modules/bmd/agents/release-chief-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/src/modules/bmd/agents/release-chief-sidecar/memories.md into permanent context</step>
|
||||
<step n="4">Load COMPLETE file {project-root}/bmd/agents/release-chief-sidecar/instructions.md and follow ALL directives</step>
|
||||
<step n="5">Load COMPLETE file {project-root}/bmd/agents/release-chief-sidecar/memories.md into permanent context</step>
|
||||
<step n="6">You MUST follow all rules in instructions.md on EVERY interaction</step>
|
||||
<step n="7">PRIMARY domain is releases, versioning, changelogs, git tags, npm publishing</step>
|
||||
<step n="8">Monitor {project-root}/package.json for version management</step>
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ agent:
|
|||
|
||||
critical_actions:
|
||||
# CRITICAL: Load sidecar files FIRST for Expert agent
|
||||
- Load COMPLETE file {project-root}/src/modules/bmd/agents/cli-chief-sidecar/instructions.md and follow ALL directives
|
||||
- Load COMPLETE file {project-root}/src/modules/bmd/agents/cli-chief-sidecar/memories.md into permanent context
|
||||
- Load COMPLETE file {project-root}/bmd/agents/cli-chief-sidecar/instructions.md and follow ALL directives
|
||||
- Load COMPLETE file {project-root}/bmd/agents/cli-chief-sidecar/memories.md into permanent context
|
||||
- You MUST follow all rules in instructions.md on EVERY interaction
|
||||
# Domain restriction for CLI focus
|
||||
- PRIMARY domain is {project-root}/tools/cli/ - this is your territory
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ agent:
|
|||
|
||||
critical_actions:
|
||||
# CRITICAL: Load sidecar files FIRST for Expert agent
|
||||
- Load COMPLETE file {project-root}/src/modules/bmd/agents/doc-keeper-sidecar/instructions.md and follow ALL directives
|
||||
- Load COMPLETE file {project-root}/src/modules/bmd/agents/doc-keeper-sidecar/memories.md into permanent context
|
||||
- Load COMPLETE file {project-root}/bmd/agents/doc-keeper-sidecar/instructions.md and follow ALL directives
|
||||
- Load COMPLETE file {project-root}/bmd/agents/doc-keeper-sidecar/memories.md into permanent context
|
||||
- You MUST follow all rules in instructions.md on EVERY interaction
|
||||
# Domain restriction for documentation focus
|
||||
- PRIMARY domain is all documentation files (*.md, README, guides, examples)
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ agent:
|
|||
|
||||
critical_actions:
|
||||
# CRITICAL: Load sidecar files FIRST for Expert agent
|
||||
- Load COMPLETE file {project-root}/src/modules/bmd/agents/release-chief-sidecar/instructions.md and follow ALL directives
|
||||
- Load COMPLETE file {project-root}/src/modules/bmd/agents/release-chief-sidecar/memories.md into permanent context
|
||||
- Load COMPLETE file {project-root}/bmd/agents/release-chief-sidecar/instructions.md and follow ALL directives
|
||||
- Load COMPLETE file {project-root}/bmd/agents/release-chief-sidecar/memories.md into permanent context
|
||||
- You MUST follow all rules in instructions.md on EVERY interaction
|
||||
# Domain restriction for release focus
|
||||
- PRIMARY domain is releases, versioning, changelogs, git tags, npm publishing
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ my-custom-module/
|
|||
|
||||
### Example: install-config.yaml
|
||||
|
||||
**Reference**: `/Users/brianmadison/dev/BMAD-METHOD/src/modules/bmm/_module-installer/install-config.yaml`
|
||||
**Reference**: `/src/modules/bmm/_module-installer/install-config.yaml`
|
||||
|
||||
```yaml
|
||||
# Module metadata
|
||||
|
|
|
|||
|
|
@ -1,328 +0,0 @@
|
|||
# BMAD Workflow Conditional Execution Antipattern Audit
|
||||
|
||||
**Date:** 2025-10-22
|
||||
**Auditor:** Claude Code (BMad Builder Agent)
|
||||
**Scope:** All markdown files under `src/`
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Critical Issue Identified:** Invalid self-closing `<check>` tag pattern found throughout BMAD workflow codebase.
|
||||
|
||||
**Impact:**
|
||||
|
||||
- **98 instances** across **14 workflow files**
|
||||
- Affects core workflows, builder workflows, and method workflows
|
||||
- Creates XML parsing ambiguity and unpredictable LLM behavior
|
||||
- Violates BMAD workflow specification (workflow.xml)
|
||||
|
||||
**Severity:** CRITICAL - Invalid XML structure, ambiguous conditional scope
|
||||
|
||||
---
|
||||
|
||||
## The Antipattern
|
||||
|
||||
### Invalid Pattern (Self-Closing Check)
|
||||
|
||||
```xml
|
||||
<!-- ❌ WRONG - Invalid XML structure -->
|
||||
<check>If condition met:</check>
|
||||
<action>Do something</action>
|
||||
<action>Do something else</action>
|
||||
```
|
||||
|
||||
**Problems:**
|
||||
|
||||
1. Check tag doesn't wrap anything (invalid XML)
|
||||
2. Ambiguous scope - unclear which actions are conditional
|
||||
3. Breaks formatters and parsers
|
||||
4. Not part of BMAD workflow spec
|
||||
5. Unpredictable LLM interpretation
|
||||
|
||||
### Correct Patterns
|
||||
|
||||
**Single conditional action:**
|
||||
|
||||
```xml
|
||||
<!-- ✅ CORRECT - Inline conditional -->
|
||||
<action if="condition met">Do something</action>
|
||||
```
|
||||
|
||||
**Multiple conditional actions:**
|
||||
|
||||
```xml
|
||||
<!-- ✅ CORRECT - Proper wrapper block -->
|
||||
<check if="condition met">
|
||||
<action>Do something</action>
|
||||
<action>Do something else</action>
|
||||
</check>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Audit Results
|
||||
|
||||
### Total Count
|
||||
|
||||
- **Total Instances:** 98
|
||||
- **Affected Files:** 14
|
||||
- **Modules Affected:** 4 (BMB, BMM, CIS, Core)
|
||||
|
||||
### Breakdown by File
|
||||
|
||||
| File | Count | Priority |
|
||||
| -------------------------------------------------------------------- | ----- | ----------- |
|
||||
| `modules/bmb/workflows/edit-workflow/instructions.md` | 21 | 🔴 CRITICAL |
|
||||
| `modules/bmm/workflows/4-implementation/dev-story/instructions.md` | 13 | 🔴 CRITICAL |
|
||||
| `modules/bmb/workflows/convert-legacy/instructions.md` | 13 | 🔴 CRITICAL |
|
||||
| `modules/bmb/workflows/create-module/instructions.md` | 12 | 🔴 CRITICAL |
|
||||
| `modules/bmb/workflows/create-agent/instructions.md` | 11 | 🔴 CRITICAL |
|
||||
| `core/workflows/party-mode/instructions.md` | 7 | 🟡 HIGH |
|
||||
| `modules/bmm/workflows/4-implementation/correct-course/checklist.md` | 5 | 🟡 HIGH |
|
||||
| `core/workflows/brainstorming/instructions.md` | 5 | 🟡 HIGH |
|
||||
| `modules/cis/workflows/storytelling/instructions.md` | 3 | 🟢 MEDIUM |
|
||||
| `modules/bmb/workflows/audit-workflow/instructions.md` | 3 | 🟢 MEDIUM |
|
||||
| `modules/bmb/workflows/create-workflow/workflow-creation-guide.md` | 2 | 🟢 MEDIUM |
|
||||
| `modules/bmm/workflows/1-analysis/product-brief/instructions.md` | 1 | 🟢 LOW |
|
||||
| `modules/bmm/workflows/1-analysis/game-brief/instructions.md` | 1 | 🟢 LOW |
|
||||
| `modules/bmb/workflows/redoc/instructions.md` | 1 | 🟢 LOW |
|
||||
|
||||
### Breakdown by Module
|
||||
|
||||
**BMB (Builder) Module: 63 instances (64%)**
|
||||
|
||||
- edit-workflow: 21
|
||||
- convert-legacy: 13
|
||||
- create-module: 12
|
||||
- create-agent: 11
|
||||
- audit-workflow: 3
|
||||
- create-workflow: 2
|
||||
- redoc: 1
|
||||
|
||||
**BMM (Method) Module: 20 instances (20%)**
|
||||
|
||||
- dev-story: 13
|
||||
- correct-course: 5
|
||||
- product-brief: 1
|
||||
- game-brief: 1
|
||||
|
||||
**Core Workflows: 12 instances (12%)**
|
||||
|
||||
- party-mode: 7
|
||||
- brainstorming: 5
|
||||
|
||||
**CIS (Creative) Module: 3 instances (3%)**
|
||||
|
||||
- storytelling: 3
|
||||
|
||||
---
|
||||
|
||||
## Example Instances
|
||||
|
||||
### Example 1: create-agent/instructions.md (Line 13-20)
|
||||
|
||||
**BEFORE (Invalid):**
|
||||
|
||||
```xml
|
||||
<check>If yes:</check>
|
||||
<action>Invoke brainstorming workflow: {project-root}/bmad/core/workflows/brainstorming/workflow.yaml</action>
|
||||
<action>Pass context data: {installed_path}/brainstorm-context.md</action>
|
||||
<action>Wait for brainstorming session completion</action>
|
||||
|
||||
<check>If no:</check>
|
||||
<action>Proceed directly to Step 0</action>
|
||||
```
|
||||
|
||||
**AFTER (Correct):**
|
||||
|
||||
```xml
|
||||
<check if="user answered yes">
|
||||
<action>Invoke brainstorming workflow: {project-root}/bmad/core/workflows/brainstorming/workflow.yaml</action>
|
||||
<action>Pass context data: {installed_path}/brainstorm-context.md</action>
|
||||
<action>Wait for brainstorming session completion</action>
|
||||
</check>
|
||||
|
||||
<check if="user answered no">
|
||||
<action>Proceed directly to Step 0</action>
|
||||
</check>
|
||||
```
|
||||
|
||||
### Example 2: dev-story/instructions.md (Line 54-55)
|
||||
|
||||
**BEFORE (Invalid):**
|
||||
|
||||
```xml
|
||||
<check>If story file inaccessible → HALT: "Cannot develop story without access to story file"</check>
|
||||
<check>If task requirements ambiguous → ASK user to clarify or HALT</check>
|
||||
```
|
||||
|
||||
**AFTER (Correct):**
|
||||
|
||||
```xml
|
||||
<action if="story file inaccessible">HALT: "Cannot develop story without access to story file"</action>
|
||||
<action if="task requirements ambiguous">ASK user to clarify or HALT</action>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact Assessment
|
||||
|
||||
### Technical Impact
|
||||
|
||||
1. **XML Validity:** Invalid structure violates XML parsing rules
|
||||
2. **Formatter Confusion:** Custom formatters incorrectly indent following content
|
||||
3. **Scope Ambiguity:** Unclear which actions are inside vs outside conditional
|
||||
4. **Maintainability:** Future developers confused by ambiguous structure
|
||||
|
||||
### LLM Adherence Impact
|
||||
|
||||
**Potential Issues:**
|
||||
|
||||
- LLM may interpret check as unconditional statement
|
||||
- Actions may execute when they shouldn't (or vice versa)
|
||||
- Inconsistent behavior across different LLMs
|
||||
- Unpredictable results in complex nested conditionals
|
||||
|
||||
**Observed Behavior:**
|
||||
|
||||
- LLMs often "figure it out" through context and proximity
|
||||
- Colon (`:`) pattern signals "here's what to do"
|
||||
- Works in simple cases but risky in complex logic
|
||||
|
||||
**Risk Level:** MEDIUM-HIGH
|
||||
|
||||
- May work "most of the time" but unreliable
|
||||
- Fails in edge cases and complex nested logic
|
||||
- Future LLMs may interpret differently
|
||||
|
||||
---
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
### Why This Happened
|
||||
|
||||
1. **Implicit convention evolved** - Self-closing check pattern emerged organically
|
||||
2. **No enforcement** - No linter or validator caught the pattern
|
||||
3. **Copy-paste propagation** - Pattern copied across workflows
|
||||
4. **Formatting hid the issue** - Manual indentation made it "look right"
|
||||
5. **LLM tolerance** - Current LLMs often figured it out despite ambiguity
|
||||
|
||||
### Meta-Problem
|
||||
|
||||
**The workflows that CREATE workflows have the antipattern:**
|
||||
|
||||
- create-workflow: 2 instances
|
||||
- create-agent: 11 instances
|
||||
- create-module: 12 instances
|
||||
- edit-workflow: 21 instances
|
||||
- convert-legacy: 13 instances
|
||||
|
||||
This means NEW workflows were being created WITH the antipattern built-in!
|
||||
|
||||
---
|
||||
|
||||
## Remediation Plan
|
||||
|
||||
### Phase 1: Immediate (High Priority Files)
|
||||
|
||||
Fix top 5 offenders (63% of problem):
|
||||
|
||||
1. edit-workflow (21 instances)
|
||||
2. dev-story (13 instances)
|
||||
3. convert-legacy (13 instances)
|
||||
4. create-module (12 instances)
|
||||
5. create-agent (11 instances)
|
||||
|
||||
**Total:** 70 instances (71% of problem)
|
||||
|
||||
### Phase 2: Core Workflows
|
||||
|
||||
Fix core workflows (critical for all modules):
|
||||
|
||||
1. party-mode (7 instances)
|
||||
2. brainstorming (5 instances)
|
||||
|
||||
**Total:** 12 instances
|
||||
|
||||
### Phase 3: Remaining
|
||||
|
||||
Fix remaining 16 instances across 7 files
|
||||
|
||||
### Phase 4: Prevention
|
||||
|
||||
1. **Update audit-workflow** ✅ DONE
|
||||
- Added antipattern detection to Step 4
|
||||
- Flags with CRITICAL severity
|
||||
- Suggests correct patterns
|
||||
|
||||
2. **Update creation guide** ✅ DONE
|
||||
- Documented antipattern in workflow-creation-guide.md
|
||||
- Clear examples of correct vs incorrect patterns
|
||||
- Added to best practices
|
||||
|
||||
3. **Update checklist** ✅ DONE
|
||||
- Added validation criteria to checklist.md
|
||||
- 3 new checks for conditional execution patterns
|
||||
|
||||
4. **Create automated fixer** (TODO)
|
||||
- Bulk conversion script for remaining instances
|
||||
- Pattern matching and replacement logic
|
||||
|
||||
---
|
||||
|
||||
## Specification Reference
|
||||
|
||||
From `bmad/core/tasks/workflow.xml`:
|
||||
|
||||
```xml
|
||||
<tag>action if="condition" - Single conditional action (inline, no closing tag needed)</tag>
|
||||
<tag>check if="condition">...</check> - Conditional block wrapping multiple items (closing tag required)</tag>
|
||||
```
|
||||
|
||||
**Key Point:** Check tags MUST have `if=""` attribute and MUST wrap content with closing tag.
|
||||
|
||||
The self-closing pattern `<check>text</check>` is **NOT in the spec** and is **invalid**.
|
||||
|
||||
---
|
||||
|
||||
## Detection Command
|
||||
|
||||
To find all instances:
|
||||
|
||||
```bash
|
||||
grep -r "<check>[^<]*</check>" src --include="*.md" -n
|
||||
```
|
||||
|
||||
To count by file:
|
||||
|
||||
```bash
|
||||
grep -r "<check>[^<]*</check>" src --include="*.md" -c | grep -v ":0$"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Actions
|
||||
|
||||
- [ ] Create bulk-fix script for automated conversion
|
||||
- [ ] Fix Phase 1 files (70 instances)
|
||||
- [ ] Fix Phase 2 files (12 instances)
|
||||
- [ ] Fix Phase 3 files (16 instances)
|
||||
- [ ] Run audit-workflow on all fixed files to verify
|
||||
- [ ] Update formatter to detect and warn on antipattern
|
||||
- [ ] Add pre-commit hook to prevent future instances
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **Workflow Spec:** `bmad/core/tasks/workflow.xml`
|
||||
- **Creation Guide:** `src/modules/bmb/workflows/create-workflow/workflow-creation-guide.md`
|
||||
- **Audit Workflow:** `src/modules/bmb/workflows/audit-workflow/`
|
||||
- **This Report:** `docs/antipattern-audit-2025-10-22.md`
|
||||
|
||||
---
|
||||
|
||||
**Report Status:** Complete
|
||||
**Action Required:** Yes - 98 instances need remediation
|
||||
**Priority:** CRITICAL - Affects core functionality and workflow creation
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Codebase Flattener Tool
|
||||
|
||||
BMad-Core includes a powerful codebase flattener for preparing existing projects to the web for AI Analysis
|
||||
|
||||
```bash
|
||||
# Basic usage - creates flattened-codebase.xml
|
||||
npx bmad-method flatten
|
||||
|
||||
# Custom input/output
|
||||
npx bmad-method flatten --input /path/to/source --output project.xml
|
||||
```
|
||||
|
||||
Features:
|
||||
|
||||
- AI-optimized XML output format
|
||||
- Smart filtering with .gitignore respect
|
||||
- Binary file detection and exclusion
|
||||
- Real-time progress tracking
|
||||
- Comprehensive completion statistics
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
# Legacy Task Conversion Report
|
||||
|
||||
**Generated**: 2025-10-26
|
||||
**Converted By**: BMad Builder Agent
|
||||
**Conversion Type**: Legacy Task → v6 XML Task
|
||||
|
||||
---
|
||||
|
||||
## Source Information
|
||||
|
||||
- **Original Location**: https://github.com/bmad-code-org/BMAD-METHOD/blob/main/bmad-core/tasks/shard-doc.md
|
||||
- **Original Format**: Markdown task (v4/legacy format)
|
||||
- **Original Type**: Document sharding utility task
|
||||
|
||||
---
|
||||
|
||||
## Target Information
|
||||
|
||||
- **New Location**: `/Users/brianmadison/dev/BMAD-METHOD/src/core/tasks/shard-doc.xml`
|
||||
- **New Format**: v6 XML task format
|
||||
- **Task ID**: `bmad/core/tasks/shard-doc`
|
||||
- **Task Name**: Shard Document
|
||||
|
||||
---
|
||||
|
||||
## Conversion Summary
|
||||
|
||||
### Components Converted
|
||||
|
||||
✅ **Task Structure**: Converted from markdown to XML format
|
||||
✅ **Execution Flow**: 6 steps properly structured in `<flow>` section (simplified to tool-only)
|
||||
✅ **Critical Instructions**: Moved to `<llm critical="true">` section
|
||||
✅ **Validation Rules**: Extracted to `<validation>` section
|
||||
✅ **Halt Conditions**: Extracted to `<halt-conditions>` section
|
||||
✅ **Special Guidelines**: Moved to `<critical-context>` section
|
||||
✅ **Output Format**: Documented in `<output-format>` section
|
||||
✅ **Tool Information**: Preserved in `<tool-info>` section
|
||||
|
||||
### Key Features Preserved
|
||||
|
||||
- **Automated Tool**: Uses @kayvan/markdown-tree-parser exclusively
|
||||
- **Simplified Flow**: Removed all manual steps, tool handles everything
|
||||
- **Code Block Awareness**: Tool automatically handles ## inside code blocks
|
||||
- **Content Preservation**: Tool preserves all markdown formatting and special content
|
||||
- **Heading Adjustment**: Tool automatically reduces heading levels by one
|
||||
- **Index Generation**: Tool automatically creates index.md
|
||||
- **Validation Steps**: Verification of tool installation and output
|
||||
- **Error Handling**: Halt conditions for tool and file system issues
|
||||
|
||||
### v6 Conventions Applied
|
||||
|
||||
- ✅ Proper `<task>` wrapper with id and name attributes
|
||||
- ✅ `<llm critical="true">` section with mandatory instructions
|
||||
- ✅ `<flow>` section with numbered `<step>` elements
|
||||
- ✅ Used `<action>` tags for required actions
|
||||
- ✅ Used `<i>` tags for instruction lists and context
|
||||
- ✅ Conditional logic with `if` attributes on actions
|
||||
- ✅ Optional steps marked with `optional="true"`
|
||||
- ✅ Supporting sections for validation, halt conditions, output format
|
||||
- ✅ Consistent with existing v6 tasks (workflow.xml, adv-elicit.xml, index-docs.xml)
|
||||
|
||||
---
|
||||
|
||||
## Structural Comparison
|
||||
|
||||
### Legacy Format (Markdown)
|
||||
|
||||
```
|
||||
# Document Sharding Task
|
||||
## Primary Method: Automated Approach
|
||||
## Manual Method (Fallback)
|
||||
1. Identify target location
|
||||
2. Parse sections
|
||||
...
|
||||
## Critical Guidelines
|
||||
```
|
||||
|
||||
### v6 Format (XML)
|
||||
|
||||
```xml
|
||||
<task id="bmad/core/tasks/shard-doc" name="Shard Document">
|
||||
<objective>...</objective>
|
||||
<llm critical="true">...</llm>
|
||||
<critical-context>...</critical-context>
|
||||
<flow>
|
||||
<step n="1" title="...">
|
||||
<action>...</action>
|
||||
</step>
|
||||
</flow>
|
||||
<halt-conditions>...</halt-conditions>
|
||||
<validation>...</validation>
|
||||
</task>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation Results
|
||||
|
||||
### XML Structure
|
||||
|
||||
- ✅ Valid XML syntax
|
||||
- ✅ Properly nested elements
|
||||
- ✅ All tags closed correctly
|
||||
- ✅ Attributes properly formatted
|
||||
|
||||
### v6 Compliance
|
||||
|
||||
- ✅ Matches v6 task conventions
|
||||
- ✅ Follows existing core task patterns
|
||||
- ✅ Uses standard v6 tag set
|
||||
- ✅ Proper section organization
|
||||
|
||||
### Content Integrity
|
||||
|
||||
- ✅ All original instructions preserved
|
||||
- ✅ No functionality lost in conversion
|
||||
- ✅ Critical warnings maintained
|
||||
- ✅ Tool information preserved
|
||||
- ✅ Validation logic intact
|
||||
|
||||
### File System
|
||||
|
||||
- ✅ Saved to correct location: `src/core/tasks/`
|
||||
- ✅ Proper filename: `shard-doc.xml`
|
||||
- ✅ Follows core task naming convention
|
||||
|
||||
---
|
||||
|
||||
## Usage Notes
|
||||
|
||||
### How to Invoke This Task
|
||||
|
||||
From an agent or workflow, reference:
|
||||
|
||||
```xml
|
||||
<invoke-task>{project-root}/bmad/core/tasks/shard-doc.xml</invoke-task>
|
||||
```
|
||||
|
||||
Or from agent menu:
|
||||
|
||||
```yaml
|
||||
menu:
|
||||
- trigger: shard
|
||||
description: 'Split large document into organized files'
|
||||
exec: '{project-root}/bmad/core/tasks/shard-doc.xml'
|
||||
```
|
||||
|
||||
### Task Capabilities
|
||||
|
||||
1. **Automated Mode**: Uses @kayvan/markdown-tree-parser for fast sharding
|
||||
2. **Manual Mode**: Step-by-step guided process for controlled sharding
|
||||
3. **Safety Checks**: Validates code blocks aren't treated as headers
|
||||
4. **Content Preservation**: Maintains all formatting, code, tables, diagrams
|
||||
5. **Index Generation**: Creates navigable index.md automatically
|
||||
6. **Validation**: Verifies completeness and integrity
|
||||
|
||||
---
|
||||
|
||||
## Post-Conversion Actions
|
||||
|
||||
### Recommended Next Steps
|
||||
|
||||
1. ✅ **Task Created**: File saved to core tasks directory
|
||||
2. **Test Task**: Invoke from an agent or workflow to verify functionality
|
||||
3. **Update Documentation**: Reference in core task documentation if needed
|
||||
4. **Integration**: Add to relevant agent menus if appropriate
|
||||
|
||||
### No Manual Adjustments Required
|
||||
|
||||
The conversion is complete and ready for use. All legacy functionality has been successfully migrated to v6 XML format.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Original legacy file can be archived or left as-is (located on GitHub)
|
||||
- This task is now a core BMAD task available to all modules
|
||||
- The task follows v6 conventions and is fully compatible with BMAD-CORE v6 alpha
|
||||
- **UPDATED 2025-10-26**: Manual fallback steps removed - task now exclusively uses @kayvan/markdown-tree-parser
|
||||
- Flow simplified from 8 steps to 6 steps (tool installation → execution → verification)
|
||||
- All manual parsing, file creation, and index generation logic removed (tool handles automatically)
|
||||
|
||||
---
|
||||
|
||||
**Conversion Status**: ✅ COMPLETE (Updated)
|
||||
**Ready for Use**: YES
|
||||
**Manual Adjustments Needed**: NONE
|
||||
**Approach**: Automated tool only (no manual fallback)
|
||||
|
|
@ -184,13 +184,3 @@ injections:
|
|||
<cmds>...</cmds>
|
||||
</agent>
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Injection points are properly named and unique
|
||||
- [ ] injections.yaml is valid YAML with correct structure
|
||||
- [ ] Content formatting is preserved after injection
|
||||
- [ ] Installation works without the IDE (injection points removed)
|
||||
- [ ] Installation works with the IDE (content properly injected)
|
||||
- [ ] Subagents/files are copied to correct locations
|
||||
- [ ] No IDE-specific content remains when different IDE selected
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# BMAD v6 Installation & Module System Reference
|
||||
# BMAD Installation & Module System Reference
|
||||
|
||||
## Table of Contents
|
||||
|
||||
|
|
@ -13,63 +13,36 @@
|
|||
|
||||
## Overview
|
||||
|
||||
BMAD v6 is a modular AI agent framework with intelligent installation, platform-agnostic support, and configuration inheritance.
|
||||
BMad Core is a modular AI agent framework with intelligent installation, platform-agnostic support, and configuration inheritance.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Modular Design**: Core + optional modules (BMM, CIS)
|
||||
- **Modular Design**: Core + optional modules (BMB, BMM, CIS)
|
||||
- **Smart Installation**: Interactive configuration with dependency resolution
|
||||
- **Multi-Platform**: Supports 15+ AI coding platforms
|
||||
- **Clean Architecture**: Centralized `bmad/` directory, no source pollution
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Interactive installation (recommended)
|
||||
bmad install
|
||||
|
||||
# Install specific modules
|
||||
bmad install -m bmm cis
|
||||
|
||||
# Full installation
|
||||
bmad install -f
|
||||
|
||||
# Check status
|
||||
bmad status
|
||||
```
|
||||
|
||||
### Installation Options
|
||||
|
||||
- `-d <path>`: Target directory (default: current)
|
||||
- `-m <modules...>`: Specific modules (bmm, cis)
|
||||
- `-f`: Full installation
|
||||
- `-c`: Core only
|
||||
- `-i <ide...>`: Configure specific IDEs
|
||||
- `--skip-ide`: Skip IDE configuration
|
||||
- `-v`: Verbose output
|
||||
- **Clean Architecture**: Centralized `bmad/` directory add to project, no source pollution with multiple folders added
|
||||
|
||||
## Architecture
|
||||
|
||||
### Directory Structure
|
||||
### Directory Structure upon installation
|
||||
|
||||
```
|
||||
project-root/
|
||||
├── bmad/ # Centralized installation
|
||||
│ ├── _cfg/ # Configuration
|
||||
│ │ ├── agents/ # Agent configs
|
||||
│ │ └── agent-party.xml # Agent manifest
|
||||
│ ├── core/ # Core module
|
||||
├── bmad/ # Centralized installation
|
||||
│ ├── _cfg/ # Configuration
|
||||
│ │ ├── agents/ # Agent configs
|
||||
│ │ └── agent-manifest.csv # Agent manifest
|
||||
│ ├── core/ # Core module
|
||||
│ │ ├── agents/
|
||||
│ │ ├── tasks/
|
||||
│ │ └── config.yaml
|
||||
│ ├── bmm/ # BMad Method module
|
||||
│ ├── bmm/ # BMad Method module
|
||||
│ │ ├── agents/
|
||||
│ │ ├── tasks/
|
||||
│ │ ├── templates/
|
||||
│ │ ├── workflows/
|
||||
│ │ └── config.yaml
|
||||
│ └── cis/ # Creative Innovation Studio
|
||||
│ └── cis/ # Creative Innovation Studio
|
||||
│ └── ...
|
||||
└── .claude/ # Platform-specific (example)
|
||||
└── .claude/ # Platform-specific (example)
|
||||
└── agents/
|
||||
```
|
||||
|
||||
|
|
@ -78,11 +51,10 @@ project-root/
|
|||
1. **Detection**: Check existing installation
|
||||
2. **Selection**: Choose modules interactively or via CLI
|
||||
3. **Configuration**: Collect module-specific settings
|
||||
4. **Platform Setup**: Configure AI coding platforms
|
||||
5. **Installation**: Process and copy files
|
||||
6. **Generation**: Create config files with inheritance
|
||||
7. **Post-Install**: Run module installers
|
||||
8. **Manifest**: Track installed components
|
||||
4. **Installation**: Compile Process and copy files
|
||||
5. **Generation**: Create config files with inheritance
|
||||
6. **Post-Install**: Run module installers
|
||||
7. **Manifest**: Track installed components
|
||||
|
||||
### Key Exclusions
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
# Technical Decisions Log
|
||||
|
||||
_Auto-updated during discovery and planning sessions - you can also add information here yourself_
|
||||
|
||||
## Purpose
|
||||
|
||||
This document captures technical decisions, preferences, and constraints discovered during project discussions. It serves as input for architecture.md and solution design documents.
|
||||
|
||||
## Confirmed Decisions
|
||||
|
||||
<!-- Technical choices explicitly confirmed by the team/user -->
|
||||
|
||||
## Preferences
|
||||
|
||||
<!-- Non-binding preferences mentioned during discussions -->
|
||||
|
||||
## Constraints
|
||||
|
||||
<!-- Hard requirements from infrastructure, compliance, or integration needs -->
|
||||
|
||||
## To Investigate
|
||||
|
||||
<!-- Technical questions that need research or architect input -->
|
||||
|
||||
## Notes
|
||||
|
||||
- This file is automatically updated when technical information is mentioned
|
||||
- Decisions here are inputs, not final architecture
|
||||
- Final technical decisions belong in architecture.md
|
||||
- Implementation details belong in solutions/\*.md and story context or dev notes.
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
<task id="bmad/core/tasks/index-docs" name="Index Docs" webskip="true">
|
||||
<task id="bmad/core/tasks/index-docs" name="Index Docs"
|
||||
description="Generates or updates an index.md of all documents in the specified directory" webskip="true" standalone="true">
|
||||
<llm critical="true">
|
||||
<i>MANDATORY: Execute ALL steps in the flow section IN EXACT ORDER</i>
|
||||
<i>DO NOT skip steps or change the sequence</i>
|
||||
|
|
@ -17,7 +18,8 @@
|
|||
</step>
|
||||
|
||||
<step n="3" title="Generate Descriptions">
|
||||
<i>Read each file to understand its actual purpose and create brief (3-10 word) descriptions based on the content, not just the filename</i>
|
||||
<i>Read each file to understand its actual purpose and create brief (3-10 word) descriptions based on the content, not just the
|
||||
filename</i>
|
||||
</step>
|
||||
|
||||
<step n="4" title="Create/Update Index">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
<tool id="bmad/core/tasks/shard-doc" name="Shard Document"
|
||||
description="Splits large markdown documents into smaller, organized files based on level 2 (default) sections" webskip="true"
|
||||
standalone="true">
|
||||
<objective>Split large markdown documents into smaller, organized files based on level 2 sections using @kayvan/markdown-tree-parser tool</objective>
|
||||
|
||||
<llm critical="true">
|
||||
<i>MANDATORY: Execute ALL steps in the flow section IN EXACT ORDER</i>
|
||||
<i>DO NOT skip steps or change the sequence</i>
|
||||
<i>HALT immediately when halt-conditions are met</i>
|
||||
<i>Each action xml tag within step xml tag is a REQUIRED action to complete that step</i>
|
||||
<i>Sections outside flow (validation, output, critical-context) provide essential context - review and apply throughout execution</i>
|
||||
</llm>
|
||||
|
||||
<critical-context>
|
||||
<i>This task ONLY supports automated sharding via @kayvan/markdown-tree-parser</i>
|
||||
<i>The tool automatically handles: section splitting, heading level adjustment, code block detection, index generation</i>
|
||||
<i>All markdown formatting is preserved during sharding</i>
|
||||
</critical-context>
|
||||
|
||||
<flow>
|
||||
<step n="1" title="Verify Tool Installation">
|
||||
<action>Check if @kayvan/markdown-tree-parser is installed globally</action>
|
||||
<action>Run: npm list -g @kayvan/markdown-tree-parser</action>
|
||||
<action if="not installed">Inform user that tool needs to be installed</action>
|
||||
<action if="not installed">Run: npm install -g @kayvan/markdown-tree-parser</action>
|
||||
<action if="installation fails">HALT with error message about npm/node requirements</action>
|
||||
</step>
|
||||
|
||||
<step n="2" title="Get Source Document">
|
||||
<action>Ask user for the source document path if not provided already</action>
|
||||
<action>Verify file exists and is accessible</action>
|
||||
<action>Verify file is markdown format (.md extension)</action>
|
||||
<action if="file not found or not markdown">HALT with error message</action>
|
||||
</step>
|
||||
|
||||
<step n="3" title="Get Destination Folder">
|
||||
<action>Determine default destination: same location as source file, folder named after source file without .md extension</action>
|
||||
<action>Example: /path/to/architecture.md → /path/to/architecture/</action>
|
||||
<action>Ask user for the destination folder path ([y] to confirm use of default: [suggested-path], else enter a new path)</action>
|
||||
<action if="user accepts default">Use the suggested destination path</action>
|
||||
<action if="user provides custom path">Use the custom destination path</action>
|
||||
<action>Verify destination folder exists or can be created</action>
|
||||
<action>Check write permissions for destination</action>
|
||||
<action if="permission denied">HALT with error message</action>
|
||||
</step>
|
||||
|
||||
<step n="4" title="Execute Sharding">
|
||||
<action>Inform user that sharding is beginning</action>
|
||||
<action>Execute command: md-tree explode [source-document] [destination-folder]</action>
|
||||
<action>Capture command output and any errors</action>
|
||||
<action if="command fails">HALT and display error to user</action>
|
||||
</step>
|
||||
|
||||
<step n="5" title="Verify Output">
|
||||
<action>Check that destination folder contains sharded files</action>
|
||||
<action>Verify index.md was created in destination folder</action>
|
||||
<action>Count the number of files created</action>
|
||||
<action if="no files created">HALT with error message</action>
|
||||
</step>
|
||||
|
||||
<step n="6" title="Report Completion">
|
||||
<action>Display completion report to user including:</action>
|
||||
<i>- Source document path and name</i>
|
||||
<i>- Destination folder path</i>
|
||||
<i>- Number of section files created</i>
|
||||
<i>- Confirmation that index.md was created</i>
|
||||
<i>- Any tool output or warnings</i>
|
||||
<action>Inform user that sharding completed successfully</action>
|
||||
</step>
|
||||
</flow>
|
||||
|
||||
<halt-conditions critical="true">
|
||||
<i>HALT if @kayvan/markdown-tree-parser cannot be installed</i>
|
||||
<i>HALT if Node.js or npm is not available</i>
|
||||
<i>HALT if source document does not exist or is inaccessible</i>
|
||||
<i>HALT if source document is not markdown format (.md)</i>
|
||||
<i>HALT if destination folder cannot be created</i>
|
||||
<i>HALT if user does not have write permissions to destination</i>
|
||||
<i>HALT if md-tree explode command fails</i>
|
||||
<i>HALT if no output files were created</i>
|
||||
</halt-conditions>
|
||||
|
||||
<tool-info>
|
||||
<name>@kayvan/markdown-tree-parser</name>
|
||||
<command>md-tree explode [source-document] [destination-folder]</command>
|
||||
<installation>npm install -g @kayvan/markdown-tree-parser</installation>
|
||||
<requirements>
|
||||
<i>Node.js installed</i>
|
||||
<i>npm package manager</i>
|
||||
<i>Global npm installation permissions</i>
|
||||
</requirements>
|
||||
<features>
|
||||
<i>Automatic section splitting by level 2 headings</i>
|
||||
<i>Automatic heading level adjustment</i>
|
||||
<i>Handles edge cases (code blocks, diagrams)</i>
|
||||
<i>Generates navigable index.md</i>
|
||||
<i>Preserves all markdown formatting</i>
|
||||
</features>
|
||||
</tool-info>
|
||||
</tool>
|
||||
|
|
@ -27,6 +27,8 @@ brain_techniques: "{installed_path}/brain-methods.csv"
|
|||
# Output configuration
|
||||
default_output_file: "{output_folder}/brainstorming-session-results-{{date}}.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "brainstorming"
|
||||
description: "Facilitate interactive brainstorming sessions using diverse creative techniques. This workflow facilitates interactive brainstorming sessions using diverse creative techniques. The session is highly interactive, with the AI acting as a facilitator to guide the user through various ideation methods to generate and refine creative solutions."
|
||||
|
|
|
|||
|
|
@ -18,4 +18,6 @@ exit_triggers:
|
|||
- "end party mode"
|
||||
- "stop party mode"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -19,5 +19,7 @@ validation: "{installed_path}/checklist.md"
|
|||
# Output configuration
|
||||
default_output_file: "{output_folder}/audit-report-{{workflow_name}}-{{date}}.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
# Web bundle configuration
|
||||
web_bundle: false # BMB workflows run locally in BMAD-METHOD project
|
||||
|
|
|
|||
|
|
@ -29,4 +29,6 @@ sub_workflows:
|
|||
- create_workflow: "{project-root}/bmad/bmb/workflows/create-workflow/workflow.yaml"
|
||||
- create_module: "{project-root}/bmad/bmb/workflows/create-module/workflow.yaml"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ standalone_output_file: "{custom_agent_location}/{{agent_filename}}.agent.yaml"
|
|||
# Optional user override file (auto-created by installer if missing)
|
||||
config_output_file: "{project-root}/bmad/_cfg/agents/{{target_module}}-{{agent_filename}}.customize.yaml"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "create-agent"
|
||||
description: "Interactive workflow to build BMAD Core compliant agents (simple, expert, or module types) with optional brainstorming for agent ideas, proper persona development, activation rules, and command structure"
|
||||
|
|
|
|||
|
|
@ -38,5 +38,7 @@ validation: "{installed_path}/checklist.md"
|
|||
# Save to custom_module_location/{{module_code}}
|
||||
installer_output_folder: "{custom_module_location}/{{module_code}}"
|
||||
|
||||
standalone: true
|
||||
|
||||
# Web bundle configuration
|
||||
web_bundle: false # BMB workflows run locally in BMAD-METHOD project
|
||||
|
|
|
|||
|
|
@ -36,5 +36,7 @@ workflow_template_path: "{installed_path}/workflow-template"
|
|||
module_output_folder: "{project-root}/bmad/{{target_module}}/workflows/{{workflow_name}}"
|
||||
standalone_output_folder: "{custom_workflow_location}/{{workflow_name}}"
|
||||
|
||||
standalone: true
|
||||
|
||||
# Web bundle configuration
|
||||
web_bundle: false # BMB workflows run locally in BMAD-METHOD project
|
||||
|
|
|
|||
|
|
@ -23,5 +23,7 @@ template: false # This is an action workflow - no template needed
|
|||
instructions: "{installed_path}/instructions.md"
|
||||
validation: "{installed_path}/checklist.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
# Web bundle configuration
|
||||
web_bundle: false # BMB workflows run locally in BMAD-METHOD project
|
||||
|
|
|
|||
|
|
@ -25,5 +25,7 @@ validation: "{installed_path}/checklist.md"
|
|||
# Output configuration
|
||||
default_output_file: "{output_folder}/module-brief-{{module_code}}-{{date}}.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
# Web bundle configuration
|
||||
web_bundle: false # BMB workflows run locally in BMAD-METHOD project
|
||||
|
|
|
|||
|
|
@ -28,5 +28,7 @@ validation: "{installed_path}/checklist.md"
|
|||
# Configuration
|
||||
autonomous: true # Runs without user checkpoints unless clarification needed
|
||||
|
||||
standalone: true
|
||||
|
||||
# Web bundle configuration
|
||||
web_bundle: false # BMB workflows run locally in BMAD-METHOD project
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# BMM - BMad Method Module
|
||||
|
||||
The BMM (BMad Method Module) is the core orchestration system for the BMad Method v6a, providing comprehensive software development lifecycle management through specialized agents, workflows, teams, and tasks.
|
||||
The BMM (BMad Method Module) is the core orchestration system for the BMad Method, providing comprehensive software development lifecycle management through specialized agents, workflows, teams, and tasks.
|
||||
|
||||
## 📚 Essential Reading
|
||||
|
||||
|
|
@ -120,17 +120,13 @@ BMM integrates seamlessly with the BMad Core framework, leveraging:
|
|||
|
||||
- [BMM Workflows Guide](./workflows/README.md) - **Start here!**
|
||||
- [Test Architect (TEA) Guide](./testarch/README.md) - Quality assurance and testing strategy
|
||||
- [Agent Documentation](./agents/README.md) - Individual agent capabilities
|
||||
- [Team Configurations](./teams/README.md) - Pre-built team setups
|
||||
- [Task Library](./tasks/README.md) - Reusable task components
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always start with the workflows** - Let workflows guide your process
|
||||
2. **Respect the scale** - Don't over-document small projects
|
||||
3. **Embrace iteration** - Use retrospectives to continuously improve
|
||||
4. **Trust the process** - The v6a methodology has been carefully designed
|
||||
3. **Trust the process** - The methodology has been carefully designed
|
||||
|
||||
---
|
||||
|
||||
For detailed information about the complete BMad Method v6a workflow system, see the [BMM Workflows README](./workflows/README.md).
|
||||
For detailed information about the complete BMad Method workflow system, see the [BMM Workflows README](./workflows/README.md).
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ dev_story_location:
|
|||
# TEA Agent Configuration
|
||||
tea_use_mcp_enhancements:
|
||||
prompt: "Enable Playwright MCP capabilities (healing, exploratory, verification)?"
|
||||
default: true
|
||||
default: false
|
||||
result: "{value}"
|
||||
# kb_location:
|
||||
# prompt: "Where should bmad knowledge base articles be stored?"
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
<i>MANDATORY: Execute ALL steps in the flow section IN EXACT ORDER</i>
|
||||
<i>DO NOT skip steps or change the sequence</i>
|
||||
<i>HALT immediately when halt-conditions are met</i>
|
||||
<i>Each andlt;actionandgt; within andlt;stepandgt; is a REQUIRED action to complete that step</i>
|
||||
<i>Each action tag within a step tag is a REQUIRED action to complete that step</i>
|
||||
<i>Sections outside flow (validation, output, critical-context) provide essential context - review and apply throughout execution</i>
|
||||
</llm>
|
||||
<flow>
|
||||
<step n="1" title="Project Context Discovery">
|
||||
<action>Check for stories folder at {project-root}{output_folder}/stories/ directory</action>
|
||||
<action>Check for stories folder at {project-root}{output_folder}/stories/</action>
|
||||
<action>Find current story by identifying highest numbered story file</action>
|
||||
<action>Read story status (In Progress, Ready for Review, etc.)</action>
|
||||
<action>Extract agent notes from Dev Agent Record, TEA Results, PO Notes sections</action>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ game_brain_methods: "{installed_path}/game-brain-methods.csv"
|
|||
# CORE brainstorming workflow to invoke
|
||||
core_brainstorming: "{project-root}/bmad/core/workflows/brainstorming/workflow.yaml"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "brainstorm-game"
|
||||
description: "Facilitate game brainstorming sessions by orchestrating the CIS brainstorming workflow with game-specific context, guidance, and additional game design techniques."
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ project_context: "{installed_path}/project-context.md"
|
|||
# CORE brainstorming workflow to invoke
|
||||
core_brainstorming: "{project-root}/bmad/core/workflows/brainstorming/workflow.yaml"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "brainstorm-project"
|
||||
description: "Facilitate project brainstorming sessions by orchestrating the CIS brainstorming workflow with project-specific context and guidance."
|
||||
|
|
|
|||
|
|
@ -35,3 +35,7 @@ recommended_inputs:
|
|||
# Output configuration - Multiple files generated in output folder
|
||||
# Primary output: {output_folder}/index.md
|
||||
# Additional files generated by sub-workflows based on project structure
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false # BMM workflows run locally in BMAD-METHOD project
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ description: "Exhaustive deep-dive documentation of specific project areas"
|
|||
author: "BMad"
|
||||
|
||||
# This is a sub-workflow called by document-project/workflow.yaml
|
||||
parent_workflow: "{project-root}/src/modules/bmm/workflows/1-analysis/document-project/workflow.yaml"
|
||||
parent_workflow: "{project-root}/bmad/bmm/workflows/1-analysis/document-project/workflow.yaml"
|
||||
|
||||
# Critical variables inherited from parent
|
||||
config_source: "{project-root}/bmad/bmb/config.yaml"
|
||||
|
|
@ -13,13 +13,13 @@ user_name: "{config_source}:user_name"
|
|||
date: system-generated
|
||||
|
||||
# Module path and component files
|
||||
installed_path: "{project-root}/src/modules/bmm/workflows/1-analysis/document-project/workflows"
|
||||
installed_path: "{project-root}/bmad/bmm/workflows/1-analysis/document-project/workflows"
|
||||
template: false # Action workflow
|
||||
instructions: "{installed_path}/deep-dive-instructions.md"
|
||||
validation: "{project-root}/src/modules/bmm/workflows/1-analysis/document-project/checklist.md"
|
||||
validation: "{project-root}/bmad/bmm/workflows/1-analysis/document-project/checklist.md"
|
||||
|
||||
# Templates
|
||||
deep_dive_template: "{project-root}/src/modules/bmm/workflows/1-analysis/document-project/templates/deep-dive-template.md"
|
||||
deep_dive_template: "{project-root}/bmad/bmm/workflows/1-analysis/document-project/templates/deep-dive-template.md"
|
||||
|
||||
# Runtime inputs (passed from parent workflow)
|
||||
workflow_mode: "deep_dive"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ description: "Complete project documentation workflow (initial scan or full resc
|
|||
author: "BMad"
|
||||
|
||||
# This is a sub-workflow called by document-project/workflow.yaml
|
||||
parent_workflow: "{project-root}/src/modules/bmm/workflows/1-analysis/document-project/workflow.yaml"
|
||||
parent_workflow: "{project-root}/bmad/bmm/workflows/1-analysis/document-project/workflow.yaml"
|
||||
|
||||
# Critical variables inherited from parent
|
||||
config_source: "{project-root}/bmad/bmb/config.yaml"
|
||||
|
|
@ -13,15 +13,15 @@ user_name: "{config_source}:user_name"
|
|||
date: system-generated
|
||||
|
||||
# Data files
|
||||
project_types_csv: "{project-root}/src/modules/bmm/workflows/1-analysis/document-project/data/project-types.csv"
|
||||
documentation_requirements_csv: "{project-root}/src/modules/bmm/workflows/1-analysis/document-project/data/documentation-requirements.csv"
|
||||
architecture_registry_csv: "{project-root}/src/modules/bmm/workflows/1-analysis/document-project/data/architecture-registry.csv"
|
||||
project_types_csv: "{project-root}/bmad/bmm/workflows/1-analysis/document-project/data/project-types.csv"
|
||||
documentation_requirements_csv: "{project-root}/bmad/bmm/workflows/1-analysis/document-project/data/documentation-requirements.csv"
|
||||
architecture_registry_csv: "{project-root}/bmad/bmm/workflows/1-analysis/document-project/data/architecture-registry.csv"
|
||||
|
||||
# Module path and component files
|
||||
installed_path: "{project-root}/src/modules/bmm/workflows/1-analysis/document-project/workflows"
|
||||
installed_path: "{project-root}/bmad/bmm/workflows/1-analysis/document-project/workflows"
|
||||
template: false # Action workflow
|
||||
instructions: "{installed_path}/full-scan-instructions.md"
|
||||
validation: "{project-root}/src/modules/bmm/workflows/1-analysis/document-project/checklist.md"
|
||||
validation: "{project-root}/bmad/bmm/workflows/1-analysis/document-project/checklist.md"
|
||||
|
||||
# Runtime inputs (passed from parent workflow)
|
||||
workflow_mode: "" # "initial_scan" or "full_rescan"
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ validation: "{installed_path}/checklist.md"
|
|||
# Output configuration
|
||||
default_output_file: "{output_folder}/game-brief-{{game_name}}-{{date}}.md"
|
||||
|
||||
# Workflow settings
|
||||
autonomous: false # This is an interactive workflow requiring user collaboration
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "game-brief"
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ validation: "{installed_path}/checklist.md"
|
|||
# Output configuration
|
||||
default_output_file: "{output_folder}/product-brief-{{project_name}}-{{date}}.md"
|
||||
|
||||
# Workflow settings
|
||||
autonomous: false # This is an interactive workflow requiring user collaboration
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "product-brief"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ template_technical: "{installed_path}/template-technical.md"
|
|||
# Output configuration (dynamic based on research type selected in router)
|
||||
default_output_file: "{output_folder}/research-{{research_type}}-{{date}}.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "research"
|
||||
description: "Adaptive research workflow supporting multiple research types: market research, deep research prompt generation, technical/architecture evaluation, competitive intelligence, user research, and domain analysis"
|
||||
|
|
|
|||
|
|
@ -39,17 +39,4 @@ default_output_file: "{output_folder}/ux-design-specification.md"
|
|||
color_themes_html: "{output_folder}/ux-color-themes.html"
|
||||
design_directions_html: "{output_folder}/ux-design-directions.html"
|
||||
|
||||
# Workflow metadata
|
||||
version: "1.0.0"
|
||||
paradigm: "visual-collaboration-driven"
|
||||
execution_time: "45-120 minutes depending on project complexity and user engagement"
|
||||
features:
|
||||
- "Design system discovery and selection"
|
||||
- "Live HTML color theme visualization"
|
||||
- "6-8 design direction mockup generation"
|
||||
- "Adaptive facilitation by skill level"
|
||||
- "Novel UX pattern design for unique concepts"
|
||||
- "Progressive document building (saves after each step)"
|
||||
- "Visual decision-making with actual mockups"
|
||||
- "WebSearch for current design systems and trends"
|
||||
- "Serves as input to follow-up workflows (wireframes, Figma, prototypes, architecture)"
|
||||
standalone: true
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ recommended_inputs:
|
|||
- narrative_design: "{output_folder}/narrative-design.md"
|
||||
- market_research: "{output_folder}/market-research.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "gdd"
|
||||
description: "Game Design Document workflow for all game project levels - from small prototypes to full AAA games. Generates comprehensive GDD with game mechanics, systems, progression, and implementation guidance."
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ recommended_inputs:
|
|||
- gdd: "{output_folder}/GDD.md"
|
||||
- product_brief: "{output_folder}/product-brief.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "narrative"
|
||||
description: "Narrative design workflow for story-driven games and applications. Creates comprehensive narrative documentation including story structure, character arcs, dialogue systems, and narrative implementation guidance."
|
||||
|
|
|
|||
|
|
@ -26,13 +26,15 @@ status_file: "{output_folder}/bmm-workflow-status.md"
|
|||
default_output_file: "{output_folder}/PRD.md"
|
||||
epics_output_file: "{output_folder}/epics.md"
|
||||
technical_decisions_file: "{output_folder}/technical-decisions.md"
|
||||
technical_decisions_template: "{project-root}/src/modules/bmm/_module-installer/assets/technical-decisions.md"
|
||||
technical_decisions_template: "{project-root}/bmad/bmm/_module-installer/assets/technical-decisions.md"
|
||||
|
||||
# Recommended input documents
|
||||
recommended_inputs:
|
||||
- product_brief: "{output_folder}/product-brief.md"
|
||||
- market_research: "{output_folder}/market-research.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "prd"
|
||||
description: "Unified PRD workflow for project levels 2-4. Produces strategic PRD and tactical epic breakdown. Hands off to architecture workflow for technical design. Note: Level 0-1 use tech-spec workflow."
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ recommended_inputs:
|
|||
- bug_report: "Bug description or issue ticket"
|
||||
- feature_request: "Brief feature description"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "tech-spec-sm"
|
||||
description: "Technical specification workflow for Level 0-1 projects. Creates focused tech spec with story generation. Level 0: tech-spec + user story. Level 1: tech-spec + epic/stories."
|
||||
|
|
|
|||
|
|
@ -50,3 +50,5 @@ features:
|
|||
- "Novel pattern design for unique concepts"
|
||||
- "Intelligent pattern identification - LLM figures out what patterns matter"
|
||||
- "Implementation patterns for agent consistency"
|
||||
|
||||
standalone: true
|
||||
|
|
|
|||
|
|
@ -34,3 +34,7 @@ recommended_inputs:
|
|||
|
||||
# Validation criteria data
|
||||
validation_criteria: "{installed_path}/validation-criteria.yaml"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -40,4 +40,6 @@ execution_modes:
|
|||
- incremental: "Recommended - Refine each edit with user collaboration"
|
||||
- batch: "Present all changes at once for review"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -45,4 +45,6 @@ recommended_inputs:
|
|||
- prd: "PRD document"
|
||||
- architecture: "Architecture (optional)"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Develop Story - Workflow Instructions
|
||||
|
||||
```xml
|
||||
<critical>The workflow execution engine is governed by: {project_root}/bmad/core/tasks/workflow.xml</critical>
|
||||
<critical>The workflow execution engine is governed by: {project-root}/bmad/core/tasks/workflow.xml</critical>
|
||||
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
|
||||
<critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical>
|
||||
<critical>Generate all documents in {document_output_language}</critical>
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
<action>Parse sections: Story, Acceptance Criteria, Tasks/Subtasks, Dev Notes, Dev Agent Record, File List, Change Log, Status</action>
|
||||
|
||||
<action>Check if context file exists at: {{story_dir}}/{{story_key}}.context.md</action>
|
||||
<action>Check if context file exists at: {{story_dir}}/{{story_key}}.context.xml</action>
|
||||
<check if="context file exists">
|
||||
<action>Read COMPLETE context file</action>
|
||||
<action>Parse all sections: story details, artifacts (docs, code, dependencies), interfaces, constraints, tests</action>
|
||||
|
|
@ -105,15 +105,15 @@ Expected ready-for-dev or in-progress. Continuing anyway...
|
|||
<action if="new or different than what is documented dependencies are needed">ASK user for approval before adding</action>
|
||||
<action if="3 consecutive implementation failures occur">HALT and request guidance</action>
|
||||
<action if="required configuration is missing">HALT: "Cannot proceed without necessary configuration files"</action>
|
||||
<action if="{{run_until_complete}} == true">Do not stop after partial progress; continue iterating tasks until all ACs are satisfied or a HALT condition triggers</action>
|
||||
<critical>Do NOT propose to pause for review, standups, or validation until Step 6 gates are satisfied</critical>
|
||||
<critical>Do not stop after partial progress; continue iterating tasks until all ACs are satisfied and tested or a HALT condition triggers</critical>
|
||||
<critical>Do NOT propose to pause for review, stand-ups, or validation until Step 6 gates are satisfied</critical>
|
||||
</step>
|
||||
|
||||
<step n="3" goal="Author comprehensive tests">
|
||||
<action>Create unit tests for business logic and core functionality introduced/changed by the task</action>
|
||||
<action>Add integration tests for component interactions where applicable</action>
|
||||
<action>Include end-to-end tests for critical user flows if applicable</action>
|
||||
<action>Cover edge cases and error handling scenarios noted in the plan</action>
|
||||
<action>Add integration tests for component interactions where desired by test plan or story notes</action>
|
||||
<action>Include end-to-end tests for critical user flows where desired by test plan or story notes</action>
|
||||
<action>Cover edge cases and error handling scenarios noted in the test plan or story notes</action>
|
||||
</step>
|
||||
|
||||
<step n="4" goal="Run validations and tests">
|
||||
|
|
|
|||
|
|
@ -7,17 +7,22 @@ config_source: "{project-root}/bmad/bmm/config.yaml"
|
|||
output_folder: "{config_source}:output_folder"
|
||||
user_name: "{config_source}:user_name"
|
||||
communication_language: "{config_source}:communication_language"
|
||||
user_skill_level: "{config_source}:user_skill_level"
|
||||
document_output_language: "{config_source}:document_output_language"
|
||||
story_dir: "{config_source}:dev_story_location"
|
||||
context_path: "{config_source}:dev_story_location"
|
||||
run_until_complete: "{config_source}:run_until_complete"
|
||||
run_tests_command: "{config_source}:run_tests_command"
|
||||
date: system-generated
|
||||
|
||||
story_file: "" # Explicit story path; auto-discovered if empty
|
||||
# Context file uses same story_key as story file (e.g., "1-2-user-authentication.context.md")
|
||||
context_file: "{story_dir}/{{story_key}}.context.md"
|
||||
# Context file uses same story_key as story file (e.g., "1-2-user-authentication.context.xml")
|
||||
context_file: "{story_dir}/{{story_key}}.context.xml"
|
||||
|
||||
# Workflow components
|
||||
installed_path: "{project-root}/bmad/bmm/workflows/4-implementation/dev-story"
|
||||
instructions: "{installed_path}/instructions.md"
|
||||
validation: "{installed_path}/checklist.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -29,4 +29,6 @@ validation: "{installed_path}/checklist.md"
|
|||
# Output configuration
|
||||
default_output_file: "{output_folder}/tech-spec-epic-{{epic_id}}.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -40,4 +40,6 @@ validation_required:
|
|||
- technical_health: "Is codebase in stable, maintainable state?"
|
||||
- blocker_resolution: "Any unresolved blockers that will impact next epic?"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
</step>
|
||||
|
||||
<step n="2" goal="Resolve story context file and specification inputs">
|
||||
<action>Locate story context file: Under Dev Agent Record → Context Reference, read referenced path(s). If missing, search {{output_folder}} for files matching pattern "story-{{epic_num}}.{{story_num}}*.context.md" and use the most recent.</action>
|
||||
<action>Locate story context file: Under Dev Agent Record → Context Reference, read referenced path(s). If missing, search {{output_folder}} for files matching pattern "story-{{epic_num}}.{{story_num}}*.context.xml" and use the most recent.</action>
|
||||
<action if="no story context file found">Continue but record a WARNING in review notes: "No story context file found"</action>
|
||||
|
||||
<action>Locate Epic Tech Spec: Search {{tech_spec_search_dir}} with glob {{tech_spec_glob_template}} (resolve {{epic_num}})</action>
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ variables:
|
|||
recommended_inputs:
|
||||
- story: "Path to the story file (auto-discovered if omitted - finds first story with status 'review')"
|
||||
- tech_spec: "Epic technical specification document (auto-discovered)"
|
||||
- story_context_file: "Story context file (.context.md) (auto-discovered)"
|
||||
- story_context_file: "Story context file (.context.xml) (auto-discovered)"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -36,4 +36,6 @@ variables:
|
|||
# Output configuration
|
||||
default_output_file: "{status_file}"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<critical>If story_path is provided, use it. Otherwise, find the first story with status "drafted" in sprint-status.yaml. If none found, HALT.</critical>
|
||||
<critical>Check if context file already exists. If it does, ask user if they want to replace it, verify it, or cancel.</critical>
|
||||
|
||||
<critical>DOCUMENT OUTPUT: Technical context file (.context.md). Concise, structured, project-relative paths only.</critical>
|
||||
<critical>DOCUMENT OUTPUT: Technical context file (.context.xml). Concise, structured, project-relative paths only.</critical>
|
||||
|
||||
<workflow>
|
||||
<step n="1" goal="Find drafted story and check for existing context" tag="sprint-status">
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ variables:
|
|||
|
||||
# Output configuration
|
||||
# Uses story_key from sprint-status.yaml (e.g., "1-2-user-authentication")
|
||||
default_output_file: "{story_dir}/{{story_key}}.context.md"
|
||||
default_output_file: "{story_dir}/{{story_key}}.context.xml"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Story Done Workflow (DEV Agent)
|
||||
name: story-done
|
||||
description: "Marks a story as done (DoD complete) and moves it from IN PROGRESS → DONE in the status file. Advances the story queue. Simple status-update workflow with no searching required."
|
||||
description: "Marks a story as done (DoD complete) and moves it from its current status → DONE in the status file. Advances the story queue. Simple status-update workflow with no searching required."
|
||||
author: "BMad"
|
||||
|
||||
# Critical variables from config
|
||||
|
|
@ -22,4 +22,6 @@ variables:
|
|||
# Output configuration - no output file, just status updates
|
||||
default_output_file: ""
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -1,343 +0,0 @@
|
|||
# Workflow Audit Report
|
||||
|
||||
**Workflow:** story-ready
|
||||
**Audit Date:** 2025-10-25
|
||||
**Auditor:** Audit Workflow (BMAD v6)
|
||||
**Workflow Type:** Action (status update workflow)
|
||||
**Module:** BMM (BMad Method)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Overall Status:** EXCELLENT
|
||||
|
||||
- Critical Issues: 2
|
||||
- Important Issues: 2
|
||||
- Cleanup Recommendations: 0
|
||||
|
||||
**Pass Rate:** 94% (66/70 checks passed)
|
||||
|
||||
The story-ready workflow is well-structured and follows most BMAD v6 conventions. The workflow correctly uses `web_bundle: false` (intentional for local-only workflow). Critical issues relate to variable inconsistencies and undeclared config variables that are used in instructions.
|
||||
|
||||
---
|
||||
|
||||
## 1. Standard Config Block Validation
|
||||
|
||||
**Status:** ⚠️ CRITICAL ISSUES FOUND
|
||||
|
||||
### Required Variables Check:
|
||||
|
||||
✅ `config_source` is defined and points to correct module config path
|
||||
✅ Uses {project-root} variable correctly
|
||||
✅ `output_folder` pulls from config_source
|
||||
✅ `user_name` pulls from config_source
|
||||
✅ `communication_language` pulls from config_source
|
||||
✅ `date` is set to system-generated
|
||||
✅ Standard config comment present: "Critical variables from config"
|
||||
|
||||
### Critical Issues:
|
||||
|
||||
#### Issue 1: Missing Config Variables in YAML
|
||||
|
||||
**Severity:** CRITICAL
|
||||
**Location:** workflow.yaml
|
||||
|
||||
The instructions.md file uses the following config variables that are NOT declared in workflow.yaml:
|
||||
|
||||
1. `{user_skill_level}` - Used in line 5: "language MUST be tailored to {user_skill_level}"
|
||||
2. `{document_output_language}` - Used in line 6: "Generate all documents in {document_output_language}"
|
||||
|
||||
**Impact:** These variables will not be resolved by the workflow engine, potentially causing confusion or errors.
|
||||
|
||||
**Fix Required:**
|
||||
|
||||
```yaml
|
||||
# Critical variables from config
|
||||
config_source: '{project-root}/bmad/bmm/config.yaml'
|
||||
output_folder: '{config_source}:output_folder'
|
||||
user_name: '{config_source}:user_name'
|
||||
communication_language: '{config_source}:communication_language'
|
||||
user_skill_level: '{config_source}:user_skill_level' # ADD THIS
|
||||
document_output_language: '{config_source}:document_output_language' # ADD THIS
|
||||
date: system-generated
|
||||
```
|
||||
|
||||
#### Issue 2: Variable Path Inconsistency
|
||||
|
||||
**Severity:** CRITICAL
|
||||
**Location:** instructions.md line 3
|
||||
|
||||
Instructions reference `{project_root}` (underscore) but workflow.yaml uses `{project-root}` (hyphen).
|
||||
|
||||
**Current:** `<critical>The workflow execution engine is governed by: {project_root}/bmad/core/tasks/workflow.xml</critical>`
|
||||
|
||||
**Should be:** `<critical>The workflow execution engine is governed by: {project-root}/bmad/core/tasks/workflow.xml</critical>`
|
||||
|
||||
**Impact:** Variable will not resolve correctly, breaking the reference path.
|
||||
|
||||
---
|
||||
|
||||
## 2. YAML/Instruction/Template Alignment
|
||||
|
||||
**Status:** ✅ GOOD (with minor observations)
|
||||
|
||||
### Variables Analyzed:
|
||||
|
||||
**Workflow.yaml variables (excluding standard config):**
|
||||
|
||||
- `story_path` - Used in instructions ✓
|
||||
- `story_dir` - Used in instructions ✓
|
||||
|
||||
**Variables Used in Instructions (not in YAML):**
|
||||
|
||||
- `{{story_key}}` - Generated dynamically, output via parsing ✓
|
||||
- `{{drafted_count}}` - Generated dynamically ✓
|
||||
- `{{list_of_drafted_story_keys}}` - Generated dynamically ✓
|
||||
- `{{non_interactive}}` - Conditional logic variable (may be from agent context)
|
||||
- `{{story_file}}` - Generated dynamically ✓
|
||||
- `{{story_id}}` - Extracted from story file ✓
|
||||
- `{{story_title}}` - Extracted from story file ✓
|
||||
|
||||
### Summary:
|
||||
|
||||
- **Variables in YAML:** 2 (story_path, story_dir)
|
||||
- **Used in Instructions:** 2/2 (100%)
|
||||
- **Unused (Bloat):** 0
|
||||
- **Dynamic Variables:** 7 (appropriate for action workflow)
|
||||
|
||||
**Status:** Excellent - No bloat detected, all YAML variables are used appropriately.
|
||||
|
||||
---
|
||||
|
||||
## 3. Config Variable Usage
|
||||
|
||||
**Status:** ⚠️ IMPORTANT ISSUES FOUND
|
||||
|
||||
### Communication Language Check:
|
||||
|
||||
✅ Instructions use {communication_language} in critical header (line 5)
|
||||
⚠️ However, the instruction says "Communicate all responses in {communication_language}" but the actual workflow outputs don't explicitly enforce language adaptation
|
||||
|
||||
### User Name Check:
|
||||
|
||||
✅ User name properly used in final output (line 87): "**Story Marked Ready for Development, {user_name}!**"
|
||||
✅ Appropriate personalization in workflow completion message
|
||||
|
||||
### Output Folder Check:
|
||||
|
||||
✅ Output folder referenced correctly: `{{output_folder}}/sprint-status.yaml` (lines 18, 70)
|
||||
✅ No hardcoded paths detected
|
||||
✅ All file operations use proper variable references
|
||||
|
||||
### Date Usage Check:
|
||||
|
||||
✅ Date is available for agent awareness
|
||||
✅ Not used in outputs (appropriate for action workflow with no document generation)
|
||||
|
||||
### User Skill Level Check:
|
||||
|
||||
❌ **CRITICAL:** Variable used but not declared in workflow.yaml (line 5)
|
||||
|
||||
### Document Output Language Check:
|
||||
|
||||
❌ **CRITICAL:** Variable used but not declared in workflow.yaml (line 6)
|
||||
|
||||
**Config Variable Summary:**
|
||||
|
||||
- `communication_language`: ✅ Properly declared and used
|
||||
- `user_name`: ✅ Properly declared and used
|
||||
- `output_folder`: ✅ Properly declared and used
|
||||
- `date`: ✅ Properly declared (available but not used - appropriate)
|
||||
- `user_skill_level`: ❌ Used but NOT declared
|
||||
- `document_output_language`: ❌ Used but NOT declared
|
||||
|
||||
---
|
||||
|
||||
## 4. Web Bundle Validation
|
||||
|
||||
**Status:** ✅ CORRECT (N/A)
|
||||
|
||||
**Web Bundle Present:** No (`web_bundle: false`)
|
||||
|
||||
**Analysis:** The workflow correctly sets `web_bundle: false`. This is appropriate because:
|
||||
|
||||
1. This is a local-only action workflow
|
||||
2. It directly modifies files in the project (sprint-status.yaml, story files)
|
||||
3. It requires access to the specific project's file system
|
||||
4. It cannot be executed in a web bundle context where file system access is sandboxed
|
||||
|
||||
**Finding:** The absence of web bundle configuration is **EXPECTED and CORRECT** for this workflow type.
|
||||
|
||||
**No issues found.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Bloat Detection
|
||||
|
||||
**Status:** ✅ EXCELLENT
|
||||
|
||||
### Bloat Analysis:
|
||||
|
||||
**Total YAML Fields (excluding metadata and standard config):** 2
|
||||
|
||||
- `story_path`
|
||||
- `story_dir`
|
||||
|
||||
**Used Fields:** 2
|
||||
**Unused Fields:** 0
|
||||
|
||||
**Bloat Percentage:** 0%
|
||||
|
||||
### Hardcoded Values Check:
|
||||
|
||||
✅ No hardcoded file paths (uses {output_folder})
|
||||
✅ No hardcoded greetings (uses {user_name})
|
||||
✅ No language-specific text (uses {communication_language})
|
||||
✅ No static dates (date variable available)
|
||||
|
||||
### Redundant Configuration:
|
||||
|
||||
✅ No duplicate fields between sections
|
||||
✅ No commented-out variables
|
||||
✅ No metadata repetition
|
||||
|
||||
**Bloat Summary:** Zero bloat detected. Workflow is lean and efficient.
|
||||
|
||||
---
|
||||
|
||||
## 6. Template Variable Mapping
|
||||
|
||||
**Status:** N/A (Not a document workflow)
|
||||
|
||||
This workflow is an action workflow (status update), not a document workflow, so template validation does not apply.
|
||||
|
||||
**No template.md file required or expected.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Additional Quality Checks
|
||||
|
||||
### Instruction Quality:
|
||||
|
||||
✅ Steps properly numbered (n="1", n="2", n="3")
|
||||
✅ Each step has clear goal attribute
|
||||
✅ XML tags used correctly (<action>, <ask>, <check>, <output>, <anchor>)
|
||||
✅ Conditional logic properly implemented (if attributes)
|
||||
✅ Flow control is clear and logical
|
||||
✅ Steps are focused on single goals
|
||||
✅ Specific, actionable instructions provided
|
||||
✅ Critical sections properly marked with <critical> tags
|
||||
|
||||
### Special Features:
|
||||
|
||||
✅ Anchor tag used correctly: `<anchor id="mark_ready" />` (line 59)
|
||||
✅ Proper GOTO logic: "GOTO mark_ready" (line 15)
|
||||
✅ Conditional user interaction: `<ask if="{{non_interactive}} == false">` (line 53)
|
||||
✅ Auto-selection for non-interactive mode (line 54)
|
||||
|
||||
### File References:
|
||||
|
||||
✅ sprint-status.yaml referenced with proper variable path
|
||||
✅ Story files referenced with proper directory variable
|
||||
✅ Preservation instructions included (line 74): "Save file, preserving ALL comments and structure including STATUS DEFINITIONS"
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Critical (Fix Immediately)
|
||||
|
||||
**1. Add Missing Config Variables to workflow.yaml**
|
||||
|
||||
```yaml
|
||||
user_skill_level: '{config_source}:user_skill_level'
|
||||
document_output_language: '{config_source}:document_output_language'
|
||||
```
|
||||
|
||||
**2. Fix Variable Path Inconsistency**
|
||||
Change line 3 in instructions.md from:
|
||||
|
||||
```
|
||||
{project_root}/bmad/core/tasks/workflow.xml
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```
|
||||
{project-root}/bmad/core/tasks/workflow.xml
|
||||
```
|
||||
|
||||
### Important (Address Soon)
|
||||
|
||||
**3. Clarify non_interactive Variable Source**
|
||||
The `{{non_interactive}}` variable is used in line 53-54 but not defined in workflow.yaml. Either:
|
||||
|
||||
- Add to workflow.yaml if it's a workflow-specific variable
|
||||
- Document that it comes from agent context
|
||||
- Remove if not implemented yet
|
||||
|
||||
**4. Document user_skill_level and document_output_language Usage**
|
||||
If these variables are standard across all BMM workflows:
|
||||
|
||||
- Ensure they exist in the module's config.yaml
|
||||
- Add comments explaining their purpose
|
||||
- Verify they're actually needed for this action workflow (this workflow generates no documents, so document_output_language may not be necessary)
|
||||
|
||||
### Cleanup (Nice to Have)
|
||||
|
||||
**No cleanup recommendations** - The workflow is already lean and efficient.
|
||||
|
||||
---
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Use this checklist to verify fixes:
|
||||
|
||||
- [ ] user_skill_level added to workflow.yaml standard config block
|
||||
- [ ] document_output_language added to workflow.yaml standard config block
|
||||
- [ ] {project_root} changed to {project-root} in instructions.md line 3
|
||||
- [ ] non_interactive variable source documented or defined
|
||||
- [ ] All standard config variables present and correct
|
||||
- [ ] No unused yaml fields (bloat removed) ✓ (already clean)
|
||||
- [ ] Config variables used appropriately in instructions ✓
|
||||
- [ ] Web bundle configuration correct ✓ (intentionally false)
|
||||
- [ ] File structure follows v6 conventions ✓
|
||||
|
||||
---
|
||||
|
||||
## Overall Assessment
|
||||
|
||||
### Strengths:
|
||||
|
||||
1. **Zero bloat** - Every YAML variable is used, no waste
|
||||
2. **Clear workflow logic** - Simple, focused status update process
|
||||
3. **Proper file handling** - Uses variables for all paths, preserves file structure
|
||||
4. **Good UX** - Helpful output messages, clear next steps
|
||||
5. **Conditional logic** - Supports both interactive and non-interactive modes
|
||||
6. **Proper web_bundle setting** - Correctly set to false for local-only workflow
|
||||
|
||||
### Weaknesses:
|
||||
|
||||
1. Uses config variables not declared in workflow.yaml (user_skill_level, document_output_language)
|
||||
2. Variable naming inconsistency (project_root vs project-root)
|
||||
3. Unclear source of non_interactive variable
|
||||
|
||||
### Overall Grade: **A-** (94%)
|
||||
|
||||
This is a well-crafted workflow that follows BMAD v6 conventions closely. The critical issues are minor and easily fixable. Once the missing config variables are added and the path inconsistency is resolved, this workflow will be production-ready.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Immediate:** Add user_skill_level and document_output_language to workflow.yaml
|
||||
2. **Immediate:** Fix {project_root} → {project-root} in instructions.md
|
||||
3. **Soon:** Clarify or remove non_interactive variable usage
|
||||
4. **Soon:** Verify these config variables exist in src/modules/bmm/config.yaml (when it's created)
|
||||
5. **Optional:** Re-run audit after fixes to verify 100% pass rate
|
||||
|
||||
---
|
||||
|
||||
**Audit Complete** - Generated by audit-workflow v1.0
|
||||
**Report Location:** /Users/brianmadison/dev/BMAD-METHOD/src/modules/bmm/workflows/4-implementation/story-ready/AUDIT-REPORT.md
|
||||
|
|
@ -22,4 +22,6 @@ variables:
|
|||
# Output configuration - no output file, just status updates
|
||||
default_output_file: ""
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,93 +0,0 @@
|
|||
# generated: 2025-10-21
|
||||
# project: todo1
|
||||
# project_key: todo1
|
||||
# tracking_system: file-system
|
||||
# story_location: {project-root}/docs/stories
|
||||
|
||||
# STATUS DEFINITIONS:
|
||||
# ==================
|
||||
# Epic Status:
|
||||
# - backlog: Epic exists in epic file but not contexted
|
||||
# - contexted: Epic tech context created (required before drafting stories)
|
||||
#
|
||||
# Story Status:
|
||||
# - backlog: Story only exists in epic file
|
||||
# - drafted: Story file created in stories folder
|
||||
# - ready-for-dev: Draft approved and story context created
|
||||
# - in-progress: Developer actively working on implementation
|
||||
# - review: Under SM review (via review-story workflow)
|
||||
# - done: Story completed
|
||||
#
|
||||
# Retrospective Status:
|
||||
# - optional: Can be completed but not required
|
||||
# - completed: Retrospective has been done
|
||||
#
|
||||
# WORKFLOW NOTES:
|
||||
# ===============
|
||||
# - Epics should be 'contexted' before stories can be 'drafted'
|
||||
# - Stories can be worked in parallel if team capacity allows
|
||||
# - SM typically drafts next story after previous one is 'done' to incorporate learnings
|
||||
# - Dev moves story to 'review', SM reviews, then Dev moves to 'done'
|
||||
|
||||
generated: 2025-10-21
|
||||
project: todo1
|
||||
project_key: todo1
|
||||
tracking_system: file-system
|
||||
story_location: "{project-root}/docs/stories"
|
||||
|
||||
development_status:
|
||||
epic-1: backlog
|
||||
1-1-project-foundation-development-environment: backlog
|
||||
1-2-app-shell-navigation-framework: backlog
|
||||
1-3-user-authentication-account-management: backlog
|
||||
1-4-plant-data-model-species-database: backlog
|
||||
1-5-add-plant-manual-species-selection: backlog
|
||||
1-6-plant-photo-identification-integration: backlog
|
||||
1-7-plant-naming-profile-creation: backlog
|
||||
1-8-plant-collection-home-screen: backlog
|
||||
1-9-plant-detail-view: backlog
|
||||
1-10-cloud-photo-storage-display: backlog
|
||||
epic-1-retrospective: optional
|
||||
|
||||
epic-2: backlog
|
||||
2-1-personality-system-data-model: backlog
|
||||
2-2-personality-prototype-testing: backlog
|
||||
2-3-llm-integration-api-setup: backlog
|
||||
2-4-chat-interface-ui: backlog
|
||||
2-5-conversational-ai-system: backlog
|
||||
2-6-graceful-degradation-library: backlog
|
||||
2-7-response-caching-cost-optimization: backlog
|
||||
2-8-personality-driven-care-reminders: backlog
|
||||
2-9-push-notification-system: backlog
|
||||
2-10-reminder-intelligence-adaptation: backlog
|
||||
2-11-mood-system-visual-indicators: backlog
|
||||
2-12-mood-calculation-logic-time-based: backlog
|
||||
2-13-personality-introduction-onboarding: backlog
|
||||
2-14-personality-tone-testing-calibration: backlog
|
||||
2-15-emergency-tone-adjustment-system: backlog
|
||||
2-16-api-reliability-monitoring-alerts: backlog
|
||||
epic-2-retrospective: optional
|
||||
|
||||
epic-3: backlog
|
||||
3-1-care-schedule-data-model: backlog
|
||||
3-2-auto-generated-care-schedules: backlog
|
||||
3-3-manual-care-logging: backlog
|
||||
3-4-care-history-view: backlog
|
||||
3-5-customizable-care-schedules: backlog
|
||||
3-6-photo-timeline-tracking: backlog
|
||||
3-7-health-status-visualization: backlog
|
||||
3-8-enhanced-mood-calculation-care-data: backlog
|
||||
epic-3-retrospective: optional
|
||||
|
||||
epic-4: backlog
|
||||
4-1-shareable-content-card-design-system: backlog
|
||||
4-2-share-plant-profile: backlog
|
||||
4-3-share-conversation-snippets: backlog
|
||||
4-4-share-growth-progress: backlog
|
||||
4-5-share-care-achievements: backlog
|
||||
4-6-freemium-tier-definition-enforcement: backlog
|
||||
4-7-premium-upgrade-flow-paywall: backlog
|
||||
4-8-payment-processing-subscription-management: backlog
|
||||
4-9-premium-analytics-dashboard: backlog
|
||||
4-10-trial-conversion-optimization: backlog
|
||||
epic-4-retrospective: optional
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
# BMM Workflow Status
|
||||
|
||||
## Project Configuration
|
||||
|
||||
PROJECT_NAME: todo1
|
||||
PROJECT_TYPE: software
|
||||
PROJECT_LEVEL: 3
|
||||
FIELD_TYPE: greenfield
|
||||
START_DATE: 2025-10-18
|
||||
WORKFLOW_PATH: greenfield-level-3.yaml
|
||||
|
||||
## Current State
|
||||
|
||||
CURRENT_PHASE: 4-implementation
|
||||
CURRENT_WORKFLOW: tech-spec
|
||||
CURRENT_AGENT: architect
|
||||
PHASE_1_COMPLETE: true
|
||||
PHASE_2_COMPLETE: true
|
||||
PHASE_3_COMPLETE: true
|
||||
PHASE_4_COMPLETE: false
|
||||
|
||||
## Next Action
|
||||
|
||||
NEXT_ACTION: Create technical specification for Epic 1 (Foundation & Core Plant Management)
|
||||
NEXT_COMMAND: /bmad:bmm:agents:architect then run \*tech-spec for Epic 1
|
||||
NEXT_AGENT: architect
|
||||
|
||||
## Story Backlog
|
||||
|
||||
**Epic 1:** 10 stories - Foundation & Core Plant Management
|
||||
**Epic 2:** 16 stories - AI Personality System & Engagement Loop
|
||||
**Epic 3:** 8 stories - Care Scheduling, Photos & Growth Tracking
|
||||
**Epic 4:** 10 stories - Social Sharing & Premium Monetization
|
||||
|
||||
**Total: 44 stories** (see epics.md for detailed breakdown)
|
||||
|
||||
## Workflow Progress
|
||||
|
||||
**Phase 1 - Analysis:**
|
||||
|
||||
- ✅ Brainstorm Project (2025-10-18)
|
||||
- ⬜ Research (optional - skipped)
|
||||
- ✅ Product Brief (2025-10-18)
|
||||
|
||||
**Phase 2 - Planning:**
|
||||
|
||||
- ✅ PRD (2025-10-19) - 44 stories across 4 epics defined
|
||||
- ✅ UX Spec (2025-10-19) - Comprehensive design system, user flows, components
|
||||
|
||||
**Phase 3 - Architecture (Required for Level 3):**
|
||||
|
||||
- ✅ Architecture (2025-10-19)
|
||||
- ✅ Assess Project Ready (2025-10-19)
|
||||
|
||||
**Phase 4 - Implementation:**
|
||||
|
||||
- 🎯 Tech Spec for Epic 1 (next up)
|
||||
- Per Epic: Tech Spec (JIT) → Stories
|
||||
- Per Story: Create → Context → Validate → Ready → Develop → Review → Approved
|
||||
- Epic Retrospectives after each epic
|
||||
|
||||
---
|
||||
|
||||
_Last Updated: 2025-10-19 (Phase 3 Complete - Starting Implementation Phase)_
|
||||
_Status Version: 6.0_
|
||||
|
|
@ -16,47 +16,59 @@ The BMM (BMAD Method Module) orchestrates software development through four dist
|
|||
|
||||
**Continuous Learning Loop**: Retrospectives feed improvements back into workflows, making each epic smoother than the last.
|
||||
|
||||
## The Four Phases
|
||||
## The Five Phases
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ PHASE 0: DOCUMENTATION (Brownfield) │
|
||||
│ (Conditional - if undocumented) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ document-project ──→ Codebase documentation │
|
||||
└────────────────────────────────────────────────────────┼─────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ PHASE 1: ANALYSIS │
|
||||
│ (Optional) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ brainstorm-game ──┐ │
|
||||
│ brainstorm-game ──┐ │
|
||||
│ brainstorm-project ├──→ research ──→ product-brief ──┐ │
|
||||
│ game-brief ────────┘ game-brief ┘ │
|
||||
│ game-brief ────────┘ game-brief │
|
||||
└────────────────────────────────────────────────────────┼─────┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ PHASE 2: PLANNING │
|
||||
│ (Scale-Adaptive Router - by type) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ SOFTWARE: prd GAMES: gdd │
|
||||
│ SOFTWARE: prd/tech-spec GAMES: gdd/narrative │
|
||||
│ ├──→ Level 0: tech-spec only ├──→ GDD (all levels) │
|
||||
│ ├──→ Level 1: tech-spec only └──→ Narrative design │
|
||||
│ ├──→ Level 2: PRD + tech-spec │
|
||||
│ └──→ Level 3-4: PRD + Epics ────────────────────────┐ │
|
||||
└──────────────────────────────────────────────────────────┼───┘
|
||||
↓
|
||||
│ ├──→ Level 1: tech-spec only └──→ Narrative (opt) │
|
||||
│ ├──→ Level 2: PRD + Epics ──────────────────────────┐ │
|
||||
│ └──→ Level 3-4: PRD + Epics ────────────────────────┼┐ │
|
||||
│ UX: create-ux-design (conditional) ││ │
|
||||
└──────────────────────────────────────────────────────────┼┼──┘
|
||||
↓↓
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ PHASE 3: SOLUTIONING │
|
||||
│ (Software Levels 3-4 / Complex Games) │
|
||||
│ (Software Levels 2-4 / Complex Games) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ 3-solutioning ──→ architecture.md │
|
||||
│ ↓ │
|
||||
│ tech-spec (per epic, JIT during implementation) │
|
||||
│ create-architecture ──→ architecture.md │
|
||||
│ validate-architecture (optional) │
|
||||
│ solutioning-gate-check (recommended/required) │
|
||||
└────────────────────────────────────────────────────────────┬─┘
|
||||
↓
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ PHASE 4: IMPLEMENTATION │
|
||||
│ (Iterative Cycle) │
|
||||
│ (Sprint-Based Cycle) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ ┌─→ create-story ──→ story-context ──→ dev-story ──┐ │
|
||||
│ │ ↓ │
|
||||
│ │ retrospective ←── [epic done] ←────── review-story │
|
||||
│ │ ↓ │
|
||||
│ └──────────── correct-course ←──[if issues]──┘ │
|
||||
│ sprint-planning ──→ sprint-status.yaml │
|
||||
│ ↓ │
|
||||
│ ┌─→ epic-tech-context (per epic) │
|
||||
│ │ ↓ │
|
||||
│ │ create-story ──→ story-context ──→ dev-story ─┐ │
|
||||
│ │ ↓ │
|
||||
│ │ retrospective ←── [epic done] ←─── review-story │
|
||||
│ │ ↓ │
|
||||
│ └──────────── correct-course ←──[if issues]───────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
|
|
@ -64,13 +76,7 @@ The BMM (BMAD Method Module) orchestrates software development through four dist
|
|||
|
||||
**Before starting any workflow, check your status!**
|
||||
|
||||
The `workflow-status` workflow is the **universal entry point** for all BMM workflows:
|
||||
|
||||
```bash
|
||||
bmad analyst workflow-status
|
||||
# or
|
||||
bmad pm workflow-status
|
||||
```
|
||||
The `workflow-status` workflow is the **universal entry point** for all BMM workflows, if you have not already set up your workflow, run `workflow-init`, but even if you just run the workflow-status and the file does not exist you should still be directed to run workflow-init.
|
||||
|
||||
**What it does:**
|
||||
|
||||
|
|
@ -83,8 +89,7 @@ bmad pm workflow-status
|
|||
**No status file?** It will:
|
||||
|
||||
1. Ask about project context (greenfield vs brownfield)
|
||||
2. Offer analysis options (full analysis, skip to planning, or quick tech spec)
|
||||
3. Guide you to the right first workflow
|
||||
2. Generate your bmm-workflow-status.md file.
|
||||
|
||||
**Status file exists?** It will:
|
||||
|
||||
|
|
@ -93,7 +98,27 @@ bmad pm workflow-status
|
|||
3. Recommend exact next action
|
||||
4. Offer to change workflow or display menu
|
||||
|
||||
**All agents (bmad-master, analyst, pm) should check workflow-status on load.**
|
||||
**All phase 1-3 workflows should check workflow-status on start of the workflow.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Documentation (Brownfield Only)
|
||||
|
||||
Required for undocumented brownfield projects before planning can begin.
|
||||
|
||||
### Workflows
|
||||
|
||||
| Workflow | Agent | Purpose | Output | When to Use |
|
||||
| -------------------- | ------- | -------------------------- | --------------------- | -------------------------------- |
|
||||
| **document-project** | Analyst | Document existing codebase | Project documentation | Brownfield without adequate docs |
|
||||
|
||||
### Flow
|
||||
|
||||
```
|
||||
Brownfield Check → document-project → Analysis/Planning (Phase 1/2)
|
||||
```
|
||||
|
||||
**Critical**: Brownfield projects require adequate documentation before planning. If workflow-init detects an undocumented brownfield project, it will direct you to run document-project first.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -103,14 +128,15 @@ Optional workflows for project discovery and requirements gathering. Output feed
|
|||
|
||||
### Workflows
|
||||
|
||||
| Workflow | Purpose | Output | When to Use |
|
||||
| ---------------------- | ------------------------------------------- | ------------------------- | ---------------------- |
|
||||
| **workflow-status** | Universal entry point and status checker | Status display + guidance | **Always start here!** |
|
||||
| **brainstorm-game** | Game concept ideation using 5 methodologies | Concept proposals | New game projects |
|
||||
| **brainstorm-project** | Software solution exploration | Architecture proposals | New software projects |
|
||||
| **game-brief** | Structured game design foundation | Game brief document | Before GDD creation |
|
||||
| **product-brief** | Strategic product planning culmination | Product brief | End of analysis phase |
|
||||
| **research** | Multi-mode research (market/technical/deep) | Research artifacts | When evidence needed |
|
||||
| Workflow | Agent | Purpose | Output | When to Use |
|
||||
| ---------------------- | ------------- | ------------------------------------------- | ------------------------- | --------------------- |
|
||||
| **workflow-status** | Analyst | Universal entry point and status checker | Status display + guidance | **Start here!** |
|
||||
| **workflow-init** | Analyst | Generate an initial workflow status file | Status display + guidance | **OR start here!** |
|
||||
| **brainstorm-game** | Game Designer | Game concept ideation using 5 methodologies | Concept proposals | New game projects |
|
||||
| **brainstorm-project** | Analyst | Software solution exploration | Architecture proposals | New software projects |
|
||||
| **game-brief** | Game Designer | Structured game design foundation | Game brief document | Before GDD creation |
|
||||
| **product-brief** | Analyst | Strategic product planning culmination | Product brief | End of analysis phase |
|
||||
| **research** | Analyst | Multi-mode research (market/technical/deep) | Research artifacts | When evidence needed |
|
||||
|
||||
### Flow
|
||||
|
||||
|
|
@ -120,140 +146,112 @@ workflow-status (check) → Brainstorming → Research → Brief → Planning (P
|
|||
|
||||
## Phase 2: Planning (Required)
|
||||
|
||||
The central orchestrator that determines project scale and generates appropriate planning artifacts.
|
||||
|
||||
### Scale Levels
|
||||
|
||||
| Level | Scope | Outputs | Next Phase |
|
||||
| ----- | ------------------------ | ------------------------------ | ------------------------------ |
|
||||
| **0** | Single atomic change | tech-spec + 1 story | → Implementation |
|
||||
| **1** | 1-10 stories, 1 epic | tech-spec + epic + 2-3 stories | → Implementation |
|
||||
| **2** | 5-15 stories, 1-2 epics | PRD + epics | → Tech-spec → Implementation |
|
||||
| **2** | 5-15 stories, 1-2 epics | PRD + epics | → Solutioning → Implementation |
|
||||
| **3** | 12-40 stories, 2-5 epics | PRD + epics | → Solutioning → Implementation |
|
||||
| **4** | 40+ stories, 5+ epics | PRD + epics | → Solutioning → Implementation |
|
||||
|
||||
**Key Changes (v6a):**
|
||||
### Available Workflows
|
||||
|
||||
- **Level 0**: Now generates a single user story in addition to tech-spec
|
||||
- **Level 1**: Now generates 2-3 stories as part of planning (prefer longer stories over more stories)
|
||||
- Both Level 0/1 skip Phase 3 and populate Phase 4 story backlog automatically
|
||||
|
||||
### Routing Logic
|
||||
|
||||
**Universal Entry Point** (workflow-status):
|
||||
|
||||
```
|
||||
workflow-status
|
||||
├─→ Check for existing status file
|
||||
│ ├─→ If exists: Display status + recommend next action
|
||||
│ └─→ If not exists: Guide workflow planning
|
||||
├─→ Determine project context (greenfield/brownfield)
|
||||
├─→ Determine project type (game/web/mobile/backend/etc)
|
||||
├─→ Assess complexity → assign Level 0-4
|
||||
├─→ Map complete workflow journey
|
||||
└─→ Generate bmm-workflow-status.md + direct to first workflow
|
||||
```
|
||||
|
||||
**Direct Routing** (no intermediate routers):
|
||||
|
||||
```
|
||||
workflow-status determines routing:
|
||||
|
||||
SOFTWARE PROJECTS:
|
||||
├─→ Level 0-1 → bmad architect tech-spec
|
||||
│ └─→ Validates status file + level
|
||||
│ └─→ Generates tech-spec.md + stories
|
||||
│ └─→ Direct to Phase 4 (implementation)
|
||||
│
|
||||
├─→ Level 2 → bmad pm prd
|
||||
│ └─→ Validates status file + level
|
||||
│ └─→ Generates PRD.md + epics.md
|
||||
│ └─→ Then: bmad architect tech-spec (lightweight solutioning)
|
||||
│ └─→ Then: Phase 4 (implementation)
|
||||
│
|
||||
└─→ Level 3-4 → bmad pm prd
|
||||
└─→ Validates status file + level
|
||||
└─→ Generates PRD.md + epics.md
|
||||
└─→ Then: Phase 3 (architecture)
|
||||
└─→ Then: Phase 4 (implementation)
|
||||
|
||||
GAME PROJECTS:
|
||||
└─→ All Levels → bmad pm gdd
|
||||
└─→ Validates status file + project type
|
||||
└─→ Generates GDD.md + epics.md
|
||||
└─→ Optional: narrative design
|
||||
└─→ Then: Phase 3 or 4 (based on complexity)
|
||||
```
|
||||
| Workflow | Agent | Purpose | Output | Levels |
|
||||
| -------------------- | ----------- | ------------------------------------ | -------------- | ----------- |
|
||||
| **prd** | PM | Product Requirements Document | PRD.md + epics | 2-4 |
|
||||
| **tech-spec** | PM | Technical specification | tech-spec.md | 0-1 |
|
||||
| **gdd** | PM | Game Design Document | GDD.md | Games (all) |
|
||||
| **narrative** | PM | Game narrative design | narrative.md | Games (opt) |
|
||||
| **create-ux-design** | UX Designer | User experience and interface design | ux-design.md | Conditional |
|
||||
|
||||
### Key Outputs
|
||||
|
||||
- **PRD.md**: Product Requirements Document (Levels 2-4)
|
||||
- **Epics.md**: Epic breakdown with stories (Levels 2-4)
|
||||
- **epic-stories.md**: Epic summary with story links (Level 1)
|
||||
- **tech-spec.md**: Technical specification (Levels 0-2 only)
|
||||
- **tech-spec.md**: Technical specification (Levels 0-1)
|
||||
- **story-{slug}.md**: Single user story (Level 0)
|
||||
- **story-{slug}-1.md, story-{slug}-2.md, story-{slug}-3.md**: User stories (Level 1)
|
||||
- **GDD.md**: Game Design Document (game projects)
|
||||
- **bmm-workflow-status.md**: Versioned workflow state tracking with story backlog
|
||||
- **narrative.md**: Narrative design (game projects, optional)
|
||||
- **ux-design.md**: UX specification (conditional, UI-heavy projects)
|
||||
- **bmm-workflow-status.md**: Versioned workflow state tracking
|
||||
|
||||
## Phase 3: Solutioning (Levels 3-4 Only)
|
||||
## Phase 3: Solutioning (Levels 2-4)
|
||||
|
||||
Architecture and technical design phase for complex projects.
|
||||
Architecture and technical design phase for medium to complex projects.
|
||||
|
||||
### Workflows
|
||||
|
||||
| Workflow | Owner | Purpose | Output | Timing |
|
||||
| ----------------- | --------- | ------------------------------ | ------------------------- | ----------------- |
|
||||
| **3-solutioning** | Architect | Create overall architecture | architecture.md with ADRs | Once per project |
|
||||
| **tech-spec** | Architect | Create epic-specific tech spec | tech-spec-epic-N.md | One per epic, JIT |
|
||||
| Workflow | Agent | Purpose | Output | When |
|
||||
| -------------------------- | --------- | -------------------------------- | ------------------------- | ----------- |
|
||||
| **create-architecture** | Architect | Create system-wide architecture | architecture.md with ADRs | Levels 2-4 |
|
||||
| **validate-architecture** | Architect | Validate architecture design | Validation report | Optional |
|
||||
| **solutioning-gate-check** | Architect | Validate PRD + UX + architecture | Gate check report | Recommended |
|
||||
|
||||
### Just-In-Time Tech Specs
|
||||
### Architecture Scope by Level
|
||||
|
||||
```
|
||||
FOR each epic in sequence:
|
||||
WHEN ready to implement epic:
|
||||
Architect: Run tech-spec workflow for THIS epic only
|
||||
→ Creates tech-spec-epic-N.md
|
||||
→ Hands off to implementation
|
||||
IMPLEMENT epic completely
|
||||
THEN move to next epic
|
||||
```
|
||||
|
||||
**Critical**: Tech specs are created ONE AT A TIME as epics are ready for implementation, not all upfront. This prevents over-engineering and incorporates learning.
|
||||
- **Level 2**: Lightweight architecture document focusing on key technical decisions
|
||||
- **Level 3-4**: Comprehensive architecture with detailed ADRs, system diagrams, integration patterns
|
||||
|
||||
## Phase 4: Implementation (Iterative)
|
||||
|
||||
The core development cycle that transforms requirements into working software.
|
||||
The core development cycle that transforms requirements into working software through sprint-based iteration.
|
||||
|
||||
### The Story State Machine
|
||||
### Sprint Planning - The Phase 4 Entry Point
|
||||
|
||||
Phase 4 uses a 4-state lifecycle to manage story progression, tracked in `bmm-workflow-status.md`:
|
||||
Phase 4 begins with the **sprint-planning** workflow, which generates a `sprint-status.yaml` file that serves as the single source of truth for all implementation tracking.
|
||||
|
||||
**What sprint-planning does:**
|
||||
|
||||
1. Extracts all epics and stories from epic files
|
||||
2. Creates ordered status tracking for every work item
|
||||
3. Auto-detects existing story files and contexts
|
||||
4. Maintains status through the development lifecycle
|
||||
|
||||
### The Sprint Status System
|
||||
|
||||
Phase 4 uses a 6-state lifecycle tracked in `sprint-status.yaml`:
|
||||
|
||||
**Epic Status Flow:**
|
||||
|
||||
```
|
||||
BACKLOG → TODO → IN PROGRESS → DONE
|
||||
backlog → contexted
|
||||
```
|
||||
|
||||
#### State Definitions
|
||||
**Story Status Flow:**
|
||||
|
||||
- **BACKLOG**: Ordered list of stories to be drafted (populated at phase transition)
|
||||
- Contains all stories with IDs, titles, and file names
|
||||
- Order is sequential (Epic 1 stories first, then Epic 2, etc.)
|
||||
```
|
||||
backlog → drafted → ready-for-dev → in-progress → review → done
|
||||
```
|
||||
|
||||
- **TODO**: Single story that needs drafting (or drafted, awaiting approval)
|
||||
- SM drafts story here using `create-story` workflow
|
||||
- Story status is "Draft" until user approves
|
||||
- User runs `story-ready` workflow to approve
|
||||
**Retrospective Status:**
|
||||
|
||||
- **IN PROGRESS**: Single story approved for development
|
||||
- Moved here by `story-ready` workflow
|
||||
- DEV implements using `dev-story` workflow
|
||||
- Story status is "Ready" or "In Review"
|
||||
```
|
||||
optional ↔ completed
|
||||
```
|
||||
|
||||
- **DONE**: Completed stories with dates and points
|
||||
- Moved here by `story-done` workflow after DoD complete
|
||||
- Immutable record of completed work
|
||||
#### Status Definitions
|
||||
|
||||
**Key Innovation**: Agents never search for "next story" - they always read the exact story from the status file.
|
||||
**Epic Statuses:**
|
||||
|
||||
- **backlog**: Epic exists in epic file but not yet contexted
|
||||
- **contexted**: Epic technical context created (prerequisite for drafting stories)
|
||||
|
||||
**Story Statuses:**
|
||||
|
||||
- **backlog**: Story only exists in epic file, not yet drafted
|
||||
- **drafted**: Story file created (e.g., `stories/1-3-plant-naming.md`)
|
||||
- **ready-for-dev**: Draft approved + story context created
|
||||
- **in-progress**: Developer actively working on implementation
|
||||
- **review**: Under SM review (via review-story workflow)
|
||||
- **done**: Story completed and deployed
|
||||
|
||||
**Retrospective Statuses:**
|
||||
|
||||
- **optional**: Can be done but not required
|
||||
- **completed**: Retrospective has been completed
|
||||
|
||||
### The Implementation Loop
|
||||
|
||||
|
|
@ -261,44 +259,45 @@ BACKLOG → TODO → IN PROGRESS → DONE
|
|||
Phase Transition (Phase 2 or 3 → Phase 4)
|
||||
↓
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ BACKLOG populated with all stories │
|
||||
│ TODO initialized with first story │
|
||||
│ SM: sprint-planning │
|
||||
│ Creates: sprint-status.yaml with all epics/ │
|
||||
│ stories set to 'backlog' │
|
||||
└───────────────────┬─────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ SM: create-story (drafts story in TODO) │
|
||||
│ Reads: status file TODO section │
|
||||
│ Output: Story file with Status="Draft" │
|
||||
│ SM: epic-tech-context (for current epic) │
|
||||
│ Creates: epic-N-context.md │
|
||||
│ Updates: Epic status to 'contexted' │
|
||||
└───────────────────┬─────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ User reviews story │
|
||||
│ ↓ │
|
||||
│ SM: story-ready (approves story) │
|
||||
│ Actions: TODO → IN PROGRESS │
|
||||
│ BACKLOG → TODO (next story) │
|
||||
│ Story Status = "Ready" │
|
||||
│ SM: create-story (drafts next backlog story) │
|
||||
│ Creates: story-{key}.md │
|
||||
│ Updates: Story status to 'drafted' │
|
||||
└───────────────────┬─────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ SM: story-context (optional but recommended) │
|
||||
│ Generates expertise injection XML │
|
||||
│ SM: story-context (creates implementation ctx) │
|
||||
│ Creates: story-{key}-context.md │
|
||||
│ Updates: Story status to 'ready-for-dev' │
|
||||
└───────────────────┬─────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ DEV: dev-story (implements story) │
|
||||
│ Reads: status file IN PROGRESS section │
|
||||
│ Implements with context injection │
|
||||
│ Reads: story + context files │
|
||||
│ Updates: Story status to 'in-progress' │
|
||||
│ then to 'review' when complete │
|
||||
└───────────────────┬─────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ User reviews implementation (DoD check) │
|
||||
│ ↓ │
|
||||
│ DEV: story-done (marks story done) │
|
||||
│ Actions: IN PROGRESS → DONE │
|
||||
│ TODO → IN PROGRESS (if exists) │
|
||||
│ BACKLOG → TODO (if exists) │
|
||||
│ Story Status = "Done" │
|
||||
│ SM: review-story (validates implementation) │
|
||||
│ Reviews: Code changes against DoD │
|
||||
│ Feedback: Iteration or approval │
|
||||
└───────────────────┬─────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ DEV/SM: Updates story status to 'done' │
|
||||
│ Manual update to sprint-status.yaml │
|
||||
└───────────────────┬─────────────────────────────┘
|
||||
↓
|
||||
┌───────┴────────┐
|
||||
|
|
@ -306,157 +305,175 @@ Phase Transition (Phase 2 or 3 → Phase 4)
|
|||
└───────┬─────────┘
|
||||
┌───┴───┐
|
||||
↓ ↓
|
||||
[Yes: Loop] [No: Epic/Project Complete]
|
||||
[Yes: Loop] [No: Epic Complete]
|
||||
↓
|
||||
[retrospective]
|
||||
┌─────────────────┐
|
||||
│ SM: retrospective│
|
||||
│ Updates: epic-N- │
|
||||
│ retrospective to │
|
||||
│ 'completed' │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### Workflow Responsibilities
|
||||
|
||||
| Workflow | Agent | Purpose | State Transition | No Search Required |
|
||||
| ------------------ | ------ | ------------------------------------- | --------------------- | ------------------------ |
|
||||
| **create-story** | SM | Draft story from TODO section | (Story stays in TODO) | Reads TODO section |
|
||||
| **story-ready** | SM | Approve drafted story for development | TODO → IN PROGRESS | Reads TODO section |
|
||||
| **story-context** | SM | Generate expertise injection XML | (No state change) | Reads IN PROGRESS |
|
||||
| **dev-story** | DEV | Implement story | (No state change) | Reads IN PROGRESS |
|
||||
| **story-done** | DEV | Mark story done after DoD complete | IN PROGRESS → DONE | Reads IN PROGRESS |
|
||||
| **review-story** | SR/DEV | Quality validation (optional) | (No state change) | Manual story selection |
|
||||
| **correct-course** | SM | Handle issues/changes | (Adaptive) | Manual story selection |
|
||||
| **retrospective** | SM | Capture epic learnings | (No state change) | Manual or epic-triggered |
|
||||
| Workflow | Agent | Purpose | Status Updates |
|
||||
| --------------------- | ----- | -------------------------------------- | ------------------------------------------- |
|
||||
| **sprint-planning** | SM | Initialize sprint status tracking | Creates sprint-status.yaml |
|
||||
| **epic-tech-context** | SM | Create epic-specific technical context | Epic: backlog → contexted |
|
||||
| **create-story** | SM | Draft individual story files | Story: backlog → drafted |
|
||||
| **story-context** | SM | Generate implementation context/XML | Story: drafted → ready-for-dev |
|
||||
| **dev-story** | DEV | Implement story | Story: ready-for-dev → in-progress → review |
|
||||
| **review-story** | SM/SR | Quality validation and feedback | (No automatic state change) |
|
||||
| **retrospective** | SM | Capture epic learnings | Retrospective: optional → completed |
|
||||
| **correct-course** | SM | Handle issues/scope changes | (Adaptive based on situation) |
|
||||
|
||||
### Story File Status Values
|
||||
### Key Guidelines
|
||||
|
||||
Stories have a `Status:` field in their markdown file that reflects their position in the state machine:
|
||||
1. **Epic Context First**: Epics should be `contexted` before their stories can be `drafted`
|
||||
2. **Sequential by Default**: Stories are typically worked in order within an epic
|
||||
3. **Parallel Work Supported**: Multiple stories can be `in-progress` if team capacity allows
|
||||
4. **Learning Transfer**: SM drafts next story after previous is `done` to incorporate learnings
|
||||
5. **Flexible Status Updates**: Agents and users can manually update sprint-status.yaml as needed
|
||||
|
||||
## Greenfield vs Brownfield Paths
|
||||
|
||||
### Greenfield Projects (New Code)
|
||||
|
||||
**Path:** Phase 1 (optional) → Phase 2 → Phase 3 (Levels 2-4) → Phase 4
|
||||
|
||||
- **Level 0-1**: Skip Phase 3, go straight to implementation with tech-spec
|
||||
- **Level 2-4**: Full solutioning with architecture before implementation
|
||||
- Clean slate for architectural decisions
|
||||
- No existing patterns to constrain design
|
||||
|
||||
### Brownfield Projects (Existing Code)
|
||||
|
||||
**Path:** Phase 0 (if undocumented) → Phase 1 (optional) → Phase 2 → Phase 3 (Levels 2-4) → Phase 4
|
||||
|
||||
**Phase 0 - Documentation (Conditional):**
|
||||
|
||||
```
|
||||
Status: Draft (Story created by create-story, awaiting user review)
|
||||
↓
|
||||
Status: Ready (User approved via story-ready, ready for implementation)
|
||||
↓
|
||||
Status: In Review (Implementation complete, awaiting final approval)
|
||||
↓
|
||||
Status: Done (User approved via story-done, DoD complete)
|
||||
```
|
||||
|
||||
**Status File Position vs Story File Status:**
|
||||
|
||||
| Status File State | Story File Status | Meaning |
|
||||
| ----------------- | -------------------- | ------------------------------------- |
|
||||
| BACKLOG | (file doesn't exist) | Story not yet drafted |
|
||||
| TODO | Draft | Story drafted, awaiting user approval |
|
||||
| IN PROGRESS | Ready or In Review | Story approved for development |
|
||||
| DONE | Done | Story complete, DoD met |
|
||||
|
||||
## Greenfield vs Brownfield Considerations
|
||||
|
||||
### Greenfield Projects
|
||||
|
||||
- Start with Phase 1 (Analysis) or Phase 2 (Planning)
|
||||
- Clean architecture decisions in Phase 3
|
||||
- Straightforward implementation in Phase 4
|
||||
|
||||
### Brownfield Projects
|
||||
|
||||
```
|
||||
workflow-init (Phase 2)
|
||||
workflow-status/workflow-init
|
||||
├─→ Check: Is existing codebase documented?
|
||||
│ ├─→ YES: Proceed with planning
|
||||
│ └─→ NO: HALT with message:
|
||||
│ "Brownfield project requires documentation.
|
||||
│ Please run codebase-analysis workflow first."
|
||||
│ └─→ [TBD: brownfield-analysis workflow]
|
||||
│ ├─→ Analyzes existing code
|
||||
│ ├─→ Documents current architecture
|
||||
│ ├─→ Identifies technical debt
|
||||
│ └─→ Creates baseline documentation
|
||||
│ ├─→ YES: Proceed to Phase 1 or 2
|
||||
│ └─→ NO: REQUIRED - Run document-project workflow
|
||||
│ ├─→ Analyzes existing code
|
||||
│ ├─→ Documents current architecture
|
||||
│ ├─→ Identifies technical debt
|
||||
│ └─→ Creates baseline documentation
|
||||
└─→ Continue with scale-adaptive planning
|
||||
```
|
||||
|
||||
**Critical for Brownfield**: Without adequate documentation of the existing system, the planning phase cannot accurately assess scope or create meaningful requirements. The brownfield-analysis workflow (coming soon) will:
|
||||
**Critical for Brownfield**:
|
||||
|
||||
- Map existing architecture
|
||||
- Document current patterns
|
||||
- Identify integration points
|
||||
- Assess technical debt
|
||||
- Create the baseline needed for planning
|
||||
- Must understand existing patterns before planning
|
||||
- Integration points need documentation
|
||||
- Technical debt must be visible in planning
|
||||
- Constraints from existing system affect scale decisions
|
||||
|
||||
## Agent Participation by Phase
|
||||
|
||||
| Phase | Primary Agents | Supporting Agents |
|
||||
| ------------------ | ------------------- | --------------------------- |
|
||||
| **Analysis** | Analyst, Researcher | PM, PO |
|
||||
| **Planning** | PM | Analyst, UX Designer |
|
||||
| **Solutioning** | Architect | PM, Tech Lead |
|
||||
| **Implementation** | SM, DEV | SR, PM (for correct-course) |
|
||||
| Phase | Primary Agents | Supporting Agents | Key Workflows |
|
||||
| --------------------- | ---------------------- | -------------------- | ------------------------------------------- |
|
||||
| **0: Documentation** | Analyst | - | document-project |
|
||||
| **1: Analysis** | Analyst, Game Designer | PM, Researcher | brainstorm-_, research, _-brief |
|
||||
| **2: Planning** | PM | UX Designer, Analyst | prd, tech-spec, gdd, narrative |
|
||||
| **3: Solutioning** | Architect | PM, Tech Lead | create-architecture, solutioning-gate-check |
|
||||
| **4: Implementation** | SM, DEV | SR (review-story) | sprint-planning, create-story, dev-story |
|
||||
|
||||
## Key Files and Artifacts
|
||||
|
||||
### Tracking Documents
|
||||
|
||||
- **bmm-workflow-status.md**: Versioned workflow state tracking with 4-section story backlog
|
||||
- **BACKLOG**: Ordered list of stories to be drafted
|
||||
- **TODO**: Single story ready for drafting (or drafted, awaiting approval)
|
||||
- **IN PROGRESS**: Single story approved for development
|
||||
- **DONE**: Completed stories with dates and points
|
||||
- Populated automatically at phase transitions
|
||||
- Single source of truth for story progression
|
||||
- Agents read (never search) to know what to work on next
|
||||
- **bmm-workflow-status.md**: Phase and workflow tracking (updated by workflow-status)
|
||||
- Current phase and progress
|
||||
- Workflow history
|
||||
- Next recommended actions
|
||||
- Project metadata and configuration
|
||||
|
||||
- **Epics.md**: Master list of epics and stories (source of truth for planning, Level 2-4)
|
||||
- **sprint-status.yaml**: Implementation tracking (Phase 4 only)
|
||||
- All epics, stories, and retrospectives
|
||||
- Current status for each item (backlog → done)
|
||||
- Single source of truth for Phase 4 progression
|
||||
- Updated by agents as work progresses
|
||||
|
||||
- **Epics.md**: Master epic/story definitions (source of truth for planning, Level 2-4)
|
||||
|
||||
### Phase Outputs
|
||||
|
||||
- **Phase 1**: Briefs and research documents
|
||||
- **Phase 0**:
|
||||
- Codebase documentation (project overview, architecture, source tree)
|
||||
|
||||
- **Phase 1**:
|
||||
- Product briefs, game briefs, research documents
|
||||
|
||||
- **Phase 2**:
|
||||
- Level 0: tech-spec.md + story-{slug}.md
|
||||
- Level 1: tech-spec.md + epic-stories.md + story-{slug}-N.md files
|
||||
- Level 2: PRD.md + epics.md (then tech-spec.md in Phase 3)
|
||||
- Level 3-4: PRD.md + epics.md (then architecture.md in Phase 3)
|
||||
- **Phase 3**: architecture.md, epic-specific tech specs
|
||||
- **Phase 4**: Story files, context XMLs, implemented code
|
||||
- Level 1: tech-spec.md + epic breakdown + story-{slug}-N.md files
|
||||
- Level 2-4: PRD.md + epics.md (+ optional ux-design.md, narrative.md)
|
||||
|
||||
- **Phase 3**:
|
||||
- architecture.md (with ADRs)
|
||||
- Validation reports
|
||||
- Gate check documentation
|
||||
|
||||
- **Phase 4**:
|
||||
- sprint-status.yaml (tracking file)
|
||||
- epic-N-context.md files (per epic)
|
||||
- story-{key}.md files (per story)
|
||||
- story-{key}-context.md files (per story)
|
||||
- Implemented code and tests
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Respect the Scale
|
||||
|
||||
- Don't create PRDs for Level 0 changes
|
||||
- Don't skip architecture for Level 3-4 projects
|
||||
- Let the workflow determine appropriate artifacts
|
||||
- Don't create PRDs for Level 0-1 changes (use tech-spec only)
|
||||
- Don't skip architecture for Level 2-4 projects
|
||||
- Let the workflow paths determine appropriate artifacts
|
||||
- Level 2 still requires Phase 3 solutioning (lighter than 3-4)
|
||||
|
||||
### 2. Embrace Just-In-Time
|
||||
### 2. Use Sprint Planning Effectively
|
||||
|
||||
- Create tech specs one epic at a time
|
||||
- Generate stories as needed, not in batches
|
||||
- Build context injections per story
|
||||
- Run sprint-planning at the start of Phase 4
|
||||
- Context epics before drafting their stories (epic-tech-context)
|
||||
- Update sprint-status.yaml as work progresses
|
||||
- Re-run sprint-planning to auto-detect new files/contexts
|
||||
|
||||
### 3. Maintain Flow Integrity
|
||||
|
||||
- Stories must be enumerated in Epics.md
|
||||
- Stories must be defined in Epics.md before sprint-planning
|
||||
- Complete epic context before story drafting
|
||||
- Create story context before implementation
|
||||
- Each phase completes before the next begins
|
||||
- Use fresh context windows for reviews
|
||||
|
||||
### 4. Document Brownfield First
|
||||
|
||||
- Never plan without understanding existing code
|
||||
- Run document-project if codebase is undocumented
|
||||
- Technical debt must be visible in planning
|
||||
- Integration points need documentation
|
||||
|
||||
### 5. Learn Continuously
|
||||
|
||||
- Run retrospectives after each epic
|
||||
- Update workflows based on learnings
|
||||
- Incorporate learnings into next story drafts
|
||||
- Update workflows based on team feedback
|
||||
- Share patterns across teams
|
||||
|
||||
## Common Pitfalls and Solutions
|
||||
|
||||
| Pitfall | Solution |
|
||||
| --------------------------------- | ------------------------------------- |
|
||||
| Creating all tech specs upfront | Use JIT approach - one epic at a time |
|
||||
| Skipping story-context generation | Always run after create-story |
|
||||
| Batching story creation | Create one story at a time |
|
||||
| Ignoring scale levels | Let workflow init determine level |
|
||||
| Planning brownfield without docs | Run brownfield-analysis first |
|
||||
| Not running retrospectives | Schedule after every epic |
|
||||
| Pitfall | Solution |
|
||||
| ------------------------------------- | ----------------------------------------------------- |
|
||||
| Skipping sprint-planning | Always run at Phase 4 start - it creates status file |
|
||||
| Creating stories without epic context | Run epic-tech-context before create-story |
|
||||
| Skipping story-context generation | Always run after create-story for better dev guidance |
|
||||
| Not updating sprint-status.yaml | Update statuses as work progresses |
|
||||
| Thinking Level 2 skips Phase 3 | Level 2 DOES require architecture (just lighter) |
|
||||
| Planning brownfield without docs | Run document-project first if undocumented |
|
||||
| Not running retrospectives | Complete after every epic for learning transfer |
|
||||
| Manually tracking stories elsewhere | Use sprint-status.yaml as single source of truth |
|
||||
|
||||
## Quick Reference Commands
|
||||
|
||||
|
|
@ -464,47 +481,67 @@ workflow-init (Phase 2)
|
|||
# Universal Entry Point (Start Here!)
|
||||
bmad analyst workflow-status # Check status and get recommendations
|
||||
|
||||
# Phase 0: Documentation (Brownfield if needed)
|
||||
bmad analyst document-project
|
||||
|
||||
# Phase 1: Analysis (Optional)
|
||||
bmad analyst brainstorm-project
|
||||
bmad analyst research
|
||||
bmad analyst product-brief
|
||||
bmad analyst brainstorm-project # Software ideation
|
||||
bmad game-designer brainstorm-game # Game ideation
|
||||
bmad analyst research # Market/technical research
|
||||
bmad analyst product-brief # Software brief
|
||||
bmad game-designer game-brief # Game brief
|
||||
|
||||
# Phase 2: Planning
|
||||
bmad pm prd # Level 2-4 software projects
|
||||
bmad architect tech-spec # Level 0-1 software projects
|
||||
bmad pm gdd # Game projects
|
||||
# Phase 2: Planning (Required)
|
||||
bmad pm prd # Level 2-4 software projects
|
||||
bmad pm tech-spec # Level 0-1 software projects
|
||||
bmad pm gdd # Game projects (all levels)
|
||||
bmad pm narrative # Game narrative (optional)
|
||||
bmad ux-designer create-ux-design # UI-heavy projects
|
||||
|
||||
# Phase 3: Solutioning (L3-4)
|
||||
bmad architect architecture
|
||||
bmad architect tech-spec # Per epic, JIT
|
||||
# Phase 3: Solutioning (Levels 2-4)
|
||||
bmad architect create-architecture # System architecture
|
||||
bmad architect validate-architecture # Validation (optional)
|
||||
bmad architect solutioning-gate-check # Gate check
|
||||
|
||||
# Phase 4: Implementation
|
||||
bmad sm create-story # Draft story from TODO section
|
||||
bmad sm story-ready # Approve story for development (after user review)
|
||||
bmad sm story-context # Generate context XML (optional but recommended)
|
||||
bmad dev dev-story # Implement story from IN PROGRESS section
|
||||
bmad dev story-done # Mark story done (after user confirms DoD)
|
||||
bmad dev review-story # Quality validation (optional)
|
||||
bmad sm correct-course # If issues arise
|
||||
bmad sm retrospective # After epic complete
|
||||
# Phase 4: Implementation (Sprint-Based)
|
||||
bmad sm sprint-planning # FIRST: Initialize sprint tracking
|
||||
bmad sm epic-tech-context # Create epic context (per epic)
|
||||
bmad sm create-story # Draft story file
|
||||
bmad sm story-context # Create story context
|
||||
bmad dev dev-story # Implement story
|
||||
bmad sm review-story # Quality validation
|
||||
# (Update sprint-status.yaml to 'done' manually or via workflow)
|
||||
bmad sm retrospective # After epic complete
|
||||
bmad sm correct-course # If issues arise
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Coming Soon
|
||||
|
||||
- **brownfield-analysis**: Automated codebase documentation generator
|
||||
- **Workflow orchestration**: Automatic phase transitions
|
||||
- **Progress dashboards**: Real-time workflow status
|
||||
- **Automated status updates**: Workflows automatically update sprint-status.yaml
|
||||
- **Workflow orchestration**: Automatic phase transitions and validation
|
||||
- **Progress dashboards**: Real-time workflow status visualization
|
||||
- **Team synchronization**: Multi-developer story coordination
|
||||
|
||||
### Under Consideration
|
||||
|
||||
- AI-assisted retrospectives
|
||||
- Automated story sizing
|
||||
- Predictive epic planning
|
||||
- AI-assisted retrospectives with pattern detection
|
||||
- Automated story sizing based on historical data
|
||||
- Predictive epic planning with risk assessment
|
||||
- Cross-project learning transfer
|
||||
- Enhanced brownfield analysis with architectural debt scoring
|
||||
|
||||
---
|
||||
|
||||
**Version**: v6-alpha
|
||||
**Last Updated**: 2025-10-26
|
||||
|
||||
This document serves as the authoritative guide to BMM v6a workflow execution. For detailed information about individual workflows, see their respective README files in the workflow folders.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **Workflow Paths**: See `workflow-status/paths/` for detailed greenfield/brownfield routing by level
|
||||
- **Phase 2 Planning**: See `2-plan-workflows/README.md` for scale-adaptive planning details
|
||||
- **Phase 4 Sprint Planning**: See `4-implementation/sprint-planning/README.md` for sprint status system
|
||||
- **Individual Workflows**: Each workflow directory contains its own README with specific instructions
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ instructions: "{installed_path}/instructions.md"
|
|||
template: "{project-root}/bmad/bmm/workflows/workflow-status/workflow-status-template.md"
|
||||
|
||||
# Path data files
|
||||
path_files: "{project-root}/src/modules/bmm/workflows/workflow-status/paths/"
|
||||
path_files: "{project-root}/bmad/bmm/workflows/workflow-status/paths/"
|
||||
|
||||
# Output configuration
|
||||
default_output_file: "{output_folder}/bmm-workflow-status.md"
|
||||
|
||||
standalone: true
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# BMM Project Scale Levels - Source of Truth
|
||||
# Reference: /src/modules/bmm/README.md lines 77-85
|
||||
# Reference: /bmad/bmm/README.md lines 77-85
|
||||
|
||||
levels:
|
||||
0:
|
||||
|
|
|
|||
|
|
@ -25,5 +25,6 @@ path_files: "{installed_path}/paths/"
|
|||
# Output configuration - reads existing status
|
||||
default_output_file: "{output_folder}/bmm-workflow-status.md"
|
||||
|
||||
# This is now a lightweight router workflow
|
||||
standalone: true
|
||||
|
||||
web_bundle: false
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ design_methods: "{installed_path}/design-methods.csv"
|
|||
# Output configuration
|
||||
default_output_file: "{output_folder}/design-thinking-{{date}}.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "design-thinking"
|
||||
description: "Guide human-centered design processes using empathy-driven methodologies. This workflow walks through the design thinking phases - Empathize, Define, Ideate, Prototype, and Test - to create solutions deeply rooted in user needs."
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ innovation_frameworks: "{installed_path}/innovation-frameworks.csv"
|
|||
# Output configuration
|
||||
default_output_file: "{output_folder}/innovation-strategy-{{date}}.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "innovation-strategy"
|
||||
description: "Identify disruption opportunities and architect business model innovation. This workflow guides strategic analysis of markets, competitive dynamics, and business model innovation to uncover sustainable competitive advantages and breakthrough opportunities."
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ solving_methods: "{installed_path}/solving-methods.csv"
|
|||
# Output configuration
|
||||
default_output_file: "{output_folder}/problem-solution-{{date}}.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "problem-solving"
|
||||
description: "Apply systematic problem-solving methodologies to crack complex challenges. This workflow guides through problem diagnosis, root cause analysis, creative solution generation, evaluation, and implementation planning using proven frameworks."
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ story_frameworks: "{installed_path}/story-types.csv"
|
|||
# Output configuration
|
||||
default_output_file: "{output_folder}/story-{{date}}.md"
|
||||
|
||||
standalone: true
|
||||
|
||||
web_bundle:
|
||||
name: "storytelling"
|
||||
description: "Craft compelling narratives using proven story frameworks and techniques. This workflow guides users through structured narrative development, applying appropriate story frameworks to create emotionally resonant and engaging stories for any purpose."
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ module.exports = {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle quick update separately
|
||||
if (config.actionType === 'quick-update') {
|
||||
const result = await installer.quickUpdate(config);
|
||||
console.log(chalk.green('\n✨ Quick update complete!'));
|
||||
console.log(chalk.cyan(`Updated ${result.moduleCount} modules with preserved settings`));
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Regular install/update flow
|
||||
const result = await installer.install(config);
|
||||
|
||||
|
|
|
|||
|
|
@ -26,22 +26,25 @@ class ConfigCollector {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Try to load existing module configs
|
||||
const modules = ['core', 'bmm', 'cis'];
|
||||
// Dynamically discover all installed modules by scanning bmad directory
|
||||
// A directory is a module ONLY if it contains a config.yaml file
|
||||
let foundAny = false;
|
||||
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
||||
|
||||
for (const moduleName of modules) {
|
||||
const moduleConfigPath = path.join(bmadDir, moduleName, 'config.yaml');
|
||||
if (await fs.pathExists(moduleConfigPath)) {
|
||||
try {
|
||||
const content = await fs.readFile(moduleConfigPath, 'utf8');
|
||||
const moduleConfig = yaml.load(content);
|
||||
if (moduleConfig) {
|
||||
this.existingConfig[moduleName] = moduleConfig;
|
||||
foundAny = true;
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
const moduleConfigPath = path.join(bmadDir, entry.name, 'config.yaml');
|
||||
if (await fs.pathExists(moduleConfigPath)) {
|
||||
try {
|
||||
const content = await fs.readFile(moduleConfigPath, 'utf8');
|
||||
const moduleConfig = yaml.load(content);
|
||||
if (moduleConfig) {
|
||||
this.existingConfig[entry.name] = moduleConfig;
|
||||
foundAny = true;
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors for individual modules
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors for individual modules
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,6 +89,203 @@ class ConfigCollector {
|
|||
return this.collectedConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect configuration for a single module (Quick Update mode - only new fields)
|
||||
* @param {string} moduleName - Module name
|
||||
* @param {string} projectDir - Target project directory
|
||||
* @param {boolean} silentMode - If true, only prompt for new/missing fields
|
||||
* @returns {boolean} True if new fields were prompted, false if all fields existed
|
||||
*/
|
||||
async collectModuleConfigQuick(moduleName, projectDir, silentMode = true) {
|
||||
this.currentProjectDir = projectDir;
|
||||
|
||||
// Load existing config if not already loaded
|
||||
if (!this.existingConfig) {
|
||||
await this.loadExistingConfig(projectDir);
|
||||
}
|
||||
|
||||
// Initialize allAnswers if not already initialized
|
||||
if (!this.allAnswers) {
|
||||
this.allAnswers = {};
|
||||
}
|
||||
|
||||
// Load module's install config schema
|
||||
const installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'install-config.yaml');
|
||||
const legacyConfigPath = path.join(getModulePath(moduleName), 'config.yaml');
|
||||
|
||||
let configPath = null;
|
||||
if (await fs.pathExists(installerConfigPath)) {
|
||||
configPath = installerConfigPath;
|
||||
} else if (await fs.pathExists(legacyConfigPath)) {
|
||||
configPath = legacyConfigPath;
|
||||
} else {
|
||||
// No config schema for this module - use existing values
|
||||
if (this.existingConfig && this.existingConfig[moduleName]) {
|
||||
if (!this.collectedConfig[moduleName]) {
|
||||
this.collectedConfig[moduleName] = {};
|
||||
}
|
||||
this.collectedConfig[moduleName] = { ...this.existingConfig[moduleName] };
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const configContent = await fs.readFile(configPath, 'utf8');
|
||||
const moduleConfig = yaml.load(configContent);
|
||||
|
||||
if (!moduleConfig) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare schema with existing config to find new/missing fields
|
||||
const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
|
||||
const existingKeys = this.existingConfig && this.existingConfig[moduleName] ? Object.keys(this.existingConfig[moduleName]) : [];
|
||||
|
||||
const newKeys = configKeys.filter((key) => {
|
||||
const item = moduleConfig[key];
|
||||
// Check if it's a config item and doesn't exist in existing config
|
||||
return item && typeof item === 'object' && item.prompt && !existingKeys.includes(key);
|
||||
});
|
||||
|
||||
// If in silent mode and no new keys, use existing config and skip prompts
|
||||
if (silentMode && newKeys.length === 0) {
|
||||
if (this.existingConfig && this.existingConfig[moduleName]) {
|
||||
if (!this.collectedConfig[moduleName]) {
|
||||
this.collectedConfig[moduleName] = {};
|
||||
}
|
||||
this.collectedConfig[moduleName] = { ...this.existingConfig[moduleName] };
|
||||
|
||||
// Also populate allAnswers for cross-referencing
|
||||
for (const [key, value] of Object.entries(this.existingConfig[moduleName])) {
|
||||
this.allAnswers[`${moduleName}_${key}`] = value;
|
||||
}
|
||||
}
|
||||
return false; // No new fields
|
||||
}
|
||||
|
||||
// If we have new fields, show prompt section and collect only new fields
|
||||
if (newKeys.length > 0) {
|
||||
console.log(chalk.yellow(`\n📋 New configuration options available for ${moduleName}`));
|
||||
if (moduleConfig.prompt) {
|
||||
const prompts = Array.isArray(moduleConfig.prompt) ? moduleConfig.prompt : [moduleConfig.prompt];
|
||||
CLIUtils.displayPromptSection(prompts);
|
||||
}
|
||||
|
||||
const questions = [];
|
||||
for (const key of newKeys) {
|
||||
const item = moduleConfig[key];
|
||||
const question = await this.buildQuestion(moduleName, key, item);
|
||||
if (question) {
|
||||
questions.push(question);
|
||||
}
|
||||
}
|
||||
|
||||
if (questions.length > 0) {
|
||||
console.log(); // Line break before questions
|
||||
const answers = await inquirer.prompt(questions);
|
||||
|
||||
// Store answers for cross-referencing
|
||||
Object.assign(this.allAnswers, answers);
|
||||
|
||||
// Process answers and build result values
|
||||
for (const key of Object.keys(answers)) {
|
||||
const originalKey = key.replace(`${moduleName}_`, '');
|
||||
const item = moduleConfig[originalKey];
|
||||
const value = answers[key];
|
||||
|
||||
let result;
|
||||
if (Array.isArray(value)) {
|
||||
result = value;
|
||||
} else if (item.result) {
|
||||
result = this.processResultTemplate(item.result, value);
|
||||
} else {
|
||||
result = value;
|
||||
}
|
||||
|
||||
if (!this.collectedConfig[moduleName]) {
|
||||
this.collectedConfig[moduleName] = {};
|
||||
}
|
||||
this.collectedConfig[moduleName][originalKey] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy over existing values for fields that weren't prompted
|
||||
if (this.existingConfig && this.existingConfig[moduleName]) {
|
||||
if (!this.collectedConfig[moduleName]) {
|
||||
this.collectedConfig[moduleName] = {};
|
||||
}
|
||||
for (const [key, value] of Object.entries(this.existingConfig[moduleName])) {
|
||||
if (!this.collectedConfig[moduleName][key]) {
|
||||
this.collectedConfig[moduleName][key] = value;
|
||||
this.allAnswers[`${moduleName}_${key}`] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newKeys.length > 0; // Return true if we prompted for new fields
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a result template with value substitution
|
||||
* @param {*} resultTemplate - The result template
|
||||
* @param {*} value - The value to substitute
|
||||
* @returns {*} Processed result
|
||||
*/
|
||||
processResultTemplate(resultTemplate, value) {
|
||||
let result = resultTemplate;
|
||||
|
||||
if (typeof result === 'string' && value !== undefined) {
|
||||
if (typeof value === 'string') {
|
||||
result = result.replace('{value}', value);
|
||||
} else if (typeof value === 'boolean' || typeof value === 'number') {
|
||||
if (result === '{value}') {
|
||||
result = value;
|
||||
} else {
|
||||
result = result.replace('{value}', value);
|
||||
}
|
||||
} else {
|
||||
result = value;
|
||||
}
|
||||
|
||||
if (typeof result === 'string') {
|
||||
result = result.replaceAll(/{([^}]+)}/g, (match, configKey) => {
|
||||
if (configKey === 'project-root') {
|
||||
return '{project-root}';
|
||||
}
|
||||
if (configKey === 'value') {
|
||||
return match;
|
||||
}
|
||||
|
||||
let configValue = this.allAnswers[configKey] || this.allAnswers[`${configKey}`];
|
||||
if (!configValue) {
|
||||
for (const [answerKey, answerValue] of Object.entries(this.allAnswers)) {
|
||||
if (answerKey.endsWith(`_${configKey}`)) {
|
||||
configValue = answerValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!configValue) {
|
||||
for (const mod of Object.keys(this.collectedConfig)) {
|
||||
if (mod !== '_meta' && this.collectedConfig[mod] && this.collectedConfig[mod][configKey]) {
|
||||
configValue = this.collectedConfig[mod][configKey];
|
||||
if (typeof configValue === 'string' && configValue.includes('{project-root}/')) {
|
||||
configValue = configValue.replace('{project-root}/', '');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return configValue || match;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect configuration for a single module
|
||||
* @param {string} moduleName - Module name
|
||||
|
|
|
|||
|
|
@ -599,6 +599,7 @@ class DependencyResolver {
|
|||
organized[module] = {
|
||||
agents: [],
|
||||
tasks: [],
|
||||
tools: [],
|
||||
templates: [],
|
||||
data: [],
|
||||
other: [],
|
||||
|
|
@ -626,6 +627,8 @@ class DependencyResolver {
|
|||
organized[module].agents.push(file);
|
||||
} else if (relative.startsWith('tasks/') || file.includes('/tasks/')) {
|
||||
organized[module].tasks.push(file);
|
||||
} else if (relative.startsWith('tools/') || file.includes('/tools/')) {
|
||||
organized[module].tools.push(file);
|
||||
} else if (relative.includes('template') || file.includes('/templates/')) {
|
||||
organized[module].templates.push(file);
|
||||
} else if (relative.includes('data/')) {
|
||||
|
|
@ -646,7 +649,8 @@ class DependencyResolver {
|
|||
|
||||
for (const [module, files] of Object.entries(organized)) {
|
||||
const isSelected = selectedModules.includes(module) || module === 'core';
|
||||
const totalFiles = files.agents.length + files.tasks.length + files.templates.length + files.data.length + files.other.length;
|
||||
const totalFiles =
|
||||
files.agents.length + files.tasks.length + files.tools.length + files.templates.length + files.data.length + files.other.length;
|
||||
|
||||
if (totalFiles > 0) {
|
||||
console.log(chalk.cyan(`\n ${module.toUpperCase()} module:`));
|
||||
|
|
|
|||
|
|
@ -55,14 +55,16 @@ class Detector {
|
|||
}
|
||||
|
||||
// Check for modules
|
||||
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory() && entry.name !== 'core' && entry.name !== '_cfg') {
|
||||
const modulePath = path.join(bmadDir, entry.name);
|
||||
// If manifest exists, use it as the source of truth for installed modules
|
||||
// Otherwise fall back to directory scanning (legacy installations)
|
||||
if (manifestData && manifestData.modules && manifestData.modules.length > 0) {
|
||||
// Use manifest module list - these are officially installed modules
|
||||
for (const moduleId of manifestData.modules) {
|
||||
const modulePath = path.join(bmadDir, moduleId);
|
||||
const moduleConfigPath = path.join(modulePath, 'config.yaml');
|
||||
|
||||
const moduleInfo = {
|
||||
id: entry.name,
|
||||
id: moduleId,
|
||||
path: modulePath,
|
||||
version: 'unknown',
|
||||
};
|
||||
|
|
@ -72,7 +74,7 @@ class Detector {
|
|||
const configContent = await fs.readFile(moduleConfigPath, 'utf8');
|
||||
const config = yaml.load(configContent);
|
||||
moduleInfo.version = config.version || 'unknown';
|
||||
moduleInfo.name = config.name || entry.name;
|
||||
moduleInfo.name = config.name || moduleId;
|
||||
moduleInfo.description = config.description;
|
||||
} catch {
|
||||
// Ignore config read errors
|
||||
|
|
@ -81,11 +83,42 @@ class Detector {
|
|||
|
||||
result.modules.push(moduleInfo);
|
||||
}
|
||||
} else {
|
||||
// Fallback: scan directory for modules (legacy installations without manifest)
|
||||
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory() && entry.name !== 'core' && entry.name !== '_cfg') {
|
||||
const modulePath = path.join(bmadDir, entry.name);
|
||||
const moduleConfigPath = path.join(modulePath, 'config.yaml');
|
||||
|
||||
// Only treat it as a module if it has a config.yaml
|
||||
if (await fs.pathExists(moduleConfigPath)) {
|
||||
const moduleInfo = {
|
||||
id: entry.name,
|
||||
path: modulePath,
|
||||
version: 'unknown',
|
||||
};
|
||||
|
||||
try {
|
||||
const configContent = await fs.readFile(moduleConfigPath, 'utf8');
|
||||
const config = yaml.load(configContent);
|
||||
moduleInfo.version = config.version || 'unknown';
|
||||
moduleInfo.name = config.name || entry.name;
|
||||
moduleInfo.description = config.description;
|
||||
} catch {
|
||||
// Ignore config read errors
|
||||
}
|
||||
|
||||
result.modules.push(moduleInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for IDE configurations from manifest
|
||||
if (result.manifest && result.manifest.ides) {
|
||||
result.ides = result.manifest.ides;
|
||||
// Filter out any undefined/null values
|
||||
result.ides = result.manifest.ides.filter((ide) => ide && typeof ide === 'string');
|
||||
}
|
||||
|
||||
// Mark as installed if we found core or modules
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
/**
|
||||
* Manages IDE configuration persistence
|
||||
* Saves and loads IDE-specific configurations to/from bmad/_cfg/ides/
|
||||
*/
|
||||
class IdeConfigManager {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Get path to IDE config directory
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @returns {string} Path to IDE config directory
|
||||
*/
|
||||
getIdeConfigDir(bmadDir) {
|
||||
return path.join(bmadDir, '_cfg', 'ides');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to specific IDE config file
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {string} ideName - IDE name (e.g., 'claude-code')
|
||||
* @returns {string} Path to IDE config file
|
||||
*/
|
||||
getIdeConfigPath(bmadDir, ideName) {
|
||||
return path.join(this.getIdeConfigDir(bmadDir), `${ideName}.yaml`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save IDE configuration
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {string} ideName - IDE name
|
||||
* @param {Object} configuration - IDE-specific configuration object
|
||||
*/
|
||||
async saveIdeConfig(bmadDir, ideName, configuration) {
|
||||
const configDir = this.getIdeConfigDir(bmadDir);
|
||||
await fs.ensureDir(configDir);
|
||||
|
||||
const configPath = this.getIdeConfigPath(bmadDir, ideName);
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Check if config already exists to preserve configured_date
|
||||
let configuredDate = now;
|
||||
if (await fs.pathExists(configPath)) {
|
||||
try {
|
||||
const existing = await this.loadIdeConfig(bmadDir, ideName);
|
||||
if (existing && existing.configured_date) {
|
||||
configuredDate = existing.configured_date;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors reading existing config
|
||||
}
|
||||
}
|
||||
|
||||
const configData = {
|
||||
ide: ideName,
|
||||
configured_date: configuredDate,
|
||||
last_updated: now,
|
||||
configuration: configuration || {},
|
||||
};
|
||||
|
||||
const yamlContent = yaml.dump(configData, {
|
||||
indent: 2,
|
||||
lineWidth: -1,
|
||||
noRefs: true,
|
||||
sortKeys: false,
|
||||
});
|
||||
|
||||
await fs.writeFile(configPath, yamlContent, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load IDE configuration
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {string} ideName - IDE name
|
||||
* @returns {Object|null} IDE configuration or null if not found
|
||||
*/
|
||||
async loadIdeConfig(bmadDir, ideName) {
|
||||
const configPath = this.getIdeConfigPath(bmadDir, ideName);
|
||||
|
||||
if (!(await fs.pathExists(configPath))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(configPath, 'utf8');
|
||||
const config = yaml.load(content);
|
||||
return config;
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Failed to load IDE config for ${ideName}:`, error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all IDE configurations
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @returns {Object} Map of IDE name to configuration
|
||||
*/
|
||||
async loadAllIdeConfigs(bmadDir) {
|
||||
const configDir = this.getIdeConfigDir(bmadDir);
|
||||
const configs = {};
|
||||
|
||||
if (!(await fs.pathExists(configDir))) {
|
||||
return configs;
|
||||
}
|
||||
|
||||
try {
|
||||
const files = await fs.readdir(configDir);
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.yaml')) {
|
||||
const ideName = file.replace('.yaml', '');
|
||||
const config = await this.loadIdeConfig(bmadDir, ideName);
|
||||
if (config) {
|
||||
configs[ideName] = config.configuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Warning: Failed to load IDE configs:', error.message);
|
||||
}
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if IDE has saved configuration
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {string} ideName - IDE name
|
||||
* @returns {boolean} True if configuration exists
|
||||
*/
|
||||
async hasIdeConfig(bmadDir, ideName) {
|
||||
const configPath = this.getIdeConfigPath(bmadDir, ideName);
|
||||
return await fs.pathExists(configPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete IDE configuration
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {string} ideName - IDE name
|
||||
*/
|
||||
async deleteIdeConfig(bmadDir, ideName) {
|
||||
const configPath = this.getIdeConfigPath(bmadDir, ideName);
|
||||
if (await fs.pathExists(configPath)) {
|
||||
await fs.remove(configPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { IdeConfigManager };
|
||||
|
|
@ -16,6 +16,7 @@ const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/p
|
|||
const { AgentPartyGenerator } = require('../../../lib/agent-party-generator');
|
||||
const { CLIUtils } = require('../../../lib/cli-utils');
|
||||
const { ManifestGenerator } = require('./manifest-generator');
|
||||
const { IdeConfigManager } = require('./ide-config-manager');
|
||||
|
||||
class Installer {
|
||||
constructor() {
|
||||
|
|
@ -28,6 +29,7 @@ class Installer {
|
|||
this.xmlHandler = new XmlHandler();
|
||||
this.dependencyResolver = new DependencyResolver();
|
||||
this.configCollector = new ConfigCollector();
|
||||
this.ideConfigManager = new IdeConfigManager();
|
||||
this.installedFiles = []; // Track all installed files
|
||||
}
|
||||
|
||||
|
|
@ -59,9 +61,19 @@ class Installer {
|
|||
previouslyConfiguredIdes = existingInstall.ides || [];
|
||||
}
|
||||
|
||||
// Load saved IDE configurations for already-configured IDEs
|
||||
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
|
||||
|
||||
// Collect IDE-specific configurations if any were selected
|
||||
const ideConfigurations = {};
|
||||
|
||||
// First, add saved configs for already-configured IDEs
|
||||
for (const ide of toolConfig.ides || []) {
|
||||
if (previouslyConfiguredIdes.includes(ide) && savedIdeConfigs[ide]) {
|
||||
ideConfigurations[ide] = savedIdeConfigs[ide];
|
||||
}
|
||||
}
|
||||
|
||||
if (!toolConfig.skipIde && toolConfig.ides && toolConfig.ides.length > 0) {
|
||||
// Determine which IDEs are newly selected (not previously configured)
|
||||
const newlySelectedIdes = toolConfig.ides.filter((ide) => !previouslyConfiguredIdes.includes(ide));
|
||||
|
|
@ -162,8 +174,15 @@ class Installer {
|
|||
}
|
||||
}
|
||||
|
||||
// Collect configurations for modules (core was already collected in UI.promptInstall if interactive)
|
||||
const moduleConfigs = await this.configCollector.collectAllConfigurations(config.modules || [], path.resolve(config.directory));
|
||||
// Collect configurations for modules (skip if quick update already collected them)
|
||||
let moduleConfigs;
|
||||
if (config._quickUpdate) {
|
||||
// Quick update already collected all configs, use them directly
|
||||
moduleConfigs = this.configCollector.collectedConfig;
|
||||
} else {
|
||||
// Regular install - collect configurations (core was already collected in UI.promptInstall if interactive)
|
||||
moduleConfigs = await this.configCollector.collectAllConfigurations(config.modules || [], path.resolve(config.directory));
|
||||
}
|
||||
|
||||
// Tool selection will be collected after we determine if it's a reinstall/update/new install
|
||||
|
||||
|
|
@ -199,7 +218,7 @@ class Installer {
|
|||
spinner.text = 'Checking for existing installation...';
|
||||
const existingInstall = await this.detector.detect(bmadDir);
|
||||
|
||||
if (existingInstall.installed && !config.force) {
|
||||
if (existingInstall.installed && !config.force && !config._quickUpdate) {
|
||||
spinner.stop();
|
||||
|
||||
console.log(chalk.yellow('\n⚠️ Existing BMAD installation detected'));
|
||||
|
|
@ -300,18 +319,84 @@ class Installer {
|
|||
console.log(chalk.dim('DEBUG: No modified files detected'));
|
||||
}
|
||||
}
|
||||
} else if (existingInstall.installed && config._quickUpdate) {
|
||||
// Quick update mode - automatically treat as update without prompting
|
||||
spinner.text = 'Preparing quick update...';
|
||||
config._isUpdate = true;
|
||||
config._existingInstall = existingInstall;
|
||||
|
||||
// Detect custom and modified files BEFORE updating
|
||||
const existingFilesManifest = await this.readFilesManifest(bmadDir);
|
||||
const { customFiles, modifiedFiles } = await this.detectCustomFiles(bmadDir, existingFilesManifest);
|
||||
|
||||
config._customFiles = customFiles;
|
||||
config._modifiedFiles = modifiedFiles;
|
||||
|
||||
// Back up custom files
|
||||
if (customFiles.length > 0) {
|
||||
const tempBackupDir = path.join(projectDir, '.bmad-custom-backup-temp');
|
||||
await fs.ensureDir(tempBackupDir);
|
||||
|
||||
spinner.start(`Backing up ${customFiles.length} custom files...`);
|
||||
for (const customFile of customFiles) {
|
||||
const relativePath = path.relative(bmadDir, customFile);
|
||||
const backupPath = path.join(tempBackupDir, relativePath);
|
||||
await fs.ensureDir(path.dirname(backupPath));
|
||||
await fs.copy(customFile, backupPath);
|
||||
}
|
||||
spinner.succeed(`Backed up ${customFiles.length} custom files`);
|
||||
config._tempBackupDir = tempBackupDir;
|
||||
}
|
||||
|
||||
// Back up modified files
|
||||
if (modifiedFiles.length > 0) {
|
||||
const tempModifiedBackupDir = path.join(projectDir, '.bmad-modified-backup-temp');
|
||||
await fs.ensureDir(tempModifiedBackupDir);
|
||||
|
||||
spinner.start(`Backing up ${modifiedFiles.length} modified files...`);
|
||||
for (const modifiedFile of modifiedFiles) {
|
||||
const relativePath = path.relative(bmadDir, modifiedFile.path);
|
||||
const tempBackupPath = path.join(tempModifiedBackupDir, relativePath);
|
||||
await fs.ensureDir(path.dirname(tempBackupPath));
|
||||
await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
|
||||
}
|
||||
spinner.succeed(`Backed up ${modifiedFiles.length} modified files`);
|
||||
config._tempModifiedBackupDir = tempModifiedBackupDir;
|
||||
}
|
||||
}
|
||||
|
||||
// Now collect tool configurations after we know if it's a reinstall
|
||||
// Skip for quick update since we already have the IDE list
|
||||
spinner.stop();
|
||||
const toolSelection = await this.collectToolConfigurations(
|
||||
path.resolve(config.directory),
|
||||
config.modules,
|
||||
config._isFullReinstall || false,
|
||||
config._previouslyConfiguredIdes || [],
|
||||
);
|
||||
let toolSelection;
|
||||
if (config._quickUpdate) {
|
||||
// Quick update already has IDEs configured, use saved configurations
|
||||
const preConfiguredIdes = {};
|
||||
const savedIdeConfigs = config._savedIdeConfigs || {};
|
||||
|
||||
// Merge tool selection into config
|
||||
for (const ide of config.ides || []) {
|
||||
// Use saved config if available, otherwise mark as already configured (legacy)
|
||||
if (savedIdeConfigs[ide]) {
|
||||
preConfiguredIdes[ide] = savedIdeConfigs[ide];
|
||||
} else {
|
||||
preConfiguredIdes[ide] = { _alreadyConfigured: true };
|
||||
}
|
||||
}
|
||||
toolSelection = {
|
||||
ides: config.ides || [],
|
||||
skipIde: !config.ides || config.ides.length === 0,
|
||||
configurations: preConfiguredIdes,
|
||||
};
|
||||
} else {
|
||||
toolSelection = await this.collectToolConfigurations(
|
||||
path.resolve(config.directory),
|
||||
config.modules,
|
||||
config._isFullReinstall || false,
|
||||
config._previouslyConfiguredIdes || [],
|
||||
);
|
||||
}
|
||||
|
||||
// Merge tool selection into config (for both quick update and regular flow)
|
||||
config.ides = toolSelection.ides;
|
||||
config.skipIde = toolSelection.skipIde;
|
||||
const ideConfigurations = toolSelection.configurations;
|
||||
|
|
@ -354,7 +439,13 @@ class Installer {
|
|||
// Install partial modules (only dependencies)
|
||||
for (const [module, files] of Object.entries(resolution.byModule)) {
|
||||
if (!config.modules.includes(module) && module !== 'core') {
|
||||
const totalFiles = files.agents.length + files.tasks.length + files.templates.length + files.data.length + files.other.length;
|
||||
const totalFiles =
|
||||
files.agents.length +
|
||||
files.tasks.length +
|
||||
files.tools.length +
|
||||
files.templates.length +
|
||||
files.data.length +
|
||||
files.other.length;
|
||||
if (totalFiles > 0) {
|
||||
spinner.start(`Installing ${module} dependencies...`);
|
||||
await this.installPartialModule(module, bmadDir, files);
|
||||
|
|
@ -385,44 +476,87 @@ class Installer {
|
|||
// Generate CSV manifests for workflows, agents, tasks AND ALL FILES with hashes BEFORE IDE setup
|
||||
spinner.start('Generating workflow and agent manifests...');
|
||||
const manifestGen = new ManifestGenerator();
|
||||
|
||||
// Include preserved modules (from quick update) in the manifest
|
||||
const allModulesToList = config._preserveModules ? [...(config.modules || []), ...config._preserveModules] : config.modules || [];
|
||||
|
||||
const manifestStats = await manifestGen.generateManifests(bmadDir, config.modules || [], this.installedFiles, {
|
||||
ides: config.ides || [],
|
||||
preservedModules: config._preserveModules || [], // Scan these from installed bmad/ dir
|
||||
});
|
||||
|
||||
spinner.succeed(
|
||||
`Manifests generated: ${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.files} files`,
|
||||
`Manifests generated: ${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools, ${manifestStats.files} files`,
|
||||
);
|
||||
|
||||
// Configure IDEs and copy documentation
|
||||
if (!config.skipIde && config.ides && config.ides.length > 0) {
|
||||
spinner.start('Configuring IDEs...');
|
||||
// Filter out any undefined/null values from the IDE list
|
||||
const validIdes = config.ides.filter((ide) => ide && typeof ide === 'string');
|
||||
|
||||
// Temporarily suppress console output if not verbose
|
||||
const originalLog = console.log;
|
||||
if (!config.verbose) {
|
||||
console.log = () => {};
|
||||
if (validIdes.length === 0) {
|
||||
console.log(chalk.yellow('⚠️ No valid IDEs selected. Skipping IDE configuration.'));
|
||||
} else {
|
||||
// Check if any IDE might need prompting (no pre-collected config)
|
||||
const needsPrompting = validIdes.some((ide) => !ideConfigurations[ide]);
|
||||
|
||||
if (!needsPrompting) {
|
||||
spinner.start('Configuring IDEs...');
|
||||
}
|
||||
|
||||
// Temporarily suppress console output if not verbose
|
||||
const originalLog = console.log;
|
||||
if (!config.verbose) {
|
||||
console.log = () => {};
|
||||
}
|
||||
|
||||
for (const ide of validIdes) {
|
||||
// Only show spinner if we have pre-collected config (no prompts expected)
|
||||
if (ideConfigurations[ide] && !needsPrompting) {
|
||||
spinner.text = `Configuring ${ide}...`;
|
||||
} else if (!ideConfigurations[ide]) {
|
||||
// Stop spinner before prompting
|
||||
if (spinner.isSpinning) {
|
||||
spinner.stop();
|
||||
}
|
||||
console.log(chalk.cyan(`\nConfiguring ${ide}...`));
|
||||
}
|
||||
|
||||
// Pass pre-collected configuration to avoid re-prompting
|
||||
await this.ideManager.setup(ide, projectDir, bmadDir, {
|
||||
selectedModules: config.modules || [],
|
||||
preCollectedConfig: ideConfigurations[ide] || null,
|
||||
verbose: config.verbose,
|
||||
});
|
||||
|
||||
// Save IDE configuration for future updates
|
||||
if (ideConfigurations[ide] && !ideConfigurations[ide]._alreadyConfigured) {
|
||||
await this.ideConfigManager.saveIdeConfig(bmadDir, ide, ideConfigurations[ide]);
|
||||
}
|
||||
|
||||
// Restart spinner if we stopped it
|
||||
if (!ideConfigurations[ide] && !spinner.isSpinning) {
|
||||
spinner.start('Configuring IDEs...');
|
||||
}
|
||||
}
|
||||
|
||||
// Restore console.log
|
||||
console.log = originalLog;
|
||||
|
||||
if (spinner.isSpinning) {
|
||||
spinner.succeed(`Configured ${validIdes.length} IDE${validIdes.length > 1 ? 's' : ''}`);
|
||||
} else {
|
||||
console.log(chalk.green(`✓ Configured ${validIdes.length} IDE${validIdes.length > 1 ? 's' : ''}`));
|
||||
}
|
||||
}
|
||||
|
||||
for (const ide of config.ides) {
|
||||
spinner.text = `Configuring ${ide}...`;
|
||||
|
||||
// Pass pre-collected configuration to avoid re-prompting
|
||||
await this.ideManager.setup(ide, projectDir, bmadDir, {
|
||||
selectedModules: config.modules || [],
|
||||
preCollectedConfig: ideConfigurations[ide] || null,
|
||||
verbose: config.verbose,
|
||||
});
|
||||
// Copy IDE-specific documentation (only for valid IDEs)
|
||||
const validIdesForDocs = (config.ides || []).filter((ide) => ide && typeof ide === 'string');
|
||||
if (validIdesForDocs.length > 0) {
|
||||
spinner.start('Copying IDE documentation...');
|
||||
await this.copyIdeDocumentation(validIdesForDocs, bmadDir);
|
||||
spinner.succeed('IDE documentation copied');
|
||||
}
|
||||
|
||||
// Restore console.log
|
||||
console.log = originalLog;
|
||||
|
||||
spinner.succeed(`Configured ${config.ides.length} IDE${config.ides.length > 1 ? 's' : ''}`);
|
||||
|
||||
// Copy IDE-specific documentation
|
||||
spinner.start('Copying IDE documentation...');
|
||||
await this.copyIdeDocumentation(config.ides, bmadDir);
|
||||
spinner.succeed('IDE documentation copied');
|
||||
}
|
||||
|
||||
// Run module-specific installers after IDE setup
|
||||
|
|
@ -841,6 +975,22 @@ class Installer {
|
|||
}
|
||||
}
|
||||
|
||||
if (files.tools && files.tools.length > 0) {
|
||||
const toolsDir = path.join(targetBase, 'tools');
|
||||
await fs.ensureDir(toolsDir);
|
||||
|
||||
for (const toolPath of files.tools) {
|
||||
const fileName = path.basename(toolPath);
|
||||
const sourcePath = path.join(sourceBase, 'tools', fileName);
|
||||
const targetPath = path.join(toolsDir, fileName);
|
||||
|
||||
if (await fs.pathExists(sourcePath)) {
|
||||
await fs.copy(sourcePath, targetPath);
|
||||
this.installedFiles.push(targetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.templates && files.templates.length > 0) {
|
||||
const templatesDir = path.join(targetBase, 'templates');
|
||||
await fs.ensureDir(templatesDir);
|
||||
|
|
@ -1349,6 +1499,116 @@ class Installer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick update method - preserves all settings and only prompts for new config fields
|
||||
* @param {Object} config - Configuration with directory
|
||||
* @returns {Object} Update result
|
||||
*/
|
||||
async quickUpdate(config) {
|
||||
const ora = require('ora');
|
||||
const spinner = ora('Starting quick update...').start();
|
||||
|
||||
try {
|
||||
const projectDir = path.resolve(config.directory);
|
||||
const bmadDir = path.join(projectDir, 'bmad');
|
||||
|
||||
// Check if bmad directory exists
|
||||
if (!(await fs.pathExists(bmadDir))) {
|
||||
spinner.fail('No BMAD installation found');
|
||||
throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
|
||||
}
|
||||
|
||||
spinner.text = 'Detecting installed modules and configuration...';
|
||||
|
||||
// Detect existing installation
|
||||
const existingInstall = await this.detector.detect(bmadDir);
|
||||
const installedModules = existingInstall.modules.map((m) => m.id);
|
||||
const configuredIdes = existingInstall.ides || [];
|
||||
|
||||
// Load saved IDE configurations
|
||||
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
|
||||
|
||||
// Get available modules (what we have source for)
|
||||
const availableModules = await this.moduleManager.listAvailable();
|
||||
const availableModuleIds = new Set(availableModules.map((m) => m.id));
|
||||
|
||||
// Only update modules that are BOTH installed AND available (we have source for)
|
||||
const modulesToUpdate = installedModules.filter((id) => availableModuleIds.has(id));
|
||||
const skippedModules = installedModules.filter((id) => !availableModuleIds.has(id));
|
||||
|
||||
spinner.succeed(`Found ${modulesToUpdate.length} module(s) to update and ${configuredIdes.length} configured tool(s)`);
|
||||
|
||||
if (skippedModules.length > 0) {
|
||||
console.log(chalk.yellow(`⚠️ Skipping ${skippedModules.length} module(s) - no source available: ${skippedModules.join(', ')}`));
|
||||
}
|
||||
|
||||
// Load existing configs and collect new fields (if any)
|
||||
console.log(chalk.cyan('\n📋 Checking for new configuration options...'));
|
||||
await this.configCollector.loadExistingConfig(projectDir);
|
||||
|
||||
let promptedForNewFields = false;
|
||||
|
||||
// Check core config for new fields
|
||||
const corePrompted = await this.configCollector.collectModuleConfigQuick('core', projectDir, true);
|
||||
if (corePrompted) {
|
||||
promptedForNewFields = true;
|
||||
}
|
||||
|
||||
// Check each module we're updating for new fields (NOT skipped modules)
|
||||
for (const moduleName of modulesToUpdate) {
|
||||
const modulePrompted = await this.configCollector.collectModuleConfigQuick(moduleName, projectDir, true);
|
||||
if (modulePrompted) {
|
||||
promptedForNewFields = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!promptedForNewFields) {
|
||||
console.log(chalk.green('✓ All configuration is up to date, no new options to configure'));
|
||||
}
|
||||
|
||||
// Add metadata
|
||||
this.configCollector.collectedConfig._meta = {
|
||||
version: require(path.join(getProjectRoot(), 'package.json')).version,
|
||||
installDate: new Date().toISOString(),
|
||||
lastModified: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Now run the full installation with the collected configs
|
||||
spinner.start('Updating BMAD installation...');
|
||||
|
||||
// Build the config object for the installer
|
||||
const installConfig = {
|
||||
directory: projectDir,
|
||||
installCore: true,
|
||||
modules: modulesToUpdate, // Only update modules we have source for
|
||||
ides: configuredIdes,
|
||||
skipIde: configuredIdes.length === 0,
|
||||
coreConfig: this.configCollector.collectedConfig.core,
|
||||
actionType: 'install', // Use regular install flow
|
||||
_quickUpdate: true, // Flag to skip certain prompts
|
||||
_preserveModules: skippedModules, // Preserve these in manifest even though we didn't update them
|
||||
_savedIdeConfigs: savedIdeConfigs, // Pass saved IDE configs to installer
|
||||
};
|
||||
|
||||
// Call the standard install method
|
||||
const result = await this.install(installConfig);
|
||||
|
||||
spinner.succeed('Quick update complete!');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
moduleCount: modulesToUpdate.length + 1, // +1 for core
|
||||
hadNewFields: promptedForNewFields,
|
||||
modules: ['core', ...modulesToUpdate],
|
||||
skippedModules: skippedModules,
|
||||
ides: configuredIdes,
|
||||
};
|
||||
} catch (error) {
|
||||
spinner.fail('Quick update failed');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private: Prompt for update action
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class ManifestGenerator {
|
|||
this.workflows = [];
|
||||
this.agents = [];
|
||||
this.tasks = [];
|
||||
this.tools = [];
|
||||
this.modules = [];
|
||||
this.files = [];
|
||||
this.selectedIdes = [];
|
||||
|
|
@ -28,8 +29,11 @@ class ManifestGenerator {
|
|||
const cfgDir = path.join(bmadDir, '_cfg');
|
||||
await fs.ensureDir(cfgDir);
|
||||
|
||||
// Store modules list
|
||||
this.modules = ['core', ...selectedModules];
|
||||
// Store modules list (all modules including preserved ones)
|
||||
const preservedModules = options.preservedModules || [];
|
||||
this.modules = ['core', ...selectedModules, ...preservedModules];
|
||||
this.updatedModules = ['core', ...selectedModules]; // Only these get rescanned
|
||||
this.preservedModules = preservedModules; // These stay as-is in CSVs
|
||||
this.bmadDir = bmadDir;
|
||||
this.allInstalledFiles = installedFiles;
|
||||
|
||||
|
|
@ -42,7 +46,8 @@ class ManifestGenerator {
|
|||
throw new TypeError('ManifestGenerator expected `options.ides` to be an array.');
|
||||
}
|
||||
|
||||
this.selectedIdes = resolvedIdes;
|
||||
// Filter out any undefined/null values from IDE list
|
||||
this.selectedIdes = resolvedIdes.filter((ide) => ide && typeof ide === 'string');
|
||||
|
||||
// Collect workflow data
|
||||
await this.collectWorkflows(selectedModules);
|
||||
|
|
@ -53,12 +58,16 @@ class ManifestGenerator {
|
|||
// Collect task data
|
||||
await this.collectTasks(selectedModules);
|
||||
|
||||
// Collect tool data
|
||||
await this.collectTools(selectedModules);
|
||||
|
||||
// Write manifest files and collect their paths
|
||||
const manifestFiles = [
|
||||
await this.writeMainManifest(cfgDir),
|
||||
await this.writeWorkflowManifest(cfgDir),
|
||||
await this.writeAgentManifest(cfgDir),
|
||||
await this.writeTaskManifest(cfgDir),
|
||||
await this.writeToolManifest(cfgDir),
|
||||
await this.writeFilesManifest(cfgDir),
|
||||
];
|
||||
|
||||
|
|
@ -66,6 +75,7 @@ class ManifestGenerator {
|
|||
workflows: this.workflows.length,
|
||||
agents: this.agents.length,
|
||||
tasks: this.tasks.length,
|
||||
tools: this.tools.length,
|
||||
files: this.files.length,
|
||||
manifestFiles: manifestFiles,
|
||||
};
|
||||
|
|
@ -130,11 +140,15 @@ class ManifestGenerator {
|
|||
? `bmad/core/workflows/${relativePath}/workflow.yaml`
|
||||
: `bmad/${moduleName}/workflows/${relativePath}/workflow.yaml`;
|
||||
|
||||
// Check for standalone property (default: false)
|
||||
const standalone = workflow.standalone === true;
|
||||
|
||||
workflows.push({
|
||||
name: workflow.name,
|
||||
description: workflow.description.replaceAll('"', '""'), // Escape quotes for CSV
|
||||
module: moduleName,
|
||||
path: installPath,
|
||||
standalone: standalone,
|
||||
});
|
||||
|
||||
// Add to files list
|
||||
|
|
@ -303,24 +317,34 @@ class ManifestGenerator {
|
|||
const files = await fs.readdir(dirPath);
|
||||
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.md')) {
|
||||
// Check for both .xml and .md files
|
||||
if (file.endsWith('.xml') || file.endsWith('.md')) {
|
||||
const filePath = path.join(dirPath, file);
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
|
||||
// Extract task metadata from content if possible
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
|
||||
// Try description attribute first, fall back to <objective> element
|
||||
const descMatch = content.match(/description="([^"]+)"/);
|
||||
const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
|
||||
const description = descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '';
|
||||
|
||||
// Check for standalone attribute in <task> tag (default: false)
|
||||
const standaloneMatch = content.match(/<task[^>]+standalone="true"/);
|
||||
const standalone = !!standaloneMatch;
|
||||
|
||||
// Build relative path for installation
|
||||
const installPath = moduleName === 'core' ? `bmad/core/tasks/${file}` : `bmad/${moduleName}/tasks/${file}`;
|
||||
|
||||
const taskName = file.replace('.md', '');
|
||||
const taskName = file.replace(/\.(xml|md)$/, '');
|
||||
tasks.push({
|
||||
name: taskName,
|
||||
displayName: nameMatch ? nameMatch[1] : taskName,
|
||||
description: objMatch ? objMatch[1].trim().replaceAll('"', '""') : '',
|
||||
description: description.replaceAll('"', '""'),
|
||||
module: moduleName,
|
||||
path: installPath,
|
||||
standalone: standalone,
|
||||
});
|
||||
|
||||
// Add to files list
|
||||
|
|
@ -336,6 +360,82 @@ class ManifestGenerator {
|
|||
return tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all tools from core and selected modules
|
||||
* Scans the INSTALLED bmad directory, not the source
|
||||
*/
|
||||
async collectTools(selectedModules) {
|
||||
this.tools = [];
|
||||
|
||||
// Get core tools from installed bmad directory
|
||||
const coreToolsPath = path.join(this.bmadDir, 'core', 'tools');
|
||||
if (await fs.pathExists(coreToolsPath)) {
|
||||
const coreTools = await this.getToolsFromDir(coreToolsPath, 'core');
|
||||
this.tools.push(...coreTools);
|
||||
}
|
||||
|
||||
// Get module tools from installed bmad directory
|
||||
for (const moduleName of selectedModules) {
|
||||
const toolsPath = path.join(this.bmadDir, moduleName, 'tools');
|
||||
|
||||
if (await fs.pathExists(toolsPath)) {
|
||||
const moduleTools = await this.getToolsFromDir(toolsPath, moduleName);
|
||||
this.tools.push(...moduleTools);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools from a directory
|
||||
*/
|
||||
async getToolsFromDir(dirPath, moduleName) {
|
||||
const tools = [];
|
||||
const files = await fs.readdir(dirPath);
|
||||
|
||||
for (const file of files) {
|
||||
// Check for both .xml and .md files
|
||||
if (file.endsWith('.xml') || file.endsWith('.md')) {
|
||||
const filePath = path.join(dirPath, file);
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
|
||||
// Extract tool metadata from content if possible
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
|
||||
// Try description attribute first, fall back to <objective> element
|
||||
const descMatch = content.match(/description="([^"]+)"/);
|
||||
const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
|
||||
const description = descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '';
|
||||
|
||||
// Check for standalone attribute in <tool> tag (default: false)
|
||||
const standaloneMatch = content.match(/<tool[^>]+standalone="true"/);
|
||||
const standalone = !!standaloneMatch;
|
||||
|
||||
// Build relative path for installation
|
||||
const installPath = moduleName === 'core' ? `bmad/core/tools/${file}` : `bmad/${moduleName}/tools/${file}`;
|
||||
|
||||
const toolName = file.replace(/\.(xml|md)$/, '');
|
||||
tools.push({
|
||||
name: toolName,
|
||||
displayName: nameMatch ? nameMatch[1] : toolName,
|
||||
description: description.replaceAll('"', '""'),
|
||||
module: moduleName,
|
||||
path: installPath,
|
||||
standalone: standalone,
|
||||
});
|
||||
|
||||
// Add to files list
|
||||
this.files.push({
|
||||
type: 'tool',
|
||||
name: toolName,
|
||||
module: moduleName,
|
||||
path: installPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write main manifest as YAML with installation info only
|
||||
* @returns {string} Path to the manifest file
|
||||
|
|
@ -364,6 +464,45 @@ class ManifestGenerator {
|
|||
return manifestPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read existing CSV and preserve rows for modules NOT being updated
|
||||
* @param {string} csvPath - Path to existing CSV file
|
||||
* @param {number} moduleColumnIndex - Which column contains the module name (0-indexed)
|
||||
* @returns {Array} Preserved CSV rows (without header)
|
||||
*/
|
||||
async getPreservedCsvRows(csvPath, moduleColumnIndex) {
|
||||
if (!(await fs.pathExists(csvPath)) || this.preservedModules.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(csvPath, 'utf8');
|
||||
const lines = content.trim().split('\n');
|
||||
|
||||
// Skip header row
|
||||
const dataRows = lines.slice(1);
|
||||
const preservedRows = [];
|
||||
|
||||
for (const row of dataRows) {
|
||||
// Simple CSV parsing (handles quoted values)
|
||||
const columns = row.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
|
||||
const cleanColumns = columns.map((c) => c.replaceAll(/^"|"$/g, ''));
|
||||
|
||||
const moduleValue = cleanColumns[moduleColumnIndex];
|
||||
|
||||
// Keep this row if it belongs to a preserved module
|
||||
if (this.preservedModules.includes(moduleValue)) {
|
||||
preservedRows.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
return preservedRows;
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Failed to read existing CSV ${csvPath}:`, error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write workflow manifest CSV
|
||||
* @returns {string} Path to the manifest file
|
||||
|
|
@ -371,12 +510,20 @@ class ManifestGenerator {
|
|||
async writeWorkflowManifest(cfgDir) {
|
||||
const csvPath = path.join(cfgDir, 'workflow-manifest.csv');
|
||||
|
||||
// Create CSV header
|
||||
let csv = 'name,description,module,path\n';
|
||||
// Get preserved rows from existing CSV (module is column 2, 0-indexed)
|
||||
const preservedRows = await this.getPreservedCsvRows(csvPath, 2);
|
||||
|
||||
// Add rows
|
||||
// Create CSV header with standalone column
|
||||
let csv = 'name,description,module,path,standalone\n';
|
||||
|
||||
// Add new rows for updated modules
|
||||
for (const workflow of this.workflows) {
|
||||
csv += `"${workflow.name}","${workflow.description}","${workflow.module}","${workflow.path}"\n`;
|
||||
csv += `"${workflow.name}","${workflow.description}","${workflow.module}","${workflow.path}","${workflow.standalone}"\n`;
|
||||
}
|
||||
|
||||
// Add preserved rows for modules we didn't update
|
||||
for (const row of preservedRows) {
|
||||
csv += row + '\n';
|
||||
}
|
||||
|
||||
await fs.writeFile(csvPath, csv);
|
||||
|
|
@ -390,14 +537,22 @@ class ManifestGenerator {
|
|||
async writeAgentManifest(cfgDir) {
|
||||
const csvPath = path.join(cfgDir, 'agent-manifest.csv');
|
||||
|
||||
// Get preserved rows from existing CSV (module is column 8, 0-indexed)
|
||||
const preservedRows = await this.getPreservedCsvRows(csvPath, 8);
|
||||
|
||||
// Create CSV header with persona fields
|
||||
let csv = 'name,displayName,title,icon,role,identity,communicationStyle,principles,module,path\n';
|
||||
|
||||
// Add rows
|
||||
// Add new rows for updated modules
|
||||
for (const agent of this.agents) {
|
||||
csv += `"${agent.name}","${agent.displayName}","${agent.title}","${agent.icon}","${agent.role}","${agent.identity}","${agent.communicationStyle}","${agent.principles}","${agent.module}","${agent.path}"\n`;
|
||||
}
|
||||
|
||||
// Add preserved rows for modules we didn't update
|
||||
for (const row of preservedRows) {
|
||||
csv += row + '\n';
|
||||
}
|
||||
|
||||
await fs.writeFile(csvPath, csv);
|
||||
return csvPath;
|
||||
}
|
||||
|
|
@ -409,12 +564,47 @@ class ManifestGenerator {
|
|||
async writeTaskManifest(cfgDir) {
|
||||
const csvPath = path.join(cfgDir, 'task-manifest.csv');
|
||||
|
||||
// Create CSV header
|
||||
let csv = 'name,displayName,description,module,path\n';
|
||||
// Get preserved rows from existing CSV (module is column 3, 0-indexed)
|
||||
const preservedRows = await this.getPreservedCsvRows(csvPath, 3);
|
||||
|
||||
// Add rows
|
||||
// Create CSV header with standalone column
|
||||
let csv = 'name,displayName,description,module,path,standalone\n';
|
||||
|
||||
// Add new rows for updated modules
|
||||
for (const task of this.tasks) {
|
||||
csv += `"${task.name}","${task.displayName}","${task.description}","${task.module}","${task.path}"\n`;
|
||||
csv += `"${task.name}","${task.displayName}","${task.description}","${task.module}","${task.path}","${task.standalone}"\n`;
|
||||
}
|
||||
|
||||
// Add preserved rows for modules we didn't update
|
||||
for (const row of preservedRows) {
|
||||
csv += row + '\n';
|
||||
}
|
||||
|
||||
await fs.writeFile(csvPath, csv);
|
||||
return csvPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write tool manifest CSV
|
||||
* @returns {string} Path to the manifest file
|
||||
*/
|
||||
async writeToolManifest(cfgDir) {
|
||||
const csvPath = path.join(cfgDir, 'tool-manifest.csv');
|
||||
|
||||
// Get preserved rows from existing CSV (module is column 3, 0-indexed)
|
||||
const preservedRows = await this.getPreservedCsvRows(csvPath, 3);
|
||||
|
||||
// Create CSV header with standalone column
|
||||
let csv = 'name,displayName,description,module,path,standalone\n';
|
||||
|
||||
// Add new rows for updated modules
|
||||
for (const tool of this.tools) {
|
||||
csv += `"${tool.name}","${tool.displayName}","${tool.description}","${tool.module}","${tool.path}","${tool.standalone}"\n`;
|
||||
}
|
||||
|
||||
// Add preserved rows for modules we didn't update
|
||||
for (const row of preservedRows) {
|
||||
csv += row + '\n';
|
||||
}
|
||||
|
||||
await fs.writeFile(csvPath, csv);
|
||||
|
|
@ -444,6 +634,9 @@ class ManifestGenerator {
|
|||
async writeFilesManifest(cfgDir) {
|
||||
const csvPath = path.join(cfgDir, 'files-manifest.csv');
|
||||
|
||||
// Get preserved rows from existing CSV (module is column 2, 0-indexed)
|
||||
const preservedRows = await this.getPreservedCsvRows(csvPath, 2);
|
||||
|
||||
// Create CSV header with hash column
|
||||
let csv = 'type,name,module,path,hash\n';
|
||||
|
||||
|
|
@ -490,11 +683,16 @@ class ManifestGenerator {
|
|||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
// Add rows
|
||||
// Add rows for updated modules
|
||||
for (const file of allFiles) {
|
||||
csv += `"${file.type}","${file.name}","${file.module}","${file.path}","${file.hash}"\n`;
|
||||
}
|
||||
|
||||
// Add preserved rows for modules we didn't update
|
||||
for (const row of preservedRows) {
|
||||
csv += row + '\n';
|
||||
}
|
||||
|
||||
await fs.writeFile(csvPath, csv);
|
||||
return csvPath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,15 +156,16 @@ class BaseIdeSetup {
|
|||
/**
|
||||
* Get list of tasks from BMAD installation
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {boolean} standaloneOnly - If true, only return standalone tasks
|
||||
* @returns {Array} List of task files
|
||||
*/
|
||||
async getTasks(bmadDir) {
|
||||
async getTasks(bmadDir, standaloneOnly = false) {
|
||||
const tasks = [];
|
||||
|
||||
// Get core tasks
|
||||
// Get core tasks (scan for both .md and .xml)
|
||||
const coreTasksPath = path.join(bmadDir, 'core', 'tasks');
|
||||
if (await fs.pathExists(coreTasksPath)) {
|
||||
const coreTasks = await this.scanDirectory(coreTasksPath, '.md');
|
||||
const coreTasks = await this.scanDirectoryWithStandalone(coreTasksPath, ['.md', '.xml']);
|
||||
tasks.push(
|
||||
...coreTasks.map((t) => ({
|
||||
...t,
|
||||
|
|
@ -179,7 +180,7 @@ class BaseIdeSetup {
|
|||
if (entry.isDirectory() && entry.name !== 'core' && entry.name !== '_cfg' && entry.name !== 'agents') {
|
||||
const moduleTasksPath = path.join(bmadDir, entry.name, 'tasks');
|
||||
if (await fs.pathExists(moduleTasksPath)) {
|
||||
const moduleTasks = await this.scanDirectory(moduleTasksPath, '.md');
|
||||
const moduleTasks = await this.scanDirectoryWithStandalone(moduleTasksPath, ['.md', '.xml']);
|
||||
tasks.push(
|
||||
...moduleTasks.map((t) => ({
|
||||
...t,
|
||||
|
|
@ -190,13 +191,157 @@ class BaseIdeSetup {
|
|||
}
|
||||
}
|
||||
|
||||
// Filter by standalone if requested
|
||||
if (standaloneOnly) {
|
||||
return tasks.filter((t) => t.standalone === true);
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan a directory for files with specific extension
|
||||
* Get list of tools from BMAD installation
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {boolean} standaloneOnly - If true, only return standalone tools
|
||||
* @returns {Array} List of tool files
|
||||
*/
|
||||
async getTools(bmadDir, standaloneOnly = false) {
|
||||
const tools = [];
|
||||
|
||||
// Get core tools (scan for both .md and .xml)
|
||||
const coreToolsPath = path.join(bmadDir, 'core', 'tools');
|
||||
if (await fs.pathExists(coreToolsPath)) {
|
||||
const coreTools = await this.scanDirectoryWithStandalone(coreToolsPath, ['.md', '.xml']);
|
||||
tools.push(
|
||||
...coreTools.map((t) => ({
|
||||
...t,
|
||||
module: 'core',
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
// Get module tools
|
||||
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory() && entry.name !== 'core' && entry.name !== '_cfg' && entry.name !== 'agents') {
|
||||
const moduleToolsPath = path.join(bmadDir, entry.name, 'tools');
|
||||
if (await fs.pathExists(moduleToolsPath)) {
|
||||
const moduleTools = await this.scanDirectoryWithStandalone(moduleToolsPath, ['.md', '.xml']);
|
||||
tools.push(
|
||||
...moduleTools.map((t) => ({
|
||||
...t,
|
||||
module: entry.name,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by standalone if requested
|
||||
if (standaloneOnly) {
|
||||
return tools.filter((t) => t.standalone === true);
|
||||
}
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of workflows from BMAD installation
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {boolean} standaloneOnly - If true, only return standalone workflows
|
||||
* @returns {Array} List of workflow files
|
||||
*/
|
||||
async getWorkflows(bmadDir, standaloneOnly = false) {
|
||||
const workflows = [];
|
||||
|
||||
// Get core workflows
|
||||
const coreWorkflowsPath = path.join(bmadDir, 'core', 'workflows');
|
||||
if (await fs.pathExists(coreWorkflowsPath)) {
|
||||
const coreWorkflows = await this.findWorkflowYamlFiles(coreWorkflowsPath);
|
||||
workflows.push(
|
||||
...coreWorkflows.map((w) => ({
|
||||
...w,
|
||||
module: 'core',
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
// Get module workflows
|
||||
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory() && entry.name !== 'core' && entry.name !== '_cfg' && entry.name !== 'agents') {
|
||||
const moduleWorkflowsPath = path.join(bmadDir, entry.name, 'workflows');
|
||||
if (await fs.pathExists(moduleWorkflowsPath)) {
|
||||
const moduleWorkflows = await this.findWorkflowYamlFiles(moduleWorkflowsPath);
|
||||
workflows.push(
|
||||
...moduleWorkflows.map((w) => ({
|
||||
...w,
|
||||
module: entry.name,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by standalone if requested
|
||||
if (standaloneOnly) {
|
||||
return workflows.filter((w) => w.standalone === true);
|
||||
}
|
||||
|
||||
return workflows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find workflow.yaml files
|
||||
* @param {string} dir - Directory to search
|
||||
* @returns {Array} List of workflow file info objects
|
||||
*/
|
||||
async findWorkflowYamlFiles(dir) {
|
||||
const workflows = [];
|
||||
|
||||
if (!(await fs.pathExists(dir))) {
|
||||
return workflows;
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Recursively search subdirectories
|
||||
const subWorkflows = await this.findWorkflowYamlFiles(fullPath);
|
||||
workflows.push(...subWorkflows);
|
||||
} else if (entry.isFile() && entry.name === 'workflow.yaml') {
|
||||
// Read workflow.yaml to get name and standalone property
|
||||
try {
|
||||
const yaml = require('js-yaml');
|
||||
const content = await fs.readFile(fullPath, 'utf8');
|
||||
const workflowData = yaml.load(content);
|
||||
|
||||
if (workflowData && workflowData.name) {
|
||||
workflows.push({
|
||||
name: workflowData.name,
|
||||
path: fullPath,
|
||||
relativePath: path.relative(dir, fullPath),
|
||||
filename: entry.name,
|
||||
description: workflowData.description || '',
|
||||
standalone: workflowData.standalone === true, // Check standalone property
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Skip invalid workflow files
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return workflows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan a directory for files with specific extension(s)
|
||||
* @param {string} dir - Directory to scan
|
||||
* @param {string} ext - File extension to match
|
||||
* @param {string|Array<string>} ext - File extension(s) to match (e.g., '.md' or ['.md', '.xml'])
|
||||
* @returns {Array} List of file info objects
|
||||
*/
|
||||
async scanDirectory(dir, ext) {
|
||||
|
|
@ -206,6 +351,9 @@ class BaseIdeSetup {
|
|||
return files;
|
||||
}
|
||||
|
||||
// Normalize ext to array
|
||||
const extensions = Array.isArray(ext) ? ext : [ext];
|
||||
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
|
|
@ -215,13 +363,88 @@ class BaseIdeSetup {
|
|||
// Recursively scan subdirectories
|
||||
const subFiles = await this.scanDirectory(fullPath, ext);
|
||||
files.push(...subFiles);
|
||||
} else if (entry.isFile() && entry.name.endsWith(ext)) {
|
||||
files.push({
|
||||
name: path.basename(entry.name, ext),
|
||||
path: fullPath,
|
||||
relativePath: path.relative(dir, fullPath),
|
||||
filename: entry.name,
|
||||
});
|
||||
} else if (entry.isFile()) {
|
||||
// Check if file matches any of the extensions
|
||||
const matchedExt = extensions.find((e) => entry.name.endsWith(e));
|
||||
if (matchedExt) {
|
||||
files.push({
|
||||
name: path.basename(entry.name, matchedExt),
|
||||
path: fullPath,
|
||||
relativePath: path.relative(dir, fullPath),
|
||||
filename: entry.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan a directory for files with specific extension(s) and check standalone attribute
|
||||
* @param {string} dir - Directory to scan
|
||||
* @param {string|Array<string>} ext - File extension(s) to match (e.g., '.md' or ['.md', '.xml'])
|
||||
* @returns {Array} List of file info objects with standalone property
|
||||
*/
|
||||
async scanDirectoryWithStandalone(dir, ext) {
|
||||
const files = [];
|
||||
|
||||
if (!(await fs.pathExists(dir))) {
|
||||
return files;
|
||||
}
|
||||
|
||||
// Normalize ext to array
|
||||
const extensions = Array.isArray(ext) ? ext : [ext];
|
||||
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Recursively scan subdirectories
|
||||
const subFiles = await this.scanDirectoryWithStandalone(fullPath, ext);
|
||||
files.push(...subFiles);
|
||||
} else if (entry.isFile()) {
|
||||
// Check if file matches any of the extensions
|
||||
const matchedExt = extensions.find((e) => entry.name.endsWith(e));
|
||||
if (matchedExt) {
|
||||
// Read file content to check for standalone attribute
|
||||
let standalone = false;
|
||||
try {
|
||||
const content = await fs.readFile(fullPath, 'utf8');
|
||||
|
||||
// Check for standalone="true" in XML files
|
||||
if (entry.name.endsWith('.xml')) {
|
||||
// Look for standalone="true" in the opening tag (task or tool)
|
||||
const standaloneMatch = content.match(/<(?:task|tool)[^>]+standalone="true"/);
|
||||
standalone = !!standaloneMatch;
|
||||
} else if (entry.name.endsWith('.md')) {
|
||||
// Check for standalone: true in YAML frontmatter
|
||||
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
||||
if (frontmatterMatch) {
|
||||
const yaml = require('js-yaml');
|
||||
try {
|
||||
const frontmatter = yaml.load(frontmatterMatch[1]);
|
||||
standalone = frontmatter.standalone === true;
|
||||
} catch {
|
||||
// Ignore YAML parse errors
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// If we can't read the file, assume not standalone
|
||||
standalone = false;
|
||||
}
|
||||
|
||||
files.push({
|
||||
name: path.basename(entry.name, matchedExt),
|
||||
path: fullPath,
|
||||
relativePath: path.relative(dir, fullPath),
|
||||
filename: entry.name,
|
||||
standalone: standalone,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,9 +83,11 @@ class AuggieSetup extends BaseIdeSetup {
|
|||
return { success: false, reason: 'no-locations' };
|
||||
}
|
||||
|
||||
// Get agents and tasks
|
||||
// Get agents, tasks, tools, and workflows (standalone only)
|
||||
const agents = await this.getAgents(bmadDir);
|
||||
const tasks = await this.getTasks(bmadDir);
|
||||
const tasks = await this.getTasks(bmadDir, true);
|
||||
const tools = await this.getTools(bmadDir, true);
|
||||
const workflows = await this.getWorkflows(bmadDir, true);
|
||||
|
||||
let totalInstalled = 0;
|
||||
|
||||
|
|
@ -93,11 +95,16 @@ class AuggieSetup extends BaseIdeSetup {
|
|||
for (const location of locations) {
|
||||
console.log(chalk.dim(`\n Installing to: ${location}`));
|
||||
|
||||
const agentsDir = path.join(location, 'agents');
|
||||
const tasksDir = path.join(location, 'tasks');
|
||||
const bmadCommandsDir = path.join(location, 'bmad');
|
||||
const agentsDir = path.join(bmadCommandsDir, 'agents');
|
||||
const tasksDir = path.join(bmadCommandsDir, 'tasks');
|
||||
const toolsDir = path.join(bmadCommandsDir, 'tools');
|
||||
const workflowsDir = path.join(bmadCommandsDir, 'workflows');
|
||||
|
||||
await this.ensureDir(agentsDir);
|
||||
await this.ensureDir(tasksDir);
|
||||
await this.ensureDir(toolsDir);
|
||||
await this.ensureDir(workflowsDir);
|
||||
|
||||
// Install agents
|
||||
for (const agent of agents) {
|
||||
|
|
@ -119,7 +126,29 @@ class AuggieSetup extends BaseIdeSetup {
|
|||
totalInstalled++;
|
||||
}
|
||||
|
||||
console.log(chalk.green(` ✓ Installed ${agents.length} agents and ${tasks.length} tasks`));
|
||||
// Install tools
|
||||
for (const tool of tools) {
|
||||
const content = await this.readFile(tool.path);
|
||||
const commandContent = this.createToolCommand(tool, content);
|
||||
|
||||
const targetPath = path.join(toolsDir, `${tool.module}-${tool.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
totalInstalled++;
|
||||
}
|
||||
|
||||
// Install workflows
|
||||
for (const workflow of workflows) {
|
||||
const content = await this.readFile(workflow.path);
|
||||
const commandContent = this.createWorkflowCommand(workflow, content);
|
||||
|
||||
const targetPath = path.join(workflowsDir, `${workflow.module}-${workflow.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
totalInstalled++;
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.green(` ✓ Installed ${agents.length} agents, ${tasks.length} tasks, ${tools.length} tools, ${workflows.length} workflows`),
|
||||
);
|
||||
}
|
||||
|
||||
console.log(chalk.green(`\n✓ ${this.name} configured:`));
|
||||
|
|
@ -217,7 +246,7 @@ BMAD ${agent.module.toUpperCase()} module
|
|||
* Create task command content
|
||||
*/
|
||||
createTaskCommand(task, content) {
|
||||
const nameMatch = content.match(/<name>([^<]+)<\/name>/);
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
|
||||
|
||||
return `# ${taskName} Task
|
||||
|
|
@ -232,6 +261,44 @@ BMAD ${task.module.toUpperCase()} module
|
|||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tool command content
|
||||
*/
|
||||
createToolCommand(tool, content) {
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const toolName = nameMatch ? nameMatch[1] : this.formatTitle(tool.name);
|
||||
|
||||
return `# ${toolName} Tool
|
||||
|
||||
## Activation
|
||||
Type \`@tool-${tool.name}\` to execute this tool.
|
||||
|
||||
${content}
|
||||
|
||||
## Module
|
||||
BMAD ${tool.module.toUpperCase()} module
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workflow command content
|
||||
*/
|
||||
createWorkflowCommand(workflow, content) {
|
||||
return `# ${workflow.name} Workflow
|
||||
|
||||
## Description
|
||||
${workflow.description || 'No description provided'}
|
||||
|
||||
## Activation
|
||||
Type \`@workflow-${workflow.name}\` to execute this workflow.
|
||||
|
||||
${content}
|
||||
|
||||
## Module
|
||||
BMAD ${workflow.module.toUpperCase()} module
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup Auggie configuration
|
||||
*/
|
||||
|
|
@ -244,22 +311,19 @@ BMAD ${task.module.toUpperCase()} module
|
|||
for (const location of locations) {
|
||||
const agentsDir = path.join(location, 'agents');
|
||||
const tasksDir = path.join(location, 'tasks');
|
||||
const toolsDir = path.join(location, 'tools');
|
||||
const workflowsDir = path.join(location, 'workflows');
|
||||
|
||||
if (await fs.pathExists(agentsDir)) {
|
||||
// Remove only BMAD files (those with module prefix)
|
||||
const files = await fs.readdir(agentsDir);
|
||||
for (const file of files) {
|
||||
if (file.includes('-') && file.endsWith('.md')) {
|
||||
await fs.remove(path.join(agentsDir, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
const dirs = [agentsDir, tasksDir, toolsDir, workflowsDir];
|
||||
|
||||
if (await fs.pathExists(tasksDir)) {
|
||||
const files = await fs.readdir(tasksDir);
|
||||
for (const file of files) {
|
||||
if (file.includes('-') && file.endsWith('.md')) {
|
||||
await fs.remove(path.join(tasksDir, file));
|
||||
for (const dir of dirs) {
|
||||
if (await fs.pathExists(dir)) {
|
||||
// Remove only BMAD files (those with module prefix)
|
||||
const files = await fs.readdir(dir);
|
||||
for (const file of files) {
|
||||
if (file.includes('-') && file.endsWith('.md')) {
|
||||
await fs.remove(path.join(dir, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ const { BaseIdeSetup } = require('./_base-ide');
|
|||
const chalk = require('chalk');
|
||||
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
||||
const { WorkflowCommandGenerator } = require('./workflow-command-generator');
|
||||
const { TaskToolCommandGenerator } = require('./task-tool-command-generator');
|
||||
const {
|
||||
loadModuleInjectionConfig,
|
||||
shouldApplyInjection,
|
||||
|
|
@ -128,8 +129,12 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||
}
|
||||
|
||||
// Process Claude Code specific injections for installed modules
|
||||
// Use pre-collected configuration if available
|
||||
if (options.preCollectedConfig) {
|
||||
// Use pre-collected configuration if available, or skip if already configured
|
||||
if (options.preCollectedConfig && options.preCollectedConfig._alreadyConfigured) {
|
||||
// IDE is already configured from previous installation, skip prompting
|
||||
// Just process with default/existing configuration
|
||||
await this.processModuleInjectionsWithConfig(projectDir, bmadDir, options, {});
|
||||
} else if (options.preCollectedConfig) {
|
||||
await this.processModuleInjectionsWithConfig(projectDir, bmadDir, options, options.preCollectedConfig);
|
||||
} else {
|
||||
await this.processModuleInjections(projectDir, bmadDir, options);
|
||||
|
|
@ -142,11 +147,22 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|||
const workflowGen = new WorkflowCommandGenerator();
|
||||
const workflowResult = await workflowGen.generateWorkflowCommands(projectDir, bmadDir);
|
||||
|
||||
// Generate task and tool commands from manifests (if they exist)
|
||||
const taskToolGen = new TaskToolCommandGenerator();
|
||||
const taskToolResult = await taskToolGen.generateTaskToolCommands(projectDir, bmadDir);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
||||
if (workflowResult.generated > 0) {
|
||||
console.log(chalk.dim(` - ${workflowResult.generated} workflow commands generated`));
|
||||
}
|
||||
if (taskToolResult.generated > 0) {
|
||||
console.log(
|
||||
chalk.dim(
|
||||
` - ${taskToolResult.generated} task/tool commands generated (${taskToolResult.tasks} tasks, ${taskToolResult.tools} tools)`,
|
||||
),
|
||||
);
|
||||
}
|
||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, bmadCommandsDir)}`));
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -25,79 +25,69 @@ class CrushSetup extends BaseIdeSetup {
|
|||
// Create .crush/commands/bmad directory structure
|
||||
const crushDir = path.join(projectDir, this.configDir);
|
||||
const commandsDir = path.join(crushDir, this.commandsDir, 'bmad');
|
||||
const agentsDir = path.join(commandsDir, 'agents');
|
||||
const tasksDir = path.join(commandsDir, 'tasks');
|
||||
|
||||
await this.ensureDir(agentsDir);
|
||||
await this.ensureDir(tasksDir);
|
||||
await this.ensureDir(commandsDir);
|
||||
|
||||
// Get agents and tasks
|
||||
// Get agents, tasks, tools, and workflows (standalone only)
|
||||
const agents = await this.getAgents(bmadDir);
|
||||
const tasks = await this.getTasks(bmadDir);
|
||||
const tasks = await this.getTasks(bmadDir, true);
|
||||
const tools = await this.getTools(bmadDir, true);
|
||||
const workflows = await this.getWorkflows(bmadDir, true);
|
||||
|
||||
// Setup agents as commands
|
||||
let agentCount = 0;
|
||||
for (const agent of agents) {
|
||||
const content = await this.readFile(agent.path);
|
||||
const commandContent = this.createAgentCommand(agent, content, projectDir);
|
||||
|
||||
const targetPath = path.join(agentsDir, `${agent.module}-${agent.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
agentCount++;
|
||||
}
|
||||
|
||||
// Setup tasks as commands
|
||||
let taskCount = 0;
|
||||
for (const task of tasks) {
|
||||
const content = await this.readFile(task.path);
|
||||
const commandContent = this.createTaskCommand(task, content);
|
||||
|
||||
const targetPath = path.join(tasksDir, `${task.module}-${task.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
taskCount++;
|
||||
}
|
||||
|
||||
// Create module-specific subdirectories for better organization
|
||||
await this.organizeByModule(commandsDir, agents, tasks, bmadDir);
|
||||
// Organize by module
|
||||
const agentCount = await this.organizeByModule(commandsDir, agents, tasks, tools, workflows, projectDir);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agent commands created`));
|
||||
console.log(chalk.dim(` - ${taskCount} task commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount.agents} agent commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount.tasks} task commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount.tools} tool commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount.workflows} workflow commands created`));
|
||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
|
||||
console.log(chalk.dim('\n Commands can be accessed via Crush command palette'));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
...agentCount,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Organize commands by module
|
||||
*/
|
||||
async organizeByModule(commandsDir, agents, tasks, bmadDir) {
|
||||
async organizeByModule(commandsDir, agents, tasks, tools, workflows, projectDir) {
|
||||
// Get unique modules
|
||||
const modules = new Set();
|
||||
for (const agent of agents) modules.add(agent.module);
|
||||
for (const task of tasks) modules.add(task.module);
|
||||
for (const tool of tools) modules.add(tool.module);
|
||||
for (const workflow of workflows) modules.add(workflow.module);
|
||||
|
||||
let agentCount = 0;
|
||||
let taskCount = 0;
|
||||
let toolCount = 0;
|
||||
let workflowCount = 0;
|
||||
|
||||
// Create module directories
|
||||
for (const module of modules) {
|
||||
const moduleDir = path.join(commandsDir, module);
|
||||
const moduleAgentsDir = path.join(moduleDir, 'agents');
|
||||
const moduleTasksDir = path.join(moduleDir, 'tasks');
|
||||
const moduleToolsDir = path.join(moduleDir, 'tools');
|
||||
const moduleWorkflowsDir = path.join(moduleDir, 'workflows');
|
||||
|
||||
await this.ensureDir(moduleAgentsDir);
|
||||
await this.ensureDir(moduleTasksDir);
|
||||
await this.ensureDir(moduleToolsDir);
|
||||
await this.ensureDir(moduleWorkflowsDir);
|
||||
|
||||
// Copy module-specific agents
|
||||
const moduleAgents = agents.filter((a) => a.module === module);
|
||||
for (const agent of moduleAgents) {
|
||||
const content = await this.readFile(agent.path);
|
||||
const commandContent = this.createAgentCommand(agent, content, bmadDir);
|
||||
const commandContent = this.createAgentCommand(agent, content, projectDir);
|
||||
const targetPath = path.join(moduleAgentsDir, `${agent.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
agentCount++;
|
||||
}
|
||||
|
||||
// Copy module-specific tasks
|
||||
|
|
@ -107,8 +97,36 @@ class CrushSetup extends BaseIdeSetup {
|
|||
const commandContent = this.createTaskCommand(task, content);
|
||||
const targetPath = path.join(moduleTasksDir, `${task.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
taskCount++;
|
||||
}
|
||||
|
||||
// Copy module-specific tools
|
||||
const moduleTools = tools.filter((t) => t.module === module);
|
||||
for (const tool of moduleTools) {
|
||||
const content = await this.readFile(tool.path);
|
||||
const commandContent = this.createToolCommand(tool, content);
|
||||
const targetPath = path.join(moduleToolsDir, `${tool.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
toolCount++;
|
||||
}
|
||||
|
||||
// Copy module-specific workflows
|
||||
const moduleWorkflows = workflows.filter((w) => w.module === module);
|
||||
for (const workflow of moduleWorkflows) {
|
||||
const content = await this.readFile(workflow.path);
|
||||
const commandContent = this.createWorkflowCommand(workflow, content);
|
||||
const targetPath = path.join(moduleWorkflowsDir, `${workflow.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
workflowCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
workflows: workflowCount,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -154,7 +172,7 @@ Part of the BMAD ${agent.module.toUpperCase()} module.
|
|||
*/
|
||||
createTaskCommand(task, content) {
|
||||
// Extract task name
|
||||
const nameMatch = content.match(/<name>([^<]+)<\/name>/);
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
|
||||
|
||||
let commandContent = `# /task-${task.name} Command
|
||||
|
|
@ -177,6 +195,60 @@ Part of the BMAD ${task.module.toUpperCase()} module.
|
|||
return commandContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tool command content
|
||||
*/
|
||||
createToolCommand(tool, content) {
|
||||
// Extract tool name
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const toolName = nameMatch ? nameMatch[1] : this.formatTitle(tool.name);
|
||||
|
||||
let commandContent = `# /tool-${tool.name} Command
|
||||
|
||||
When this command is used, execute the following tool:
|
||||
|
||||
## ${toolName} Tool
|
||||
|
||||
${content}
|
||||
|
||||
## Command Usage
|
||||
|
||||
This command executes the ${toolName} tool from the BMAD ${tool.module.toUpperCase()} module.
|
||||
|
||||
## Module
|
||||
|
||||
Part of the BMAD ${tool.module.toUpperCase()} module.
|
||||
`;
|
||||
|
||||
return commandContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workflow command content
|
||||
*/
|
||||
createWorkflowCommand(workflow, content) {
|
||||
const workflowName = workflow.name ? this.formatTitle(workflow.name) : 'Workflow';
|
||||
|
||||
let commandContent = `# /${workflow.name} Command
|
||||
|
||||
When this command is used, execute the following workflow:
|
||||
|
||||
## ${workflowName} Workflow
|
||||
|
||||
${content}
|
||||
|
||||
## Command Usage
|
||||
|
||||
This command executes the ${workflowName} workflow from the BMAD ${workflow.module.toUpperCase()} module.
|
||||
|
||||
## Module
|
||||
|
||||
Part of the BMAD ${workflow.module.toUpperCase()} module.
|
||||
`;
|
||||
|
||||
return commandContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format name as title
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -28,18 +28,22 @@ class CursorSetup extends BaseIdeSetup {
|
|||
|
||||
await this.ensureDir(bmadRulesDir);
|
||||
|
||||
// Get agents and tasks
|
||||
// Get agents, tasks, tools, and workflows (standalone only)
|
||||
const agents = await this.getAgents(bmadDir);
|
||||
const tasks = await this.getTasks(bmadDir);
|
||||
const tasks = await this.getTasks(bmadDir, true);
|
||||
const tools = await this.getTools(bmadDir, true);
|
||||
const workflows = await this.getWorkflows(bmadDir, true);
|
||||
|
||||
// Create directories for each module
|
||||
const modules = new Set();
|
||||
for (const item of [...agents, ...tasks]) modules.add(item.module);
|
||||
for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module);
|
||||
|
||||
for (const module of modules) {
|
||||
await this.ensureDir(path.join(bmadRulesDir, module));
|
||||
await this.ensureDir(path.join(bmadRulesDir, module, 'agents'));
|
||||
await this.ensureDir(path.join(bmadRulesDir, module, 'tasks'));
|
||||
await this.ensureDir(path.join(bmadRulesDir, module, 'tools'));
|
||||
await this.ensureDir(path.join(bmadRulesDir, module, 'workflows'));
|
||||
}
|
||||
|
||||
// Process and copy agents
|
||||
|
|
@ -70,25 +74,57 @@ class CursorSetup extends BaseIdeSetup {
|
|||
taskCount++;
|
||||
}
|
||||
|
||||
// Process and copy tools
|
||||
let toolCount = 0;
|
||||
for (const tool of tools) {
|
||||
const content = await this.readAndProcess(tool.path, {
|
||||
module: tool.module,
|
||||
name: tool.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadRulesDir, tool.module, 'tools', `${tool.name}.mdc`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
toolCount++;
|
||||
}
|
||||
|
||||
// Process and copy workflows
|
||||
let workflowCount = 0;
|
||||
for (const workflow of workflows) {
|
||||
const content = await this.readAndProcess(workflow.path, {
|
||||
module: workflow.module,
|
||||
name: workflow.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadRulesDir, workflow.module, 'workflows', `${workflow.name}.mdc`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
workflowCount++;
|
||||
}
|
||||
|
||||
// Create BMAD index file (but NOT .cursorrules - user manages that)
|
||||
await this.createBMADIndex(bmadRulesDir, agents, tasks, modules);
|
||||
await this.createBMADIndex(bmadRulesDir, agents, tasks, tools, workflows, modules);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
||||
console.log(chalk.dim(` - ${taskCount} tasks installed`));
|
||||
console.log(chalk.dim(` - ${toolCount} tools installed`));
|
||||
console.log(chalk.dim(` - ${workflowCount} workflows installed`));
|
||||
console.log(chalk.dim(` - Rules directory: ${path.relative(projectDir, bmadRulesDir)}`));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
workflows: workflowCount,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create BMAD index file for easy navigation
|
||||
*/
|
||||
async createBMADIndex(bmadRulesDir, agents, tasks, modules) {
|
||||
async createBMADIndex(bmadRulesDir, agents, tasks, tools, workflows, modules) {
|
||||
const indexPath = path.join(bmadRulesDir, 'index.mdc');
|
||||
|
||||
let content = `---
|
||||
|
|
@ -99,7 +135,7 @@ alwaysApply: true
|
|||
|
||||
# BMAD Method - Cursor Rules Index
|
||||
|
||||
This is the master index for all BMAD agents and tasks available in your project.
|
||||
This is the master index for all BMAD agents, tasks, tools, and workflows available in your project.
|
||||
|
||||
## Installation Complete!
|
||||
|
||||
|
|
@ -111,6 +147,8 @@ BMAD rules have been installed to: \`.cursor/rules/bmad/\`
|
|||
|
||||
- Reference specific agents: @bmad/{module}/agents/{agent-name}
|
||||
- Reference specific tasks: @bmad/{module}/tasks/{task-name}
|
||||
- Reference specific tools: @bmad/{module}/tools/{tool-name}
|
||||
- Reference specific workflows: @bmad/{module}/workflows/{workflow-name}
|
||||
- Reference entire modules: @bmad/{module}
|
||||
- Reference this index: @bmad/index
|
||||
|
||||
|
|
@ -140,6 +178,26 @@ BMAD rules have been installed to: \`.cursor/rules/bmad/\`
|
|||
}
|
||||
content += '\n';
|
||||
}
|
||||
|
||||
// List tools for this module
|
||||
const moduleTools = tools.filter((t) => t.module === module);
|
||||
if (moduleTools.length > 0) {
|
||||
content += `**Tools:**\n`;
|
||||
for (const tool of moduleTools) {
|
||||
content += `- @bmad/${module}/tools/${tool.name} - ${tool.name}\n`;
|
||||
}
|
||||
content += '\n';
|
||||
}
|
||||
|
||||
// List workflows for this module
|
||||
const moduleWorkflows = workflows.filter((w) => w.module === module);
|
||||
if (moduleWorkflows.length > 0) {
|
||||
content += `**Workflows:**\n`;
|
||||
for (const workflow of moduleWorkflows) {
|
||||
content += `- @bmad/${module}/workflows/${workflow.name} - ${workflow.name}\n`;
|
||||
}
|
||||
content += '\n';
|
||||
}
|
||||
}
|
||||
|
||||
content += `
|
||||
|
|
@ -148,13 +206,15 @@ BMAD rules have been installed to: \`.cursor/rules/bmad/\`
|
|||
- All BMAD rules are Manual type - reference them explicitly when needed
|
||||
- Agents provide persona-based assistance with specific expertise
|
||||
- Tasks are reusable workflows for common operations
|
||||
- Tools provide specialized functionality
|
||||
- Workflows orchestrate multi-step processes
|
||||
- Each agent includes an activation block for proper initialization
|
||||
|
||||
## Configuration
|
||||
|
||||
BMAD rules are configured as Manual rules (alwaysApply: false) to give you control
|
||||
over when they're included in your context. Reference them explicitly when you need
|
||||
specific agent expertise or task workflows.
|
||||
specific agent expertise, task workflows, tools, or guided workflows.
|
||||
`;
|
||||
|
||||
await this.writeFile(indexPath, content);
|
||||
|
|
@ -182,6 +242,8 @@ specific agent expertise or task workflows.
|
|||
// Determine the type and description based on content
|
||||
const isAgent = content.includes('<agent');
|
||||
const isTask = content.includes('<task');
|
||||
const isTool = content.includes('<tool');
|
||||
const isWorkflow = content.includes('workflow:') || content.includes('name:');
|
||||
|
||||
let description = '';
|
||||
let globs = '';
|
||||
|
|
@ -191,16 +253,22 @@ specific agent expertise or task workflows.
|
|||
const titleMatch = content.match(/title="([^"]+)"/);
|
||||
const title = titleMatch ? titleMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Agent: ${title}`;
|
||||
|
||||
// Manual rules for agents don't need globs
|
||||
globs = '';
|
||||
} else if (isTask) {
|
||||
// Extract task name if available
|
||||
const nameMatch = content.match(/<name>([^<]+)<\/name>/);
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const taskName = nameMatch ? nameMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Task: ${taskName}`;
|
||||
|
||||
// Tasks might be auto-attached to certain file types
|
||||
globs = '';
|
||||
} else if (isTool) {
|
||||
// Extract tool name if available
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const toolName = nameMatch ? nameMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Tool: ${toolName}`;
|
||||
globs = '';
|
||||
} else if (isWorkflow) {
|
||||
// Workflow
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Workflow: ${metadata.name}`;
|
||||
globs = '';
|
||||
} else {
|
||||
description = `BMAD ${metadata.module.toUpperCase()}: ${metadata.name}`;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,13 @@ class IdeManager {
|
|||
// Get all JS files in the IDE directory
|
||||
const files = fs.readdirSync(ideDir).filter((file) => {
|
||||
// Skip base class, manager, utility files (starting with _), and helper modules
|
||||
return file.endsWith('.js') && !file.startsWith('_') && file !== 'manager.js' && file !== 'workflow-command-generator.js';
|
||||
return (
|
||||
file.endsWith('.js') &&
|
||||
!file.startsWith('_') &&
|
||||
file !== 'manager.js' &&
|
||||
file !== 'workflow-command-generator.js' &&
|
||||
file !== 'task-tool-command-generator.js'
|
||||
);
|
||||
});
|
||||
|
||||
// Sort alphabetically for consistent ordering
|
||||
|
|
@ -41,7 +47,12 @@ class IdeManager {
|
|||
if (HandlerClass) {
|
||||
const instance = new HandlerClass();
|
||||
// Use the name property from the instance (set in constructor)
|
||||
this.handlers.set(instance.name, instance);
|
||||
// Only add if the instance has a valid name
|
||||
if (instance.name && typeof instance.name === 'string') {
|
||||
this.handlers.set(instance.name, instance);
|
||||
} else {
|
||||
console.log(chalk.yellow(` Warning: ${moduleName} handler missing valid 'name' property`));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(chalk.yellow(` Warning: Could not load ${moduleName}: ${error.message}`));
|
||||
|
|
@ -60,9 +71,17 @@ class IdeManager {
|
|||
const ides = [];
|
||||
|
||||
for (const [key, handler] of this.handlers) {
|
||||
// Skip handlers without valid names
|
||||
const name = handler.displayName || handler.name || key;
|
||||
|
||||
// Filter out invalid entries (undefined name, empty key, etc.)
|
||||
if (!key || !name || typeof key !== 'string' || typeof name !== 'string') {
|
||||
continue;
|
||||
}
|
||||
|
||||
ides.push({
|
||||
value: key,
|
||||
name: handler.displayName || handler.name || key,
|
||||
name: name,
|
||||
preferred: handler.preferred || false,
|
||||
});
|
||||
}
|
||||
|
|
@ -71,10 +90,7 @@ class IdeManager {
|
|||
ides.sort((a, b) => {
|
||||
if (a.preferred && !b.preferred) return -1;
|
||||
if (!a.preferred && b.preferred) return 1;
|
||||
// Ensure both names exist before comparing
|
||||
const nameA = a.name || '';
|
||||
const nameB = b.name || '';
|
||||
return nameA.localeCompare(nameB);
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
return ides;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const chalk = require('chalk');
|
|||
const yaml = require('js-yaml');
|
||||
const { BaseIdeSetup } = require('./_base-ide');
|
||||
const { WorkflowCommandGenerator } = require('./workflow-command-generator');
|
||||
const { TaskToolCommandGenerator } = require('./task-tool-command-generator');
|
||||
|
||||
const { getAgentsFromBmad } = require('./shared/bmad-artifacts');
|
||||
|
||||
|
|
@ -13,7 +14,7 @@ const { getAgentsFromBmad } = require('./shared/bmad-artifacts');
|
|||
*/
|
||||
class OpenCodeSetup extends BaseIdeSetup {
|
||||
constructor() {
|
||||
super('opencode', 'OpenCode', false);
|
||||
super('opencode', 'OpenCode', true); // Mark as preferred/recommended
|
||||
this.configDir = '.opencode';
|
||||
this.commandsDir = 'command';
|
||||
this.agentsDir = 'agent';
|
||||
|
|
@ -64,11 +65,22 @@ class OpenCodeSetup extends BaseIdeSetup {
|
|||
workflowCommandCount++;
|
||||
}
|
||||
|
||||
// Install task and tool commands
|
||||
const taskToolGen = new TaskToolCommandGenerator();
|
||||
const taskToolResult = await taskToolGen.generateTaskToolCommands(projectDir, bmadDir, commandsBaseDir);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents installed to .opencode/agent/bmad/`));
|
||||
if (workflowCommandCount > 0) {
|
||||
console.log(chalk.dim(` - ${workflowCommandCount} workflow commands generated to .opencode/command/bmad/`));
|
||||
}
|
||||
if (taskToolResult.generated > 0) {
|
||||
console.log(
|
||||
chalk.dim(
|
||||
` - ${taskToolResult.generated} task/tool commands generated (${taskToolResult.tasks} tasks, ${taskToolResult.tools} tools)`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
|
|
|||
|
|
@ -37,18 +37,22 @@ class QwenSetup extends BaseIdeSetup {
|
|||
// Clean up old configuration if exists
|
||||
await this.cleanupOldConfig(qwenDir);
|
||||
|
||||
// Get agents and tasks
|
||||
// Get agents, tasks, tools, and workflows (standalone only for tools/workflows)
|
||||
const agents = await getAgentsFromBmad(bmadDir, options.selectedModules || []);
|
||||
const tasks = await getTasksFromBmad(bmadDir, options.selectedModules || []);
|
||||
const tools = await this.getTools(bmadDir, true);
|
||||
const workflows = await this.getWorkflows(bmadDir, true);
|
||||
|
||||
// Create directories for each module (including standalone)
|
||||
const modules = new Set();
|
||||
for (const item of [...agents, ...tasks]) modules.add(item.module);
|
||||
for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module);
|
||||
|
||||
for (const module of modules) {
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module));
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'agents'));
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'tasks'));
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'tools'));
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'workflows'));
|
||||
}
|
||||
|
||||
// Create TOML files for each agent
|
||||
|
|
@ -75,7 +79,7 @@ class QwenSetup extends BaseIdeSetup {
|
|||
name: task.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadCommandsDir, task.module, 'agents', `${agent.name}.toml`);
|
||||
const targetPath = path.join(bmadCommandsDir, task.module, 'tasks', `${task.name}.toml`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
|
||||
|
|
@ -83,15 +87,51 @@ class QwenSetup extends BaseIdeSetup {
|
|||
console.log(chalk.green(` ✓ Added task: /bmad:${task.module}:tasks:${task.name}`));
|
||||
}
|
||||
|
||||
// Create TOML files for each tool
|
||||
let toolCount = 0;
|
||||
for (const tool of tools) {
|
||||
const content = await this.readAndProcess(tool.path, {
|
||||
module: tool.module,
|
||||
name: tool.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadCommandsDir, tool.module, 'tools', `${tool.name}.toml`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
|
||||
toolCount++;
|
||||
console.log(chalk.green(` ✓ Added tool: /bmad:${tool.module}:tools:${tool.name}`));
|
||||
}
|
||||
|
||||
// Create TOML files for each workflow
|
||||
let workflowCount = 0;
|
||||
for (const workflow of workflows) {
|
||||
const content = await this.readAndProcess(workflow.path, {
|
||||
module: workflow.module,
|
||||
name: workflow.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadCommandsDir, workflow.module, 'workflows', `${workflow.name}.toml`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
|
||||
workflowCount++;
|
||||
console.log(chalk.green(` ✓ Added workflow: /bmad:${workflow.module}:workflows:${workflow.name}`));
|
||||
}
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents configured`));
|
||||
console.log(chalk.dim(` - ${taskCount} tasks configured`));
|
||||
console.log(chalk.dim(` - ${toolCount} tools configured`));
|
||||
console.log(chalk.dim(` - ${workflowCount} workflows configured`));
|
||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, bmadCommandsDir)}`));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
workflows: workflowCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -177,6 +217,8 @@ class QwenSetup extends BaseIdeSetup {
|
|||
// Determine the type and description based on content
|
||||
const isAgent = content.includes('<agent');
|
||||
const isTask = content.includes('<task');
|
||||
const isTool = content.includes('<tool');
|
||||
const isWorkflow = content.includes('workflow:') || content.includes('name:');
|
||||
|
||||
let description = '';
|
||||
|
||||
|
|
@ -187,9 +229,17 @@ class QwenSetup extends BaseIdeSetup {
|
|||
description = `BMAD ${metadata.module.toUpperCase()} Agent: ${title}`;
|
||||
} else if (isTask) {
|
||||
// Extract task name if available
|
||||
const nameMatch = content.match(/<name>([^<]+)<\/name>/);
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const taskName = nameMatch ? nameMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Task: ${taskName}`;
|
||||
} else if (isTool) {
|
||||
// Extract tool name if available
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const toolName = nameMatch ? nameMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Tool: ${toolName}`;
|
||||
} else if (isWorkflow) {
|
||||
// Workflow
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Workflow: ${metadata.name}`;
|
||||
} else {
|
||||
description = `BMAD ${metadata.module.toUpperCase()}: ${metadata.name}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const csv = require('csv-parse/sync');
|
||||
const chalk = require('chalk');
|
||||
|
||||
/**
|
||||
* Generates Claude Code command files for standalone tasks and tools
|
||||
*/
|
||||
class TaskToolCommandGenerator {
|
||||
/**
|
||||
* Generate task and tool commands from manifest CSVs
|
||||
* @param {string} projectDir - Project directory
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {string} baseCommandsDir - Optional base commands directory (defaults to .claude/commands/bmad)
|
||||
*/
|
||||
async generateTaskToolCommands(projectDir, bmadDir, baseCommandsDir = null) {
|
||||
const tasks = await this.loadTaskManifest(bmadDir);
|
||||
const tools = await this.loadToolManifest(bmadDir);
|
||||
|
||||
// Filter to only standalone items
|
||||
const standaloneTasks = tasks ? tasks.filter((t) => t.standalone === 'true' || t.standalone === true) : [];
|
||||
const standaloneTools = tools ? tools.filter((t) => t.standalone === 'true' || t.standalone === true) : [];
|
||||
|
||||
// Base commands directory - use provided or default to Claude Code structure
|
||||
const commandsDir = baseCommandsDir || path.join(projectDir, '.claude', 'commands', 'bmad');
|
||||
|
||||
let generatedCount = 0;
|
||||
|
||||
// Generate command files for tasks
|
||||
for (const task of standaloneTasks) {
|
||||
const moduleTasksDir = path.join(commandsDir, task.module, 'tasks');
|
||||
await fs.ensureDir(moduleTasksDir);
|
||||
|
||||
const commandContent = this.generateCommandContent(task, 'task');
|
||||
const commandPath = path.join(moduleTasksDir, `${task.name}.md`);
|
||||
|
||||
await fs.writeFile(commandPath, commandContent);
|
||||
generatedCount++;
|
||||
}
|
||||
|
||||
// Generate command files for tools
|
||||
for (const tool of standaloneTools) {
|
||||
const moduleToolsDir = path.join(commandsDir, tool.module, 'tools');
|
||||
await fs.ensureDir(moduleToolsDir);
|
||||
|
||||
const commandContent = this.generateCommandContent(tool, 'tool');
|
||||
const commandPath = path.join(moduleToolsDir, `${tool.name}.md`);
|
||||
|
||||
await fs.writeFile(commandPath, commandContent);
|
||||
generatedCount++;
|
||||
}
|
||||
|
||||
return {
|
||||
generated: generatedCount,
|
||||
tasks: standaloneTasks.length,
|
||||
tools: standaloneTools.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate command content for a task or tool
|
||||
*/
|
||||
generateCommandContent(item, type) {
|
||||
const description = item.description || `Execute ${item.displayName || item.name}`;
|
||||
|
||||
// Convert path to use {project-root} placeholder
|
||||
let itemPath = item.path;
|
||||
if (itemPath.startsWith('bmad/')) {
|
||||
itemPath = `{project-root}/${itemPath}`;
|
||||
}
|
||||
|
||||
return `---
|
||||
description: '${description.replaceAll("'", "''")}'
|
||||
---
|
||||
|
||||
# ${item.displayName || item.name}
|
||||
|
||||
LOAD and execute the ${type} at: ${itemPath}
|
||||
|
||||
Follow all instructions in the ${type} file exactly as written.
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load task manifest CSV
|
||||
*/
|
||||
async loadTaskManifest(bmadDir) {
|
||||
const manifestPath = path.join(bmadDir, '_cfg', 'task-manifest.csv');
|
||||
|
||||
if (!(await fs.pathExists(manifestPath))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const csvContent = await fs.readFile(manifestPath, 'utf8');
|
||||
return csv.parse(csvContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load tool manifest CSV
|
||||
*/
|
||||
async loadToolManifest(bmadDir) {
|
||||
const manifestPath = path.join(bmadDir, '_cfg', 'tool-manifest.csv');
|
||||
|
||||
if (!(await fs.pathExists(manifestPath))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const csvContent = await fs.readFile(manifestPath, 'utf8');
|
||||
return csv.parse(csvContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { TaskToolCommandGenerator };
|
||||
|
|
@ -27,39 +27,74 @@ class TraeSetup extends BaseIdeSetup {
|
|||
|
||||
await this.ensureDir(rulesDir);
|
||||
|
||||
// Get agents and tasks
|
||||
// Get agents, tasks, tools, and workflows (standalone only)
|
||||
const agents = await this.getAgents(bmadDir);
|
||||
const tasks = await this.getTasks(bmadDir);
|
||||
const tasks = await this.getTasks(bmadDir, true);
|
||||
const tools = await this.getTools(bmadDir, true);
|
||||
const workflows = await this.getWorkflows(bmadDir, true);
|
||||
|
||||
// Process agents as rules
|
||||
let ruleCount = 0;
|
||||
let agentCount = 0;
|
||||
for (const agent of agents) {
|
||||
const content = await this.readFile(agent.path);
|
||||
const processedContent = this.createAgentRule(agent, content, bmadDir, projectDir);
|
||||
|
||||
const targetPath = path.join(rulesDir, `${agent.module}-${agent.name}.md`);
|
||||
await this.writeFile(targetPath, processedContent);
|
||||
ruleCount++;
|
||||
agentCount++;
|
||||
}
|
||||
|
||||
// Process tasks as rules
|
||||
let taskCount = 0;
|
||||
for (const task of tasks) {
|
||||
const content = await this.readFile(task.path);
|
||||
const processedContent = this.createTaskRule(task, content);
|
||||
|
||||
const targetPath = path.join(rulesDir, `task-${task.module}-${task.name}.md`);
|
||||
await this.writeFile(targetPath, processedContent);
|
||||
ruleCount++;
|
||||
taskCount++;
|
||||
}
|
||||
|
||||
// Process tools as rules
|
||||
let toolCount = 0;
|
||||
for (const tool of tools) {
|
||||
const content = await this.readFile(tool.path);
|
||||
const processedContent = this.createToolRule(tool, content);
|
||||
|
||||
const targetPath = path.join(rulesDir, `tool-${tool.module}-${tool.name}.md`);
|
||||
await this.writeFile(targetPath, processedContent);
|
||||
toolCount++;
|
||||
}
|
||||
|
||||
// Process workflows as rules
|
||||
let workflowCount = 0;
|
||||
for (const workflow of workflows) {
|
||||
const content = await this.readFile(workflow.path);
|
||||
const processedContent = this.createWorkflowRule(workflow, content);
|
||||
|
||||
const targetPath = path.join(rulesDir, `workflow-${workflow.module}-${workflow.name}.md`);
|
||||
await this.writeFile(targetPath, processedContent);
|
||||
workflowCount++;
|
||||
}
|
||||
|
||||
const totalRules = agentCount + taskCount + toolCount + workflowCount;
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${ruleCount} rules created`));
|
||||
console.log(chalk.dim(` - ${agentCount} agent rules created`));
|
||||
console.log(chalk.dim(` - ${taskCount} task rules created`));
|
||||
console.log(chalk.dim(` - ${toolCount} tool rules created`));
|
||||
console.log(chalk.dim(` - ${workflowCount} workflow rules created`));
|
||||
console.log(chalk.dim(` - Total: ${totalRules} rules`));
|
||||
console.log(chalk.dim(` - Rules directory: ${path.relative(projectDir, rulesDir)}`));
|
||||
console.log(chalk.dim(` - Agents can be activated with @{agent-name}`));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
rules: ruleCount,
|
||||
rules: totalRules,
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
workflows: workflowCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +149,7 @@ Part of the BMAD ${agent.module.toUpperCase()} module.
|
|||
*/
|
||||
createTaskRule(task, content) {
|
||||
// Extract task name from content
|
||||
const nameMatch = content.match(/<name>([^<]+)<\/name>/);
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
|
||||
|
||||
let ruleContent = `# ${taskName} Task Rule
|
||||
|
|
@ -139,6 +174,64 @@ Part of the BMAD ${task.module.toUpperCase()} module.
|
|||
return ruleContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create rule content for a tool
|
||||
*/
|
||||
createToolRule(tool, content) {
|
||||
// Extract tool name from content
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const toolName = nameMatch ? nameMatch[1] : this.formatTitle(tool.name);
|
||||
|
||||
let ruleContent = `# ${toolName} Tool Rule
|
||||
|
||||
This rule defines the ${toolName} tool.
|
||||
|
||||
## Tool Definition
|
||||
|
||||
When this tool is triggered, execute the following:
|
||||
|
||||
${content}
|
||||
|
||||
## Usage
|
||||
|
||||
Reference this tool with \`@tool-${tool.name}\` to execute it.
|
||||
|
||||
## Module
|
||||
|
||||
Part of the BMAD ${tool.module.toUpperCase()} module.
|
||||
`;
|
||||
|
||||
return ruleContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create rule content for a workflow
|
||||
*/
|
||||
createWorkflowRule(workflow, content) {
|
||||
let ruleContent = `# ${workflow.name} Workflow Rule
|
||||
|
||||
This rule defines the ${workflow.name} workflow.
|
||||
|
||||
## Workflow Description
|
||||
|
||||
${workflow.description || 'No description provided'}
|
||||
|
||||
## Workflow Definition
|
||||
|
||||
${content}
|
||||
|
||||
## Usage
|
||||
|
||||
Reference this workflow with \`@workflow-${workflow.name}\` to execute the guided workflow.
|
||||
|
||||
## Module
|
||||
|
||||
Part of the BMAD ${workflow.module.toUpperCase()} module.
|
||||
`;
|
||||
|
||||
return ruleContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format agent/task name as title
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -27,18 +27,22 @@ class WindsurfSetup extends BaseIdeSetup {
|
|||
|
||||
await this.ensureDir(workflowsDir);
|
||||
|
||||
// Get agents and tasks
|
||||
// Get agents, tasks, tools, and workflows (standalone only)
|
||||
const agents = await this.getAgents(bmadDir);
|
||||
const tasks = await this.getTasks(bmadDir);
|
||||
const tasks = await this.getTasks(bmadDir, true);
|
||||
const tools = await this.getTools(bmadDir, true);
|
||||
const workflows = await this.getWorkflows(bmadDir, true);
|
||||
|
||||
// Create directories for each module
|
||||
const modules = new Set();
|
||||
for (const item of [...agents, ...tasks]) modules.add(item.module);
|
||||
for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module);
|
||||
|
||||
for (const module of modules) {
|
||||
await this.ensureDir(path.join(workflowsDir, module));
|
||||
await this.ensureDir(path.join(workflowsDir, module, 'agents'));
|
||||
await this.ensureDir(path.join(workflowsDir, module, 'tasks'));
|
||||
await this.ensureDir(path.join(workflowsDir, module, 'tools'));
|
||||
await this.ensureDir(path.join(workflowsDir, module, 'workflows'));
|
||||
}
|
||||
|
||||
// Process agents as workflows with organized structure
|
||||
|
|
@ -65,9 +69,35 @@ class WindsurfSetup extends BaseIdeSetup {
|
|||
taskCount++;
|
||||
}
|
||||
|
||||
// Process tools as workflows with organized structure
|
||||
let toolCount = 0;
|
||||
for (const tool of tools) {
|
||||
const content = await this.readFile(tool.path);
|
||||
const processedContent = this.createToolWorkflowContent(tool, content);
|
||||
|
||||
// Organized path: module/tools/tool-name.md
|
||||
const targetPath = path.join(workflowsDir, tool.module, 'tools', `${tool.name}.md`);
|
||||
await this.writeFile(targetPath, processedContent);
|
||||
toolCount++;
|
||||
}
|
||||
|
||||
// Process workflows with organized structure
|
||||
let workflowCount = 0;
|
||||
for (const workflow of workflows) {
|
||||
const content = await this.readFile(workflow.path);
|
||||
const processedContent = this.createWorkflowWorkflowContent(workflow, content);
|
||||
|
||||
// Organized path: module/workflows/workflow-name.md
|
||||
const targetPath = path.join(workflowsDir, workflow.module, 'workflows', `${workflow.name}.md`);
|
||||
await this.writeFile(targetPath, processedContent);
|
||||
workflowCount++;
|
||||
}
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
||||
console.log(chalk.dim(` - ${taskCount} tasks installed`));
|
||||
console.log(chalk.dim(` - ${toolCount} tools installed`));
|
||||
console.log(chalk.dim(` - ${workflowCount} workflows installed`));
|
||||
console.log(chalk.dim(` - Organized in modules: ${[...modules].join(', ')}`));
|
||||
console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, workflowsDir)}`));
|
||||
|
||||
|
|
@ -75,7 +105,8 @@ class WindsurfSetup extends BaseIdeSetup {
|
|||
if (options.showHints !== false) {
|
||||
console.log(chalk.dim('\n Windsurf workflow settings:'));
|
||||
console.log(chalk.dim(' - auto_execution_mode: 3 (recommended for agents)'));
|
||||
console.log(chalk.dim(' - auto_execution_mode: 2 (recommended for tasks)'));
|
||||
console.log(chalk.dim(' - auto_execution_mode: 2 (recommended for tasks/tools)'));
|
||||
console.log(chalk.dim(' - auto_execution_mode: 1 (recommended for workflows)'));
|
||||
console.log(chalk.dim(' - Workflows can be triggered via the Windsurf menu'));
|
||||
}
|
||||
|
||||
|
|
@ -83,6 +114,8 @@ class WindsurfSetup extends BaseIdeSetup {
|
|||
success: true,
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
workflows: workflowCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +144,36 @@ description: task-${task.name}
|
|||
auto_execution_mode: 2
|
||||
---
|
||||
|
||||
${content}`;
|
||||
|
||||
return workflowContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workflow content for a tool
|
||||
*/
|
||||
createToolWorkflowContent(tool, content) {
|
||||
// Create simple Windsurf frontmatter matching original format
|
||||
let workflowContent = `---
|
||||
description: tool-${tool.name}
|
||||
auto_execution_mode: 2
|
||||
---
|
||||
|
||||
${content}`;
|
||||
|
||||
return workflowContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workflow content for a workflow
|
||||
*/
|
||||
createWorkflowWorkflowContent(workflow, content) {
|
||||
// Create simple Windsurf frontmatter matching original format
|
||||
let workflowContent = `---
|
||||
description: ${workflow.name}
|
||||
auto_execution_mode: 1
|
||||
---
|
||||
|
||||
${content}`;
|
||||
|
||||
return workflowContent;
|
||||
|
|
|
|||
|
|
@ -24,13 +24,16 @@ class WorkflowCommandGenerator {
|
|||
return { generated: 0 };
|
||||
}
|
||||
|
||||
// Filter to only standalone workflows
|
||||
const standaloneWorkflows = workflows.filter((w) => w.standalone === 'true' || w.standalone === true);
|
||||
|
||||
// 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 workflows) {
|
||||
// Generate a command file for each standalone workflow, organized by module
|
||||
for (const workflow of standaloneWorkflows) {
|
||||
const moduleWorkflowsDir = path.join(baseCommandsDir, workflow.module, 'workflows');
|
||||
await fs.ensureDir(moduleWorkflowsDir);
|
||||
|
||||
|
|
@ -42,7 +45,7 @@ class WorkflowCommandGenerator {
|
|||
}
|
||||
|
||||
// Also create a workflow launcher README in each module
|
||||
const groupedWorkflows = this.groupWorkflowsByModule(workflows);
|
||||
const groupedWorkflows = this.groupWorkflowsByModule(standaloneWorkflows);
|
||||
await this.createModuleWorkflowLaunchers(baseCommandsDir, groupedWorkflows);
|
||||
|
||||
return { generated: generatedCount };
|
||||
|
|
@ -55,9 +58,12 @@ class WorkflowCommandGenerator {
|
|||
return { artifacts: [], counts: { commands: 0, launchers: 0 } };
|
||||
}
|
||||
|
||||
// Filter to only standalone workflows
|
||||
const standaloneWorkflows = workflows.filter((w) => w.standalone === 'true' || w.standalone === true);
|
||||
|
||||
const artifacts = [];
|
||||
|
||||
for (const workflow of workflows) {
|
||||
for (const workflow of standaloneWorkflows) {
|
||||
const commandContent = await this.generateCommandContent(workflow, bmadDir);
|
||||
artifacts.push({
|
||||
type: 'workflow-command',
|
||||
|
|
@ -68,7 +74,7 @@ class WorkflowCommandGenerator {
|
|||
});
|
||||
}
|
||||
|
||||
const groupedWorkflows = this.groupWorkflowsByModule(workflows);
|
||||
const groupedWorkflows = this.groupWorkflowsByModule(standaloneWorkflows);
|
||||
for (const [module, launcherContent] of Object.entries(this.buildModuleWorkflowLaunchers(groupedWorkflows))) {
|
||||
artifacts.push({
|
||||
type: 'workflow-launcher',
|
||||
|
|
@ -82,7 +88,7 @@ class WorkflowCommandGenerator {
|
|||
return {
|
||||
artifacts,
|
||||
counts: {
|
||||
commands: workflows.length,
|
||||
commands: standaloneWorkflows.length,
|
||||
launchers: Object.keys(groupedWorkflows).length,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,12 +35,22 @@ class UI {
|
|||
name: 'actionType',
|
||||
message: 'What would you like to do?',
|
||||
choices: [
|
||||
{ name: 'Update BMAD Installation', value: 'install' },
|
||||
{ name: 'Quick Update (Settings Preserved)', value: 'quick-update' },
|
||||
{ name: 'Modify BMAD Installation (Confirm or change each setting)', value: 'install' },
|
||||
{ name: 'Compile Agents (Quick rebuild of all agent .md files)', value: 'compile' },
|
||||
],
|
||||
default: 'quick-update',
|
||||
},
|
||||
]);
|
||||
|
||||
// Handle quick update separately
|
||||
if (actionType === 'quick-update') {
|
||||
return {
|
||||
actionType: 'quick-update',
|
||||
directory: confirmedDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
// Handle agent compilation separately
|
||||
if (actionType === 'compile') {
|
||||
return {
|
||||
|
|
@ -99,6 +109,11 @@ class UI {
|
|||
if (configuredIdes.length > 0) {
|
||||
ideChoices.push(new inquirer.Separator('── Previously Configured ──'));
|
||||
for (const ideValue of configuredIdes) {
|
||||
// Skip empty or invalid IDE values
|
||||
if (!ideValue || typeof ideValue !== 'string') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the IDE in either preferred or other lists
|
||||
const preferredIde = preferredIdes.find((ide) => ide.value === ideValue);
|
||||
const otherIde = otherIdes.find((ide) => ide.value === ideValue);
|
||||
|
|
@ -111,6 +126,9 @@ class UI {
|
|||
checked: true, // Previously configured IDEs are checked by default
|
||||
});
|
||||
processedIdes.add(ide.value);
|
||||
} else {
|
||||
// Warn about unrecognized IDE (but don't fail)
|
||||
console.log(chalk.yellow(`⚠️ Previously configured IDE '${ideValue}' is no longer available`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue