Compare commits
6 Commits
dde139a560
...
3c1df006fe
| Author | SHA1 | Date |
|---|---|---|
|
|
3c1df006fe | |
|
|
95b437023d | |
|
|
5e8289fe26 | |
|
|
3aaa37125b | |
|
|
0a3f48f13f | |
|
|
90ea3cbed7 |
|
|
@ -78,15 +78,15 @@ Work through phases 1-3. **Use fresh chats for each workflow.**
|
|||
### Phase 1: Analysis (Optional)
|
||||
|
||||
All workflows in this phase are optional:
|
||||
- **brainstorming** — Guided ideation
|
||||
- **research** — Market and technical research
|
||||
- **create-product-brief** — Recommended foundation document
|
||||
- **brainstorming** (`bmad-bmm-brainstorming`) — Guided ideation
|
||||
- **research** (`bmad-bmm-research`) — Market and technical research
|
||||
- **create-product-brief** (`bmad-bmm-create-product-brief`) — Recommended foundation document
|
||||
|
||||
### Phase 2: Planning (Required)
|
||||
|
||||
**For BMad Method and Enterprise tracks:**
|
||||
1. Load the **PM agent** in a new chat
|
||||
2. Run the `prd` workflow
|
||||
2. Run the `create-prd` workflow (`bmad-bmm-create-prd`)
|
||||
3. Output: `PRD.md`
|
||||
|
||||
**For Quick Flow track:**
|
||||
|
|
@ -100,7 +100,7 @@ If your project has a user interface, load the **UX-Designer agent** and run the
|
|||
|
||||
**Create Architecture**
|
||||
1. Load the **Architect agent** in a new chat
|
||||
2. Run `create-architecture`
|
||||
2. Run `create-architecture` (`bmad-bmm-create-architecture`)
|
||||
3. Output: Architecture document with technical decisions
|
||||
|
||||
**Create Epics and Stories**
|
||||
|
|
@ -110,12 +110,12 @@ Epics and stories are now created *after* architecture. This produces better qua
|
|||
:::
|
||||
|
||||
1. Load the **PM agent** in a new chat
|
||||
2. Run `create-epics-and-stories`
|
||||
2. Run `create-epics-and-stories` (`bmad-bmm-create-epics-and-stories`)
|
||||
3. The workflow uses both PRD and Architecture to create technically-informed stories
|
||||
|
||||
**Implementation Readiness Check** *(Highly Recommended)*
|
||||
1. Load the **Architect agent** in a new chat
|
||||
2. Run `check-implementation-readiness`
|
||||
2. Run `check-implementation-readiness` (`bmad-bmm-check-implementation-readiness`)
|
||||
3. Validates cohesion across all planning documents
|
||||
|
||||
## Step 2: Build Your Project
|
||||
|
|
@ -124,7 +124,7 @@ Once planning is complete, move to implementation. **Each workflow should run in
|
|||
|
||||
### Initialize Sprint Planning
|
||||
|
||||
Load the **SM agent** and run `sprint-planning`. This creates `sprint-status.yaml` to track all epics and stories.
|
||||
Load the **SM agent** and run `sprint-planning` (`bmad-bmm-sprint-planning`). This creates `sprint-status.yaml` to track all epics and stories.
|
||||
|
||||
### The Build Cycle
|
||||
|
||||
|
|
@ -132,11 +132,11 @@ For each story, repeat this cycle with fresh chats:
|
|||
|
||||
| Step | Agent | Workflow | Purpose |
|
||||
| ---- | ----- | -------------- | ---------------------------------- |
|
||||
| 1 | SM | `create-story` | Create story file from epic |
|
||||
| 2 | DEV | `dev-story` | Implement the story |
|
||||
| 3 | DEV | `code-review` | Quality validation *(recommended)* |
|
||||
| 1 | SM | `create-story` (`bmad-bmm-create-story`) | Create story file from epic |
|
||||
| 2 | DEV | `dev-story` (`bmad-bmm-dev-story`) | Implement the story |
|
||||
| 3 | DEV | `code-review` (`bmad-bmm-code-review`) | Quality validation *(recommended)* |
|
||||
|
||||
After completing all stories in an epic, load the **SM agent** and run `retrospective`.
|
||||
After completing all stories in an epic, load the **SM agent** and run `retrospective` (`bmad-bmm-retrospective`).
|
||||
|
||||
## What You've Accomplished
|
||||
|
||||
|
|
@ -162,17 +162,17 @@ your-project/
|
|||
|
||||
## Quick Reference
|
||||
|
||||
| Workflow | Agent | Purpose |
|
||||
| -------------------------------- | --------- | ------------------------------------ |
|
||||
| `help` | Any | Get guidance on what to do next |
|
||||
| `prd` | PM | Create Product Requirements Document |
|
||||
| `create-architecture` | Architect | Create architecture document |
|
||||
| `create-epics-and-stories` | PM | Break down PRD into epics |
|
||||
| `check-implementation-readiness` | Architect | Validate planning cohesion |
|
||||
| `sprint-planning` | SM | Initialize sprint tracking |
|
||||
| `create-story` | SM | Create a story file |
|
||||
| `dev-story` | DEV | Implement a story |
|
||||
| `code-review` | DEV | Review implemented code |
|
||||
| Workflow | Slash Command | Agent | Purpose |
|
||||
| -------------------------------- | ------------------------------------- | --------- | ------------------------------------ |
|
||||
| `help` | `bmad-help` | Any | Get guidance on what to do next |
|
||||
| `create-prd` | `bmad-bmm-create-prd` | PM | Create Product Requirements Document |
|
||||
| `create-architecture` | `bmad-bmm-create-architecture` | Architect | Create architecture document |
|
||||
| `create-epics-and-stories` | `bmad-bmm-create-epics-and-stories` | PM | Break down PRD into epics |
|
||||
| `check-implementation-readiness` | `bmad-bmm-check-implementation-readiness` | Architect | Validate planning cohesion |
|
||||
| `sprint-planning` | `bmad-bmm-sprint-planning` | SM | Initialize sprint tracking |
|
||||
| `create-story` | `bmad-bmm-create-story` | SM | Create a story file |
|
||||
| `dev-story` | `bmad-bmm-dev-story` | DEV | Implement a story |
|
||||
| `code-review` | `bmad-bmm-code-review` | DEV | Review implemented code |
|
||||
|
||||
## Common Questions
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ Only for BMad Method and Enterprise tracks. Quick Flow skips from tech-spec to i
|
|||
Yes. The SM agent has a `correct-course` workflow for handling scope changes.
|
||||
|
||||
**What if I want to brainstorm first?**
|
||||
Load the Analyst agent and run `brainstorming` before starting your PRD.
|
||||
Load the Analyst agent and run `brainstorming` (`bmad-bmm-brainstorming`) before starting your PRD.
|
||||
|
||||
**Do I need to follow a strict order?**
|
||||
Not strictly. Once you learn the flow, you can run workflows directly using the Quick Reference above.
|
||||
|
|
@ -192,7 +192,7 @@ Not strictly. Once you learn the flow, you can run workflows directly using the
|
|||
|
||||
- **During workflows** — Agents guide you with questions and explanations
|
||||
- **Community** — [Discord](https://discord.gg/gk8jAdXWmj) (#bmad-method-help, #report-bugs-and-issues)
|
||||
- **Stuck?** — Run `help` to see what to do next
|
||||
- **Stuck?** — Run `help` (`bmad-help` on most platforms) to see what to do next
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ input_file_patterns:
|
|||
<check if="{{story_path}} is provided by user or user provided the epic and story number such as 2-4 or 1.6 or epic 1 story 5">
|
||||
<action>Parse user-provided story path: extract epic_num, story_num, story_title from format like "1-2-user-auth"</action>
|
||||
<action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action>
|
||||
<action>GOTO step 2a</action>
|
||||
<action>GOTO step 2</action>
|
||||
</check>
|
||||
|
||||
<action>Check if {{sprint_status}} file exists for auto discover</action>
|
||||
|
|
@ -85,12 +85,12 @@ input_file_patterns:
|
|||
<check if="user provides epic-story number">
|
||||
<action>Parse user input: extract epic_num, story_num, story_title</action>
|
||||
<action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action>
|
||||
<action>GOTO step 2a</action>
|
||||
<action>GOTO step 2</action>
|
||||
</check>
|
||||
|
||||
<check if="user provides story docs path">
|
||||
<action>Use user-provided path for story documents</action>
|
||||
<action>GOTO step 2a</action>
|
||||
<action>GOTO step 2</action>
|
||||
</check>
|
||||
</check>
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ input_file_patterns:
|
|||
<output>📊 Epic {{epic_num}} status updated to in-progress</output>
|
||||
</check>
|
||||
|
||||
<action>GOTO step 2a</action>
|
||||
<action>GOTO step 2</action>
|
||||
</check>
|
||||
</step>
|
||||
|
||||
|
|
@ -174,10 +174,17 @@ input_file_patterns:
|
|||
(As a, I want, so that) - Detailed acceptance criteria (already BDD formatted) - Technical requirements specific to this story -
|
||||
Business context and value - Success criteria <!-- Previous story analysis for context continuity -->
|
||||
<check if="story_num > 1">
|
||||
<action>Load previous story file: {{story_dir}}/{{epic_num}}-{{previous_story_num}}-*.md</action> **PREVIOUS STORY INTELLIGENCE:** -
|
||||
Dev notes and learnings from previous story - Review feedback and corrections needed - Files that were created/modified and their
|
||||
patterns - Testing approaches that worked/didn't work - Problems encountered and solutions found - Code patterns established <action>Extract
|
||||
all learnings that could impact current story implementation</action>
|
||||
<action>Set {{previous_story_num}} = {{story_num}} - 1</action>
|
||||
<action>Load previous story file: {{story_dir}}/{{epic_num}}-{{previous_story_num}}-*.md</action>
|
||||
<action>Extract previous story intelligence:
|
||||
- Dev notes and learnings from previous story
|
||||
- Review feedback and corrections needed
|
||||
- Files that were created/modified and their patterns
|
||||
- Testing approaches that worked/didn't work
|
||||
- Problems encountered and solutions found
|
||||
- Code patterns established
|
||||
</action>
|
||||
<action>Extract all learnings that could impact current story implementation</action>
|
||||
</check>
|
||||
|
||||
<!-- Git intelligence for previous work patterns -->
|
||||
|
|
|
|||
|
|
@ -15,9 +15,17 @@
|
|||
- Set status_file_found = false
|
||||
- Set standalone_mode = true
|
||||
- Set warning = ""
|
||||
- Set status_load_error_reason = ""
|
||||
- Set suggestion = ""
|
||||
- Set next_workflow = ""
|
||||
- Set next_agent = ""
|
||||
- Set status_file_path = ""
|
||||
- Set field_type = ""
|
||||
- Set workflow_mode = ""
|
||||
- Set scan_level = ""
|
||||
- Set subworkflow_success = false
|
||||
- Set status_update_success = false
|
||||
- Set cached_project_types = ""
|
||||
</action>
|
||||
|
||||
<action>Attempt to load workflow status directly from `{output_folder}/bmm-workflow-status.yaml`:
|
||||
|
|
@ -29,10 +37,18 @@
|
|||
- Extract field_type, warning, suggestion, next_workflow, next_agent if present
|
||||
- If file is missing, unreadable, or malformed:
|
||||
- Keep defaults and continue in standalone mode
|
||||
- Set status_load_error_reason from the caught file/parse error (e.g., missing file, permission denied, YAML parse error)
|
||||
- Set warning = "Unable to load workflow status from {output_folder}/bmm-workflow-status.yaml: {{status_load_error_reason}}"
|
||||
- Output warning and continue in standalone mode
|
||||
</action>
|
||||
|
||||
<check if="status_exists == false">
|
||||
<output>{{suggestion}}</output>
|
||||
<check if="suggestion != ''">
|
||||
<output>{{suggestion}}</output>
|
||||
</check>
|
||||
<check if="warning != ''">
|
||||
<output>{{warning}}</output>
|
||||
</check>
|
||||
<output>Note: Documentation workflow can run standalone. Continuing without progress tracking.</output>
|
||||
<action>Set standalone_mode = true</action>
|
||||
<action>Set status_file_found = false</action>
|
||||
|
|
@ -62,7 +78,9 @@
|
|||
<output>Note: This may be auto-invoked by prd for brownfield documentation.</output>
|
||||
<ask>Continue with documentation? (y/n)</ask>
|
||||
<check if="n">
|
||||
<output>{{suggestion}}</output>
|
||||
<check if="suggestion != ''">
|
||||
<output>{{suggestion}}</output>
|
||||
</check>
|
||||
<action>Exit workflow</action>
|
||||
</check>
|
||||
</check>
|
||||
|
|
@ -78,18 +96,40 @@
|
|||
|
||||
<check if="project-scan-report.json exists">
|
||||
<action>Read state file and extract: timestamps, mode, scan_level, current_step, completed_steps, project_classification</action>
|
||||
<action>Validate last_updated from state file:
|
||||
- If last_updated is missing or invalid, set state_age_hours = 999 and mark state as stale
|
||||
- Otherwise parse last_updated into validated_last_updated
|
||||
</action>
|
||||
<action>Extract cached project_type_id(s) from state file if present</action>
|
||||
<action>Calculate age of state file (current time - last_updated)</action>
|
||||
<action>Calculate state_age_hours:
|
||||
- If validated_last_updated exists, compute current time - validated_last_updated
|
||||
- Otherwise keep state_age_hours = 999
|
||||
</action>
|
||||
|
||||
<check if="state file age >= 24 hours">
|
||||
<check if="state_age_hours >= 24">
|
||||
<action>Display: "Found old state file (>24 hours). Starting fresh scan."</action>
|
||||
<action>Create archive directory: {output_folder}/.archive/</action>
|
||||
<action>Archive old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action>
|
||||
<action>Attempt to create archive directory: {output_folder}/.archive/</action>
|
||||
<check if="archive directory creation failed">
|
||||
<output>Failed to create archive directory at {output_folder}/.archive/. Keeping existing state and exiting to avoid data loss.</output>
|
||||
<action>Set resume_mode = true</action>
|
||||
<action>Exit workflow</action>
|
||||
</check>
|
||||
<action>Attempt to archive old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action>
|
||||
<check if="archive move failed">
|
||||
<output>Failed to archive old state file. Keeping existing state and exiting to avoid data loss.</output>
|
||||
<action>Set resume_mode = true</action>
|
||||
<action>Exit workflow</action>
|
||||
</check>
|
||||
<action>Set resume_mode = false</action>
|
||||
<action>Set workflow_mode = ""</action>
|
||||
<action>Set scan_level = ""</action>
|
||||
<action>Set cached_project_types = ""</action>
|
||||
<action>Set current_step = ""</action>
|
||||
<action>Set subworkflow_success = false</action>
|
||||
<action>Continue to Step 3</action>
|
||||
</check>
|
||||
|
||||
<check if="state file age < 24 hours">
|
||||
<check if="state_age_hours < 24">
|
||||
|
||||
<ask>I found an in-progress workflow state from {{last_updated}}.
|
||||
|
||||
|
|
@ -112,15 +152,25 @@ Your choice [1/2/3]:
|
|||
|
||||
<check if="user selects 1">
|
||||
<action>Set resume_mode = true</action>
|
||||
<action>Set workflow_mode = {{mode}}</action>
|
||||
<action>Validate persisted mode before assigning workflow_mode:
|
||||
- If mode is one of [deep_dive, initial_scan, full_rescan], set workflow_mode = {{mode}}
|
||||
- Otherwise set workflow_mode = "full_rescan", set resume_mode = false, and continue as fresh scan
|
||||
</action>
|
||||
<action>Set subworkflow_success = false</action>
|
||||
<action>Load findings summaries from state file</action>
|
||||
<action>Load cached project_type_id(s) from state file</action>
|
||||
|
||||
<critical>CONDITIONAL CSV LOADING FOR RESUME:</critical>
|
||||
<check if="cached_project_types == ''">
|
||||
<output>No cached project types found. Falling back to full CSV load.</output>
|
||||
<action>Load project-types.csv and architecture_registry.csv</action>
|
||||
<action>Load documentation_requirements_csv for active project classification</action>
|
||||
</check>
|
||||
<check if="cached_project_types != ''">
|
||||
<action>For each cached project_type_id, load ONLY the corresponding row from: {documentation_requirements_csv}</action>
|
||||
<action>Skip loading project-types.csv and architecture_registry.csv (not needed on resume)</action>
|
||||
<action>Store loaded doc requirements for use in remaining steps</action>
|
||||
</check>
|
||||
|
||||
<action>Display: "Resuming {{workflow_mode}} from {{current_step}} with cached project type(s): {{cached_project_types}}"</action>
|
||||
|
||||
|
|
@ -152,9 +202,21 @@ Your choice [1/2/3]:
|
|||
</check>
|
||||
|
||||
<check if="user selects 2">
|
||||
<action>Create archive directory: {output_folder}/.archive/</action>
|
||||
<action>Move old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action>
|
||||
<action>Attempt to create archive directory: {output_folder}/.archive/</action>
|
||||
<check if="archive directory creation failed">
|
||||
<output>Failed to create archive directory. Keeping existing state and exiting to avoid data loss.</output>
|
||||
<action>Set resume_mode = true</action>
|
||||
<action>Exit workflow</action>
|
||||
</check>
|
||||
<action>Attempt to move old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action>
|
||||
<check if="archive move failed">
|
||||
<output>Failed to archive old state file. Keeping existing state and exiting to avoid data loss.</output>
|
||||
<action>Set resume_mode = true</action>
|
||||
<action>Exit workflow</action>
|
||||
</check>
|
||||
<action>Set resume_mode = false</action>
|
||||
<action>Reset workflow_mode, scan_level, cached_project_types, current_step to defaults</action>
|
||||
<action>Set subworkflow_success = false</action>
|
||||
<action>Continue to Step 3</action>
|
||||
</check>
|
||||
|
||||
|
|
@ -198,6 +260,7 @@ Your choice [1/2/3]:
|
|||
|
||||
<check if="user selects 1">
|
||||
<action>Set workflow_mode = "full_rescan"</action>
|
||||
<action>Set scan_level = "standard"</action>
|
||||
<action>Display: "Starting full project rescan..."</action>
|
||||
<action>Read fully and follow: {installed_path}/workflows/full-scan-instructions.md</action>
|
||||
<action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action>
|
||||
|
|
@ -234,6 +297,7 @@ Your choice [1/2/3]:
|
|||
|
||||
<check if="index.md does not exist">
|
||||
<action>Set workflow_mode = "initial_scan"</action>
|
||||
<action>Set scan_level = "initial"</action>
|
||||
<action>Display: "No existing documentation found. Starting initial project scan..."</action>
|
||||
<action>Read fully and follow: {installed_path}/workflows/full-scan-instructions.md</action>
|
||||
<action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action>
|
||||
|
|
@ -282,8 +346,13 @@ Your choice [1/2/3]:
|
|||
<output>**Status Updated:** Progress tracking updated.
|
||||
|
||||
**Next Steps:**
|
||||
- **Next required:** {{next_workflow}} ({{next_agent}} agent)
|
||||
- Run `bmad-help` if you need recommended next workflows.</output>
|
||||
<check if="next_workflow != '' AND next_agent != ''">
|
||||
<output>- **Next required:** {{next_workflow}} ({{next_agent}} agent)</output>
|
||||
</check>
|
||||
<check if="next_workflow == '' OR next_agent == ''">
|
||||
<output>- **Next required:** not specified</output>
|
||||
</check>
|
||||
</check>
|
||||
|
||||
<check if="status_file_found == false OR status_update_success != true">
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ Execute a validation checklist against a target file and report findings clearly
|
|||
- Path-like tokens in checklist items
|
||||
- First matching path from glob patterns supplied by checklist/input
|
||||
- Normalize all candidate paths relative to repo root and resolve `.`/`..`.
|
||||
- Validate candidate existence and expected file type (`.yaml`, `.yml`, `.json`, or checklist-defined extension).
|
||||
- Validate candidate existence and expected file type (`.md`, `.yaml`, `.yml`, `.json`, or checklist-defined extension).
|
||||
- If multiple valid candidates remain, prefer explicit key fields over inferred tokens.
|
||||
- If no valid candidate is found, prompt user with schema example:
|
||||
- `Please provide the exact file path (relative to repo root), e.g. ./workflows/ci.yml`
|
||||
|
|
@ -50,11 +50,17 @@ Execute a validation checklist against a target file and report findings clearly
|
|||
- If checklist requires edits/auto-fixes, follow safe-edit protocol:
|
||||
- Ask for confirmation before editing.
|
||||
- Create backup snapshot of target file before changes.
|
||||
- Use deterministic backup location: `{project-root}/.bmad-tmp/validate-workflow/`.
|
||||
- Name backup as `{target-file-name}.{timestamp}.bak` and diff as `{target-file-name}.{timestamp}.diff`.
|
||||
- If temp backup directory cannot be created, fall back to adjacent backup file `{target-file}.bak`.
|
||||
- Generate reversible diff preview and show it to user.
|
||||
- Apply edits only after user approval.
|
||||
- Run syntax/validation checks against edited file.
|
||||
- If validation fails or user cancels, rollback from backup and report rollback status.
|
||||
- Record backup/diff locations in task output.
|
||||
- Record full backup and diff paths in task output.
|
||||
- Support `retain_artifacts` flag (default `false`) to keep backup/diff artifacts when requested.
|
||||
|
||||
6. **Finalize**
|
||||
- Confirm completion and provide the final validation summary.
|
||||
- If edits succeeded and `retain_artifacts` is `false`, delete backup/diff artifacts and report cleanup status.
|
||||
- If edits failed or rollback occurred, preserve backup/diff artifacts and report rollback path explicitly.
|
||||
|
|
|
|||
|
|
@ -114,6 +114,12 @@ async function runTests() {
|
|||
console.log(`========================================${colors.reset}\n`);
|
||||
|
||||
const projectRoot = path.join(__dirname, '..');
|
||||
const tmpRoots = [];
|
||||
const trackTmp = async (prefix) => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||
tmpRoots.push(dir);
|
||||
return dir;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Test 1: YAML → XML Agent Compilation (In-Memory)
|
||||
|
|
@ -386,7 +392,7 @@ async function runTests() {
|
|||
path.join(projectRoot, 'src', 'bmm', 'workflows', 'document-project'),
|
||||
path.join(projectRoot, 'tools', 'cli', 'installers', 'lib'),
|
||||
];
|
||||
const allowedExtensions = new Set(['.md', '.yaml', '.yml', '.xml']);
|
||||
const allowedExtensions = new Set(['.md', '.yaml', '.yml', '.xml', '.js', '.cjs', '.mjs']);
|
||||
const forbiddenRefPattern = /(^|[^a-zA-Z0-9_-])workflow\.xml\b/;
|
||||
const offenders = [];
|
||||
|
||||
|
|
@ -432,7 +438,7 @@ async function runTests() {
|
|||
console.log(`${colors.yellow}Test Suite 11: Gemini Template Extension Guard${colors.reset}\n`);
|
||||
|
||||
try {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-gemini-install-'));
|
||||
const tmpRoot = await trackTmp('bmad-gemini-install-');
|
||||
const projectDir = path.join(tmpRoot, 'project');
|
||||
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
|
||||
await fs.ensureDir(projectDir);
|
||||
|
|
@ -470,7 +476,7 @@ async function runTests() {
|
|||
console.log(`${colors.yellow}Test Suite 12: Manifest Stale Entry Cleanup Guard${colors.reset}\n`);
|
||||
|
||||
try {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-manifest-clean-'));
|
||||
const tmpRoot = await trackTmp('bmad-manifest-clean-');
|
||||
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
|
||||
await fs.copy(path.join(projectRoot, 'src', 'core'), path.join(bmadDir, 'core'));
|
||||
await fs.copy(path.join(projectRoot, 'src', 'bmm'), path.join(bmadDir, 'bmm'));
|
||||
|
|
@ -505,7 +511,7 @@ async function runTests() {
|
|||
console.log(`${colors.yellow}Test Suite 13: Internal Task Exposure Guard${colors.reset}\n`);
|
||||
|
||||
try {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-task-filter-'));
|
||||
const tmpRoot = await trackTmp('bmad-task-filter-');
|
||||
const projectDir = path.join(tmpRoot, 'project');
|
||||
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
|
||||
const commandsDir = path.join(tmpRoot, 'commands');
|
||||
|
|
@ -592,7 +598,7 @@ web_bundle:
|
|||
console.log(`${colors.yellow}Test Suite 16: Task/Tool Standalone + CRLF Guard${colors.reset}\n`);
|
||||
|
||||
try {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-standalone-crlf-'));
|
||||
const tmpRoot = await trackTmp('bmad-standalone-crlf-');
|
||||
const coreTasksDir = path.join(tmpRoot, '_bmad', 'core', 'tasks');
|
||||
const coreToolsDir = path.join(tmpRoot, '_bmad', 'core', 'tools');
|
||||
await fs.ensureDir(coreTasksDir);
|
||||
|
|
@ -717,7 +723,7 @@ internal: true
|
|||
console.log(`${colors.yellow}Test Suite 18: Codex Task Visibility Guard${colors.reset}\n`);
|
||||
|
||||
try {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-codex-visibility-'));
|
||||
const tmpRoot = await trackTmp('bmad-codex-visibility-');
|
||||
const projectDir = path.join(tmpRoot, 'project');
|
||||
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
|
||||
await fs.ensureDir(projectDir);
|
||||
|
|
@ -751,7 +757,7 @@ internal: true
|
|||
console.log(`${colors.yellow}Test Suite 19: Empty Artifact Target Guard${colors.reset}\n`);
|
||||
|
||||
try {
|
||||
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-empty-target-'));
|
||||
const tmpRoot = await trackTmp('bmad-empty-target-');
|
||||
const projectDir = path.join(tmpRoot, 'project');
|
||||
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
|
||||
await fs.ensureDir(projectDir);
|
||||
|
|
@ -856,6 +862,10 @@ internal: true
|
|||
|
||||
console.log('');
|
||||
|
||||
for (const tmpRoot of tmpRoots) {
|
||||
await fs.remove(tmpRoot).catch(() => {});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Summary
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -1201,19 +1201,11 @@ class Installer {
|
|||
lines.push(` ${icon} ${r.step}${detail}`);
|
||||
}
|
||||
|
||||
// Add context info
|
||||
// Context and warnings
|
||||
lines.push('');
|
||||
if (context.bmadDir) {
|
||||
lines.push(` Installed to: ${color.dim(context.bmadDir)}`);
|
||||
}
|
||||
if (context.modules && context.modules.length > 0) {
|
||||
lines.push(` Modules: ${color.dim(context.modules.join(', '))}`);
|
||||
}
|
||||
if (context.ides && context.ides.length > 0) {
|
||||
lines.push(` Tools: ${color.dim(context.ides.join(', '))}`);
|
||||
}
|
||||
|
||||
// Custom/modified file warnings
|
||||
if (context.customFiles && context.customFiles.length > 0) {
|
||||
lines.push(` ${color.cyan(`Custom files preserved: ${context.customFiles.length}`)}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,10 +75,10 @@ class WorkflowCommandGenerator {
|
|||
if (workflowRelPath.includes('_bmad/')) {
|
||||
const parts = workflowRelPath.split(/_bmad\//);
|
||||
if (parts.length > 1) {
|
||||
workflowRelPath = parts.slice(1).join('/');
|
||||
workflowRelPath = parts.at(-1);
|
||||
}
|
||||
} else if (workflowRelPath.includes('/src/')) {
|
||||
const match = workflowRelPath.match(/\/src\/([^/]+)\/(.+)/);
|
||||
} else if (workflowRelPath.includes('/src/') || workflowRelPath.startsWith('src/')) {
|
||||
const match = workflowRelPath.match(/(?:^|\/)src\/([^/]+)\/(.+)/);
|
||||
if (match) {
|
||||
workflowRelPath = `${match[1]}/${match[2]}`;
|
||||
}
|
||||
|
|
@ -119,30 +119,9 @@ class WorkflowCommandGenerator {
|
|||
* Generate command content for a workflow
|
||||
*/
|
||||
async generateCommandContent(workflow, bmadDir) {
|
||||
// Determine template based on workflow file type
|
||||
const templatePath = path.join(path.dirname(this.templatePath), 'workflow-commander.md');
|
||||
|
||||
// Load the appropriate template
|
||||
const template = await fs.readFile(templatePath, 'utf8');
|
||||
|
||||
// Convert source path to installed path
|
||||
// From: /Users/.../src/bmm/workflows/.../workflow.md
|
||||
// To: {project-root}/_bmad/bmm/workflows/.../workflow.md
|
||||
let workflowPath = workflow.path;
|
||||
|
||||
// Extract the relative path from source
|
||||
if (workflowPath.includes('/src/bmm/')) {
|
||||
// bmm is directly under src/
|
||||
const match = workflowPath.match(/\/src\/bmm\/(.+)/);
|
||||
if (match) {
|
||||
workflowPath = `${this.bmadFolderName}/bmm/${match[1]}`;
|
||||
}
|
||||
} else if (workflowPath.includes('/src/core/')) {
|
||||
const match = workflowPath.match(/\/src\/core\/(.+)/);
|
||||
if (match) {
|
||||
workflowPath = `${this.bmadFolderName}/core/${match[1]}`;
|
||||
}
|
||||
}
|
||||
// Load the workflow command template
|
||||
const template = await fs.readFile(this.templatePath, 'utf8');
|
||||
const workflowPath = this.mapSourcePathToInstalled(workflow.path);
|
||||
|
||||
// Replace template variables
|
||||
return template
|
||||
|
|
@ -212,14 +191,15 @@ class WorkflowCommandGenerator {
|
|||
When running any workflow:
|
||||
1. Resolve loader paths:
|
||||
- Primary: {project-root}/${this.bmadFolderName}/core/tasks/workflow.md
|
||||
- Fallback: {project-root}/src/core/tasks/workflow.md
|
||||
- Optional dev fallback: {project-root}/src/core/tasks/workflow.md (only if it exists and is readable)
|
||||
2. Check the primary path exists and is readable before loading
|
||||
3. If primary is missing/unreadable, log a warning with the path and error, then try fallback
|
||||
4. If fallback is also missing/unreadable, log an error with both attempted paths and stop
|
||||
5. LOAD the resolved workflow loader file
|
||||
6. Pass the workflow path as 'workflow-config' parameter
|
||||
7. Follow workflow.md instructions EXACTLY
|
||||
8. Save outputs after EACH section
|
||||
3. If primary is missing/unreadable, log a warning with the primary path and error
|
||||
4. Only if the dev fallback exists and is readable, try the fallback path; otherwise skip it
|
||||
5. If no readable loader is found, log an error with all attempted readable paths and stop
|
||||
6. LOAD the resolved workflow loader file
|
||||
7. Pass the workflow path as 'workflow-config' parameter
|
||||
8. Follow workflow.md instructions EXACTLY
|
||||
9. Save outputs after EACH section
|
||||
|
||||
## Modes
|
||||
- Normal: Full interaction
|
||||
|
|
@ -230,21 +210,33 @@ When running any workflow:
|
|||
}
|
||||
|
||||
transformWorkflowPath(workflowPath) {
|
||||
let transformed = workflowPath;
|
||||
return this.mapSourcePathToInstalled(workflowPath, true);
|
||||
}
|
||||
|
||||
if (workflowPath.includes('/src/bmm/')) {
|
||||
const match = workflowPath.match(/\/src\/bmm\/(.+)/);
|
||||
if (match) {
|
||||
transformed = `{project-root}/${this.bmadFolderName}/bmm/${match[1]}`;
|
||||
}
|
||||
} else if (workflowPath.includes('/src/core/')) {
|
||||
const match = workflowPath.match(/\/src\/core\/(.+)/);
|
||||
if (match) {
|
||||
transformed = `{project-root}/${this.bmadFolderName}/core/${match[1]}`;
|
||||
}
|
||||
mapSourcePathToInstalled(sourcePath, includeProjectRootPrefix = false) {
|
||||
if (!sourcePath) {
|
||||
return sourcePath;
|
||||
}
|
||||
|
||||
return transformed;
|
||||
const normalized = sourcePath.replaceAll('\\', '/');
|
||||
const srcMatch = normalized.match(/(?:^|\/)src\/([^/]+)\/(.+)/);
|
||||
if (srcMatch) {
|
||||
const mapped = `${this.bmadFolderName}/${srcMatch[1]}/${srcMatch[2]}`;
|
||||
return includeProjectRootPrefix ? `{project-root}/${mapped}` : mapped;
|
||||
}
|
||||
|
||||
if (normalized.includes('_bmad/')) {
|
||||
const parts = normalized.split(/_bmad\//);
|
||||
const relative = parts.at(-1);
|
||||
const mapped = `${this.bmadFolderName}/${relative}`;
|
||||
return includeProjectRootPrefix ? `{project-root}/${mapped}` : mapped;
|
||||
}
|
||||
|
||||
if (normalized.startsWith(`${this.bmadFolderName}/`)) {
|
||||
return includeProjectRootPrefix ? `{project-root}/${normalized}` : normalized;
|
||||
}
|
||||
|
||||
return sourcePath;
|
||||
}
|
||||
|
||||
async loadWorkflowManifest(bmadDir) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ name: '{{name}}'
|
|||
description: '{{description}}'
|
||||
---
|
||||
|
||||
Read the entire workflow file at: {project-root}/_bmad/{{workflow_path}}
|
||||
|
||||
Follow all instructions in the workflow file exactly as written.
|
||||
1. Load the workflow runner at {project-root}/_bmad/core/tasks/workflow.md
|
||||
2. Read the runner fully
|
||||
3. Run it with workflow-config: {{workflow_path}}
|
||||
4. Follow all runner instructions exactly
|
||||
|
|
|
|||
|
|
@ -4,4 +4,8 @@ description: '{{description}}'
|
|||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{project-root}/{{bmadFolderName}}/{{path}}, READ its entire contents and follow its directions exactly!
|
||||
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS:
|
||||
1. LOAD the FULL @{project-root}/{{bmadFolderName}}/core/tasks/workflow.md
|
||||
2. READ its entire contents
|
||||
3. Execute workflow runner with parameter: workflow-config: {{bmadFolderName}}/{{path}}
|
||||
4. FOLLOW the runner instructions exactly as written
|
||||
|
|
|
|||
|
|
@ -2,15 +2,11 @@ description = """{{description}}"""
|
|||
prompt = """
|
||||
Execute the BMAD '{{name}}' workflow.
|
||||
|
||||
CRITICAL: This is a structured YAML workflow. Follow these steps precisely:
|
||||
CRITICAL: Use the workflow runner task, not direct workflow-file execution.
|
||||
|
||||
1. LOAD the workflow definition from {project-root}/{{bmadFolderName}}/{{workflow_path}}
|
||||
2. PARSE the YAML structure to understand:
|
||||
- Workflow phases and steps
|
||||
- Required inputs and outputs
|
||||
- Dependencies between steps
|
||||
3. EXECUTE each step in order
|
||||
4. VALIDATE outputs before proceeding to next step
|
||||
|
||||
WORKFLOW FILE: {project-root}/{{bmadFolderName}}/{{workflow_path}}
|
||||
WORKFLOW INSTRUCTIONS:
|
||||
1. LOAD the workflow runner from {project-root}/{{bmadFolderName}}/core/tasks/workflow.md
|
||||
2. READ its entire contents
|
||||
3. PASS this parameter to the runner: workflow-config: {{workflow_path}}
|
||||
4. FOLLOW every runner step exactly as specified
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@ description = """{{description}}"""
|
|||
prompt = """
|
||||
Execute the BMAD '{{name}}' workflow.
|
||||
|
||||
CRITICAL: You must load and follow the workflow definition exactly.
|
||||
CRITICAL: Use the workflow runner task, not direct workflow-file execution.
|
||||
|
||||
WORKFLOW INSTRUCTIONS:
|
||||
1. LOAD the workflow file from {project-root}/{{bmadFolderName}}/{{workflow_path}}
|
||||
1. LOAD the workflow runner from {project-root}/{{bmadFolderName}}/core/tasks/workflow.md
|
||||
2. READ its entire contents
|
||||
3. FOLLOW every step precisely as specified
|
||||
4. DO NOT skip or modify any steps
|
||||
|
||||
WORKFLOW FILE: {project-root}/{{bmadFolderName}}/{{workflow_path}}
|
||||
3. PASS this parameter to the runner: workflow-config: {{workflow_path}}
|
||||
4. FOLLOW every runner step exactly as specified
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -4,4 +4,11 @@ inclusion: manual
|
|||
|
||||
# {{name}}
|
||||
|
||||
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL #[[file:{{bmadFolderName}}/{{path}}]], READ its entire contents and follow its directions exactly!
|
||||
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS:
|
||||
1. Always LOAD the FULL #[[file:{{bmadFolderName}}/core/tasks/workflow.md]]
|
||||
2. READ its entire contents
|
||||
3. Execute workflow runner with YAML parameter:
|
||||
```yaml
|
||||
workflow-config: {{bmadFolderName}}/{{path}}
|
||||
```
|
||||
4. FOLLOW the runner instructions exactly as written
|
||||
|
|
|
|||
|
|
@ -4,12 +4,10 @@ description: '{{description}}'
|
|||
|
||||
Execute the BMAD '{{name}}' workflow.
|
||||
|
||||
CRITICAL: You must load and follow the workflow definition exactly.
|
||||
CRITICAL: Use the workflow runner task, not direct workflow-file execution.
|
||||
|
||||
WORKFLOW INSTRUCTIONS:
|
||||
1. LOAD the workflow file from {project-root}/{{bmadFolderName}}/{{path}}
|
||||
1. LOAD the workflow runner from {project-root}/{{bmadFolderName}}/core/tasks/workflow.md
|
||||
2. READ its entire contents
|
||||
3. FOLLOW every step precisely as specified
|
||||
4. DO NOT skip or modify any steps
|
||||
|
||||
WORKFLOW FILE: {project-root}/{{bmadFolderName}}/{{path}}
|
||||
3. PASS this parameter to the runner: workflow-config: {{bmadFolderName}}/{{path}}
|
||||
4. FOLLOW every runner step exactly as specified
|
||||
|
|
|
|||
|
|
@ -4,12 +4,10 @@ description: '{{description}}'
|
|||
|
||||
Execute the BMAD '{{name}}' workflow.
|
||||
|
||||
CRITICAL: You must load and follow the workflow definition exactly.
|
||||
CRITICAL: Use the workflow runner task, not direct workflow-file execution.
|
||||
|
||||
WORKFLOW INSTRUCTIONS:
|
||||
1. LOAD the workflow file from {project-root}/{{bmadFolderName}}/{{path}}
|
||||
1. LOAD the workflow runner from {project-root}/{{bmadFolderName}}/core/tasks/workflow.md
|
||||
2. READ its entire contents
|
||||
3. FOLLOW every step precisely as specified
|
||||
4. DO NOT skip or modify any steps
|
||||
|
||||
WORKFLOW FILE: {project-root}/{{bmadFolderName}}/{{path}}
|
||||
3. PASS this parameter to the runner: workflow-config: {{bmadFolderName}}/{{path}}
|
||||
4. FOLLOW every runner step exactly as specified
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
---
|
||||
|
||||
Read the entire workflow file at: {project-root}/_bmad/{{workflow_path}}
|
||||
|
||||
Follow all instructions in the workflow file exactly as written.
|
||||
1. Load the workflow runner at {project-root}/_bmad/core/tasks/workflow.md
|
||||
2. Read the runner fully
|
||||
3. Run it with workflow-config: {{workflow_path}}
|
||||
4. Follow all runner instructions exactly
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
## Instructions
|
||||
|
||||
Read the entire workflow file at: {project-root}/_bmad/{{workflow_path}}
|
||||
|
||||
Follow all instructions in the workflow file exactly as written.
|
||||
1. Load the workflow runner at {project-root}/_bmad/core/tasks/workflow.md
|
||||
2. Read the runner fully
|
||||
3. Run it with workflow-config: {{workflow_path}}
|
||||
4. Follow all runner instructions exactly
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ auto_execution_mode: "iterate"
|
|||
|
||||
# {{name}}
|
||||
|
||||
Read the entire workflow file at {project-root}/_bmad/{{workflow_path}}
|
||||
|
||||
Follow all instructions in the workflow file exactly as written.
|
||||
1. Load the workflow runner at {project-root}/_bmad/core/tasks/workflow.md
|
||||
2. Read the runner fully
|
||||
3. Run it with workflow-config: {{workflow_path}}
|
||||
4. Follow all runner instructions exactly
|
||||
|
|
|
|||
|
|
@ -3,4 +3,8 @@ description: '{{description}}'
|
|||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{{workflow_path}}, READ its entire contents and follow its directions exactly!
|
||||
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS:
|
||||
1. LOAD the FULL @{project-root}/_bmad/core/tasks/workflow.md
|
||||
2. READ its entire contents
|
||||
3. Execute workflow runner with parameter: workflow-config: {{workflow_path}}
|
||||
4. FOLLOW the runner instructions exactly as written
|
||||
|
|
|
|||
|
|
@ -813,7 +813,6 @@ class ModuleManager {
|
|||
const newline = frontmatterMatch[0].includes('\r\n') ? '\r\n' : '\n';
|
||||
|
||||
try {
|
||||
const yaml = require('yaml');
|
||||
const parsed = yaml.parse(frontmatterMatch[1]);
|
||||
|
||||
if (!parsed || typeof parsed !== 'object' || !Object.prototype.hasOwnProperty.call(parsed, 'web_bundle')) {
|
||||
|
|
@ -897,7 +896,6 @@ class ModuleManager {
|
|||
let manifestData = {};
|
||||
if (await fs.pathExists(manifestPath)) {
|
||||
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
||||
const yaml = require('yaml');
|
||||
manifestData = yaml.parse(manifestContent);
|
||||
}
|
||||
if (!manifestData.agentCustomizations) {
|
||||
|
|
@ -906,7 +904,6 @@ class ModuleManager {
|
|||
manifestData.agentCustomizations[path.relative(bmadDir, customizePath)] = originalHash;
|
||||
|
||||
// Write back to manifest
|
||||
const yaml = require('yaml');
|
||||
// Clean the manifest data to remove any non-serializable values
|
||||
const cleanManifestData = structuredClone(manifestData);
|
||||
|
||||
|
|
@ -1241,16 +1238,31 @@ class ModuleManager {
|
|||
}
|
||||
}
|
||||
|
||||
const installerPath = path.join(sourcePath, '_module-installer', 'installer.js');
|
||||
const installerDir = path.join(sourcePath, '_module-installer');
|
||||
// Prefer .cjs (always CommonJS) then fall back to .js
|
||||
const cjsPath = path.join(installerDir, 'installer.cjs');
|
||||
const jsPath = path.join(installerDir, 'installer.js');
|
||||
const hasCjs = await fs.pathExists(cjsPath);
|
||||
const installerPath = hasCjs ? cjsPath : jsPath;
|
||||
|
||||
// Check if module has a custom installer
|
||||
if (!(await fs.pathExists(installerPath))) {
|
||||
if (!hasCjs && !(await fs.pathExists(jsPath))) {
|
||||
return; // No custom installer
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the module installer
|
||||
const moduleInstaller = require(installerPath);
|
||||
// .cjs files are always CommonJS and safe to require().
|
||||
// .js files may be ESM (when the package sets "type":"module"),
|
||||
// so use dynamic import() which handles both CJS and ESM.
|
||||
let moduleInstaller;
|
||||
if (hasCjs) {
|
||||
moduleInstaller = require(installerPath);
|
||||
} else {
|
||||
const { pathToFileURL } = require('node:url');
|
||||
const imported = await import(pathToFileURL(installerPath).href);
|
||||
// CJS module.exports lands on .default; ESM default can be object, function, or class
|
||||
moduleInstaller = imported.default == null ? imported : imported.default;
|
||||
}
|
||||
|
||||
if (typeof moduleInstaller.install === 'function') {
|
||||
// Get project root (parent of bmad directory)
|
||||
|
|
@ -1276,8 +1288,12 @@ class ModuleManager {
|
|||
await prompts.log.warn(`Module installer for ${moduleName} returned false`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
await prompts.log.error(`Error running module installer for ${moduleName}: ${error.message}`);
|
||||
} catch {
|
||||
// Post-install scripts are optional; module files are already installed.
|
||||
// TODO: Eliminate post-install scripts entirely by adding a `directories` key
|
||||
// to module.yaml that declares which config keys are paths to auto-create.
|
||||
// The main installer can then handle directory creation centrally, removing
|
||||
// the need for per-module installer.js scripts and their CJS/ESM issues.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -274,7 +274,6 @@ class UI {
|
|||
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
|
||||
} else {
|
||||
selectedModules = await this.selectAllModules(installedModuleIds);
|
||||
selectedModules = selectedModules.filter((m) => m !== 'core');
|
||||
}
|
||||
|
||||
// After module selection, ask about custom modules
|
||||
|
|
@ -362,6 +361,9 @@ class UI {
|
|||
selectedModules.push(...customModuleResult.selectedCustomModules);
|
||||
}
|
||||
|
||||
// Filter out core - it's always installed via installCore flag
|
||||
selectedModules = selectedModules.filter((m) => m !== 'core');
|
||||
|
||||
// Get tool selection
|
||||
const toolSelection = await this.promptToolSelection(confirmedDirectory, options);
|
||||
|
||||
|
|
@ -899,107 +901,10 @@ class UI {
|
|||
}
|
||||
|
||||
/**
|
||||
* Prompt for module selection
|
||||
* @param {Array} moduleChoices - Available module choices
|
||||
* @returns {Array} Selected module IDs
|
||||
*/
|
||||
async selectModules(moduleChoices, defaultSelections = null) {
|
||||
// If defaultSelections is provided, use it to override checked state
|
||||
// Otherwise preserve the checked state from moduleChoices (set by getModuleChoices)
|
||||
const choicesWithDefaults = moduleChoices.map((choice) => ({
|
||||
...choice,
|
||||
...(defaultSelections === null ? {} : { checked: defaultSelections.includes(choice.value) }),
|
||||
}));
|
||||
|
||||
// Add a "None" option at the end for users who changed their mind
|
||||
const choicesWithSkipOption = [
|
||||
...choicesWithDefaults,
|
||||
{
|
||||
value: '__NONE__',
|
||||
label: '\u26A0 None / I changed my mind - skip module installation',
|
||||
checked: false,
|
||||
},
|
||||
];
|
||||
|
||||
const selected = await prompts.multiselect({
|
||||
message: 'Select modules to install (use arrow keys, space to toggle):',
|
||||
choices: choicesWithSkipOption,
|
||||
required: true,
|
||||
});
|
||||
|
||||
// If user selected both "__NONE__" and other items, honor the "None" choice
|
||||
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
||||
await prompts.log.warn('"None / I changed my mind" was selected, so no modules will be installed.');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter out the special '__NONE__' value
|
||||
return selected ? selected.filter((m) => m !== '__NONE__') : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get external module choices for selection
|
||||
* @returns {Array} External module choices for prompt
|
||||
*/
|
||||
async getExternalModuleChoices() {
|
||||
const externalManager = new ExternalModuleManager();
|
||||
const modules = await externalManager.listAvailable();
|
||||
|
||||
return modules.map((mod) => ({
|
||||
name: mod.name,
|
||||
value: mod.code, // Use the code (e.g., 'cis') as the value
|
||||
checked: mod.defaultSelected || false,
|
||||
hint: mod.description || undefined, // Show description as hint
|
||||
module: mod, // Store full module info for later use
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt for external module selection
|
||||
* @param {Array} externalModuleChoices - Available external module choices
|
||||
* @param {Array} defaultSelections - Module codes to pre-select
|
||||
* @returns {Array} Selected external module codes
|
||||
*/
|
||||
async selectExternalModules(externalModuleChoices, defaultSelections = []) {
|
||||
// Build a message showing available modules
|
||||
const message = 'Select official BMad modules to install (use arrow keys, space to toggle):';
|
||||
|
||||
// Mark choices as checked based on defaultSelections
|
||||
const choicesWithDefaults = externalModuleChoices.map((choice) => ({
|
||||
...choice,
|
||||
checked: defaultSelections.includes(choice.value),
|
||||
}));
|
||||
|
||||
// Add a "None" option at the end for users who changed their mind
|
||||
const choicesWithSkipOption = [
|
||||
...choicesWithDefaults,
|
||||
{
|
||||
name: '⚠ None / I changed my mind - skip external module installation',
|
||||
value: '__NONE__',
|
||||
checked: false,
|
||||
},
|
||||
];
|
||||
|
||||
const selected = await prompts.multiselect({
|
||||
message,
|
||||
choices: choicesWithSkipOption,
|
||||
required: true,
|
||||
});
|
||||
|
||||
// If user selected both "__NONE__" and other items, honor the "None" choice
|
||||
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
||||
await prompts.log.warn('"None / I changed my mind" was selected, so no external modules will be installed.');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter out the special '__NONE__' value
|
||||
return selected ? selected.filter((m) => m !== '__NONE__') : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all modules (core + official + community) using grouped multiselect
|
||||
* Select all modules (official + community) using grouped multiselect.
|
||||
* Core is shown as locked but filtered from the result since it's always installed separately.
|
||||
* @param {Set} installedModuleIds - Currently installed module IDs
|
||||
* @returns {Array} Selected module codes
|
||||
* @returns {Array} Selected module codes (excluding core)
|
||||
*/
|
||||
async selectAllModules(installedModuleIds = new Set()) {
|
||||
const { ModuleManager } = require('../installers/lib/modules/manager');
|
||||
|
|
@ -1068,11 +973,7 @@ class UI {
|
|||
}
|
||||
}
|
||||
}
|
||||
allOptions.push(...communityModules.map(({ label, value, hint }) => ({ label, value, hint })), {
|
||||
// "None" option at the end
|
||||
label: '\u26A0 None - Skip module installation',
|
||||
value: '__NONE__',
|
||||
});
|
||||
allOptions.push(...communityModules.map(({ label, value, hint }) => ({ label, value, hint })));
|
||||
|
||||
const selected = await prompts.autocompleteMultiselect({
|
||||
message: 'Select modules to install:',
|
||||
|
|
@ -1083,14 +984,7 @@ class UI {
|
|||
maxItems: allOptions.length,
|
||||
});
|
||||
|
||||
// If user selected both "__NONE__" and other items, honor the "None" choice
|
||||
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
||||
await prompts.log.warn('"None" was selected, so no modules will be installed.');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter out the special '__NONE__' value
|
||||
const result = selected ? selected.filter((m) => m !== '__NONE__') : [];
|
||||
const result = selected ? selected.filter((m) => m !== 'core') : [];
|
||||
|
||||
// Display selected modules as bulleted list
|
||||
if (result.length > 0) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue